Mago 1.9.0
This release brings PHP 8.5 deprecation detection, new return type providers for sprintf() and array_map(), generator type inference, several new formatter options, and a large number of bug fixes across the analyzer, formatter, linter, and type system.
✨ Features
Analyzer
- Return type provider for
sprintf()andPsl\Str\format(): The analyzer now resolves return types forsprintf()calls with constant format strings, producing precise literal string types when all arguments are known at analysis time (#1073) - Return type provider for
array_map()preserving array shapes: When callingarray_map()with a typed callback on a keyed array, the analyzer now preserves the array shape in the return type instead of widening toarray<key, value>(#1144) - Generator key and value type inference: Closures and arrow functions containing
yieldbut no explicit return type annotation now have theirGenerator<K, V, S, R>type inferred from the yielded keys, values, and return statements (#1150)
Linter
- PHP 8.5 deprecation rules: Three new rules to prepare your codebase for PHP 8.5:
deprecated-cast: Detects deprecated non-canonical type casts ((integer),(boolean),(double), and(binary)) and suggests their canonical replacementsdeprecated-shell-execute-string: Detects usage of the backtick shell execute syntax (`ls -l`), which is deprecated in PHP 8.5, and suggests usingshell_exec()insteaddeprecated-switch-semicolon: Detects use of semicolons (;) as case separators in switch statements, deprecated in PHP 8.5, and suggests using colons (:) instead
- Improved
mago-ignore/mago-expectdiagnostics: When an ignore or expect pragma does not match any issue, the diagnostic now highlights the specific issue code that was not matched, making it easier to identify stale or incorrect pragmas (#1123)
Formatter
method-chain-semicolon-on-next-linesetting: New option to place the semicolon on its own line when a method chain breaks across multiple lines, equivalent to PHP-CS-Fixer'smultiline_whitespace_before_semicolons: new_line_for_chained_calls. Disabled by default, enabled in the Laravel/Pint preset (#1105)null_pipe_lastvariant fornull-type-hintsetting: New option that converts?TtoT|nulland reorders union types to placenulllast, providing PER-CS 3.0 compliance for null type positioning (#1133, #1134)- Space after
/**in single-line doc blocks: The formatter now ensures a space is present after the opening/**in single-line doc blocks (e.g.,/**@var int _/becomes/\*\* @var int _/) (#1077)
CLI
analyze --list-codes: New flag that outputs all analyzer issue codes as a JSON array of strings, useful for tooling integration (#1146)lint --list-rules --jsonnow includes severity: The JSON output of--list-rulesnow includes alevelfield (Error,Warning,Help, orNote) for each rule, matching the information shown in the human-readable table (#1142)
🐛 Bug Fixes
Analyzer
- Property narrowing invalidation after method calls with object arguments: Property type narrowings and assertion clauses are now correctly invalidated when a method call receives an object as an argument, since the method could mutate the object's state
- Nullsafe operator false positives: Improved null compatibility checks prevent
redundant-nullsafe-operatorfrom producing code-breaking false positives when the nullsafe operator is legitimately needed (#1131) - Numeric type subtraction in reconciler: Fixed incorrect
paradoxical-condition/impossible-conditionerrors when narrowing numeric types withis_numeric()checks on multiple variables (#1130) - Callable resolution with nullable/falsable types: The analyzer now correctly skips nullable and falsable variants when resolving callable targets, fixing false
invalid-callableerrors on functions likeClosure::bind()(#1127) - Docblock nullability merging for expandable types: Fixed a bug where nullability from docblock types was not merged for expandable types (like conditional return types), causing false
null-argumentpositives (#1126) - Variadic parameter type: Variadic parameters are now correctly typed as
array<K, T>instead oflist<T>, since named arguments can produce string keys (#1138) - Sealed/unsealed keyed array merging: The type combiner no longer incorrectly merges sealed and unsealed keyed arrays, preserving array shape precision in union types
- Unknown class handling in type identity comparison: Fixed a crash when comparing types involving unresolved class names (#1145)
Formatter
- Trailing comments moving to opening brace: Fixed a bug where end-of-line inline comments (e.g.,
// @phpstan-ignore method.unused) on method signatures were incorrectly moved to the opening brace line when usingmethod-brace-style = "always-next-line"(#1124) - Echo tag indentation in inline HTML: Fixed incorrect indentation for inline PHP echo tags (
<?= ?>) and single-expression echo statements within HTML templates (#1149) - Unary prefix comment oscillation: Fixed an idempotency bug where comments inside parenthesized unary prefix expressions (e.g.,
!(/* comment */ $x)) would oscillate between different positions on each format pass (#1135) - Print width with indentation: The formatter now correctly accounts for the current indentation level when calculating print width for breaking calls with zero-argument call arguments (#993, #1136)
- Method chain semicolon placement for nested chains: The
method-chain-semicolon-on-next-linesetting now correctly applies only when the method chain is the direct expression of the statement, not when a chain appears nested inside another expression (e.g., as a function argument)
Linter
no-trailing-spacefixer panic on CRLF files: Fixed a panic when the fixer encountered multibyte characters on lines with CRLF line endings (#1137)
Lexer
- Namespaced identifiers in braced string interpolation: The lexer now correctly handles namespaced constant identifiers used as array keys inside braced string interpolation (e.g.,
"{$arr[Foo\BAR]}") (#1128)
Prelude (Type Stubs)
parse_str()@param-outtype: Fixed the output parameter type annotation forparse_str()(#1140)- PHP 8.5 deprecation stubs: Updated type stubs to mark functions and features deprecated in PHP 8.5
Closure::bind()stubs: Added proper stub definitions to prevent falseinvalid-callableerrors (#1127)
Composer
- Runtime function stubs: Added function stubs for Composer runtime compatibility, preventing errors when the Mago Composer plugin is loaded in a PHP context (#1122)
🏗️ Internal
- Updated prelude stubs for PHP 8.5 deprecations
- Added
IssueCode::all()method for listing all analyzer codes - Added
RuleEntrystruct for serializing linter rules with metadata and severity level
🙏 Thank You
Contributors
A huge thank you to everyone who contributed code to this release:
- @ddanielou — #1140
- @dotdash — #1123, #1138
- @kzmshx — #1134, #1137, #1145
- @ptondereau — #1136
- @veewee — #1122
Issue Reporters
Thank you to everyone who reported issues and requested features that shaped this release:
- @bendavies — #1126
- @bfenton-smugmug — #1128
- @dotdash — #1144
- @HeySora — #1131
- @HugoHeneault — #1105
- @iparnamaa — #1149
- @karoun — #1127
- @kzmshx — #1133
- @klunejko — #993
- @lightla — #1135
- @MartkCz — #1124
- @Nadyita — #1130
- @seanjohnson08 — #1142, #1146
- @WalterWoshid — #1073, #1077
- @wryk — #1150
Full Changelog: 1.8.0...1.9.0