github carthage-software/mago 1.60.0
Mago 1.60.0

latest release: 1.60.1
one hour ago

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 Linter

  • Twig linting: adds mago twig:lint with 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-covariance setting 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 new expressions 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 mixed after elseif branches. (#2637, a4189a4)
  • If branches: keeps elseif and else assignments, and the negated condition of an if that 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 if conditions 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 after exit. (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 break on every pass, and ignores switch breaks. (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 to Traversable on is_iterable(). (#2620, 59aa9a1, 641ee0f)
  • Type checks: narrows by (bool) casts, method_exists(), static::class, and count($a, COUNT_NORMAL). (99e17bb, 10894b7, f5b6d33)
  • Scalar checks: drops int on is_float(), keeps truthy mixed truthy, and skips ctype_* on known types. (aa868cd, 075061b, 0655a03)
  • Callables: narrows callables after negated is_string(), is_array(), and is_object() checks. (b85cb35)
  • Nonnull values: treats a nonnull mixed value as never null in comparisons and narrowing. (701ad13)
  • Int range: drops int from 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 by class_exists(). (8c4bdab, a18b3a1)
  • Nested isset: keeps inner isset() narrowing under an outer isset(). (#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 that isset() proved. (7ef6481, ffc5172)
  • Template bounds: removes null from template bounds on isset(), 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-assert results 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 null in conditions. (b78e19b)
  • Variable variables: accepts $$name as 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-return and @ignore-falsable-return in array keys. (e02f0ea)
  • ArrayAccess: types reads by offsetGet(), 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 their name and value properties. (#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(), and array_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-string from 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() or mb_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 as float and ~ on numbers as int. (c2c380e, 5030df1, 7bc782a)
  • Truncated floats: reports % by floats that truncate to zero, and shifts by negative floats. (8daabb9)
  • Numeric types: accepts numeric where int, float, and string are 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: gives new SplObjectStorage() open templates. (1f46001)
  • Late static binding: keeps static late-bound in final methods, and binds it through template receivers. (00b717f, 9202d68, 42b243d)
  • Static calls: binds $this when calling an ancestor method by name, and static in first-class method callables. (f175a10, 7d95ab2)
  • get_called_class(): types the result as the class-string of static. (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 @method tag 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 self and parent to 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 static and parent from self, 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 @property on interfaces. (b14454a, c9456b2)
  • Dynamic properties: treats stdClass subclasses like stdClass. (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 stdClass with 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 return as void, 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)
  • never returns: honors @return never on 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 of self. (1acb9bc, 7672153)
  • Docblock resolution: resolves docblocks before use statements, and reports types naming missing class constants. (9c883c5, cc0ae9f)
  • Throw sites: reports unhandled-thrown-type sites in source order. (#2660, #2661, 1406af5)
  • Member access messages: shows the full type on possibly invalid member access. (#2626, 597afa8)

Codex

  • @method tags: keeps @method signatures 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-constructor from abstract classes, interfaces, and ancestors. (72207b7)
  • static types: accepts $this for invariant static, and resolves it for subclasses and other receivers. (9e0809f, 23a140a, 4d8a5eb)
  • Generator types: pads bare Generator docblock 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&shape as 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 implicit string return type. (42d9afd)
  • Utility types: leaves static properties out of properties-of, and keeps declared casing in enum from() 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 Traversable as iterable. (fdf3da0)
  • Object shapes: compares class properties and @property tags 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|null as 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 to DateTime and DateTimeImmutable. (#2622, 8e82956)
  • Array functions: types array_splice(), array_keys(), and array_walk() results more precisely. (7a7b743, a7799aa, 35b3144)
  • Push and implode: adds int keys in array_push() and array_unshift(), and requires implode()'s separator. (8fd26c2)
  • Class functions: class_parents() and class_uses() never return false for objects. (1fd0810)
  • Stub types: types callback diff functions, exec() output, and get_class() more precisely. (0c58e0d)
  • Hash functions: types md5(), sha1(), and hash() results as non-empty strings. (5b5faaa)
  • extract(): takes its array by value. (9cec839)
  • Dates and exceptions: returns static from DateTime::createFromFormat(), and types exception message and code. (2ebcf12, 856b14b)
  • Ds\Collection: makes it an IteratorAggregate. (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 ldap and 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

  • Final classes: skips abstract classes in must-be-final rules. (#2114, #2115, d19721b)

📖 Documentation

Recipes

  • Drupal: recommends the official drupal/mago extension for Drupal projects. (#2703, b73d576)

Reference

  • Linter rules: regenerates the linter rule reference. (e2b0e3e, 26a0848)

🏗️ 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_prefix and trim_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:

Issue Reporters

Thank you to everyone who reported issues that shaped this release:

Full Changelog: 1.59.0...1.60.0

Don't miss a new mago release

NewReleases is sending notifications on new releases.