Mago 1.60.0 adds a Twig formatter and linter, new analyzer and linter checks, and more than 300 fixes across the analyzer, type system, formatter, and stubs.
Twig formatter and linter, Sponsored by monbillet.ch
Thanks to monbillet.ch for sponsoring Mago's new Twig formatter and linter.
✨ Features
Twig Formatter
- Twig formatting: adds
mago twig:fmtto format Twig tags and expressions without touching HTML. (#303, #2679, #2708, #2709, #2710, b7e6a70, 15f9c01, e67c79b, 6280e01)
Twig Linter
- Twig linting: adds
mago twig:lintwith 25 rules, pragmas, baselines, and safe fixes. (#303, #2707, 993c741)
See the Twig formatter and Twig linter documentation to get started.
Analyzer
- Throw covariance: adds the opt-in
check-throw-type-covariancesetting for overrides and interface methods. (#2502, #2510, 2b1f018) - Array-to-int casts: warns on
(int)casts of arrays. (#2673, #2702, 47f52e0) - Float precision: reports float operands that lose precision in int operators such as
%,|, and<<. (e44aa44) - Generator returns: reports generators that can finish without returning their declared value. (8fdaf97)
- Dead
||operands: reports||operands that earlier conditions already rule out. (6561819) - Discarded objects: reports
newexpressions whose result is unused and whose creation has no effect. (#2672, 2e7b523)
Linter
prefer-null-coalescing-assignment: suggests??=when the right side of??assigns to the left. (#2682, e6e10d1)no-unused-return-value: reports private methods whose return value every call discards. (#2671, d867966)
🐛 Bug Fixes
Analyzer
Control Flow
- Elseif chains: stops widening variables to
mixedafterelseifbranches. (#2637, a4189a4) - If branches: keeps
elseifandelseassignments, and the negated condition of anifthat exits. (234208d, a5b59c4) - Branch effects: keeps an
unset()made in a branch, and forgets offset narrowing when a branch redefines the array. (faa9e07, 49e3b1c) - Ternaries: keeps the state of the branch that finishes when the other branch throws. (e5fc02c)
- Null coalescing: narrows after
?? throw, and stops marking the result of??as possibly undefined. (0162594, 3dc7442) - Short-circuit assignments: treats assignments in
??,&&, and||operands as conditional. (a952925, c369384, 0349881) - Try and catch: joins every catch after a try that always exits, and keeps variables assigned only in a catch. (a88cc48, 0dd5363)
- Finally blocks: sees returns and throws in the try body, and keeps narrowing and unsets made in
finally. (8105546, 4fb2695) - Switch: treats variables assigned in every case as assigned, and narrows
switch (true)cases by earlier ones. (cef4032, 4a1bda3) - Paradoxical conditions: avoids false reports after an equality guard returns early. (#2642, d53a023)
- Condition proofs: needs full proof before reporting a paradox, and reports
ifconditions already ruled out. (7d35d64, da3040c) - Stored conditions: keeps narrowing from negated and chained assignments of conditions. (f45dd97, da3bf6a)
- Clause logic: keeps clauses that the consensus rule cannot safely remove. (9ea4c9b)
- Dead code: skips a closing
?>tag when reporting code afterexit. (497e2e1)
Loops
- Loop entry: decides whether a loop runs from its condition's start values, including numeric strings and floats. (c35d061, ab5527a)
- Optional loops: keeps the pre-loop types and the variables set inside a loop that may not run. (f50ab30, 3e1b67b)
- Always-entered loops: keeps variables that only some passes assign possibly undefined after the loop. (da88607)
- Loop conditions: rebuilds conditions that compare with booleans or assign, before narrowing on exit. (47c9f6e, d31ca45, a9c3f93)
- Condition assignments: keeps what a loop condition assigns, including in
do-while, after the loop. (2a29581, f3ceaf2, c4c2608) - Exit conditions: defines variables that the exit condition proves set, and narrows by call assertions. (41b01e3, 61f4572)
- Breaks: records variables first assigned before a
breakon every pass, and ignoresswitchbreaks. (86bd748, b19a1dc, 91504b6) - Endless loops: types variables after
while (true)from its breaks alone. (17fbb36, b6da088) - Do-while loops: resets property narrowing per pass, skips first-pass dead code, and reads
||whole. (4509d8c, 39e572a, 2abcc05) - Loop passes: types loop variables from the previous pass, and makes variables unset in a loop possibly undefined. (e1a27cf, 5e655c0)
- Foreach: assigns the key after the value, and carries by-reference writes back to the array. (8e4a45a, a264edf)
Narrowing
is_subclass_of(): excludes the class itself from the narrowed type. (#2650, #2651, 2a52dd8)- Iterables: narrows to arrays after
!is_object(), and objects toTraversableonis_iterable(). (#2620, 59aa9a1, 641ee0f) - Type checks: narrows by
(bool)casts,method_exists(),static::class, andcount($a, COUNT_NORMAL). (99e17bb, 10894b7, f5b6d33) - Scalar checks: drops
intonis_float(), keeps truthymixedtruthy, and skipsctype_*on known types. (aa868cd, 075061b, 0655a03) - Callables: narrows callables after negated
is_string(),is_array(), andis_object()checks. (b85cb35) - Nonnull values: treats a
nonnullmixed value as never null in comparisons and narrowing. (701ad13) - Int range: drops
intfrom a union when a comparison goes past the int range. (2941bbb) - Instanceof: narrows
instanceof self,static, and$this, and checks literal class-string variables. (22c67ee, c49ab53) - Sealed classes: keeps concrete sealed classes, and drops matching inheritors, after a negated
instanceof. (fb33795, 2a07e58) - Class names: narrows
get_class()in match arms, and treats a final class's literal name like::class. (cabaa1a, d1c6fdb) - Existence guards: honors fully qualified guards and namespaced fallbacks, also inside closures. (05a4e8f, b19a42b, ab21426)
- Missing symbols: trusts
function_exists()for unavailable functions, and narrows literals byclass_exists(). (8c4bdab, a18b3a1) - Nested isset: keeps inner
isset()narrowing under an outerisset(). (#2701, 7abeb44) - Isset on properties: keeps typed properties non-null after
!isset(), and handles properties some members lack. (0e5582f, 0ae9272) - Isset reads and writes: checks call arguments in
isset(), and allows writes to properties thatisset()proved. (7ef6481, ffc5172) - Template bounds: removes
nullfrom template bounds onisset(), and checks bounds before reporting it impossible. (00a7edb, 2d588f1) - Negated union assertions: skips members that cannot occur in
@phpstan-assert !A|B. (#2640, 283b2a2) - Repeated assertions: stops applying earlier
@psalm-assertresults again on each call. (#2698, 4eb3b80) - Assertion calls: narrows through the clauses in scope after a call, and applies non-empty assertions to lists. (6990285, 5e092af)
- Nullsafe receivers: narrows receivers after non-null checks on method results. (#2621, ef5d2ff)
- Asserted nullsafe chains: narrows pure receivers when an assertion targets a nullsafe chain. (#2656, f7be382)
- Nullsafe conditions: narrows roots on truthy assignments, loose comparisons, and
!empty(). (f01ce66, 69ac044, 9591a7c) - Nullsafe properties: narrows the plain property through a nullsafe condition. (215df3b)
- Nullsafe chains: drops the nullsafe mark on stored chain values, and treats argument assignments as conditional. (99cf6ec, bc11fe6)
Variables and References
- Rebinding: discards stale narrowing when a variable is reassigned. (#2662, ae339b6)
- Undefined variables: reads possibly undefined variables as
nullin conditions. (b78e19b) - Variable variables: accepts
$$nameas an assignment target. (1cbbac0) - Missing keys: creates missing array entries when taking a reference. (#2639, 409737e)
- By-reference arguments: treats new or unset variables as
null, and writes back array items. (6b86ef1, 92dd4a7, 5795dbd, aef269d)
Arrays
- Key guards: narrows optional shape entries after key-existence checks. (#2681, 0c73f83)
- Missing indexes: keeps the missing-index case through property access, as in
$list[0]->prop ?? null. (#2641, #2658, 5185945) - List writes: extends sealed lists on append, and turns a list into a keyed array on negative or string keys. (ec872e9, 3175e56, c1dd579)
- Array writes: analyzes the key before the value, and treats writes through several literal keys as possible. (e63254b, 614d3d1)
- Sealed shapes: reports int indexes and literal key unions that may miss a sealed list or shape. (0347303, dfb1039)
- Optional keys: keeps an open shape's optional key when not set, and the key type when narrowing finds no overlap. (66b6848, b4f049d)
- Array spreads: keeps counts, string keys, optional items, and generic items through
...in array literals. (86ab1d0, 58be287) - Shape unions: types the union of a shape and a list, and narrows sealed shapes by
array_is_list(). (b0ba014, 4e483eb) - Ignored returns: honors
@ignore-nullable-returnand@ignore-falsable-returnin array keys. (e02f0ea) ArrayAccess: types reads byoffsetGet(), and stops treating writes as proof that an offset is set. (e67fe7c, 0734cd9)array_column(): keys results by the cast index column, reads int keys, and reports object index columns. (8ee4ac7, 63c67c8, 7ddeb60)- Enum columns: types
array_column()over enum cases by theirnameandvalueproperties. (#2695, c69b112) array_map(): types results over several arrays as lists of the callback's inferred return. (2d96468, 4f65548)array_filter(): narrows by overlapping and union callback assertions, and resolves first-class callables. (2188abc, 9d03ca1)array_reduce(): keeps a typed accumulator's own carry type. (6b2ee5e)- Merging arrays: keeps shapes through
array_merge(),array_merge_recursive(), andarray_replace(). (5918c3e, 7fc7e83) filter_var(): reads flags from an options array and types array results. (7203156)
Strings and Numbers
strlen(): infers exact lengths for unions of literal strings. (#2655, 76f28b2)- Concatenation: infers
non-falsy-stringfrom two non-empty strings. (#2623, 54d0302) - String inference: infers truthy interpolations, keeps casing, and concatenates small int ranges as literals. (9c2c188, eeec898, aa76484)
- String casts: types
strval()and a lone interpolated value like a(string)cast. (49c57dd) - String narrowing: keeps literal and casing refinements, and narrows by
strlen()ormb_strlen()against zero. (382fc5c, 72fcce5) implode(): infers non-empty results from the pieces and separator. (1956f2d)- Arithmetic: reports division by
0.0, and types negative powers asfloatand~on numbers asint. (c2c380e, 5030df1, 7bc782a) - Truncated floats: reports
%by floats that truncate to zero, and shifts by negative floats. (8daabb9) - Numeric types: accepts
numericwhereint,float, andstringare all allowed. (3d9e2ec) - Operators: types a postfix increment as the old value, and template arithmetic from the bound. (41bf574, ea85f4e)
Generics
- Template inference: infers templates from template argument bounds and from every member of a union parameter. (7fde4f9, 39b4ee7)
- Template binding: binds a bare template only to argument parts its siblings miss, and keeps receiver-bound templates. (5e6466b, a8cfc24)
- Template narrowing: narrows template bounds on negated
instanceof, scalar checks, and non-empty checks. (aa93d36, b475d1e, 26e0579) - Class strings: keeps the template of a class-string narrowed by
is_a(). (647da0f) - Conditional types: resolves per template part, on
never, and on disjoint arrays and subjects. (e75ee9b, d57fbc0, 09eefad, 2137569) - Conditional bounds: bounds a template by its condition inside a conditional type branch. (66077ac)
- Unused templates: counts templates used in aliases,
@param-out,@assert,@throws,@mixin, and@property. (b4ffbea, 023ded4) - Variance: checks template variance inside nested parameter and return types. (1d0773c)
- Generic iterables: unpacks templates by iterable bounds, and narrows to subclasses with type arguments. (adb8292, c9e4860, 797c260)
SplObjectStorage: givesnew SplObjectStorage()open templates. (1f46001)- Late static binding: keeps
staticlate-bound in final methods, and binds it through template receivers. (00b717f, 9202d68, 42b243d) - Static calls: binds
$thiswhen calling an ancestor method by name, andstaticin first-class method callables. (f175a10, 7d95ab2) get_called_class(): types the result as the class-string ofstatic. (ce4d687)- Mixins: picks the nearest mixin, follows nested and generic mixins, and binds property templates. (01a5601, e44562f, 6a0cb13, 3f00d75)
Classes and Members
- Abstract methods: reports exact calls to abstract methods, such as
self::method(). (#2667, #2678, a5f2430) - Abstract
@method: reports abstract methods that only a@methodtag covers. (f252e34) - Override on properties: reports
#[\Override]on properties without a parent property. (#2690, #2692, 7b394cd) - Override attribute: reports an invalid
#[\Override]by default. (b81972b) - Readonly narrowing: keeps readonly property narrowing across method calls. (#2629, abae518)
- Parent constructors: trusts properties that parent constructors set, including across files. (#2638, 9d18a13, fd8dd5a)
- Falsable returns: honors ignored falsable returns on member access, fixing
PDO::prepare()calls. (#2618, 4fb6065) - Visibility: allows private access through subclass instances, and fixes protected and inherited checks. (a3d2acc, 312776a, 6c6c5d4)
- Magic calls: routes calls to inaccessible methods through
__call()and__callStatic(). (55d633c) - Traits: resolves trait
selfandparentto the using class, also in signatures and callables. (2862659, c686a58, ecbbf97, 0b7dd69) - Trait adaptations: reports collisions and adaptations that name missing traits or methods. (f51846d)
- Inheritance: reports duplicate parents and circular inheritance. (ad16e55)
- Variadic overrides: lets a variadic parameter cover the parent's parameters. (d7fa734)
- Override checks: allows renames under
@no-named-arguments, and reports by-reference changes. (2ecdf87, e0bf92d) - Class constants: joins late-bound values, tells
staticandparentfromself, and checks native types. (dfb0b48, d0e0bad, 2f34c17) - Enums: uses constant values as case values, and resolves static calls on a bare
enum-string. (ecd1df1, 4ee623b) - Magic properties: types undocumented ones from
__get()and__set(), and allows@propertyon interfaces. (b14454a, c9456b2) - Dynamic properties: treats
stdClasssubclasses likestdClass. (7cf43c0) - Static properties: drops narrowing after static calls and writes via other names, and checks item writes. (980a43e, 4283c81, a75dff1)
- Property checks: reports docblocks that contradict native types, and unsets of readonly or restricted-set properties. (38720d0, 1d954a6)
- Missing classes: reports static property access on a missing class. (64bc0aa)
- Intersections: uses the narrowest method return, and keeps the type arguments of each member. (63db2c3, 7307a04)
- Intersection members: checks property hooks and static members on every part of an intersection. (b6aedb5, 282d949)
- Intersection narrowing: narrows intersections by subclass checks, and joins method assertions across unions. (5b0ff82)
- Object shapes: narrows shapes through property checks and assertions, and removes unset properties. (addf38a, 4e7e439, 640ba96)
- Open shapes: intersects open object shapes and
stdClasswith a shape. (d541bbf) - Member names: reads
$object->{Foo::class}as a member name. (7ae5f69) - Attributes: analyzes attributes on closures, arrow functions, and hook parameters. (733c591)
Callables
- Closure returns: types closures that end in a bare
returnasvoid, and honors declared return types. (aaec1e3, c3a9ab7, 9588f81) - Closure captures: captures possibly undefined variables as nullable, and carries only readonly property narrowing. (7405968, d598902)
- Generic callbacks: binds a callback's own template from the expected parameter type. (d806368)
- Callable defaults: reports non-null defaults for callable parameters. (4a3c6e9)
- String callables: reports string callables that need more arguments than given. (bdf05b3)
- Named arguments: rejects unknown names for built-in variadics, and checks names other variadics collect. (6a1ec8a, 6293996, b1d74e0)
neverreturns: honors@return neveron functions with a native return type. (#2625, 592d5d5)yield from: types the expression as the inner generator's return value. (49f85b3)
Docblocks and Diagnostics
- Inline
@var: drops stale known elements on redeclaration, and keeps the class casing ofself. (1acb9bc, 7672153) - Docblock resolution: resolves docblocks before
usestatements, and reports types naming missing class constants. (9c883c5, cc0ae9f) - Throw sites: reports
unhandled-thrown-typesites in source order. (#2660, #2661, 1406af5) - Member access messages: shows the full type on possibly invalid member access. (#2626, 597afa8)
Codex
@methodtags: keeps@methodsignatures off real methods, and prefers real methods to sibling tags. (#2686, #2687, edeca18, 944637a)- Subtype arguments: stops overwriting a subtype's template argument at the supertype's position. (#2676, #2677, 6f2b87b)
- Array callables: specializes array callables to their generic object, fixing
Closure::fromCallable()types. (#2683, 04f9bbc) - Inherited returns: keeps iterable,
$this, and docblock returns that fit a narrower native return. (1ca8da9, 9c2b877, 8303ac9) - Inherited properties: inherits property docblocks from the nearest ancestor, also for promoted parameters. (85e0266, 90b5022, 540aa61)
- Inherited generics: resolves nested template arguments,
@require-*arguments, and inherited mixins. (3187bfc, 2c4ed3d, 69c8362) - Consistent constructors: applies
@consistent-constructorfrom abstract classes, interfaces, and ancestors. (72207b7) statictypes: accepts$thisfor invariantstatic, and resolves it for subclasses and other receivers. (9e0809f, 23a140a, 4d8a5eb)- Generator types: pads bare
Generatordocblock types to four template arguments. (#2632, 1ce83a6) - Docblock escapes: reads escapes in strings and quoted shape keys, and escapes control bytes in type names. (4ac7127, d64e370)
- Docblock intersections: flattens them, expands aliases in them, and reads
array&shapeas an open shape. (77e241e, 950e535, d06f71a) - Type aliases: keeps a class's type aliases out of later declarations. (a02bea8)
- Closure docblocks: reads a closure's docblock placed before its assignment. (df6f49c)
__toString(): gives an untyped__toString()its implicitstringreturn type. (42d9afd)- Utility types: leaves static properties out of
properties-of, and keeps declared casing in enumfrom()results. (c84698d, a5078ad) - List shapes: accepts an explicit zero key, rejects key gaps, and keeps
[]when it joins a sealed list. (6143834, 8855cf5, 613dab2) - Array types: never treats a non-empty array as identical to
[], and widens known keys by an open rest type. (a88a0d2, 790cf35) - Array keys: stores numeric-string key types as
int. (9d1290b) - Constant expressions: infers more expressions, including casts, negations, and objects created with
new. (6bc2d65, 1dbb15b, 7459ff2) - Constant values: keeps class constant keys in arrays, and joins values defined in several branches. (2e99845, eff57ab)
- Constant types: populates the native type of class constants, and types build-dependent version constants as strings. (8c32b54, 885057c)
- Class strings: checks interface, enum, and trait strings by kind, and literal class names against
class-string<T>. (73fa634, 92942db) - Callables: checks that callable-string literals name callables, and rejects callables needing an optional parameter. (ac32053, 2ee3e1e)
- Iterables: never treats a final class that is not
Traversableas iterable. (fdf3da0) - Object shapes: compares class properties and
@propertytags against object shapes in the right direction. (555afaf, 09b3911) - Literal strings: keeps an incompatible literal string in a union whichever side it is on. (dcb0f2e)
PHPDoc Syntax
- Int ranges: accepts a trailing comma in
int<min, max>and keeps it in the syntax tree. (a5e7f51, 57176df) - Callable types: binds
[]after a callable's return type to that type, and accepts=after a parameter name. (82def77, 2fa34ec) $this|null: reads@param $this|nullas a type. (e9c8d8a)
Prelude
SensitiveParameterValue: makes it generic in its wrapped value. (#2666, 625f283)array_chunk(): marks the chunks as non-empty. (#2624, b23128c)DateTimeInterface: seals it toDateTimeandDateTimeImmutable. (#2622, 8e82956)- Array functions: types
array_splice(),array_keys(), andarray_walk()results more precisely. (7a7b743, a7799aa, 35b3144) - Push and implode: adds int keys in
array_push()andarray_unshift(), and requiresimplode()'s separator. (8fd26c2) - Class functions:
class_parents()andclass_uses()never returnfalsefor objects. (1fd0810) - Stub types: types callback diff functions,
exec()output, andget_class()more precisely. (0c58e0d) - Hash functions: types
md5(),sha1(), andhash()results as non-empty strings. (5b5faaa) extract(): takes its array by value. (9cec839)- Dates and exceptions: returns
staticfromDateTime::createFromFormat(), and types exceptionmessageandcode. (2ebcf12, 856b14b) Ds\Collection: makes it anIteratorAggregate. (2dc1b15)- SPL: defaults the inner iterator template of SPL wrappers, and uses PHP 8 parameter names. (440c831, 67ac7c5)
- PHP 8.4 and 8.5: adds missing symbols, and updates
ldapand SPL iterator stubs. (038172e, e37cad8)
Linter
no-empty-loop: marks its fix as unsafe, since loop conditions can have side effects. (#2635, #2649, fb67ca9)no-missing-format-argument: counts unpacked arguments, and shows the right counts in messages. (#2685, 3d9ba08)no-redundant-method-override: skips overrides that add method attributes. (#2680, 50528df)literal-named-argument: checks method calls as well as function calls. (#2663, f5f61d7)
Formatter
- Instanceof: keeps required parentheses on the right of
instanceof. (#2643, #2644, e747eed) - Long parameters: breaks a single parameter that exceeds the print width. (#2646, 3eac970)
- Decorative comments: leaves
/*****block comments untouched. (#2645, 4285688) - Class constants: aligns typed class constants by type and name. (#2634, af2960e)
- Stable output: produces stable output on the first pass across settings. (#2668, dab1acd)
Semantics
- Void casts: rejects a
(void)cast used as a value. (13df672) - Promoted properties: reports promotion outside a constructor, even when the method has no return type. (#2704, #2705, b17238e)
- Readonly classes: reports untyped and static properties in readonly classes. (#2711, #2712, aff95d5)
Syntax
- Constant casts: allows casts in constant expressions on PHP 8.5. (cb7e0a0)
Guard
📖 Documentation
Recipes
Reference
🏗️ Internal
Toolchain
- Rust 1.100: requires Rust 1.100.0 to build from source. (2b62ae0)
- Dependency cooldown: waits seven days before taking new dependency versions. (8118d28)
- Dependencies: updates Rust dependencies. (#2654, 073d983, 95fdd97, 26e9733, e35de51)
- Cleanup: uses
trim_prefixandtrim_suffix, and silences Cargo manifest lints. (af565ba, 8a9e30c)
CI
- Nightly checks: fixes nightly Clippy lints and pins CodSpeed to Ubuntu 24.04. (e9a2cde)
Testing
- Downstream checks: fixes false positives that new checks raised in PSL and the PHP SDK before release. (546772c)
- Generic callables: covers
Closure::fromCallable()on a generic instance. (#2683, eff79f4)
Sponsors
🧩 Extension Protocol
No protocol changes in this release.
🙏 Thank You
Contributors
Thank you to everyone who contributed to this release:
- @AnnaDamm: #2510
- @Bleksak: #2687
- @kastriotkastrati: #2677
- @maoxor: #2644
- @mvanhorn: #2658
- @Norbytus: #2115
- @roryabraham: #2678, #2702
- @shadowhand: #2651
- @thelastbackspace: #2661
- @thunderer: #2632, #2666, #2692, #2705, #2712
- @ynaamane: #2649
- @devnix for investigating #2683 in #2684.
Issue Reporters
Thank you to everyone who reported issues that shaped this release:
- @adamSerbakowski: #2638
- @alfredbez: #2620, #2621, #2622, #2623, #2624, #2625
- @amateescu: #2708, #2709, #2710
- @AnnaDamm: #2502
- @balthild: #2646
- @bendavies: #2680, #2698
- @Bleksak: #2686
- @darealfive: #2634
- @devnix: #2683
- @EwaldBorger: #2663
- @homersimpsons: #2681
- @kastriotkastrati: #2676
- @Kenneth-Sills: #2618
- @lavalade: #2682
- @maoxor: #2643, #2645
- @MichaelOwenDyer: #2637, #2639, #2640, #2641, #2642, #2655, #2656, #2662
- @MisatoTremor: #2701
- @mmodler: #2635
- @mytskine: #2626
- @Norbytus: #2114
- @robrich82: #2660
- @roryabraham: #2667, #2668, #2671, #2672, #2673
- @shadowhand: #2650
- @stloyd: #2695
- @syntlyx: #2685
- @thunderer: #2690, #2704, #2711
- @yoannblot: #2629
Full Changelog: 1.59.0...1.60.0