Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mago Laravel Package

carthage-software/mago

Mago is an extremely fast PHP linter, formatter, and static analyzer written in Rust. It brings Rust-inspired speed and reliability to PHP projects with a modern toolchain and great developer experience, plus multiple install options (script, Homebrew, Composer).

View on GitHub
Deep Wiki
Context7
1.19.0

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](https://github.com/experimental) usage detection: Warn when calling functions, methods, or classes marked with the [@experimental](https://github.com/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: https://github.com/carthage-software/mago/compare/1.18.1...1.19.0

1.18.1

Mago 1.18.1

Patch release fixing a CI failure that prevented 1.18.0 binaries from being built for aarch64-unknown-linux-gnu and aarch64-unknown-linux-musl targets.

🐛 Bug Fixes

Tests

  • Skip stdin_input integration tests when cross-compiling: The --stdin-input integration tests spawn the built mago binary as a subprocess. When cross-compiling (e.g., aarch64 target on an x86_64 CI runner), the binary can't execute, causing all 5 tests to fail. These tests now detect cross-compilation and skip gracefully.

Full Changelog: https://github.com/carthage-software/mago/compare/1.18.0...1.18.1

1.18.0

Mago 1.18.0

Mago 1.18.0 is a packed release with 5 new features, 17 bug fixes across all tools, and improvements to the docblock parser. Highlights include full callable-string type support with function_exists/is_callable narrowing, --stdin-input for editor integrations on analyze/lint/guard, precise range() return types, a Psl\Dict\select_keys type provider, and numerous false positive fixes in the analyzer, linter, and formatter.

✨ Features

Analyzer

  • callable-string type support: Added callable-string, lowercase-callable-string, and uppercase-callable-string types. function_exists() narrows strings to callable-string (#1532)
  • range() return type provider: Infers precise return types based on argument values - range(1, 5) returns non-empty-list<int<1, 5>>, range(0, 5.0) returns non-empty-list<float>, range('a', 'z') returns non-empty-list<non-empty-string> (#1510)
  • Psl\Dict\select_keys return type provider: Narrows the return type to a shaped array when called with literal keys, handling keyed arrays, generic arrays, and union/iterable types (#1357)
  • Detect redundant identity comparisons in loops: $v === false where $v is int is now flagged as redundant even inside foreach/for/while loops, when the types are from fundamentally incompatible categories (#1555)

CLI

  • --stdin-input for analyze, lint, and guard: Pipe unsaved buffer content from editors and use the real file path for baseline matching and issue locations. Supports path normalization for consistent baseline behavior (#1253)
  • Auto-detect reporting format: Automatically uses GitHub or GitLab reporting format when running in CI environments (#1550)
  • Auto-detect editor URL: Detects terminal editor from environment variables for clickable file paths in issue output
  • Helpful --only error for analyze: Instead of a confusing clap suggestion, mago analyze --only now explains that the analyzer is not rule-based and suggests --retain-code (#774)

🐛 Bug Fixes

Analyzer

  • Fixed callable-string false positives: Resolved too-many-arguments when calling dynamic callable strings, never narrowing after function_exists false branch, and unreachable-else-clause for function_exists checks. The scalar comparator now properly checks is_callable during assertion contexts, and cast_atomic_to_callable uses a mixed signature (#1561)
  • Fixed isset narrowing lost with post-increment index: $arr[$i++] inside an isset($arr[$i]) block now preserves the narrowed type by saving it before the increment side-effect invalidates it (#1556)
  • Fixed !empty not removing false/null from unions: When !empty($row['key']) proves a key exists, non-array types like false and null are now removed from the parent union (#1565)
  • Fixed count() not generating NonEmptyCountable assertion: count($arr) in truthy context now properly narrows the array to non-empty
  • Fixed match arm conditions affected by loop flag: Cleared the inside_loop flag when analyzing match arm conditions to fix false non-exhaustive match errors
  • Fixed array_merge with unpacked arguments: array_merge(...$lists) where $lists is list<list<int>> now correctly returns list<int> instead of array<non-negative-int, int> (#1548)
  • Fixed net_get_interfaces return type: Corrected the prelude stub to match PHP documentation (#1550)

Linter

  • Fixed Pest ->not leaking across ->and() boundaries: The use-specific-expectations rule no longer carries the ->not modifier past ->and() calls, which reset the expectation context in Pest chains (#1511)
  • Fixed halstead rule emitting multiple issues per node: Volume, difficulty, and effort violations are now consolidated into a single issue, so // [@mago-expect](https://github.com/mago-expect) lint:halstead suppresses all of them (#1452)
  • Fixed kan-defect score in issue title breaking baseline: Removed the metric value from the issue title so baseline matching remains stable when the score improves (#1320)
  • Fixed first-class callable suggestion for spread arguments: fn(array $nums) => Calculator::sum(...$nums) is no longer incorrectly suggested to be replaced with Calculator::sum(...) (#1246)

Formatter

  • Fixed unnecessary chain breaking on short chains: Raised the force-break threshold for $this-> and static method chains from 3 to 5 accesses, so chains like $this->tokenStorage->getToken()->getUser()->getFoo() stay on one line when they fit within print-width (#1451)
  • Fixed [@mago-format-ignore-next](https://github.com/mago-format-ignore-next) corrupting code in sub-expressions: The ignore marker inside match arms, arrays, or function parameters no longer leaks to the next class member, which previously duplicated raw source content (#1513)
  • Fixed line break before comparison operator in parenthesized logical chain: Non-logical operators like > inside && chains now get their own group, preventing unwanted line breaks (#1562)
  • Fixed JSON schema enum values: BraceStyle, MethodChainBreakingStyle, EndOfLine, and NullTypeHint now use snake_case in the generated schema, with PascalCase preserved as aliases for backwards compatibility (#1530)

Docblock

  • Support multi-line inline tags: {[@internal](https://github.com/internal) ...} and other inline tags can now span multiple lines in PHPDoc comments (#1257)

Codex

  • Skip private properties inheritance: Private properties from parent classes are no longer inherited by child classes (#1559)

Database

  • Fixed glob metacharacters in file paths: Files with [], *, ?, or {} in their path are now treated as literal paths when they exist on disk, instead of being interpreted as glob patterns (#1459)

🏗️ Internal

  • Added regression test for [@psalm-assert](https://github.com/psalm-assert) template narrowing (#1517)
  • Added types_share_category helper for loop-aware redundant comparison detection
  • Added is_gap_insignificant helper for robust ignore-next marker validation

🙏 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: https://github.com/carthage-software/mago/compare/1.17.0...1.18.0

1.17.0

Mago 1.17.0

Mago 1.17.0 continues the focus on analyzer accuracy with 20+ bug fixes and several new features. Highlights include detection of array access on non-array types (false, scalars), support for PHP 8.4's #[\Deprecated] attribute, literal float arithmetic tracking, a new [@suspends-fiber](https://github.com/suspends-fiber) annotation for async code, and invalidation of memoized $this properties on self-method calls. The linter gains new rules for fully-qualified global imports and ambiguous constant access, plus an auto-fixer for redundant parentheses.

✨ Features

Analyzer

  • Detect array access on non-array types: New false-array-access, possibly-false-array-access, possibly-invalid-array-access, and invalid-array-access issue codes report when array access is performed on false, scalars, or other non-array types, including assignment contexts (#1542)
  • Support #[\Deprecated] PHP attribute: Functions, methods, classes, constants, and enums with the #[\Deprecated] attribute are now recognized as deprecated (#1541)
  • [@suspends-fiber](https://github.com/suspends-fiber) annotation: A new docblock tag [@suspends-fiber](https://github.com/suspends-fiber) marks methods that suspend fibers. Calls to these methods invalidate all memoized $this property narrowings, preventing false positives in async code. Revolt\EventLoop\Suspension::suspend() is recognized automatically (#1536)
  • Invalidate memoized properties on self-method calls: Non-pure $this->method() calls now clear memoized $this-> property types, fixing false positives where property narrowings survived across method calls that could modify them (#1536)
  • Extend min/max/abs providers to Psl\Math functions: Psl\Math\max, Psl\Math\maxva, Psl\Math\min, Psl\Math\minva, and Psl\Math\abs now use the same precise integer range providers as the built-in functions
  • Check reference constraints on array modifications: Array append ($arr[] = $val) and indexed assignment ($arr[$k] = $val) on by-reference parameters now validate against the parameter's type constraint (#1539)

Linter

  • no-fully-qualified-global-function, no-fully-qualified-global-constant, no-fully-qualified-global-class rules: New rules that flag fully-qualified references (#1494)
  • ambiguous-constant-access rule: Detects ambiguous constant access patterns (#1508)
  • Auto-fixer for redundant parentheses: The redundant-parentheses rule now has an auto-fixer (#1549)

🐛 Bug Fixes

Analyzer

  • Fixed false positives from truthy/falsy-mixed absorbing literals: The type combiner no longer absorbs falsy literal strings like string('') into truthy-mixed, which caused false redundant-condition warnings (#1534)
  • Fixed false positive for isset on string offsets: isset($s[0]) on a generic string no longer returns true unconditionally - empty strings have no characters, so the result is correctly bool (#1537)
  • Fixed parse_url component return types: parse_url() with a component argument now correctly includes false in the return type, since malformed URLs return false regardless of the component (#1546)
  • Fixed empty array variants lost in loops with break: Infinite loops with break no longer drop the pre-loop empty array variant, fixing false positives where count($arr) was always truthy after a while(true) loop (#1535)
  • Fixed empty array variants not removed after isset check: When if (!isset($arr[$key])) { throw; } exits, empty array variants are now removed from the base variable since isset proves the array is non-empty
  • Fixed switch fall-through after never-returning call: Cases after a never-returning call (like exit()) without break are no longer incorrectly marked as unreachable - the switch can still jump directly to those cases (#1531)
  • Fixed if-body clauses leaking to post-if context: Clauses from non-exiting if-bodies (e.g., if ($m === 0) { echo; }) no longer leak to subsequent code, preventing incorrect type narrowing (#1509)
  • Fixed by-reference argument types not propagated through &&: Variables passed by reference in function calls on the left side of && now correctly update their type for the right side evaluation (#1524)
  • Fixed dynamic key array assignment overwriting known items: $arr[$dynamic] = value no longer replaces the value types of existing known keys in the array (#1527)
  • Fixed isset/empty checks on union types with lists: The impossible-isset check now considers TArray::List variants in union types, and non-empty arrays with literal key types no longer produce false possibly-undefined warnings (#1512)
  • Fixed nested array keys not marked definite after loops: The post-loop key-definiteness fix now recurses into nested arrays (#1522)
  • Fixed integer type unions in min/max/abs providers: Multi-integer unions like int|int(0) are now properly handled by computing combined bounds (#1526)
  • Fixed literal float arithmetic: Operations between literal ints and floats (e.g., 1000 * 0.5) now compute the actual result (float(500.0)) instead of returning unspecified float (#1540)
  • Fixed non-empty array with literal key type access: Accessing a non-empty generic array with its exact literal key type no longer produces possibly-undefined warnings (#1512)

Formatter

  • Fixed fits() handling of LineSuffix: Corrected line-suffix width calculation in the formatter (#1516)

Prelude

  • Fixed getimagesize/getimagesizefromstring param-out: Added [@param-out](https://github.com/param-out) ?array annotation matching the nullable parameter type (#1523)

Codex

  • Fixed truthy/falsy-mixed containment: truthy-mixed no longer contains falsy values and falsy-mixed no longer contains truthy values in type comparisons (#1534)
  • Recognized #[\Deprecated] attribute: The codex scanner now sets the deprecated flag from PHP attributes in addition to [@deprecated](https://github.com/deprecated) docblock tags (#1541)

Composer

  • Fixed race condition in binary download: Concurrent Composer installations no longer corrupt the binary (#1544)
  • Formatted internal binary download code: Cleaned up the Composer wrapper's download logic (#1545)

🏗️ Internal

  • Ignored analyzer test files from typos check

🙏 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:

  • @UweOhse - #1512, #1522, #1523, #1524, #1526, #1527, #1528, #1531, #1534, #1535, #1536, #1537, #1539, #1540, #1541, #1542, #1546
  • @regnerisch - #1518
  • @laylatichy - #1525

Full Changelog: https://github.com/carthage-software/mago/compare/1.16.0...1.17.0

1.16.0

Mago 1.16.0

Mago 1.16.0 is a significant release focused on analyzer accuracy. This release fixes over 20 false positives across loop analysis, type narrowing, integer range tracking, switch fall-through handling, and comparison assertions. It also adds new features including duplicate enum value detection, argument validation hooks for setcookie, session_set_save_handler, and session_set_cookie_params, and improved return type inference for min, max, abs, and array_reverse.

✨ Features

Analyzer

  • Detect duplicate enum values: The analyzer now reports an error when a backed enum has multiple cases with the same value (#1475, #1478)
  • Argument validation for setcookie / setrawcookie: When the 3rd argument is an array (options form), additional positional arguments are now flagged as errors (#1467, #1492)
  • Argument validation for session_set_save_handler: Validates both the object form (1-2 args) and callable form (6-9 args), reporting errors for mismatched argument counts (#1468)
  • Argument validation for session_set_cookie_params: When the 1st argument is an array, extra arguments are now flagged as errors (#1468)
  • Improved min / max / abs return types: Multi-argument min and max calls now return precise integer range types, and a new abs provider returns non-negative ranges (#1477, #1480)

🐛 Bug Fixes

Analyzer

  • Fixed false positives in loop analysis: Resolved multiple issues where the analyzer incorrectly flagged conditions inside loops as impossible, including accumulative operations like $total += $delta (#1491, #1493, #1499)
  • Fixed condition side effects lost in if-statements: Post-increment expressions like $n++ inside if-conditions had their side effects discarded when the if-body always exited, causing false "impossible condition" reports (#1504)
  • Fixed non-comparison loop conditions: Assignment-based loop conditions like while ($row = func()) were incorrectly treated as always-true, causing variables modified inside the loop to lose their pre-loop values (#1505)
  • Fixed switch fall-through type tracking: Variables modified in fall-through switch cases now correctly preserve types from both direct-entry and fall-through paths (#1490)
  • Fixed switch cases after default treated as unreachable: Cases placed after the default clause are no longer incorrectly flagged (#1484, #1485)
  • Fixed integer range narrowing in comparisons: Range bounds from non-literal types (e.g., non-negative-int) on the secondary variable in less-than comparisons no longer produce incorrect type narrowing when negated (#1503)
  • Fixed count() === N not narrowing empty arrays: Empty arrays without generic parameters are now correctly removed when reconciling exact count assertions (#1506)
  • Fixed loose equality narrowing to never: Loose equality (==) with strings no longer incorrectly narrows numeric types to never (#1488)
  • Fixed closure parameter null stripping: Inferred null types on closure parameters are no longer incorrectly removed when no explicit type hint is present (#1489)
  • Fixed instanceof self / instanceof static: These expressions are now properly resolved for type computation (#1464)
  • Fixed increment/decrement on never producing mixed: Operations on never types now correctly propagate never instead of falling back to mixed (#1502)
  • Fixed numeric in string concatenation: The numeric type is now accepted in string concatenation operations (#1500)
  • Fixed integer range precision in loops: Bitwise and shift operations inside loops no longer unnecessarily widen integer ranges (#1499)
  • Fixed isset return type: isset() now returns true instead of bool when all checked values are definitely set, and array keys are marked as definite after loops that always enter (#1486, #1493)
  • Fixed loop integer widening: Variables with integer ranges that grow uniformly across loop iterations are now properly widened to prevent false impossible-condition reports (#1493)

Formatter

  • Fixed stdin formatting memory issue: The source database is no longer populated when formatting from stdin (#1483)
  • Fixed conditional expression comment placement: Comments in conditional (ternary) expressions are now placed correctly (#1465)

Prelude

  • Fixed array_reverse return type: Non-list arrays (e.g., array<string, int>) now correctly preserve their key type instead of being narrowed to list<V> (#1466)
  • Fixed non-positive-int type mapping: Was incorrectly mapped to positive-int due to a copy-paste error (#1479)
  • Removed false [@deprecated](https://github.com/deprecated) on mb_scrub: The function is not deprecated in PHP (#1476, #1481)
  • Fixed getrusage function signature: Corrected the return type to match PHP documentation (#1501)
  • Fixed iconv mode parameter: Corrected the mode parameter type to int<0, 3> (#1497)
  • Fixed openssl_pkey_get_details return type: Made all keys optional since algorithm-specific keys are only present for their key type
  • Consolidated session_set_save_handler stub: Merged duplicate declarations into a single signature with union types (#1468)
  • Consolidated session_set_cookie_params stub: Merged duplicate declarations into a single signature (#1468)

Codex

  • Improved integer bitwise operations: Bitwise AND with a non-negative mask now correctly produces a bounded non-negative result (#1499)

📖 Documentation

  • Added Run On Save as alternative formatter: Documented the Run On Save VS Code extension as an alternative to format-on-save (#1469)
  • Removed outdated Docker limitations: The limitations section was removed from the Docker recipe page as the issues have been resolved (#1438)

🏗️ Internal

  • Updated dependencies
  • Fixed clippy warnings
  • Updated sponsors list

🙏 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: https://github.com/carthage-software/mago/compare/1.15.3...1.16.0

1.15.3

Mago 1.15.3

Mago 1.15.3 is a patch release that fixes formatter idempotency issues with trailing closing tags and non-standard line endings, adds a comment placement infrastructure for stable binary expression formatting, fixes incorrect OpenSSL function signatures, and resolves a CPU spin-lock in the Composer wrapper.

✨ Features

Formatter

  • Comment placement infrastructure for binary expressions: Added a structural comment placement phase that pre-assigns comments to AST nodes via DFS before formatting. This stabilizes should_break decisions in binary expressions and keeps comments inside parenthesized subexpressions, fixing formatting oscillation between passes (#817, #1456)

🐛 Bug Fixes

Formatter

  • Fixed idempotency when trailing ?> is removed: The opening tag formatting decision used source_text.contains("?>") to detect inline PHP templates. When remove_trailing_close_tag removed a trailing ?> on the first pass, the second pass saw no ?> and changed the formatting. Replaced with an AST walker that counts closing tags at any depth, excluding the trailing one that gets removed (#1350, #1457)
  • Fixed bare \r not recognized as line terminator: Files with \r\r\n, bare \r (classic Mac OS), or other non-standard line endings caused the formatter to merge lines and crash on multi-line block comments. Fixed skip_newline, split_lines, replace_end_of_line, and print_comment to handle bare \r as a line terminator. Also fixed line_starts in the database crate which only scanned for \n, causing incorrect line numbering for bare-CR files (#1460, #1462)

Prelude

  • Fixed OpenSSL function signatures: Several openssl_x509_* functions incorrectly accepted OpenSSLCertificate|false instead of the correct OpenSSLCertificate|string parameter type (#1463)

Composer

  • Fixed CPU spin-lock in Composer wrapper: The PHP wrapper process polled proc_get_status in a tight loop with no sleep, causing 100% CPU usage on one core during --watch mode. Added a 10ms sleep interval and fixed signal-based exit code propagation (#1454, #1455)

🏗️ Internal

  • Migrated test corpus to php-standard-library and added api-platform (#1458)

🙏 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: https://github.com/carthage-software/mago/compare/1.15.2...1.15.3

1.15.2

Mago 1.15.2

Mago 1.15.2 is a re-release of 1.15.1 with corrected version metadata. The 1.15.1 release was tagged before the version bump was applied, causing binaries to report 1.15.0 in --version output and preventing publication to crates.io. This release contains no code changes beyond the version bump.

For the full list of changes since 1.15.0, see the 1.15.1 release notes.


Full Changelog: https://github.com/carthage-software/mago/compare/1.15.1...1.15.2

1.15.1

Mago 1.15.1

Mago 1.15.1 is a patch release that distinguishes $this from static return types, reverts a formatter regression with parenthesis removal in binary expressions, and restores glibc 2.17 compatibility for the Linux GNU build.

✨ Features

Codex

  • Distinguish $this from static return types: Added a separate is_static flag to the type system to properly differentiate $this (same instance) from static (same class, possibly different instance). Returning new static() from a method declared as [@return](https://github.com/return) $this is now correctly flagged, while return $this remains valid. return new static() continues to be accepted for : static return types (#1429)

🐛 Bug Fixes

Formatter

  • Reverted excessive parenthesis removal in binary expressions: Reverted #1407 which incorrectly removed parentheses from mixed-precedence binary expression chains, causing semantic changes in some expressions (#1430, #1432)

CI/CD

  • Restored glibc 2.17 compatibility for Linux GNU builds: The x86_64-unknown-linux-gnu binary is now built with cross using the manylinux2014 container again, restoring compatibility with older Linux distributions. PGO optimization for this target has been removed as it was incompatible with the cross-compilation setup (#1431, #1433, #1434)

🙏 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: https://github.com/carthage-software/mago/compare/1.15.0...1.15.1

1.15.0

Mago 1.15.0

Mago 1.15.0 brings three new linter rules, configurable minimum-fail-level in TOML files, automatic watch mode restarts on config changes, PGO-optimized Linux x86 builds, and a wave of analyzer and codex bug fixes improving generics, intersection types, and type inference accuracy.

✨ Features

Analyzer

  • PSL Psl\Type\nullish() return type provider: Added type narrowing support for Psl\Type\nullish(), complementing existing PSL type providers (#1390, #1391)
  • PSL Psl\Async\all() and Psl\Async\concurrently() return type providers: Both functions now preserve sealed array shapes, list structure, and non-empty status. For example, Psl\Async\all(['foo' => $awaitableString, 'bar' => $awaitableInt]) correctly returns array{foo: string, bar: int} (#1423)

Linter

  • New no-alternative-syntax rule: Detects alternative control structure syntax (if/endif, while/endwhile, etc.) and suggests using brace-based syntax instead (#1313)
  • New no-short-bool-cast rule: Flags !!$expr double-negation casts and suggests using (bool) $expr for clarity (#1312)
  • New prefer-pre-increment rule: Suggests ++$i over $i++ and --$i over $i-- when the return value is unused, as pre-increment avoids an unnecessary copy (#1311)
  • Added fixer for no-alias-function rule: The no-alias-function rule now supports automatic fixing, replacing aliased PHP functions with their canonical equivalents (#1297)

CLI

  • minimum-fail-level configuration option: The minimum-fail-level setting can now be configured in mago.toml under [analyzer], [linter], and [guard] sections, removing the need to pass --minimum-fail-level on every invocation. The CLI flag still overrides the config value (#1343, #1384)
  • Auto-restart watch mode on config changes: mago analyze --watch now monitors the configuration file, baseline file, composer.json, and composer.lock for changes and automatically restarts the analysis session when they are modified (#1402)

🐛 Bug Fixes

Analyzer

  • Fixed false positive ambiguous-object-method-access after method_exists narrowing: When method_exists($this, 'foo') adds a HasMethod intersection type, calling other methods on $this no longer falsely reports ambiguous access (#1413, #1426)
  • Fixed properties not resolved from object shapes in intersection types: Accessing properties on intersection types like stdClass&object{tags: list<Tag>} now correctly resolves the property from the shaped object part (#1387, #1421)
  • Fixed false positive for unimplemented-abstract-property-hook with traits: Concrete properties from used traits now correctly satisfy interface abstract property hooks (#1415, #1420)
  • Fixed $this/static return type not enforced for non-final classes: Returning new self() from a method declared as [@return](https://github.com/return) $this or : static now correctly reports a type mismatch in non-final classes. Anonymous classes are treated as effectively final (#1410, #1411, #1418)
  • Fixed stale dynamic property cache across loops: Dynamic property types are no longer incorrectly cached between loop iterations (#1416, #1417)
  • Fixed parent class template parameter resolution via [@extends](https://github.com/extends): Template parameters inherited through [@extends](https://github.com/extends) annotations are now correctly resolved when accessing shaped array keys (#1412, #1414)
  • Fixed negative numeric string increment/decrement sign: The sign is now preserved when incrementing or decrementing negative numeric strings (#1404)
  • Fixed integer literals exceeding i64::MAX treated as int instead of float: Very large integer literals are now correctly inferred as float (#1405)

Codex

  • Fixed empty array literals incorrectly marked as non_empty: Empty array [] used as a default parameter value is now correctly typed as non_empty=false, fixing false positive docblock-type-mismatch errors when using generic classes with default empty arrays (#1422, #1425)
  • Fixed modulo type range inference: The modulo operator now correctly infers the result type range (#1403)
  • Fixed shift amount bounds checking in constant inference: Shift operations with out-of-range amounts no longer panic (#1400)
  • Fixed list identity comparison using wrong part: Fixed an incorrect comparison in list type identity checks (#1396)

Linter

  • Fixed variable-name rule for underscore-prefixed variables: Variables starting with _ (e.g., $_unused) are no longer flagged by the variable-name naming rule (#1395, #1398)
  • Fixed memory leak in linter rule registry: Replaced Box::leak with owned storage for rule descriptions (#1408)

Formatter

  • Fixed parentheses added unnecessarily around mixed multiplicative operators: Parentheses are now only added when actually needed for operator precedence, not for same-precedence multiplicative operations (#1407)
  • Fixed backslash handling in quote selection: The formatter now correctly handles backslashes when choosing between single and double quotes (#1401)

Type Syntax

  • Fixed overflowing integers in shape keys: Shape keys with integer values exceeding i64::MAX are now rejected instead of silently overflowing (#1406)

Syntax Core

  • Fixed string escaping edge cases: Resolved edge cases in string escape sequence handling (#1394)

Atom

  • Fixed uppercase range in SIMD case folding: Corrected the [A-Z] range used in SIMD-accelerated case folding (#1393)

Database

  • Fixed file read after deletion: The database watcher no longer attempts to read files that have been deleted (#1397)

📖 Documentation

  • Fixed exit code and sort order documentation (#1409)
  • Updated analyzer, linter, and guard configuration references with minimum-fail-level option
  • Added watch mode documentation with automatic restart behavior

🏗️ Internal

  • Enabled PGO (Profile-Guided Optimization) for Linux x86_64 GNU and musl builds (#1399)
  • Added pointer-equality fast path in atomic type comparator (#1236)
  • Re-generated linter documentation for new rules (#1424)

🙏 Thank You

Contributors

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

  • @edsrzf — #1297
  • @npo-mmenke — #1236
  • @ostark — #1311, #1312, #1313
  • @veewee — #1391
  • @xHeaven — #1393, #1394, #1396, #1397, #1400, #1401, #1403, #1404, #1405, #1406, #1407, #1408, #1409, #1414, #1417

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.14.1...1.15.0

1.14.1

Mago 1.14.1

Mago 1.14.1 is a patch release focused on fixing false positives across the analyzer's generics and type inference system. Highlights include proper template inference for spread arguments, template constraint violation detection during class instantiation, correct class-string<T> inference from $object::class, improved array_filter handling for union array types, and fixes for variable definedness tracking in try-catch blocks. The formatter also receives several fixes, and the Docker image now includes git for --staged support.

🐛 Bug Fixes

Analyzer

  • Fixed template inference for spread arguments to variadic parameters: Spread arguments like ...$list where $list is list<Child> now correctly infer the template type (e.g., T=Child) instead of falling back to the constraint default (#1368)
  • Detect template constraint violations during class instantiation: When constructing a generic class like new ViewTable(models: [new Model()]), the analyzer now correctly reports an error if the inferred type doesn't satisfy the template constraint (e.g., T of Textable) (#1355)
  • Fixed $object::class to infer class-string<T> for generic parameters: When $object is typed as a generic parameter T of object, $object::class now correctly produces class-string<T> instead of bare class-string (#1372)
  • Fixed method_exists/property_exists narrowing string to never: When method_exists($className, 'method') is used with a string variable, the variable is now narrowed to class-string in the truthy branch instead of being discarded as never (#1374)
  • Preserved generic class-string<T> identity during match exhaustiveness narrowing: Match expressions that compare a class-string<T> against class constants no longer lose the generic parameter T, preventing false positive return type mismatches (#1375)
  • Fixed array_filter return type for union array types: array_filter now correctly removes nullable types when the input array comes from a match expression or other constructs that produce a union of array types (#1365)
  • Fixed variable definedness tracking in try-catch with multiple leaving paths: When a catch block has multiple exit paths (e.g., throw in one branch and continue in another), variables assigned in the try block are now correctly considered defined after the try-catch (#1352)
  • Fixed false positive for incompatible-property-hook-parameter-type: The analyzer now uses the effective type (considering [@var](https://github.com/var) docblock narrowing) when checking property hook parameter compatibility (#1342)
  • Fixed false positive property-type-coercion for unresolved generic parameters: When instantiating generic classes without explicit type arguments (e.g., new WeakMap()), unresolved generic parameters now use placeholder types instead of constraint defaults, preventing false coercion warnings when assigned to typed properties (#1346)
  • Fixed debug panic from stale by-reference counters: Resolved a debug-mode assertion failure caused by stale by-reference counters during analysis (#1242)

Codex

  • Preserved all union members when replacing class-string generic templates: Fixed a bug where class-string<A>|class-string<B> passed to a class-string<T> parameter would lose one of the union members during template resolution (#1341, #1344)
  • Fixed closure/arrow function skipping in incremental builds: Closures and arrow functions are no longer skipped during incremental builds in watch mode, preventing types from becoming unknown references (#1359, #1363)

Formatter

  • Preserved parentheses around unbounded constructs in binary expressions: Parentheses around require, include, print, and similar constructs are now preserved when used in binary expressions (#1348, #1353)
  • Stabilized chain-breaking heuristic for method chains: Fixed an idempotency issue where method chains with complex arguments could produce different output on repeated formatting passes (#1351, #1371)
  • Removed unnecessary parentheses around new in partial application: The formatter no longer wraps new expressions in unnecessary parentheses when used in array or argument contexts (#1370, #1373)

Linter

  • Marked str-contains/str-starts-with fixers as potentially unsafe: The auto-fixers for these rules are now flagged as potentially unsafe since the transformation may change behavior in edge cases (#1358)

Docker

  • Installed git in Docker image: The Docker image now includes git, enabling --staged options for lint and format commands (#1362, #1369)

🙏 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: https://github.com/carthage-software/mago/compare/1.14.0...1.14.1

1.14.0

Mago 1.14.0

Mago 1.14.0 is a feature-packed release that brings full parent type hint support across the analyzer and codex, two new linter rules, PER-CS 3.0 compliance for the formatter's opening tag placement, a new parameter-attribute-on-new-line formatter setting, authenticated GitHub API requests for self-update, and a batch of bug fixes across the analyzer, formatter, syntax, and prelude.

✨ Features

Analyzer

  • Full parent type hint support: The parent type hint is now fully supported across the codex, analyzer, scanner, builder, and expander — including return types, parameter types, properties, generics, arrays, and nested types (#1249)
  • Type provider for Psl\Type\int_range: Added type narrowing support for Psl\Type\int_range (#1329)

Linter

  • New switch-continue-to-break rule: Detects continue inside switch cases and suggests using break instead, since continue in a switch behaves identically to break in PHP and can mislead readers (#1314)
  • New no-redundant-binary-string-prefix rule: Flags redundant b or B prefixes on string literals that don't contain any bytes outside the ASCII range (#1324)

Formatter

  • opening-tag-on-own-line option for PER-CS 3.0 compliance: Ensures <?php is placed on its own line in pure PHP files by default, per PER-CS 3.0 Section 3. The setting defaults to true but can be set to false to preserve the previous behavior. Template files are unaffected (#1293)
  • parameter_attribute_on_new_line setting: When enabled (default, PER-CS 12.2 compliant), parameter attributes are placed on their own line before the parameter (#1298)

CLI

  • Authenticated GitHub API requests for self-update: mago self-update now uses GitHub tokens (from GITHUB_TOKEN, or GH_TOKEN) for authenticated API requests, avoiding rate-limit failures (#1284)

Docker

  • Alpine base image: The Docker image now uses alpine:3 instead of scratch, providing a shell (/bin/sh) so CI runners like GitLab CI can execute commands correctly (#1285)

🐛 Bug Fixes

Analyzer

  • Fixed false positive possibly-null-property-access with null-coalesce in short-circuit evaluation: The reconciler now correctly narrows types when ($x ?? null) === null is used in short-circuit || expressions (#1278)
  • Fixed false positive redundant-null-coalesce on uninitialized typed properties: Typed properties without default values are now correctly marked as possibly-undefined, preventing false redundant-null-coalesce diagnostics when using ??= (#1286)
  • Fixed protected method resolution through [@require-extends](https://github.com/require-extends) traits: Protected methods from traits used by a [@require-extends](https://github.com/require-extends) class are now correctly resolved as accessible (#1287)
  • Fixed sealed keyed arrays with disjoint keys merged incorrectly in unions: Sealed keyed arrays with disjoint keys are now kept as separate union members instead of being merged into a single array with all keys made optional (#1291)

Linter

  • Narrowed annotation spans: Cyclomatic-complexity and kan-defect diagnostics now highlight minimal code regions instead of entire class bodies (#1282)
  • Fixed else if flagged by block-statement rule: The block-statement rule no longer incorrectly flags else if (two keywords) as missing a block body (#1299)

Formatter

  • Fixed compact inline array alignment: align-assignment-like no longer pads compact inline arrays into columns (#1321)
  • Fixed parentheses stripped from unbounded constructs in member access chains: Parentheses around require, include, include_once, require_once, and print are now preserved when used as the base of a member access chain (#1322)
  • Fixed brace placement for preserved single-parameter lists: Restored correct opening brace placement for functions/methods with a single parameter when preserve_breaking_parameter_list is enabled (#1290)
  • Fixed trailing comment drift in try-catch-finally chains: Trailing comments on try/catch/finally closing braces no longer cause idempotency issues (#1303)

Syntax

  • Fixed lexer panic on invalid octal array keys in docblocks: The lexer no longer panics when encountering invalid octal literals in docblock types (#1295)
  • Added support for b string prefix: The b and B binary string prefixes are now correctly parsed (#1301)
  • Fixed braced interpolation in heredoc after escaped backslash: \\{$var} in heredoc and shell-execute strings is now correctly parsed as an escaped backslash followed by braced interpolation (#1300)

Prelude

  • Corrected OpenSSL stubs: Fixed incorrect function signatures in the OpenSSL stubs (#1327)
  • Updated zstd stubs: Fixed incorrect function signatures in the zstd stubs (#1328)

🏗️ Internal

  • Updated to Rust 1.94.0 (#1326)
  • Updated sponsors list

🙏 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: https://github.com/carthage-software/mago/compare/1.13.3...1.14.0

1.13.3

Mago 1.13.3

This is a patch release packed with bug fixes across the analyzer, codex, and formatter. The analyzer fixes address several false positives involving generics, union types, type inference, and unreachable code detection. The codex fixes resolve issues with type combination and [@inheritDoc](https://github.com/inheritDoc) inheritance. The formatter fixes resolve issues with concatenation chain formatting and preserve-break propagation.

🐛 Bug Fixes

Analyzer

  • Deduplicated signature compatibility checks in diamond inheritance: Fixed duplicate diagnostics (e.g., incompatible-parameter-name reported multiple times) when a class implements multiple interfaces sharing a common ancestor (#1245)
  • Fixed potentially undefined array element in union types: Fixed incorrect inference when accessing array elements that may be undefined within union types (#1263)
  • Fixed empty array inference for bare generic parameter templates: Fixed a bug where generic type information was lost when passing an empty array to a function with a bare generic parameter template (#1264)
  • Fixed static type resolution in method parameters for final classes: Fixed a false positive less-specific-argument when using generics that reference static in method parameters. static is now resolved using the method context and treated as exact for final classes (#1265)
  • Fixed false positive unreachable code for declarations: Fixed a false positive where class, function, and other declarations following an unconditional return or throw were incorrectly reported as unreachable. Declarations are now excluded from unreachable code diagnostics and dead-code filtering (#1259)

Codex

  • Fixed literal floats dropped from union types during type combination: Fixed a bug where the type combiner's fast path incorrectly dropped literal float types from unions. For example, a T|null parameter where T was inferred as a float would collapse to just null (#1262)
  • Fixed [@inheritDoc](https://github.com/inheritDoc) overwriting narrower child return types: Fixed a bug where [@inheritDoc](https://github.com/inheritDoc) on a child method would replace the child's more specific native return type with the parent's broader docblock type. The inherited type is now narrowed against the child's native return type when the child is more specific (#1266)

Formatter

  • Fixed preserve breaks affecting parent groups: Fixed an idempotency issue where preserve_breaking_* settings caused structural break propagation to parent groups, leading to different output on subsequent formatting passes. Preserve-sourced breaks are now isolated from structural breaks (#1260)
  • Fixed concatenation chains staying hugged in argument lists: Fixed a bug where long string concatenation chains (3+ operands) inside constructor or function arguments were incorrectly "hugged" within the parentheses, causing the concatenation operators to break at the statement's indentation level instead of properly expanding the argument list (#1255)

🏗️ Internal

  • Applied fixes from nightly Clippy (#1274)

🙏 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: https://github.com/carthage-software/mago/compare/1.13.2...1.13.3

1.13.2

Mago 1.13.2

This is a patch release with several bug fixes across the analyzer, formatter, and prelude, along with a batch of performance improvements.

🐛 Bug Fixes

Analyzer

  • False positive for backed properties with set-only hooks: Fixed a false positive where reading a backed property with only a set hook was incorrectly reported as reading a write-only property. In PHP 8.4, backed properties always have an implicit get that returns the backing value — only virtual properties with a set-only hook are truly write-only (#1226)
  • False possibly-undefined keys when building arrays in loops: Fixed a false positive where array shape keys were inferred as optional ('key'?: type) when building arrays incrementally inside a loop with conditional branches (#1230)

Formatter

  • Parentheses stripped from yield in ternary expressions: Fixed a bug where the formatter removed necessary parentheses around yield expressions used as operands in ternary or elvis expressions, producing invalid code (#1231)

Reporting

  • Non-deterministic output ordering: Fixed an issue where running mago analyze multiple times on the same codebase could produce diagnostics in a different order, making diff-based CI checks unreliable (#1232)

Prelude

  • Incorrect function signatures: Corrected the signatures for socket_* and proc_open functions in the built-in stubs (#1239)

⚡ Performance

  • Faster type equality checks: Added fast paths in TUnion::eq to short-circuit common cases (#1225)
  • Reduced allocations in formula clause filtering: Avoid unnecessary allocations when filtering formula clauses during analysis (#1224)
  • Optimized Binary::span edge traversal: Reduced overhead when computing spans for binary expressions (#1229)
  • Reduced per-path overhead in file loading: Lowered per-path allocation costs in load_paths (#1228)
  • Reduced sorting allocations in hierarchy populator: Fewer temporary allocations when sorting class hierarchies (#1233)

🙏 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: https://github.com/carthage-software/mago/compare/1.13.1...1.13.2

1.13.1

Mago 1.13.1

This is a patch release that adds official Docker container image support and documentation for using Mago in containerized environments.

✨ Features

Docker

  • Official container image: Mago is now published as a multi-arch container image (linux/amd64 and linux/arm64) to the GitHub Container Registry on every release. The image is built from scratch using statically-linked musl binaries, weighing only ~26 MB. Available tags include latest, exact versions (1.13.1), minor (1.13), and major (1) (#1125)

📖 Documentation

  • Docker recipe: Added a comprehensive Docker usage guide covering quick start, CI/CD examples for GitHub Actions, GitLab CI, and Bitbucket Pipelines, shell alias setup, and known limitations
  • Installation page: Added Docker as an installation method
  • GitHub Actions recipe: Added an alternative workflow using the Docker container image

🙏 Thank You

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.13.0...1.13.1

1.13.0

Mago 1.13.0

This release adds a new no-inline linter rule, fixes a formatter idempotency bug with comments in ternary expressions, fixes missing type diagnostics for abstract methods, replaces the self_update dependency to unblock cargo install mago, and improves self-update error messages.

✨ Features

Linter

  • New no-inline rule: Disallows inline content (text outside of <?php tags) in source files. Most modern PHP applications are source-code only and do not use PHP as a templating language. Inline content before <?php, after ?>, or between PHP tags is typically unintentional and can cause issues such as unexpected output or "headers already sent" errors. This rule is disabled by default and is intended for codebases that do not use PHP templates (#1220)

🐛 Bug Fixes

Analyzer

  • Missing type diagnostics for abstract methods: Fixed a bug where missing-parameter-type, missing-return-type, and imprecise type diagnostics were not reported for abstract methods in classes and interfaces (#1223)

Formatter

  • Comment reordering in ternary expressions: Fixed an idempotency bug where line comments between ?/: and their operands in ternary expressions were reordered and merged on each formatting pass, requiring up to 4 passes before stabilizing. Comments after ? and : are now correctly preserved in their original order (#1221)

CLI

  • Better self-update error messages: When a release does not include a binary for the current platform, the error message now lists the user's detected platform and the available assets, making it easier to diagnose the issue (#1218)

🏗️ Internal

  • Replaced self_update crate with custom updater module: The self_update crate was pinned to a git ref, which blocked cargo publish and cargo install mago. It has been replaced with a minimal, purpose-built updater using ureq, self-replace, and direct archive handling
  • Fixed WASM playground cache: The playground now busts the WASM cache on every release, ensuring users always get the latest version (#1219)

💛 Sponsors

Mago is developed and maintained as an independent open-source project. A special thank you to all of our sponsors whose support makes continued development possible:

Building and maintaining a tool like Mago takes a significant amount of time and effort. While we are incredibly grateful for the support we receive, it does not yet cover full-time development. If Mago is saving you time or improving your codebase, please consider supporting its development through GitHub Sponsors or an enterprise support contract with Carthage Software.

🙏 Thank You

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.12.1...1.13.0

1.12.1

Mago 1.12.1

This is a patch release with bug fixes for the analyzer and formatter.

🐛 Bug Fixes

Analyzer

  • False positive duplicate-array-key with spread operator: Fixed a false positive where explicit keys following a spread expression (e.g., [...self::DEFAULTS, 'title' => 'Override']) were incorrectly flagged as duplicate keys. Overriding spread keys with explicit entries is a common PHP pattern and is no longer reported (#1215)

Formatter

  • Parentheses stripped from non-associative comparison chains: Fixed a bug where the formatter removed necessary parentheses from chained comparison expressions (e.g., ($a === 'b') === $c was flattened into the invalid $a === 'b' === $c). PHP declares comparison operators as non-associative, so chaining them without parentheses is a parse error (#1216)
  • Attribute formatting for anonymous classes: Fixed inconsistent formatting of attributes on anonymous classes. Attributes are now correctly placed on the next line after the new keyword, aligning with the PER-CS specification (#1115, #1210)

🏗️ Internal

  • Specified Rust edition in .rustfmt.toml (#1212)
  • Updated dependencies

🙏 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: https://github.com/carthage-software/mago/compare/1.12.0...1.12.1

1.12.0

Mago 1.12.0

This release adds clickable file paths in terminal output via OSC 8 hyperlinks, a --fail-on-remaining flag for CI workflows using --fix, improved --staged handling for partially staged files, and several bug fixes in the analyzer, linter, and configuration system.

✨ Features

Reporting

  • Clickable file paths via OSC 8 hyperlinks: File paths in diagnostic output are now clickable in terminals that support OSC 8 hyperlinks (iTerm2, Wezterm, Kitty, Windows Terminal, Ghostty, and others). Clicking a path opens the file directly in your editor at the correct line and column. Configure via editor-url in mago.toml or the MAGO_EDITOR_URL environment variable with %file%, %line%, and %column% placeholders. Supported in rich, medium, short, and emacs reporting formats. Hyperlinks are automatically disabled when output is piped or when --colors=never is used (#1188)

CLI

  • --fail-on-remaining flag for lint and analyze: When using --fix, Mago now exits with code 0 even if some issues could not be auto-fixed. The new --fail-on-remaining flag restores a non-zero exit code when unfixed issues remain, making it easy to enforce that all issues are resolved in CI pipelines (#1208)
  • Improved --staged --fix handling: When using --staged with --fix, Mago now detects partially staged files and only processes the staged content. Fixed files are automatically re-staged so that fixes are included in the commit without accidentally staging unstaged changes (#1199)

🐛 Bug Fixes

Analyzer

  • Broad type subtraction with generic parameters in negated assertions: Fixed incorrect removal of broad types (e.g., object, iterable) when subtracting generic type parameters in negated assertion contexts, which could produce false redundant-condition diagnostics (#1207)
  • Type narrowing from identity comparison with typed variables: The analyzer now correctly narrows variable types from identity comparisons (===) with other typed variables. Previously, the left-hand variable could remain typed as mixed after comparison with a more precisely typed value (#1206)

Linter

  • Overlapping edit detection in TextEditor: Fixed a long-standing bug where adjacent non-empty text edit ranges were incorrectly rejected as overlapping, causing auto-fix failures in rules like array-style and str-starts-with when multiple fixes applied to nearby code (#877)

Configuration

  • XDG_CONFIG_HOME fallback: When XDG_CONFIG_HOME is not set, Mago now correctly falls back to $HOME/.config before checking $HOME, matching the XDG Base Directory Specification. Previously, only $HOME was checked, causing global configuration files in ~/.config/mago.toml to be ignored (#1211)

📖 Documentation

  • Configuration file discovery: Documented the full configuration file lookup order: workspace directory, $XDG_CONFIG_HOME, ~/.config, ~ (#1211)
  • Editor integration guide: Added documentation for the editor-url configuration option with URL templates for VS Code, Cursor, Windsurf, PhpStorm/IntelliJ, Zed, Sublime Text, Emacs, and Atom (#1188)
  • Default PHP version corrected: Fixed the documentation to accurately state that the default PHP version is release-dependent (latest stable supported by the Mago release), not a hardcoded value (#1174)

🙏 Thank You

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.11.0...1.12.0

1.11.0

Mago 1.11.0

This release adds a --staged flag for seamless pre-commit hook integration, a new imprecise-type analyzer diagnostic for bare array/iterable type hints, support for class-like constants in array shape keys, and several bug fixes across the analyzer, formatter, and Composer installer.

✨ Features

CLI

  • --staged flag for lint and analyze: New --staged flag restricts linting and analysis to files currently staged in Git, making pre-commit hook setup straightforward. When combined with --fix, changed files are automatically re-staged so that fixes are included in the commit (#1199)

Analyzer

  • imprecise-type diagnostic for bare array/iterable type hints: The analyzer now warns when parameters, returns, or properties use bare array or iterable type hints without a corresponding docblock annotation specifying the element types. This encourages more precise type documentation across your codebase (#1191)

Type System

  • Class-like constants as array shape keys: PHPDoc array shapes now support class constants as keys (e.g., array{MyClass::FOO: string, MyEnum::Bar: int}), enabling more precise type definitions for constant-keyed arrays (#1190)

🐛 Bug Fixes

Codex

  • Type aliases in class-string<T> generics: Fixed a bug where type aliases (e.g., [@psalm-type](https://github.com/psalm-type), [@phpstan-type](https://github.com/phpstan-type)) used as the generic parameter in class-string<T> annotations were not resolved, causing false positives (#1202)
  • [@inheritDoc](https://github.com/inheritDoc) across intermediate classes: Fixed false incompatible-parameter-type positives when a child class inherited docblock types from a grandparent through an intermediate class that didn't redeclare the method (#1189)

Analyzer

  • Improved #[Override] error for trait methods: When #[Override] is used on a trait method that doesn't override any parent, the diagnostic now suggests adding a [@require-implements](https://github.com/require-implements) annotation to the trait if the method is intended to override an interface method (#1192)

Formatter

  • Parentheses around elvis/ternary in property access chains: The formatter no longer strips necessary parentheses from expressions like ($a ?: $b)->method() or ($a ? $b : $c)->prop, which would change the runtime semantics (#1198)

Prelude (Type Stubs)

  • Incorrect [@pure](https://github.com/pure) annotations removed: Removed incorrect [@pure](https://github.com/pure) annotations from debug_zval_dump(), error_log(), phpinfo(), and php_sapi_name() which could mask side-effect analysis (#1195)
  • Multiple stub corrections: Fixed several incorrect type stubs affecting analysis accuracy

Composer

  • Windows binary download fixed: The Composer installer now correctly downloads .zip archives for Windows MSVC targets instead of requesting non-existent .tar.gz files, which caused 404 errors when invoking vendor/bin/mago (#1196)
  • Installer refactored with proper architecture support: The installer script has been reorganized into composable, documented functions under the Mago\Internal namespace. Unsupported architecture targets have been removed and ARM v5/v6 detection has been added, with clear error messages when a platform has no pre-built binary

📖 Documentation

  • Reserved MAGO_ environment variable prefix: Documented that all environment variables starting with MAGO_ are reserved for configuration. Unrecognized MAGO_-prefixed variables (e.g., from CI tools) cause configuration errors. The docs now explain how to diagnose and resolve this (#844)
  • Dynamic benchmarks page: The benchmarks documentation page now fetches live data from the PHP toolchain benchmarks dashboard instead of using hardcoded numbers, keeping performance comparisons up to date
  • Pre-commit hook recipes: Updated documentation with examples for using --staged with --fix to automatically fix and re-stage files in pre-commit hooks

🙏 Thank You

Contributors

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

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.10.0...1.11.0

1.10.0

Mago 1.10.0

This release introduces wildcard pragma suppression ([@mago-ignore](https://github.com/mago-ignore) all), auto-fix for unused pragmas, a new just-in-time binary download for Composer, and a large number of bug fixes across the analyzer, linter, formatter, and type system.

✨ Features

Collector (Pragma System)

  • [@mago-ignore](https://github.com/mago-ignore) all / [@mago-expect](https://github.com/mago-expect) all wildcard pragmas: You can now use all as the issue code in suppress pragmas to suppress all issues within a category (e.g., [@mago-ignore](https://github.com/mago-ignore) lint:all) or across all categories (e.g., [@mago-ignore](https://github.com/mago-ignore) all). This is especially useful for legacy code where listing individual codes is impractical (#1034)
  • Auto-fix suggestions for unused pragmas: When running mago lint --fix or mago analyze --fix, unused [@mago-ignore](https://github.com/mago-ignore) and unfulfilled [@mago-expect](https://github.com/mago-expect) directives are now automatically stripped. The fixer handles three cases: removing a single code from a comma-separated list, removing a directive line from a multi-line comment, or deleting the entire comment when all pragmas are unused (#1187)

Linter

  • check-functions option for prefer-first-class-callable: The rule now supports a check-functions config option (default: false). When disabled, the rule only suggests first-class callable conversion for method and static method calls, avoiding false positives with internal PHP functions that throw ArgumentCountError on extra arguments (#1147, #1160)
  • exclude-setters-and-constructors option for no-boolean-flag-parameter: The rule now supports excluding setter methods and constructors from boolean flag parameter detection, reducing noise for legitimate boolean setter patterns (#1155)

Composer

  • Just-in-time binary download: The Composer plugin has been replaced with a lightweight wrapper script that detects the platform and downloads the correct Mago binary on first run. This eliminates the need to allow a Composer plugin and supports multi-platform environments (e.g., Docker on macOS with mounted volumes) (#1141, #1159)

CLI

  • Fully resolved formatter settings in config output: The mago config --show formatter command now displays all resolved formatter settings flattened alongside excludes, matching the TOML configuration structure. The --schema output has also been updated to reflect the flat structure. Previously, only {"excludes": []} was shown (#1180)

🐛 Bug Fixes

Analyzer

  • False positive redundant-condition for is_float() on int|float union: The int ⊂ float containment rule is now guarded by assertion context, preventing incorrect redundant-condition and redundant-type-comparison diagnostics when is_float() or is_double() is called on int|float variables (#1186)
  • Bogus diagnostic when writing an unknown array index: Fixed false undefined-string-array-index errors when writing to (not reading from) unknown array keys with allow-possibly-undefined-array-keys set to false (#1168, #1171)
  • Class names reported as written instead of normalized: The catch-type-not-throwable diagnostic now shows class names in their original casing instead of all-lowercase (#1185)
  • Multiple incremental analysis bugs in watch mode: Fixed several issues where editing one file would clear diagnostics in other files, and body-only changes would lose existing errors. The populator now correctly clears invalid_dependencies during class-like metadata re-population, and the incremental analysis service properly tracks per-file issues (#1176, #1178)
  • False positive missing-magic-method with trait [@property](https://github.com/property)/[@method](https://github.com/method): Real inherited properties and methods from parent classes now correctly override trait pseudo [@property](https://github.com/property) and [@method](https://github.com/method) annotations, preventing false positives when a trait declares magic accessors that shadow real parent members (#1184)
  • Mixin types inherited from parent classes: Mixin types declared via [@mixin](https://github.com/mixin) on parent classes are now correctly inherited by child classes during method and property resolution (#1169)

Linter

  • prefer-arrow-function disabled inside constant expressions: Arrow function suggestions are no longer emitted inside constant expressions (e.g., class constant initializers) where closures are the only valid syntax (#1166)

Formatter

  • Pint preset: method-chain-semicolon-on-next-line disabled by default: The Pint preset now correctly defaults this setting to false, matching Pint's actual behavior (#1164)

Syntax

  • yield expression detected inside return statement: The parser now correctly identifies yield expressions when used as a return value (e.g., return yield $value), preventing incorrect diagnostics on generator functions (#1167)

Prelude (Type Stubs)

  • dir() second argument marked as optional: The second parameter of dir() is now correctly annotated as optional, fixing false too-few-arguments errors when calling dir() with a single argument (#1163)

Reporter

  • Writer buffer flushed in watch mode: The JSON reporter now flushes its writer buffer after each report, preventing incomplete JSON output when piped to external tools like VS Code extensions (#1170)

Atom (Case Folding)

  • Case folding matches PHP's behavior for non-ASCII characters: Fixed a bug where non-ASCII characters were incorrectly lowercased using full Unicode rules instead of ASCII-only folding, matching PHP's actual strtolower() behavior. This could cause mago to fail to detect errors resulting from case differences in non-ASCII class names (#1161)

Playground

  • file-name rule disabled by default: The file-name lint rule is now disabled in the Mago playground to reduce noise for single-file examples (#1162)

🙏 Thank You

Contributors

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

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.9.1...1.10.0

1.9.1

Mago 1.9.1

Patch release with several analyzer and formatter bug fixes, plus support for [@psalm-mutation-free](https://github.com/psalm-mutation-free) annotations.

✨ Features

Codex (Type System)

  • [@psalm-mutation-free](https://github.com/psalm-mutation-free) and [@psalm-external-mutation-free](https://github.com/psalm-external-mutation-free) annotations: The codex scanner now recognizes these Psalm annotations and maps them to Mago's internal mutation-free flags, improving interoperability with Psalm-annotated codebases (#1157)

🐛 Bug Fixes

Analyzer

  • Property narrowings preserved across pure and mutation-free function calls: Previously, all property type narrowings were cleared after any function call that received an object argument. The analyzer now preserves property narrowings when the called function is marked as [@pure](https://github.com/pure), [@mutation-free](https://github.com/mutation-free), or [@external-mutation-free](https://github.com/external-mutation-free), since these functions are guaranteed not to mutate the object's state (#1157)
  • Generic type parameters preserved during instanceof narrowing on sealed class hierarchies: When narrowing a generic type (e.g., Result<T>) with instanceof against a sealed inheritor (e.g., Success<T>), the type parameters from the parent type are now correctly carried over to the narrowed type, preventing unexpected never type results (#1156)
  • Non-existent classes detected in [@var](https://github.com/var) annotations and instanceof RHS: The analyzer now reports non-existent-class-like errors for undefined types used in [@var](https://github.com/var) docblock annotations and on the right-hand side of instanceof expressions, matching the existing behavior for parameter types, return types, and property types (#1007)
  • Possibly undefined keys in unions of sealed/unsealed arrays: Fixed incorrect undefined-string-array-index errors when accessing keys on union types containing both sealed and unsealed array variants (e.g., array{foo: int, ...}|array{foo: int}). The unsealed variant's generic parameters are now properly considered when determining whether a key might exist (#1154)
  • Deduplicated undefined key errors across union variants: When accessing a non-existent key on a union of multiple keyed arrays, the undefined-string-array-index error is now reported only once instead of once per union variant

Formatter

  • Trailing comments preserved on opening parenthesis line in multiline parameter lists: Fixed a bug where end-of-line inline comments (e.g., // [@phpstan-ignore](https://github.com/phpstan-ignore) method.unused) on method signatures with multiline parameter lists were incorrectly moved to the next line (#1153)

Prelude (Type Stubs)

  • DateTimeImmutable and DateTimeZone methods annotated as mutation-free: Methods on DateTimeImmutable and DateTimeZone that do not modify state are now annotated with [@mutation-free](https://github.com/mutation-free), preventing false positive property narrowing invalidations when calling these methods (#1157)
  • ReflectionClass::getReflectionConstants() return type: Added missing return type information (#1152)

Documentation

  • Removed undocumented space_after_colon_in_enum_backing_type setting: Removed a reference to a non-existent formatter setting from the configuration reference documentation (#1151)

🙏 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: https://github.com/carthage-software/mago/compare/1.9.0...1.9.1

1.9.0

Mago 1.9.0

This release brings PHP 8.5 deprecation detection, new return type providers for sprintf() and array_map(), generator type inference, several new formatter options, and a large number of bug fixes across the analyzer, formatter, linter, and type system.

✨ Features

Analyzer

  • Return type provider for sprintf() and Psl\Str\format(): The analyzer now resolves return types for sprintf() calls with constant format strings, producing precise literal string types when all arguments are known at analysis time (#1073)
  • Return type provider for array_map() preserving array shapes: When calling array_map() with a typed callback on a keyed array, the analyzer now preserves the array shape in the return type instead of widening to array<key, value> (#1144)
  • Generator key and value type inference: Closures and arrow functions containing yield but no explicit return type annotation now have their Generator<K, V, S, R> type inferred from the yielded keys, values, and return statements (#1150)

Linter

  • PHP 8.5 deprecation rules: Three new rules to prepare your codebase for PHP 8.5:
    • deprecated-cast: Detects deprecated non-canonical type casts ((integer), (boolean), (double), and (binary)) and suggests their canonical replacements
    • deprecated-shell-execute-string: Detects usage of the backtick shell execute syntax (`ls -l`), which is deprecated in PHP 8.5, and suggests using shell_exec() instead
    • deprecated-switch-semicolon: Detects use of semicolons (;) as case separators in switch statements, deprecated in PHP 8.5, and suggests using colons (:) instead
  • Improved mago-ignore / mago-expect diagnostics: When an ignore or expect pragma does not match any issue, the diagnostic now highlights the specific issue code that was not matched, making it easier to identify stale or incorrect pragmas (#1123)

Formatter

  • method-chain-semicolon-on-next-line setting: New option to place the semicolon on its own line when a method chain breaks across multiple lines, equivalent to PHP-CS-Fixer's multiline_whitespace_before_semicolons: new_line_for_chained_calls. Disabled by default, enabled in the Laravel/Pint preset (#1105)
  • null_pipe_last variant for null-type-hint setting: New option that converts ?T to T|null and reorders union types to place null last, providing PER-CS 3.0 compliance for null type positioning (#1133, #1134)
  • Space after /**in single-line doc blocks: The formatter now ensures a space is present after the opening/**in single-line doc blocks (e.g.,/**[@var](https://github.com/var) int _/becomes/\*\* [@var](https://github.com/var) int _/) (#1077)

CLI

  • analyze --list-codes: New flag that outputs all analyzer issue codes as a JSON array of strings, useful for tooling integration (#1146)
  • lint --list-rules --json now includes severity: The JSON output of --list-rules now includes a level field (Error, Warning, Help, or Note) for each rule, matching the information shown in the human-readable table (#1142)

🐛 Bug Fixes

Analyzer

  • Property narrowing invalidation after method calls with object arguments: Property type narrowings and assertion clauses are now correctly invalidated when a method call receives an object as an argument, since the method could mutate the object's state
  • Nullsafe operator false positives: Improved null compatibility checks prevent redundant-nullsafe-operator from producing code-breaking false positives when the nullsafe operator is legitimately needed (#1131)
  • Numeric type subtraction in reconciler: Fixed incorrect paradoxical-condition / impossible-condition errors when narrowing numeric types with is_numeric() checks on multiple variables (#1130)
  • Callable resolution with nullable/falsable types: The analyzer now correctly skips nullable and falsable variants when resolving callable targets, fixing false invalid-callable errors on functions like Closure::bind() (#1127)
  • Docblock nullability merging for expandable types: Fixed a bug where nullability from docblock types was not merged for expandable types (like conditional return types), causing false null-argument positives (#1126)
  • Variadic parameter type: Variadic parameters are now correctly typed as array<K, T> instead of list<T>, since named arguments can produce string keys (#1138)
  • Sealed/unsealed keyed array merging: The type combiner no longer incorrectly merges sealed and unsealed keyed arrays, preserving array shape precision in union types
  • Unknown class handling in type identity comparison: Fixed a crash when comparing types involving unresolved class names (#1145)

Formatter

  • Trailing comments moving to opening brace: Fixed a bug where end-of-line inline comments (e.g., // [@phpstan-ignore](https://github.com/phpstan-ignore) method.unused) on method signatures were incorrectly moved to the opening brace line when using method-brace-style = "always-next-line" (#1124)
  • Echo tag indentation in inline HTML: Fixed incorrect indentation for inline PHP echo tags (<?= ?>) and single-expression echo statements within HTML templates (#1149)
  • Unary prefix comment oscillation: Fixed an idempotency bug where comments inside parenthesized unary prefix expressions (e.g., !(/* comment */ $x)) would oscillate between different positions on each format pass (#1135)
  • Print width with indentation: The formatter now correctly accounts for the current indentation level when calculating print width for breaking calls with zero-argument call arguments (#993, #1136)
  • Method chain semicolon placement for nested chains: The method-chain-semicolon-on-next-line setting now correctly applies only when the method chain is the direct expression of the statement, not when a chain appears nested inside another expression (e.g., as a function argument)

Linter

  • no-trailing-space fixer panic on CRLF files: Fixed a panic when the fixer encountered multibyte characters on lines with CRLF line endings (#1137)

Lexer

  • Namespaced identifiers in braced string interpolation: The lexer now correctly handles namespaced constant identifiers used as array keys inside braced string interpolation (e.g., "{$arr[Foo\BAR]}") (#1128)

Prelude (Type Stubs)

  • parse_str() [@param-out](https://github.com/param-out) type: Fixed the output parameter type annotation for parse_str() (#1140)
  • PHP 8.5 deprecation stubs: Updated type stubs to mark functions and features deprecated in PHP 8.5
  • Closure::bind() stubs: Added proper stub definitions to prevent false invalid-callable errors (#1127)

Composer

  • Runtime function stubs: Added function stubs for Composer runtime compatibility, preventing errors when the Mago Composer plugin is loaded in a PHP context (#1122)

🏗️ Internal

  • Updated prelude stubs for PHP 8.5 deprecations
  • Added IssueCode::all() method for listing all analyzer codes
  • Added RuleEntry struct for serializing linter rules with metadata and severity level

🙏 Thank You

Contributors

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

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.8.0...1.9.0

1.8.0

Mago 1.8.0

This release delivers major improvements to the incremental analysis engine for watch mode, new type narrowing capabilities, return type providers for filter_var() / filter_input(), and a large number of bug fixes across the analyzer, linter, formatter, and type system.

✨ Features

Analyzer

  • is_a() and is_subclass_of() type narrowing: The analyzer now narrows types after calls to is_a() and is_subclass_of(), including support for class-string parameters (#1102)
  • Return type providers for filter_var() and filter_input(): These functions now return precise types based on the filter and flags arguments (e.g., FILTER_VALIDATE_INT returns int|false, FILTER_VALIDATE_EMAIL with FILTER_NULL_ON_FAILURE returns string|null) (#1117)
  • Discriminated union narrowing: When narrowing a union of keyed arrays (e.g., array{valid: true, result: string}|array{valid: false, errorCode: string}), the analyzer now correctly filters out incompatible variants based on the narrowed key type, instead of blindly overwriting all variants. This also works for object property narrowing on union types (#1093)

Linter

  • no-isset array access ignore option: The no-isset rule now supports an allow-array-access option, allowing you to flag isset($var) while still permitting isset($array['key']) for array offset checks (#1097, #1120) by @dotdash

Semantics

  • Enforce parentheses for immediately invoked closures: The semantics checker now flags function() { ... }() as error, requiring parentheses around the closure for immediate invocation (#1118)

⚡ Performance

Incremental Analysis Engine

The watch mode (mago analyze --watch) received a complete overhaul of its incremental analysis pipeline:

  • Signature-only fingerprinting: Body-only changes (e.g., changing a function implementation without modifying its signature) now skip cascade invalidation, resulting in significantly faster re-analysis cycles
  • Targeted O(dirty) repopulation: Only changed symbols are re-populated, skipping safe symbols entirely
  • Incremental codebase patching: New extend_ref and remove_entries operations allow fine-grained metadata updates without rebuilding the entire codebase
  • Safe symbol restoration: The reference graph now supports restoring safe symbols and targeted cleanup, reducing unnecessary re-analysis
  • Body-only docblock resolution: Fixed a bug where body-only changes left docblock type references unresolved, causing spurious non-existent-class-like errors in watch mode
  • Improved file watcher stability: Better debounce handling, stability checks, and explicit path handling for the file watcher
  • Watch mode is no longer experimental: The experimental warning has been removed

🐛 Bug Fixes

Analyzer

  • require-extends/require-implements resolution: Members from [@require-extends](https://github.com/require-extends) and [@require-implements](https://github.com/require-implements) types are now correctly resolved (#1064, #1070)
  • Unused property false positive with trait overrides: Properties that override trait properties via constructor promotion are no longer incorrectly flagged as unused (#1119)
  • FQN literal constants: Fully-qualified constant accesses \true, \false, and \null are now correctly recognized (#1099, #1100) by @kzmshx
  • Class-level template parameters for static calls: Template parameters defined at the class level are now properly resolved when making static method calls on generic types (#1103)
  • Abstract method compatibility checking: The get_substituted_method function is now correctly applied to the child method when checking method signature compatibility, fixing false positives with generic abstract method inheritance
  • Mixin type parameters preservation: Type parameters on mixin types (e.g., IteratorIterator) are now preserved during method resolution, fixing incorrect return types (#1106)
  • Integer narrowing with non-variable expressions: Fixed incorrect narrowing when comparing integers against non-variable expressions like function calls (#1088)
  • For-loop condition narrowing: Integer literals in loop conditions (e.g., for ($i = 0; $i < 10; $i++)) are now properly extracted from the AST for type narrowing (#1089)
  • Redundant type comparison in OR conditions: Fixed false positive redundant-type-comparison when using count checks or string narrowing in || conditions (#1112)
  • List count with unknown size: HasAtLeastCount assertions no longer incorrectly set an exact known_count on lists with unknown count, preventing false unreachable-code reports (#1104)
  • Array spread with unknown count: Fixed false positive when spreading a list with unknown count into an array literal (#1108)
  • Class constant [@var](https://github.com/var) docblock type: The analyzer now prefers [@var](https://github.com/var) docblock types over inferred types for class constants, fixing cases where properly typed array values stayed as mixed (#1090, #1094)

Codex (Type System)

  • never as bottom type: never is now correctly treated as a subtype of all types in extends_or_implements checks (#1107, #1109) by @kzmshx
  • Type alias forward references: All local [@psalm-type](https://github.com/psalm-type) / [@phpstan-type](https://github.com/phpstan-type) alias names are now pre-registered before parsing, so aliases can reference each other regardless of declaration order (#1116)
  • String casing detection: Fixed incorrect impossible-condition false positives when comparing strtolower()/strtoupper() results with literals containing non-alphabetic characters (spaces, digits, etc.) (#1086)
  • Bidirectional TUnion equality: Type union equality checks are now bidirectional, preventing false positives when comparing types with different ordering (#1087)

Linter

  • no-redundant-use whole-word matching: Docblock reference checking now uses whole-word matching instead of substring matching, so use Config; is correctly flagged as unused even when ConfigUsage appears in a docblock (#1078)
  • inline-variable-return with by-reference assignment: The fixer no longer inlines assignments of by-reference expressions, which would produce invalid PHP (#1114)
  • prefer-early-continue with non-block body: Fixed the fixer for cases where the loop body is a single statement without braces (#1085) by @chrisopperwall-qz

Formatter

  • First-class callable with argument unpacking: First-class callable expressions (e.g., $foo(...)) are no longer incorrectly treated as breaking expressions, fixing misformatted output (#1091)
  • Redundant grouping parentheses: The formatter now correctly removes redundant grouping parentheses in more cases (#1121) by @Michael4d45
  • Heredoc span calculation: Fixed incorrect span calculation for heredoc/nowdoc strings, which could cause formatting issues (#1092)

Prelude (Type Stubs)

  • explode() return type: Corrected to properly return list<string> instead of non-empty-list<string> when the separator could be empty (#1095)
  • array_slice() return type: Now correctly preserves string keys in the return type (#1096)
  • ldap_sasl_bind() stubs: Updated all arguments except the first to be nullable (#1098)
  • bin2hex() stubs: Improved type definition (#1101) by @veewee

🏗️ Internal

  • Removed experimental warning for analyzer watch mode
  • New IncrementalAnalysisService encapsulating the full incremental analysis pipeline for watch mode and LSP
  • CodebaseDiff::between() for metadata comparison and mark_safe_symbols() for incremental analysis
  • Signature-only fingerprint mode for detecting body-only vs. signature changes
  • Applied clippy fixes across codex and linter crates
  • Re-generated linter documentation

🙏 Thank You

Contributors

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

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.7.0...1.8.0

1.7.0

Mago 1.7.0

This release introduces new type system features, improved type inference for built-in functions, a new linter rule, and numerous bug fixes for the analyzer, formatter, and type system. A significant internal effort also went into reducing dependencies and binary size.

✨ Features

Type System

  • uppercase-string and non-empty-uppercase-string types: Full support for these PHPDoc types in type syntax, codex, and analyzer. This resolves cascading errors when these types were previously unrecognized (#1057)

Analyzer

  • Return type providers for min() and max(): These functions now return precise types based on their arguments (#1074)

  • array_filter() callback parameter type inference: The analyzer now respects the mode argument (ARRAY_FILTER_USE_KEY, ARRAY_FILTER_USE_BOTH) when inferring closure parameter types, fixing incorrect mixed inference for callback parameters (#1031)

  • Switch statement fallthrough analysis: The analyzer now correctly recognizes that non-terminating code paths in a case block fall through to the next case. A case with a conditional return followed by a case that always returns is no longer flagged as missing-return-statement (#1081)

Linter

  • no-redundant-isset rule: New rule that detects redundant arguments in isset() calls. For example, in isset($a, $a['key'], $a['key']['nested']), the first two checks are redundant because isset on a nested access implicitly checks all parent accesses (#769)

CLI

  • --ignore-baseline flag: New flag for lint and analyze commands that temporarily ignores the baseline file, useful for reviewing and fixing baselined issues (#1076)

⚡ Performance

  • Reduced AST size: Optimized AST node representation to reduce memory usage during parsing
  • Leaner binary: Removed 7 third-party dependencies (reqwest, openssl, num_cpus, strum_macros, derivative, strsim, bitflags, async-walkdir), replacing them with standard library equivalents or manual implementations. reqwest/openssl were replaced with ureq/rustls for a significantly smaller and faster-compiling binary

🐛 Bug Fixes

Analyzer

  • Unused method false positives: Methods referenced in literal arrays ([$this, 'method']) and string callbacks ('ClassName::method') are now correctly tracked as used (#1069, #1044)
  • Property/constant access type expansion: Property access and constant access expressions now have their types properly expanded, fixing incorrect type inference (#1071)
  • Non-optional list items in array merge: Fixed an issue where non-optional list items were incorrectly skipped during array merging

Codex

  • [@psalm-require-extends](https://github.com/psalm-require-extends) support in traits: Methods, properties, and class constants inherited from required parent classes via [@psalm-require-extends](https://github.com/psalm-require-extends) or [@phpstan-require-extends](https://github.com/phpstan-require-extends) are now properly resolved in traits, eliminating false non-existent-property, non-existent-class-constant, and unknown-ref errors (#1064, #1068, #1070)
  • Enum types in generic comparator: Fixed incorrect type comparison when enums implement generic interfaces, resolving false invalid-return-type errors (#1061)
  • Platform-aware constant types: Predefined constants like PHP_INT_SIZE, PHP_INT_MAX, and PHP_FLOAT_DIG now use platform-aware range/union types instead of host-specific literal values. PHP_INT_SIZE > 4 is no longer flagged as a redundant comparison (#1084)

Formatter

  • Assignment alignment leaking into nested arrays: When align-assignment-like is enabled, the alignment context from consecutive variable assignments no longer leaks into nested array key-value pairs (#1082)

Linter

  • prefer-first-class-callable with reference captures: Skip suggesting first-class callable syntax when the callee variable is captured by reference in a closure's use clause, as the two forms have different semantics (#1067, #1063) by @kzmshx

Prelude (Type Stubs)

  • array_walk generics: Fixed generic templates for array_walk to properly infer callback parameter types (#1066, #1045) by @ddanielou
  • array_splice type precision: Improved type definitions for array_splice to preserve list<T> types and correctly handle non-array replacement arguments (#1072, #1080)
  • Sorting functions type precision: Enhanced type definitions for sorting functions (usort, uasort, uksort, etc.) to preserve non-empty array types (#1083)

🏗️ Internal

  • Replaced reqwest + openssl with ureq + rustls in self-update module
  • Replaced num_cpus with std::thread::available_parallelism()
  • Replaced bitflags with manual bit flag implementations
  • Removed derivative, strum_macros, strsim, and async-walkdir dependencies
  • Improved array inference logic in codex

🙏 Thank You

Contributors

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

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.6.0...1.7.0

1.6.0

Mago 1.6.0

This release brings new analyzer checks for class design enforcement, new linter rules for file organization, path-scoped ignore/exclusion support, formatter fixes, and numerous bug fixes across the board.

✨ Features

Analyzer

  • class-must-be-final check: New opt-in enforce-class-finality setting that reports classes not declared final, abstract, or annotated with [@api](https://github.com/api)/[@psalm-api](https://github.com/psalm-api) when they have no children (#1054)

    [analyzer]
    enforce-class-finality = true
    
  • missing-api-or-internal check: New opt-in require-api-or-internal setting that requires abstract classes, interfaces, and traits to have [@api](https://github.com/api) or [@internal](https://github.com/internal) annotations, forcing projects to declare extensibility intent (#1055)

    [analyzer]
    require-api-or-internal = true
    
  • Path-scoped ignore entries: The ignore option now supports scoping ignored codes to specific paths (#1037, #1043)

    [analyzer]
    ignore = [
      "mixed-argument",
      { code = "missing-return-type", in = "tests/" },
      { code = "unused-parameter", in = ["tests/", "src/Generated/"] },
    ]
    
  • Literal types for enum properties: Enum name and value properties now return literal types instead of generic string/int, enabling more precise type inference (#1035, #952) by @veewee

  • Severity level in code-count format: The code-count reporting format now includes the severity level for each issue code (#987)

Linter

  • file-name rule: New rule that enforces file names match the class/interface/enum/trait they contain (#1049)

  • single-class-per-file rule: New rule that enforces each file contains at most one class-like declaration

  • Per-rule path exclusions: Linter rules now support path-based exclusions, allowing you to disable specific rules for specific directories (#1037)

    [linter.rules]
    no-isset = { exclude = ["src/Legacy/"] }
    
  • no-isset and readable-literal enabled by default: These rules are now enabled out of the box

  • Removed deprecated rules: The deprecated constant-type, no-boolean-literal-comparison, parameter-type, property-type, and return-type linter rules have been removed — their functionality has been moved to the analyzer

Formatter

  • PER-CS compatibility: inline-empty-function-braces and inline-empty-method-braces now default to true, matching the PER Coding Style specification (#1053)

CLI

  • Multiple --only rules: The --only flag now accepts multiple comma-separated rules (#1046)

⚡ Performance

  • foldhash replaces ahash: Switched to foldhash for faster hashing across the codebase
  • Arc<T> replaces Box<T> in codex: Improves cloning performance for shared metadata

🐛 Bug Fixes

Analyzer

  • Switch statement type narrowing: Non-exhaustive switch statements no longer incorrectly narrow variable types, preventing false redundant null-check warnings (#1038)
  • Type alias resolution in [@var](https://github.com/var) docblocks: Imported type aliases are now correctly expanded in [@var](https://github.com/var) docblock annotations (#1029, #1030)
  • Type alias resolution in template extends: Imported type aliases are now resolved before being used as template arguments in extends/implements (#1040)
  • Template inference for non-generic objects: Non-generic named objects are no longer incorrectly included in template inference, preventing false positives (#1036)
  • FORCE_COLOR support: The FORCE_COLOR environment variable is now respected in reporting output (#1042)
  • CLI argument parser: Disabled enum variants are no longer shown in CLI argument parser output

Linter

  • readable-literal false positive on floats: Fixed the rule triggering on float literals like 123.45 where neither side of the decimal benefits from separators, producing a no-op suggestion

Formatter

  • Empty array line break: Fixed incorrect line break added at the end of empty arrays (#1047, #978) by @Zuruuh
  • Laravel Pint preset parity: Updated the Pint preset to match actual Laravel Pint formatting behavior (#1052) by @nikspyratos

Prelude (Type Stubs)

  • array_walk generics: Added generic templates to array_walk for proper type inference (#1045)
  • curl_multi_exec signature: Corrected the signature of curl_multi_exec (#1033)
  • setcookie duplicate: Removed duplicate definition for setcookie that only accepted the old syntax (#1032)

Documentation

  • Fixed typo on homepage (#1028)

🔒 Security & CI

  • GitHub artifact attestations: Build artifacts now include provenance attestations for supply chain security (#948)
  • Explicit permissions: GitHub Actions workflows now use explicit permissions
  • CD caching: Improved CI/CD pipeline with build caching
  • Removed deprecated atty dependency

🏗️ Internal

  • Bump time from 0.3.46 to 0.3.47 (#1039) by @dependabot
  • Disable incremental build and add memory-debug profile
  • Update sponsors list

🙏 Thank You

Contributors

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

Issue Reporters

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


Full Changelog: https://github.com/carthage-software/mago/compare/1.5.0...1.6.0

1.5.0

Mago 1.5.0

This release brings PSR-11 container support, custom help text for disallowed functions, and several important bug fixes for type narrowing and linter false positives.

✨ Features

Analyzer

  • PSR-11 container support: New psr-container plugin that infers types from class-string arguments when calling ContainerInterface::get(). This eliminates mixed-method-access and mixed-argument errors when using PSR-11 service containers (#1015) by @Noojuno

    [analyzer]
    plugins = ["psr-container"]
    

Linter

  • Custom help text for disallowed functions: The disallowed-functions rule now supports custom help messages per function or extension. Entries can be simple strings or objects with name and optional help fields (#1024)

    [linter.rules]
    disallowed-functions = {
        functions = [
            "eval",
            { name = "error_log", help = "Use MyLogger instead." },
        ],
        extensions = [
            "curl",
            { name = "ffi", help = "FFI is disabled for security reasons." },
        ],
    }
    

🐛 Bug Fixes

Analyzer

  • Nullsafe operator type narrowing: Fixed incorrect type narrowing in the false branch of if statements using nullsafe operators (?->). Previously, the variable was incorrectly narrowed to null in the else branch. Now if ($user?->isAuthorized()) correctly preserves the original type in the else branch, matching the semantics of $user !== null && $user->isAuthorized() (#1025)

  • Redundant instanceof detection: Fixed false positive for undefined variables when a variable is assigned in exhaustive if/elseif branches over a union type. The analyzer now correctly detects redundant instanceof checks and tracks variable assignments across all branches (#1026)

Linter

  • False positive in prefer-first-class-callable: Skip suggesting first-class callable syntax for runtime-dependent call targets where conversion would change evaluation semantics. This includes method chains (adminUrlGenerator()->generateUrl()), nullsafe calls ($obj?->method()), and dynamic method names ($obj->$method()) (#1027, #1020) by @kzmshx

🏗️ Internal

  • Bump bytes from 1.11.0 to 1.11.1 (#1023) by @dependabot
  • Documentation updates and optimizations

🙏 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 were fixed in this release:


Full Changelog: https://github.com/carthage-software/mago/compare/1.4.1...1.5.0

1.4.1

Mago 1.4.1

Patch release with two bug fixes.

🐛 Bug Fixes

Syntax

  • Void cast parsing: Fix parsing of (void) cast expressions (#1021)

Analyzer

  • JSON_THROW_ON_ERROR detection: Detect JSON_THROW_ON_ERROR flag when combined with other flags using bitwise OR (e.g., JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) (#1022, #886) by @kzmshx

🙏 Thank You


Full Changelog: https://github.com/carthage-software/mago/compare/1.4.0...1.4.1

1.4.0

This release brings significant improvements including method call assertions, a new parser configuration section, fault-tolerant parsing, new linter fixers, and numerous bug fixes addressing false positives in the analyzer. A massive thank you to everyone who contributed!

✨ Features

Analyzer

  • Method call assertions: Support for [@assert-if-true](https://github.com/assert-if-true) and [@assert-if-false](https://github.com/assert-if-false) docblock assertions on method calls, enabling type narrowing based on method return values (#763)
  • Unreachable else detection: Detect unreachable else clauses when all enum cases are already handled in if-elseif chains (#356)

Linter

  • Fixer for no-redundant-string-concat: Automatically fix redundant string concatenations (#1018) by @dotdash
  • Fixer for no-trailing-space: Automatically remove trailing whitespace (#1017) by @dotdash
  • Fixer for braced-string-interpolation: Automatically add braces around interpolated variables (#1013) by @dotdash
  • Configurable early-continue threshold: New max-statements-before-early-continue option to control when the early-continue rule recommends refactoring (#979, #975) by @dotdash

Parser

  • New [parser] configuration section: Configure parser-level settings including the ability to disable short opening tag recognition via enable-short-tags = false (#841)
  • Fault-tolerant parsing: Major parser rewrite introducing fault-tolerant parsing, a foundational step towards LSP support. The parser now recovers from syntax errors and continues analysis, providing better diagnostics for files with minor issues (#988) by @azjezz

CLI

  • FORCE_COLOR environment variable: Force colored output even when piping to files or other commands, taking precedence over NO_COLOR (#1012)

⚡ Performance

  • Parser rewrite: Complete rewrite of the parser resulting in ~3x faster parsing and ~2x faster type parsing (#988) by @azjezz
  • MiMalloc allocator: Use MiMalloc on GNU targets for improved memory allocation performance (#999) by @dotdash
  • Atom optimizations: Improve performance of ascii_lowercase_atom for common cases

🐛 Bug Fixes

Analyzer

  • False positive with references in closures: Skip undefined variable errors for reference captures in closure use clauses (#1003, #982) by @kzmshx
  • False positive with intersection types: Resolve properties on intersection types from instanceof checks (#996)
  • False positive in intersection type ordering: Flatten nested intersection types during scanning to resolve method lookup (#1002, #1004)
  • False positive with asymmetric visibility: Allow inheritance of properties with private(set) in child classes (#985)
  • False positive with nullsafe chains: Clear nullsafe_null flag when null is removed during type narrowing (#998)
  • False positive with redundant comparisons in loops: Suppress redundant comparison warnings inside loops where values change (#1006)
  • False positive with dynamic array access: Mark list access with non-literal index as possibly undefined (#1005)
  • False positive with empty() on optional keys: Narrow possibly-undefined array keys after !empty() check (#973)
  • False positive with array building in loops: Convert never-typed array elements to empty arrays inside loops (#976, #968)
  • $_SERVER array shape typo: Fix arvc typo to argc in $_SERVER array shape (#990, #972) by @kzmshx

Formatter

  • Comments not moving with sorted methods: When sort-class-methods is enabled, comments now move with their associated methods instead of stacking on the first method (#994)
  • Preset override bug: User-specified values that match framework defaults are no longer incorrectly overwritten by preset values (#1010)
  • Pint preset accuracy: Set space-around-assignment-in-declare to false in Pint preset to match actual Pint behavior (#1011, #974) by @kzmshx

Linter

  • False positive with aliased imports: Handle aliases correctly in same-namespace import redundancy check (#989, #983) by @kzmshx
  • False positive with reference captures: Skip inline-variable-return when closure has reference capture (#997, #981) by @kzmshx

Docblock

  • UTF-8 boundary panic: Fix panic on multi-byte UTF-8 whitespace in docblock parsing (#971, #967) by @kzmshx

Prelude (Type Stubs)

  • Array pointer functions: Improve signatures of end, prev, next, reset with [@param-out](https://github.com/param-out) annotations to preserve array type after by-reference calls (#980, #984)
  • array_unshift signature: Preserve list type in [@param-out](https://github.com/param-out) annotation (#970)

Codex

  • Constant references in defaults: Resolve constant references in function default parameter values, fixing issues with functions like pathinfo() where the second argument is optional (#969)
  • Internal interfaces: Hide internal interfaces from public API (#1000)

🏗️ Internal

  • Update .gitattributes to exclude more files from export (#1008) by @shyim
  • Add consts to typos dictionary

🙏 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 were fixed in this release:


Full Changelog: https://github.com/carthage-software/mago/compare/1.3.0...1.4.0

1.3.0

Mago 1.3.0

This release comes with significant performance improvements, bug fixes addressing false positives, and new formatter options. A massive thank you to everyone who reported issues and contributed!

✨ Features

Formatter

  • always-next-line brace style: New always-next-line value for brace style options (method-brace-style, function-brace-style, closure-brace-style, classlike-brace-style, control-brace-style) that always places the opening brace on the next line, even for multiline signatures (#897)
  • Drupal preset: Add Drupal coding standards preset (#905) by @klausi

⚡ Performance

This release includes substantial performance optimizations:

  • Reduced lock contention: Optimize empty atom handling to reduce lock contention (#956) by @dotdash
  • Faster lexer/parser: Reduce allocations in lexer and parser for faster parsing
  • Optimized clause lookups: Use AtomSet instead of Vec for clause key lookups
  • Template type improvements: Remove template_types clone and use HashMap for bounds
  • Algebra optimizations: Use index-based tracking to avoid expensive clause cloning
  • Faster context cloning: Use AtomMap and ControlActionSet bitfield for faster block context cloning
  • Type combination thresholds: Add literal type combination thresholds to prevent O(n²) complexity (#939, #940)
  • HashMap for constants: Use HashMap instead of IndexMap for constants and enum_cases where ordering is not required
  • Deterministic method resolution: Use IndexMap for overridden_method_ids to ensure deterministic ordering (#907)

🐛 Bug Fixes

Analyzer

  • Static return type resolution: Resolve static return types correctly in method chains across inheritance (#880, #949, #964)
  • Docblock inheritance: Prevent incorrect docblock inheritance when child class narrows return type (#960, #962)
  • Unused method false positives: Skip trait-overriding methods in unused method check (#945)
  • Unused property false positives: Skip non-private overridden properties in unused property check (#941, #943)
  • Type narrowing for mixed arrays: Preserve type narrowing for array elements when base type is mixed (#946, #947)
  • Nested isset narrowing: Handle nested array access in isset() type narrowing (#900)
  • defined()/function_exists() assertions: Propagate assertions through && operator (#912)
  • Anonymous class constructors: Validate anonymous class constructor arguments (#958)
  • Callable parameter inference: Make callable parameter type inference order-independent
  • Never to string cast: Allow casting never to string
  • Combiner thresholds: Use user-configured combiner thresholds instead of defaults

Formatter

  • Pint preset accuracy: Update Pint preset to match actual Pint formatting (#920)

CLI

  • NO_COLOR support: Respect NO_COLOR environment variable and --colors never flag globally across all output (#922)
  • Baseline fix filtering: Filter out baseline issues before applying fixes (#910)

Docblock

  • By-reference parameters: Allow by-reference parameter syntax (&$param) in [@param](https://github.com/param) tags (#955)

Prelude (Type Stubs)

  • sprintf family: Allow null and bool values in sprintf, printf, sscanf, and fscanf functions (#953)
  • $_FILES superglobal: Mark $_FILES superglobal as potentially empty (#954)

🏗️ Internal

  • Migrate template types from Vec to IndexMap with GenericTemplate struct for cleaner code
  • Replace block context boolean flags with u32 bitflags
  • Skip path separator normalization on non-Windows platforms
  • Drop support for powerpc* and s390x tier 2 targets
  • Update toolchain and dependencies

🙏 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 were fixed in this release:


Full Changelog: https://github.com/carthage-software/mago/compare/1.2.2...1.3.0

1.2.2

Patch release to fix binary builds broken by upstream dependency.

🐛 Bug Fixes

📝 Notes

The 1.2.1 release binaries failed to build due to a breaking change in cargo-pgo v0.2.10 (released January 24, 2026) which changed how arguments are passed to the optimize command. This release contains no code changes-only a CI workflow fix.

Full Changelog: https://github.com/carthage-software/mago/compare/1.2.1...1.2.2

1.2.1

🙈 Oops

Turns out releasing at 4 AM after a break isn't the best idea. Sorry about that!

🐛 Bug Fix

  • Static method closure types: Fixed incorrect return type inference for first-class callables on inherited static methods (e.g., Y::create(...) now correctly returns Y instead of the parent class X)

Full Changelog: https://github.com/carthage-software/mago/compare/1.2.0...1.2.1

1.2.0

First of all, I want to apologize for the delayed release — I've been on a break the past couple of weeks. Thank you all for your patience! 🙏

This release brings significant improvements to the analyzer, including unused code detection, better type inference, and numerous bug fixes addressing false positives reported by the community.

✨ Features

Analyzer

  • Unused code detection: Mago now detects unused private methods and properties (#867, #929)
  • Write-only property detection: Detect private properties that are written to but never read
  • Undefined type reference errors: Report errors when referencing classes, interfaces, enums, or type aliases that don't exist (#891)
  • Symbol namespace tracking: Keep track of symbol namespaces for improved import validation

Linter

  • no-isset rule: New rule to prevent usage of the isset construct (#924) by @djschilling
  • Redundant import detection: Detect redundant same-namespace and root-namespace imports in no-redundant-use rule (#921)
  • Auto-fixers for lowercase-type-hint and lowercase-keyword: New fixers for these linter rules (#911) by @dotdash

Configuration

  • Distribution config files: Add support for loading mago.dist.toml and mago.dist.json files (#903) by @Bleksak

Formatter

  • Inline abstract property hooks: New inline-abstract-property-hooks setting for PER-CS 4.10 compliant property hook formatting (#919)

🐛 Bug Fixes

Analyzer

  • Nullsafe operator chain fix: Track nullsafe-origin null to prevent false positives in chained access (#931)
  • Partial application symbol tracking: Track symbol references from closure creation ($foo->bar(...)) and partial function application ($foo->bar(?, ?)) to prevent false positive unused method errors
  • Namespace imports: Allow imports of symbol namespaces (#933)
  • Magic method parent:: calls: Treat parent:: calls as instance calls for magic methods (#916)
  • #[Override] for [@method](https://github.com/method): Don't suggest #[Override] attribute for [@method](https://github.com/method) pseudo-methods (#914)
  • Null coalesce with never RHS: Suppress false positive for possibly-undefined array index in null coalesce with never RHS (#923)
  • String concatenation memory usage: Add threshold to stop exponential memory usage with complex string concatenations (#908, #909) by @Noojuno
  • Inherited docblock narrowing: Narrow inherited docblock return types based on child's native type

Prelude (Type Stubs)

  • DatePeriod type parameters: Specify IteratorAggregate type parameters for DatePeriod (#932)
  • strpos offset type: strpos supports negative offsets, replace int<0, max> with int (#890) by @Bleksak

Formatter

  • Property assignment alignment: Align property assignments with variable assignments (#926)

Linter

  • CLI flags in no-insecure-comparison: Ignore CLI flags like --password in the rule (#917)

CLI

  • NO_COLOR environment variable: Respect NO_COLOR env variable (#922)

Documentation

  • Slow docs page loading: Fix slow docs page loading (#915) by @shyim

🏗️ Internal

  • Update Nix flake to Rust 1.91.1 (#927) by @Zuruuh
  • Simplify FormatterPreset config deserialization (#879) by @magic-akari

🙏 Thank You

Contributors

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

Issue Reporters

Thank you to everyone who reported issues that were fixed in this release:

🚀 What's Next

More improvements and features coming soon! Stay tuned.

Full Changelog: https://github.com/carthage-software/mago/compare/1.1.0...1.2.0

1.1.0

Mago 1.1.0

This release includes a significant number of new features, bug fixes, and improvements across the formatter, analyzer, linter, and codex.

Note: This release includes a breaking change in the formatter's default behavior for heredoc indentation.

Breaking Changes

Formatter

  • Heredoc content is now indented by default. To restore the previous behavior, set indent-heredoc to false in your configuration.

Features

Formatter

  • Added formatter presets support for quick configuration (psr-12, per-cs, drupal, etc.) (#839)
  • Added following-clause-on-newline option to place else, catch, and finally on a new line (#860)
  • Added uppercase-literal-keyword option for uppercase TRUE, FALSE, NULL literals (Drupal style) (#857)
  • Added empty-line-before-class-like-close setting for empty line before closing brace (#855)
  • Added newline-after-class-like-opening-brace setting (#853)

Analyzer

  • Added support for [@mixin](https://github.com/mixin) docblock annotations
  • Added docblock type mismatch detection for function parameters and return types

Linter

  • Added Pest-specific lint rules for better Pest PHP testing framework support
  • Added auto-fix for the explicit-nullable-param lint (#847)

Guard

  • Added mode selection and ability to skip unconfigured guards

Bug Fixes

Formatter

  • Fixed kebab-case alias support for settings values
  • Fixed indentation in arrays when expression spans multiple lines (#864)

Analyzer

  • Fixed method signature validation to compare against direct interface instead of grandparent interface (#871)
  • Fixed type assertion propagation through null coalesce expressions ($x['foo'] ?? null)
  • Fixed invalid-extend reporting when a readonly class extends a non-readonly class (#873)
  • Fixed static and $this type handling and expansion in template contexts
  • Fixed recursive template constraint inference
  • Fixed array_filter type narrowing to support first-class callables and string literals
  • Fixed readonly property reinitialization in clone for PHP 8.3+
  • Fixed incorrect type narrowing propagation in OR conditions

Codex

  • Fixed type alias resolution in member reference expansion (e.g., self::TypeAlias in [@extends](https://github.com/extends))
  • Fixed parameter type inheritance when method has partial docblock
  • Fixed asymmetric visibility handling for hooked properties
  • Fixed native return type metadata population

Orchestrator

  • Fixed analysis progress bar to use 4-byte emoji for better terminal compatibility (#866)

Internal

  • Improved analyzer onboarding experience in CLI
  • Added Pest and Tempest to the init flow
  • Added support for non-empty-mixed type syntax
  • Added filename fallback for Tempest view detection in linter

New Contributors


Full Changelog: https://github.com/carthage-software/mago/compare/1.0.3...1.1.0

1.0.3

This release includes several bug fixes across the formatter, linter, analyzer, and prelude, along with new features.

As always, we'd love to hear from you! Please keep filing bug reports and feature requests - your feedback is what drives Mago forward.

Our team will be taking a well-deserved break over the holidays. Merry Christmas to those who celebrate, and happy holidays to everyone! We'll be back in full swing in the new year, but rest assured we'll still be keeping an eye out for any critical issues.

Bug Fixes

Formatter

  • Fix idempotency issue with match expressions containing trailing comments on conditions
  • Fix nullable union type formatting (e.g., null|array no longer causes formatting oscillation)
  • Fix call arguments breaking when containing wide binary expressions
  • Avoid early return inside wrap! macro to prevent node stack corruption

Linter

  • Avoid overlapping edits between prefer-arrow-function and prefer-static-closure rules
  • Fix prefer-anonymous-migration rule to correctly flag named migration classes
  • Strip leading backslash from use statement FQNs in no-redundant-use rule

Analyzer

  • Handle multiple docblocks before statements correctly (#818)
  • Detect Closure::bind scope changes for protected member access analysis

Text Edit

  • Detect overlapping edits when insert and replace operations share the same start offset

Prelude

  • Mark getimagesizefromstring second parameter as optional

Features

Linter

  • Add new no-assign-in-argument rule to detect assignments in call arguments (#821)

Analyzer

  • Detect Closure::bind scope changes, allowing accurate analysis of protected/private member access within rebound closures

Internal

  • Refactor formatter to use if let to fix unnecessary_unwrap warning (#781)

New Contributors


Full Changelog: https://github.com/carthage-software/mago/compare/1.0.2...1.0.3

1.0.2

Mago 1.0.2

Bug Fixes

Formatter

  • Fixed idempotent formatting issues with comments inside binary operations (#796)
  • Fixed idempotent formatting for comments inside nested parenthesized binary expressions (#812)
  • Fixed idempotent formatting for trailing comments on if/elseif blocks (#813)
  • Changed inline-empty-classlike-braces default to true per PER-CS specification
  • Relaxed binary inlining rules for improved formatting

Analyzer

  • Fixed dependent template constraint resolution in [@extends](https://github.com/extends) validation
  • Fixed docblock array shapes support for closure parameters
  • array_map now correctly returns non-empty-array when given a non-empty-array (#815)

Docblock

  • Fixed panic on multi-byte UTF-8 whitespace in docblock tags

Prelude

  • Corrected current() return type

Features

  • Docblock variables are now always populated (#802)

Configuration Changes

  • PSR naming rules are now disabled by default (#805)

Documentation

  • Fixed documentation: null-pipe is not a valid value for null-type-hint setting (#814)

Release Assets

  • Added source-code.tar.gz and source-code.zip archives containing full source code
  • Removed Debian packages from release assets

New Contributors


Full Changelog: https://github.com/carthage-software/mago/compare/1.0.1...1.0.2

1.0.1

Mago 1.0.1

This release includes various bug fixes across the analyzer, formatter, codex, and prelude.

Bug Fixes

Analyzer

  • Correctly detect backed vs virtual properties in PHP 8.4 hooks
  • Fix type flags propagation in iterables
  • Improve [@var](https://github.com/var) docblock handling when used on top of an assignment

Codex

  • Treat void as falsy

Formatter

  • Ensure idempotent formatting for trailing comments after breaking parameter lists
  • Keep inline comments on same line before closing tags
  • Preserve block comment positions in argument/parameter lists
  • Ensure method chain idempotence with breaking arguments

Prelude

  • Correct current function return type
  • Add missing DirectoryIterator::isDot method

Full Changelog: https://github.com/carthage-software/mago/compare/1.0.0...1.0.1

1.0.0

Mago 1.0.0

After over 1,000 commits, 13 release candidates, 34 betas, and 12 alphas, we are thrilled to announce Mago 1.0.0 - the first stable release of the Mago PHP toolchain.

Mago is a comprehensive PHP toolchain written in Rust that combines a linter, formatter, and static analyzer into a single, blazingly fast binary. Whether you're working on a small project or a massive codebase with millions of lines, Mago delivers consistent, reliable feedback in seconds.

What's New Since 0.26.1?

If you last used Mago at version 0.26.1, the tool has evolved dramatically:

  • Complete rewrite of the analyzer with deep type inference, generics, and control flow analysis
  • Plugin system for extensible type providers
  • 135 linter rules across 9 categories with auto-fixes
  • 50+ formatter settings with PER-CS compliance
  • Guard for enforcing architectural boundaries and structural rules
  • Property initialization checking
  • Exception tracking with [@throws](https://github.com/throws) validation
  • Baseline support for gradual adoption in existing codebases
  • Web playground at mago.carthage.software/playground

Features

Linter

The linter includes 135 rules organized into 9 categories:

Category Focus
Best Practices Idiomatic PHP patterns and conventions
Clarity Code readability and expressiveness
Consistency Uniform coding style across your codebase
Correctness Logic errors and potential bugs
Deprecation Outdated patterns and deprecated features
Maintainability Long-term code health
Redundancy Unnecessary or duplicate code
Safety Type safety and null handling
Security Potential security vulnerabilities

Many rules include automatic fixes that can be applied with mago lint --fix.

Formatter

The formatter produces clean, consistent code following PER Coding Style with over 50 customization options:

  • Print width, indentation style, and line endings
  • Brace placement and spacing rules
  • Method chain and argument list breaking
  • Closure and arrow function formatting
  • And much more

Run mago format --check in CI to ensure consistent formatting across your team.

Analyzer

The analyzer performs deep static analysis with:

  • Type inference supporting generics, intersections, unions, and keyed arrays
  • Control flow analysis with type narrowing in conditionals
  • 292+ issue types covering type errors, unused code, and more
  • Exception tracking (check-throws) to ensure exceptions are caught or documented
  • Property initialization checking to catch uninitialized typed properties
  • Missing type hint detection for parameters, return types, and properties

Plugin System

The analyzer supports plugins for library-specific type inference:

Plugin Description
stdlib PHP built-in functions (enabled by default)
psl azjezz/psl type providers
flow-php flow-php/etl type providers

Enable plugins in your mago.toml:

[analyzer]
plugins = ["psl", "flow-php"]

More plugins coming soon for Symfony, Laravel, Doctrine, and PHPUnit.

Guard (Architectural Boundaries)

Guard enforces architectural rules and dependency boundaries across your codebase:

[guard.perimeter]
# Defines the architectural layers from core to infrastructure.
layering = [
    "CarthageSoftware\\Domain",
    "CarthageSoftware\\Application",
    "CarthageSoftware\\UI",
    "CarthageSoftware\\Infrastructure"
]

# Creates reusable aliases for groups of namespaces.
[guard.perimeter.layers]
core = ["[@native](https://github.com/native)", "Psl\\**"]
psr = ["Psr\\**"]
framework = ["Symfony\\**", "Doctrine\\**"]

# Defines dependency rules for specific namespaces.
[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Domain"
permit = ["[@layer](https://github.com/layer):core"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Application"
permit = ["[@layer](https://github.com/layer):core", "[@layer](https://github.com/layer):psr"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Infrastructure"
permit = ["[@layer](https://github.com/layer):core", "[@layer](https://github.com/layer):psr", "[@layer](https://github.com/layer):framework"]

[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Tests"
permit = ["[@all](https://github.com/all)"]

Run mago guard to check for architectural violations. This helps maintain clean architecture by:

  • Perimeter rules: Control which namespaces can depend on which others
  • Structural rules: Enforce class modifiers, inheritance, and naming conventions
  • Layer enforcement: Prevent lower layers from depending on higher layers

Production Ready

Mago 1.0.0 is production ready and actively used by companies analyzing millions of lines of PHP code daily. The toolchain has been battle-tested across diverse codebases, from legacy monoliths to modern frameworks.

Gradual Adoption with Baselines

Introducing static analysis to an existing codebase can be overwhelming. Mago supports baselines that let you:

  1. Generate a baseline of current issues: mago analyze --generate-baseline
  2. Ignore existing issues while catching new ones: mago analyze --baseline baseline.toml
  3. Gradually fix issues at your own pace

Fast Bug Fixes

We are committed to fixing reported issues quickly. Most bug reports are addressed within 1-2 days. While false positives may occasionally occur, we treat them as high-priority bugs.

Changes Since RC.13

This release includes the following changes since the last release candidate:

Analyzer

  • properties-of<T> now correctly expands to array<non-empty-string, mixed> for generic object constraints
  • Backed properties with auto-generated hooks are now recognized correctly
  • Improved possibly-null-array-index warnings for keyed arrays with null key coercion
  • Comparison between array and iterable types now handled correctly

Linter

  • Added auto-fixer for the explicit-octal rule

Performance

Mago is significantly faster than traditional PHP-based tools. On the wordpress-develop codebase:

Analyzer Time
Mago 3.88s
Psalm 45.53s
PHPStan 120.35s

Mago analyzes the entire WordPress codebase in under 4 seconds—12x faster than Psalm and 31x faster than PHPStan.

For full benchmarks including the linter and formatter, see mago.carthage.software/benchmarks.

Installation

Shell Script (macOS/Linux)

curl --proto '=https' --tlsv1.2 -sSfO https://carthage.software/mago.sh && bash mago.sh

Homebrew

brew install carthage-software/tap/mago

Composer

composer require --dev carthage-software/mago

Cargo

cargo install mago

For more installation options, see the Installation Guide.

Getting Started

  1. Initialize Mago in your project:

    mago init
    
  2. Run the linter:

    mago lint
    
  3. Run the analyzer:

    mago analyze
    
  4. Format your code:

    mago format
    

For comprehensive documentation, visit mago.carthage.software.

Looking Forward

The 1.0.0 release is just the beginning. Our roadmap includes:

  • PHP Version Manager: Manage multiple PHP versions seamlessly
  • Extension Installation: Install and manage PHP extensions
  • Migration Assistant: Help migrate between PHP versions
  • Framework Plugins: Symfony, Laravel, Doctrine, PHPUnit

Acknowledgements

Inspiration

Mago would not exist without the pioneering work of:

  • Hakana by Matt Brown - The direct inspiration for Mago's analyzer architecture. Hakana demonstrated that a Rust-based PHP analyzer could achieve both speed and precision.
  • Psalm - The gold standard for PHP static analysis, whose type system concepts influenced our approach.
  • PHPStan - Another excellent analyzer that pushed the boundaries of what's possible in PHP static analysis.
  • OXC - Proof that a Rust-based toolchain can revolutionize a language ecosystem.
  • Clippy - Inspiration for our linting architecture.

Carthage Software

Mago is developed and maintained by Carthage Software. For companies interested in supporting Mago or needing expert PHP tooling consulting, get in touch.

Sponsors

Thank you to our sponsors who make this work possible:

Become a sponsor to support Mago's continued development.

Community

Thank you to everyone in our Discord community who reported bugs, suggested features, and provided feedback. Your contributions have been invaluable in shaping Mago into what it is today.

Links


Full Changelog: 0.26.1...1.0.0

1.0.0-rc.13

This release introduces an internal plugin system for the analyzer, adds new linter rules, and continues the focus on minimizing false positives.

New Features

Analyzer

  • Internal plugin system: Adds an extensible architecture for type providers, enabling custom return type inference for functions and methods
  • UnitEnum::cases() return type provider: Properly infers the return type of cases() on unit enums

Linter

  • New instanceof-stringable rule: Suggests using $x instanceof Stringable instead of the verbose is_object($x) && method_exists($x, '__toString') pattern

Bug Fixes

Analyzer

  • parent:: now works in traits with [@require-extends](https://github.com/require-extends) annotation (#732)
  • Objects with __toString method can be cast to string without errors
  • Concrete-to-template type assertions no longer flagged as redundant
  • Explicit type assertions in ternary expressions are preserved correctly
  • Null access warnings suppressed on RHS of nullsafe operations (?->)
  • Redundant type comparisons are now properly reported
  • Template parameters with compatible constraints are allowed
  • Static enum types resolve correctly
  • String callables accepted; declared list parameter types in closures respected
  • Memoized values cleared after any invocation to prevent stale type information

Codex

  • $this handling corrected in type expansion
  • Static type resolves to concrete class when class name is known
  • Strings combiner fixed for accurate type unions
  • Array/list identical check corrected

Formatter

  • Arrow function ternary bodies wrapped in parentheses; outer calls break correctly
  • Argument lists break when an argument has a trailing line comment

Linter

  • Annotation spans narrowed to highlight minimal code regions

Type Syntax

  • Trailing comma allowed after open array additional fields

Full Changelog: https://github.com/carthage-software/mago/compare/1.0.0-rc.12...1.0.0-rc.13

1.0.0-rc.12

Mago 1.0.0-rc.12

This release continues our focus on reducing false positives, improving generic type handling, and enhancing PHP 8.4+ property hooks support.

New Features

Analyzer

  • Uninitialized Property Detection: Added detection for uninitialized properties and missing constructors, helping catch potential runtime errors before they occur.

Formatter

  • Staged Formatting Support: Added fmt --staged command for formatting only staged files, along with a pre-commit hooks guide for seamless CI integration.

Bug Fixes

Analyzer

  • Generic Parameter Literal Equality: Fixed false "impossible condition" warnings when comparing generic parameters to literal values (=== '', === 0, === 1.5, === true, === false) after type narrowing in OR conditions.
  • Property Hook Inheritance: Fixed false positive for unimplemented-abstract-property-hook when a concrete class inherits the hook implementation from a parent class.
  • is_callable() Narrowing: Added support for is_callable() type narrowing on array callables with class-string elements (e.g., array{class-string<Foo>, 'method'}).
  • isset() on Open Array Shapes: Fixed false positives when using isset() checks on open array shapes with mixed values.
  • Dynamic Property Access on stdClass: Allowed dynamic property access on stdClass without triggering errors.
  • Method Override Compatibility: Added verification of method compatibility when overriding non-abstract methods from parent classes.
  • Generic Parameter Constraint Binding: Fixed issue where generic parameters were incorrectly bound when constraints were not satisfied.

Codex

  • Property Hook Types: Fixed population of property hook parameter and return types for proper type checking.
  • Deterministic Output: Ensured consistent ordering of parent classes and interfaces for deterministic analysis results.

Docblock

  • [@method](https://github.com/method) Tag Parsing: Fixed parsing of [@method](https://github.com/method) tags with leading whitespace.

Formatter

  • Heredoc/Nowdoc Comment Preservation: Fixed critical bug where comments before heredoc/nowdoc (e.g., /** [@lang](https://github.com/lang) SQL */) were incorrectly moved after the opening identifier, resulting in invalid code.
  • Consistent Defaults: Made empty_line_after_opening_tag default behavior consistent across configurations.

New Contributors


Full Changelog: https://github.com/carthage-software/mago/compare/1.0.0-rc.11...1.0.0-rc.12

1.0.0-rc.11

This release focuses on reducing false positives, improving type system accuracy, performance optimizations, and introducing new linter rules.

New Features

Linter

  • New property-name rule (#703) Added a new linter rule to enforce consistent property naming conventions across your codebase.

  • New use-specific-assertions rule Introduced a new rule that encourages using specific assertion methods (e.g., assertTrue, assertNull) instead of generic assertEquals/assertSame with literal values for better clarity and intent.

Analyzer

  • Type narrowing for symbol existence checks The analyzer now properly narrows types based on symbol and member existence checks like function_exists(), method_exists(), property_exists(), and defined(). This eliminates false positives when conditionally using symbols after checking their existence.

Bug Fixes

Analyzer

  • Fixed false positives for class-string comparisons and static variable initialization Resolved issues where the analyzer incorrectly flagged valid code involving class-string type comparisons and static variable initialization patterns.

  • Fixed false positive for count() comparison on non-empty-list The analyzer no longer incorrectly reports issues when comparing the count of a non-empty-list with integer values.

  • Fixed list type preservation when narrowing with is_array() Using is_array() on a union type containing a list no longer incorrectly loses the list type information.

  • Fixed interface method resolution for __callStatic The analyzer now only reports interface implementation issues when resolving actual methods, not when resolving magic __callStatic calls.

  • Fixed invalid array access assignment value checking Improved detection of invalid values being assigned through array access expressions.

Reconciler

  • Fixed list type narrowing preserving assertion element types Type narrowing on list types now correctly preserves the element type assertions, preventing false positives in generic list operations.

Docblock

  • Fixed [@method](https://github.com/method) tag with static return type The docblock parser now correctly handles [@method](https://github.com/method) tags that specify static as their return type.

Linter

  • Made strict-assertions rule less strict The strict-assertions rule has been adjusted to reduce noise while still catching problematic assertion patterns.

WASM

  • Matched analyzer default settings The WASM build now uses the same default analyzer settings as the native build for consistent behavior.

Reporting

  • Fixed duplicate issue collection Removed unnecessary duplicate checking when collecting issues, improving performance and correctness.

Performance Improvements

  • Optimized type combiner Significant performance improvements to the type combination logic, reducing analysis time for codebases with complex type operations.

  • Lowered analysis thresholds Adjusted internal thresholds for formula complexity and algebra operations to improve analysis speed on large codebases without sacrificing accuracy.

  • Early return optimization for pragma collection Added early return when no pragmas are present, avoiding unnecessary processing.

Build Improvements

  • Profile-Guided Optimization (PGO) for Tier 1 targets Release binaries for macOS and Windows are now built with PGO, resulting in 5-15% performance improvements.

Full Changelog: https://github.com/carthage-software/mago/compare/1.0.0-rc.10...1.0.0-rc.11

1.0.0-rc.10

Mago 1.0.0-rc.10

This release focuses on reducing false positives across multiple analysis scenarios, improving type system accuracy, and enhancing PHP compatibility.

Bug Fixes

Analyzer

  • Fixed impossible type assertion false positive when comparing interfaces - a class can implement multiple interfaces, so Foo can be Bar at runtime (#707)
  • Corrected template resolution regression affecting PSL and other libraries using generic parameters from class-strings
  • Fixed false positive when checking $value === [] on non-null or mixed types (#701)
  • Resolved false positive unimplemented-abstract-property-hook incorrectly reported on interfaces
  • Infer Iterator key/value types from key() and current() method return types
  • Support float and array-key in string concatenation with improved __toString trait detection
  • Fixed callable-to-array reconciliation and interface throwable checks in catch blocks
  • Added missing $_COOKIE superglobal

Codex

  • Resolved false positive redundant condition for integer range identity comparisons (#706)
  • Fixed trait self type resolution to use intersection with [@require-implements](https://github.com/require-implements)/[@require-extends](https://github.com/require-extends) constraints

Prelude

  • Fixed call_user_func and sprintf stubs with Stringable support

Improvements

Composer

  • Added PHP 8.5 and 8.6 as compatible versions (#699)

Full Changelog: https://github.com/carthage-software/mago/compare/1.0.0-rc.9...1.0.0-rc.10

1.0.0-rc.9

Bug Fixes

  • Fixed stack overflow when analyzing projects with complex generic types - Analysis of certain codebases (including tempest-framework) would crash with a stack overflow due to infinite recursion in type expansion. Added cycle detection to prevent recursive expansion loops while preserving correct type resolution.

Full Changelog: https://github.com/carthage-software/mago/compare/1.0.0-rc.8...1.0.0-rc.9

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport