Mago 1.26.0
A feature release with two big analyzer additions: closures, arrow functions, plain functions, and methods now auto-infer if-true / if-false assertions from their bodies, so array_filter($xs, fn ($x) => $x instanceof Foo) narrows the result type without any docblock, and two new opt-in settings (strict-array-index-existence, allow-array-truthy-operand) tighten array-ops semantics. The linter gains prefer-array-spread and a deny mode for yoda-conditions. The formatter picks up inline-single-breaking-value-argument (used in the Tempest preset). On the CLI side, mago init now writes a $schema URL pinned to the running version into the generated config, and the published JSON schema is now versioned. The rest is a large batch of analyzer false-positive fixes and a --retain-code-respecting --fix dispatcher.
✨ Features
Analyzer
- Auto-inferred assertions: closures, arrow fns, functions, methods infer
if_true/if_falsefrom their body. (#1794, 14269d6) strict-array-index-existence: opt-in; tightens array indexing; deprecatesallow-possibly-undefined-array-keys. (#1766, 0e563c9)allow-array-truthy-operand: opt-in setting that suppressesinvalid-operandwhen arrays are used in logical ops. (#1765, 07f6a24)
Linter
prefer-array-spreadrule: detectsarray_merge(...)calls that can be rewritten as spread. (#1767, bc8d67a)yoda-conditionsdeny mode: rule gains a deny mode that flags constants on the right of comparisons. (#1785, a4e47e5)
Formatter
inline-single-breaking-value-argument: keeps a lone breaking value argument inline; off by default, on in Tempest. (#1148, 8964575)
CLI
$schemainmago.toml: configs accept a$schemaURL;mago initwrites one pinned to the installed version. (#1771, e71e78b)- Versioned config schema: schema is now published per release at
https://mago.carthage.software/<version>/schema.json. (f1e705d)
🐛 Bug Fixes
Analyzer
array_filternarrowing via callback: result type is now narrowed using the callback's inferred assertions. (#1794, 14269d6)list-destructure-string-keyfalse positive: suppressed when known items already cover the destructure positions. (#1796, b3b11c5)- Interface property reads possibly undefined: silences false
isset/??redundancy on interface fields. (#1786, 9e873b0) voidmaterialization in unions: combiningvoidwith another type now producesT|null, notT. (#1787, 47fb42a)redundant-type-comparisonafter impure@assert: suppressed for impure or value-returning assert calls. (#1781, #831, 53dad31)::classon undefined classes: now reportsnon-existent-class-likeinstead of silently typing asclass-string. (#1768, 052e0cd)unset($var)no longer firespossibly-undefined-variable:unsetis a write target, not a read. (#1764, 3f0e7f8)- Unbounded loop boundary:
for ($i = 0; ; $i++)no longer trips the boundary check. (#1762, c64199b) - Template parameter through
new $class-string<T>(): preservesTinstead of widening to the constraint. (#1760, 64f3df8) - Loose-equal falsy values on
!= <falsy>: drops false-positive narrowings that ignored PHP's loose comparison rules. (#1757, 6b45a66) - Assigned vars marked possibly-in-scope: removes spurious
undefined-variableafter conditional assignment. (#1759, 659a8d3)
Codex
- Discriminated tagged-array unions through the combiner: preserves discriminator keys when unioning shapes. (#1775, 426f536)
Closurenamed-object cast: now routes through a closure-flagged signature instead of__invoke. (#1770, 068a7eb)
Linter
no-redundant-yield-fromtrailing-comma fix: auto-fix no longer leaves a stray,after the inlined element. (#1797, 4b49d01)prefer-static-closureandparent::: closures whose body usesparent::are no longer suggested asstatic. (#1783, cf2e944)yoda-conditionsand class constants: treats class constants as constant-like for the position check. (#1784, f945b75)
Formatter
- Hardline after declaration-attached docblocks: forces a hardline after multi-line docblocks attached to declarations. (#1774, 2249d6a)
CLI
--retain-codehonored before--fix: filter applied before fix dispatch, so fixes run only for retained codes. (#1782, 35f1a37)
Database
column_numberat line start: fixes wrong column offsets ingitlab/github/checkstyle/sarifreports. (#1790, 856ceca)
Type-syntax / Docblock
- By-reference parameters in callable type strings:
Closure(int &$x): voidparses correctly in docblocks. (#1772, be2d08f)
Prelude
📖 Documentation
- README links: fixes broken
/guide/installationand/guide/getting-startedlinks to point at/latest/en/.... (#1788, 3f29da7) - Rule examples stacked vertically: bad/good examples now render on top of each other. (#1773, 1892eff)
- French translations polish: fixes calques, translation errors, untranslated code comments. (105a518)
- Chinese translations polish: idiomatic phrasing and formal register pass across
zh/**. (edd0fc3) - OG / Twitter previews: padded
banner.pngfor cleaner social cards. (ddbb0f7) - Versioned dropdown: docs site renders the version dropdown from
/versions.jsonat runtime. (66f406c) - Legacy bookmarks redirect: requests to old paths redirect to
/latest. (4d1f370)
🏗️ Internal
Performance
- Iterator-based argument / parameter accessors: replaces
Vec-returning helpers; one-pass superglobal narrowing. (f43a5d1)
Refactor
MetadataFlagsper file type: extracts the per-file-type metadata flag construction into a single helper. (#1769, 1fdb15f)
CI / Tooling
- Stricter workspace clippy lints: tightens the lint baseline across crates. (#1761, 5de4859)
- Run clippy on all targets: tests and benches included. (#1776, 82cd8ae)
- Pinned Rust toolchains: CI now pins exact toolchain versions. (#1777, 72da50b)
- Signed API commits in nightly workflow: nightly bot commits via the signed-API path. (#1789, e801af1)
🙏 Thank You
Contributors
A huge thank you to everyone who contributed code to this release:
- @dotdash: #1789, #1785, #1784, #1778, #1777, #1776, #1769
- @Zuruuh: #1773, #1771
- @edsrzf: #1768, #1767
Issue Reporters
Thank you to everyone who reported issues that shaped this release:
- @bendavies: #1797, #1796
- @Ocramius: #1794
- @llaville: #1790
- @roguecat0: #1788
- @mathroc: #1787, #1782
- @oruborus: #1786, #1781
- @dragosprotung: #1783
- @ddanielou: #1775, #1770
- @giorgiopogliani: #1774
- @neodc: #1772
- @rogerespel: #1766, #1765, #1764, #1762
- @MacGritsch: #1760, #1759, #1758, #1757
- @innocenzi: #1148
- @rauanmayemir: #831
Full Changelog: 1.25.2...1.26.0