github carthage-software/mago 1.19.0
Mago 1.19.0

7 hours ago

Mago 1.19.0

Mago 1.19.0 is a major quality release with 20+ bug fixes, 10 new linter rules, significant performance improvements to the database layer, and several new features. Highlights include sweeping improvements to loop type inference (while/for/foreach), proper isset() scoping so sub-expressions are still checked, array_filter and array_column return type preservation for shaped arrays, resolution of self:: references in class constant inference, detection of never-returning calls in switch cases, and 6 new linter rules including missing-docs, no-parameter-shadowing, and no-array-accumulation-in-loop. The database layer received SIMD-accelerated line counting and several allocation optimizations.

✨ Features

Analyzer

  • @experimental usage detection: Warn when calling functions, methods, or classes marked with the @experimental docblock tag
  • Wildcard type support: Added support for _ and * wildcard types in generic type argument positions (#1571)
  • Incorrect casing detection for class-likes and functions: Warn about incorrect casing when referencing class-likes and functions
  • count() assertion negatable: !count($x) now correctly narrows the array to empty

Linter

  • missing-docs rule: Enforces documentation on public API elements (#1585)
  • no-parameter-shadowing rule: Detects function parameters that shadow variables from an outer scope
  • no-array-accumulation-in-loop rule: Flags array accumulation patterns inside loops that may cause performance issues
  • no-iterator-to-array-in-foreach rule: Detects iterator_to_array() calls that could be replaced with direct iteration
  • sorted-integer-keys rule: Flags arrays with integer keys that are not in sorted order
  • no-is-null rule: Suggests replacing is_null($x) with $x === null (#1557)
  • method-name rule: Enforces method naming conventions
  • constructor-threshold option for excessive-parameter-list: Configure a separate threshold for constructor parameters
  • no-redundant-readonly detects promoted properties: The rule now catches redundant readonly on constructor promoted properties in readonly classes (#1543)

Formatter

  • indent-binary-expression-continuation option: Control indentation of continued binary expressions
  • preserve-breaking-conditions option: Preserve line breaks in conditions as authored (#1222)

Guard

  • Brace expansion in pattern matching: Guard patterns now support {a,b,c} brace expansion syntax

CLI

  • Baseline JSON schema: mago config --schema --show baseline outputs the JSON schema for baseline files, enabling IDE integration and validation (#986)

Playground

  • Sync all analyzer settings: The playground now exposes all analyzer configuration options with improved responsiveness

🐛 Bug Fixes

Analyzer

  • Fixed loop type inference for by-reference mutations: Removed the types_share_category heuristic and fixed the underlying inference — the combiner now absorbs empty arrays into lists, array append forces non-empty, and by-reference mutations propagate across loop passes (#1574, #1575)
  • Fixed never-returning calls in switch cases: array_pop-style functions with never return types in switch default branches now correctly mark the branch as terminating, so variables defined in other branches are visible after the switch (#1578)
  • Fixed false redundant-condition in nested loops with continue/break: Variables unchanged at continue points are now recorded so they don't get overwritten by break-path assignments (#1586)
  • Fixed break-path types leaking into loop between-pass widening: Break-path types (e.g. $look = null from a break on null-check) no longer contaminate the next iteration's entry state in while(true) loops (#1587)
  • Scoped inside_isset to outermost access: isset($arr[$row['key']]) now correctly reports issues on the index sub-expression $row['key']isset only suppresses checks on the outermost $arr[...] access (#1594)
  • possibly-null-array-index reported inside isset(): Using a nullable value as an array key is a type-safety issue separate from undefined-key checks, so it's now reported even inside isset() (#1594)
  • Fixed ?? isset semantics for non-variable LHS: func($arr[$key]) ?? $default no longer suppresses issues in $arr[$key] — only variable/property/array-access LHS expressions get isset-like treatment (#1594)
  • Fixed duplicate issues on compound assignments: $arr[$key] += $val no longer reports the same issue twice on the index sub-expression (#1594)
  • Fixed false-array-access null propagation: $row['key'] where $row is array|false now correctly includes null in the result type (PHP returns null when accessing an index on false) (#1592)
  • Suppressed possibly-undefined-variable inside isset(): isset($x) is the canonical way to check if a variable exists — no warning about it being undefined (#1603)
  • Resolved self:: references in class constant inference: const MAP = ['a' => self::A] now correctly infers the referenced constant/enum-case types instead of degrading to mixed (#1602)
  • Preserved shaped arrays in array_filter return type: array_filter on a shaped array like array{a: ?string, b: ?int} now preserves the shape with entries marked optional, instead of flattening to a generic array (#1590)
  • Supported keyed-array shapes in array_column: array_column on list<array{name: string, id: int}> with literal keys now returns the correct shaped result (#1591)
  • Propagated right-side && assignments in while conditions: while (check() && ($row = fetch()) !== false) now correctly narrows $row in the loop body (#1593)
  • Detected always-false loose equality on int and bool types: $intVar == $otherInt where the types provably differ is now flagged, matching strict identity behavior for same-category comparisons (#1576)
  • String flag overlap in identity comparisons: non-empty-string !== lowercase-string is no longer falsely reported as always-true — string types with independent constraint flags (casing, non-emptiness) can share concrete values
  • Skipped by-ref out-type update on never arguments: Calling a templated by-reference function on a never-typed argument no longer widens the variable to mixed
  • Fixed phpversion() prelude stub: Corrected the return type (#1584)

Linter

  • Fixed braced-string-interpolation fixer for bareword array keys: "$o[user_id]" is now correctly fixed to "{$o['user_id']}" instead of the broken "{$o[user_id]}" which treats user_id as an undefined constant (#1588)
  • Fixed inline-variable-return for return-by-reference functions: Functions declared with function &name() no longer get the inline suggestion, which would break the by-reference return contract (#1600)

Guard

  • Fixed perimeter rule matching: Uses the most-specific matching rule instead of unioning all matches

CLI

  • Fixed NO_COLOR not disabling linter output colors: NO_COLOR=0 --colors=auto now correctly disables colors for all output, not just the logger (#1599)

Codex

  • Fixed private property inheritance: Private properties from parent classes are no longer inherited by child classes (#1558)

⚡ Performance

Database

  • SIMD-accelerated line counting: line_starts now uses memchr for ~4x faster newline scanning (#1581)
  • Avoided wasteful buffer allocation: File reading no longer pre-allocates based on metadata size (#1580)
  • Avoided HashMap rehashing: Database building pre-allocates HashMap capacity (#1583)
  • Avoided per-file canonicalize(): Path exclusion checks use the already-canonicalized workspace root (#1589)
  • Removed dead is_canonical flag: Cleaned up unused path collection bookkeeping (#1596)

Syntax

  • Fixed quadratic allocation in Node::filter_map: Replaced repeated Vec::insert(0, ...) with push + reverse (#1598)

📖 Documentation

  • Baseline JSON schema docs: Added documentation for the new mago config --schema --show baseline command in the baseline guide

🏗️ Internal

  • Docblock refactoring: Removed annotation support, added tag metadata, and allowed PHP identifier chars in tag names
  • Prelude: Ignored falsable return from hrtime(), applied clippy fixes

🙏 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.18.1...1.19.0

Don't miss a new mago release

NewReleases is sending notifications on new releases.