github carthage-software/mago 1.25.0
Mago 1.25.0

latest release: 1.25.1
8 hours ago

Mago 1.25.0

Mago 1.25.0 is a big release. Seven new linter rules (no-dead-store, no-redundant-variable, no-redundant-else, no-negated-ternary, no-unused-static, no-unused-global, no-unused-closure-capture), default values on @template parameters, and a config extends directive for shared mago.toml inheritance. The analyzer picks up an opt-in pipe-callable hint relaxation, branch-discriminator narrowing, integer-bound widening in loop fixpoints, and a long list of correctness fixes around shapes, by-reference params, nullsafe scope, and finally state. Performance got serious attention: CodSpeed CI is wired up, hot allocations are gone from the comparator, populator, algebra, and syntax crates, and type-syntax now arena-allocates its AST.

A new feature-gated mago language-server subcommand ships as an unstable preview behind --features language-server, and the install script now auto-verifies release attestations through gh (with --always-verify and --no-verify escape hatches) for teams that want supply-chain checks at install time.

✨ Features

Analyzer

  • Default template types: support default values on @template parameters in docblocks. (#899, 9d592d2)
  • Pipe callable hint relaxation: allow-implicit-pipe-callable-types skips type-hint checks on |> callables. (#1634, 67d1736)
  • Boundary-widen literal types at property assignments: tightens shape inference when properties are mutated. (86ebd89)
  • Warn on list-destructuring with string or negative integer keys: catches mismatches between target shape and source. (#1740, 85587dc)
  • Detect array append after PHP_INT_MAX key: flags $arr[] once the implicit key would overflow. (8a47607)
  • Narrow through if/else branch-discriminator booleans: improves narrowing on if ($flag) {} else {} patterns. (9828771)
  • Expose AnalysisArtifacts and analyze_with_artifacts: orchestrator API for embedders that need per-expression types. (79e332a)

Linter

  • no-dead-store and no-redundant-variable: writes whose values are never read, and obvious aliases. (#938, fce1b6e)
  • no-redundant-else: warns on else blocks after a return/throw/etc. (#1705, dca1497)
  • no-negated-ternary: rewrites !cond ? a : b to cond ? b : a. (#1721, 39ca373)
  • no-unused-{static,global,closure-capture}: three rules covering unread captures and unused declarations. (#1739, 3547a92)

CLI

  • mago format --stdin-filepath: route stdin through the right configuration and respect excludes. (#1726, c103de0)
  • Config extends directive: shared base configurations across projects via a per-file include chain. (#1173, 485ff4a)
  • Log resolved baseline paths under MAGO_LOG=debug: makes baseline troubleshooting visible. (#1694, 1d51d2e)

Reporting

  • Precise begin/end positions in GitLab format: per-issue ranges instead of synthesised line spans. (#1723, d5f8ed4)

Prelude

  • memcached extension: full stub coverage for the Memcached PECL extension. (#1692, 9ac7e88)

Install

  • Auto-verify release attestations via gh: install script verifies on auto-detect, with --always-verify and --no-verify. (cd4cf4d)

Language server (unstable preview)

  • mago language-server: feature-gated unstable LSP behind --features language-server. (8c3b589)
  • Wire mago.toml into the LSP: linter / analyzer / formatter / source config flow through the language server. (b027fe2)

🐛 Bug Fixes

Analyzer

  • Re-analyze global-scope files when dependencies change: incremental analysis no longer skips affected globals. (#1735, 6a144f0)
  • Propagate parent class docblock changes: subclasses re-analyze when their parent's docblock changes. (#1708, 0730075)
  • Seed finally scope with combined try state: avoids stale post-try locals leaking into finally. (#1742, ec4e1db)
  • Widen integer bounds independently in loop fixpoint: prevents premature convergence on co-varying bounds. (#1729, 802fc74)
  • Dedupe method targets via require-extends intersection: removes duplicate diagnostics on intersected method calls. (#1727, e137325)
  • Narrow through low-precedence and/or: typing now follows assignment-style boolean operators. (218a4fa)
  • Detect null assignment to non-nullable typed property: catches $obj->prop = null on declared non-nullable. (1f95315)
  • Flag arithmetic-on-zero and shift-by-negative: surfaces guaranteed-runtime-error operands at compile time. (e98e879)
  • Compose + operator on array shapes per PHP semantics: array union now matches PHP's left-key-wins behaviour. (73a947a)
  • Suppress alt-arm template violations on callable unions: stops false positives when only one branch satisfies. (#1722, eacaa33)
  • Invalidate superglobals and global-declared vars across calls: model state changes a callee can perform. (#1712, d279a7a)
  • Widen known items on dynamic-key array assignments: avoids false positives when the key isn't a literal. (#1709, d848132)
  • Scope conflicting-clause clear to fresh assignment vars: prevents over-broad invalidation across statements. (#1707, 5b4be9f)
  • Reset conflicting-clause flags between statements: same family, applied at statement boundaries. (#1696, d746e0f)
  • Narrow nullsafe bases to non-null in surviving scope: $x?->y narrows $x after the survivor. (#1695, 7d9b9ac)
  • Stop flagging $v !== [] on mixed as redundant: that comparison is meaningful when $v could be an array. (#1703, 67be0a0)
  • Infer non-empty-string from sprintf with non-empty args: tightens return type when args are statically non-empty. (#1688, c3a422e)
  • Preserve declared enum-like by-ref param types: by-reference parameters keep their narrow declared types. (#1693, a9bf5bf)
  • Model array_map null-callback passthrough and zip: null-callback variant returns zipped pairs. (fba9d06)
  • Preserve user-declared narrowings through generic by-ref calls: outer narrowings survive through generics. (4c360c0)
  • Clarify redundant-type-comparison wording for positive assertions: message distinguishes === from !==. (8bf96d3)

Codex

  • Reject string callables passed to Closure parameters: 'strlen' cannot match a Closure(...) parameter. (d1050f3)
  • Saturate array-shape offset increment past i64::MAX: prevents overflow on absurdly long fixed-shape arrays. (d5fdbbe)
  • Drop is_callable when widening callable-string with literals: avoids spurious callable-string contamination. (bfc8ed3)
  • Float scrape order-independent in the combiner: deterministic union of float literals regardless of input order. (0a1c837)
  • Register type aliases before resolving @template constraints: aliases defined later in the file resolve correctly. (#1691, fb0b796)
  • Honor resource open/closed state in truthiness checks: closed resources are truthy in PHP. (#1706, a648361)
  • Preserve declared known keys when combining with spread shape: spread no longer wipes explicit keys. (#1704, bb41160)
  • Flag strict callables with too few params: Closure(int): void requires the callable accepts the int. (#1700, 01a128b)
  • Bound Literal % From modulo by |dividend|: correct modulo bounds against int<min, max> divisors. (#1701, c11c9bf)
  • Preserve i64::MIN in scanner's unary-negation inference: -(-9223372036854775808) no longer overflows. (#1702, c9776e9)
  • Enforce template variance soundness: widening at construction and @var widening for invariant generics. (f223d4a)

Linter

  • Skip TRUE/FALSE/NULL in lowercase-keyword under Drupal: Drupal coding standards keep these uppercase. (#1743, 5056e78)
  • Skip string-style when concat contains non-interpolable expressions: avoids fixers that change runtime behaviour. (#1738, f07f207)
  • Mark str-starts-with annotation as primary: diagnostic location lands on the actual call site. (86724f8)

Formatter

  • Force breaks at logical ops only when condition must break: comparison operators stay inline; only &&/|| split. (#1744, f63eba1)
  • Converge corpus idempotency on a single pass: format-then-format-again produces identical output. (715db54)
  • Make keyed-array and logical-chain formatting idempotent: stops oscillation between two layouts. (cfe060f)

Syntax

  • Apply recursion limit to statements: rejects pathological inputs with a clear error instead of stack overflow. (d952858)
  • Handle null byte within string interpolation: lexer no longer aborts on "\0" inside ${ } blocks. (44015e4)
  • Resolve parser false-positives: cleans up several spurious parse errors found via fuzzing. (374575c)
  • Require digits after float exponent marker: type-syntax lexer no longer accepts 1e without an exponent. (3fdbb99)
  • Stop forcing inline on type-syntax lexer/stream helpers: faster builds, no measurable runtime regression. (254b40c)

Prelude

  • bcmath stub improvements: tightened return types and parameter contracts across the extension. (#1733, 3271032)
  • DateTimeInterface::getTimezone() never returns false: matches the actual contract. (#1687, e3b21f5)
  • Conditional return for implode and join: return type narrows when args are statically non-empty. (#1690, 6864306)
  • Preserve list-shape through array_pad: pad result keeps list<> rather than collapsing to array<>. (06bebab)
  • Tighten array_chunk length and array_fill count to non-zero ranges: catches array_chunk($arr, 0). (27fdac7)
  • Require non-empty arrays for single-arg min/max: min([]) is a runtime error. (2be0a40)
  • Tighten implode return type for single-element arrays: returns the original element. (becde73)

Reporting

  • Fall back to first annotation when issue has no primary: avoids reporting issues with no location attached. (acac500)

📖 Documentation

  • GitHub Actions recipe: fix CI short-circuiting and format --dry-run example. (#1698, 709988f)
  • Install script pinning example: point at the commit that shipped --always-verify. (d02d18d)

🏗️ Internal

Performance

  • Hoist container predicates in union comparator: cuts comparator overhead on hot paths. (#1741, 7922a3e)
  • Optimise object expansion in codex: fewer allocations on type expansion. (#1731, bc47811)
  • Cut allocations in populator hierarchy and method inheritance: lower memory pressure during populate. (#1736, 8c40ac6)
  • Reduce allocations in algebra disjoin/saturate/group_impossibilities: hot CNF paths get tighter. (#1734, 3d2bef2)
  • Avoid unnecessary allocations and repeated interning in analyzer: less work per analysed expression. (#1732, 220cde2)
  • Cache file id and add stream early-return fast paths in syntax crates: lex/parse path becomes lighter. (#1730, 15d9c93)
  • Bump arena reset per stub file in prelude: better memory locality during prelude builds. (0996bda)
  • Fused span helpers, single-byte lexer dispatch in type-syntax: faster type-syntax lexer. (57cd833)

Refactoring

  • Adopt shared syntax-core primitives: unify Sequence, TokenSeparatedSequence, LookaheadBuf across syntax crates. (7b408f0)
  • Arena-allocate type-syntax AST: matches the rest of the parser stack. (8501f0f)
  • Vec instead of HashMap for template_variance: smaller and faster for typical sizes. (#1724, d709d84)
  • Drop dead type_coerced_to_literal flag from ComparisonResult: removes an unused field path. (7ad0b84)
  • Replace standin walker with slim definition_type_replacer: simpler indirection. (e901a11)
  • Drop the config crate: configuration parsing handled directly in the CLI. (aeb224e)

CI / Benchmarks

  • CodSpeed continuous performance testing: track regressions on every PR. (#1718, d781033)
  • Criterion benches across analyzer, codex, type-syntax, prelude, twig-syntax: scaffold for the perf work. (438bfc9)
  • Corpus smoke test: format-then-reformat real-world corpora to surface idempotency regressions. (ed6e3e4)
  • Lexer/parser fuzzing for type-syntax, twig-syntax, and syntax: cargo-fuzz harnesses for grammar surfaces. (18893d7)
  • Property-based combiner/comparator tests: 12-axiom suite covering combiner and comparator invariants. (2077d34, 55fb478)

Misc

  • Swap serde_yml for serde_norway: replaces an unmaintained YAML dependency. (7f46ae8)

🙏 Thank You

Contributors

A huge thank you to everyone who contributed code to this release:

Issue Reporters

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

Full Changelog: 1.24.0...1.25.0

Don't miss a new mago release

NewReleases is sending notifications on new releases.