Skip to content

Releases: carthage-software/mago

Mago 1.0.0-alpha.12

17 Aug 15:19
ee10745
Compare
Choose a tag to compare

Mago v1.0.0-alpha.12 is here, with a major focus on giving you powerful new ways to configure the analyzer! This release introduces issue categories and project-wide issue ignoring, making it easier than ever to tailor the analysis to your needs and gradually adopt Mago on any codebase. We've also completely overhauled the mago init experience to get you started faster.


⚙️ Advanced Analyzer Configuration

This release introduces two powerful new ways to control the issues Mago reports.

New: Issue Categories

All analyzer diagnostics have been grouped into logical categories. This allows you to disable entire groups of checks in your mago.toml to reduce noise, which is perfect for gradual adoption.

All categories are enabled by default. To disable one, for example:

[analyzer]
# Suppress all issues related to null values.
nullable_issues = false
The full list of available categories
  • ambiguity_issues
  • argument_issues
  • array_issues
  • deprecation_issues
  • existence_issues
  • falsable_issues
  • generator_issues
  • impossibility_issues
  • iterator_issues
  • method_issues
  • mixed_issues
  • nullable_issues
  • operand_issues
  • property_issues
  • redundancy_issues
  • reference_issues
  • return_issues
  • template_issues
  • unreachable_issues

New: Ignore Specific Issues

You can now create a project-wide baseline of ignored issues using the analyze.ignore configuration option. This is a great alternative to cluttering your code with numerous @mago-expect pragmas.

[analyzer]
ignore = ["mixed-argument", "unhandled-thrown-type"]

✨ Improved User Experience

Self-Documenting mago.toml

The mago init command now generates a comprehensive configuration file with all available analyzer settings commented out and explained. This makes every option easily discoverable without needing to consult external documentation.

Standardized Configuration

The keys in mago.toml have been standardized to [formatter], [linter], and [analyzer] for consistency. To ensure backward compatibility, aliases for the old keys ([format], [lint]) and common variations ([analyser], [analyze], [analyse]) have been added, so existing configuration files will continue to work without modification.

🛠️ Fixes & Improvements

  • Docblock Parser: The parser now "correctly" handles types terminated with a . (e.g., @return string. A description), improving compatibility with WordPress-style docblocks.
  • Formatter: Fixed a bug where comments and whitespace around PHP closing tags (?>) could be formatted incorrectly, leading to non-idempotent results. (#299)

Full Changelog: 1.0.0-alpha.11...1.0.0-alpha.12

Mago 1.0.0-alpha.11

16 Aug 16:35
fd5e37a
Compare
Choose a tag to compare

This release marks a massive step forward in the power and stability of the Mago analyzer. We're thrilled to introduce a completely rewritten match expression analyzer and a new, optional check for unhandled exceptions.

As a sign of our growing confidence in the tool's stability, the large "EXPERIMENTAL" warning has been removed from the analyze command. This release also includes significant refactoring and polish for both the analyzer and the formatter.


✨ Major Analyzer Features

All-New match Analyzer

The match expression analyzer has been completely rewritten from the ground up to resolve numerous bugs and correctness issues. The new implementation correctly simulates match semantics by sequentially analyzing each arm and narrowing the subject's type, providing far more accurate and useful diagnostics.

Key capabilities include:

  • Accurate Exhaustiveness Checking
  • Precise Unreachable Code Detection
  • Redundant Condition Analysis
  • Correct Type Inference for the match expression's result

New: Unhandled Exception Checking

A new, powerful check for unhandled exceptions has been added. This is an opt-in feature, disabled by default, which can be enabled with the check_throws = true config setting or the --check-throws=true CLI flag. When active, it will report an unhandled-thrown-type error if a function or method can throw an exception that is neither caught nor documented in a @throws tag.


⚙️ Analyzer Improvements & Fixes

  • Improved Error Messages: Diagnostics for by-reference constraint conflicts are now significantly clearer and more helpful.
  • Improved Pragma Applicability (#146): It is now possible to suppress file-level issues, as @mago-expect comments now apply to any issue that contains them (in addition to issues they contain).
  • Internal Refactoring: The logic for reconciling context between conditional branches (if, switch, try, etc.) has been centralized, and a large amount of dead code has been removed from the analyzer's core, improving maintainability.

💅 Formatter Polish

  • More Opinionated, Less Configuration: A number of rarely-used, stylistic options have been removed to promote a single, consistent formatting style across all projects.
  • Intelligent Comment Formatting (#296): The formatter now preserves custom prefixes in multi-line block comments, correctly formatting decorative blocks (like those in Laravel config files) without destroying their layout.
  • Bug Fixes: Resolved several idempotency issues related to newlines after use statements, comments around the closing parenthesis of function calls, and parenthesis handling for unary operators.

✔️ Closed Issues

This release closes the following issues:


Full Changelog: 1.0.0-alpha.10...1.0.0-alpha.11

Mago 1.0.0-alpha.10

15 Aug 11:20
4e240a5
Compare
Choose a tag to compare

This is a landmark release! 🎉 We are thrilled to introduce comprehensive support for by-reference analysis, a major step forward in Mago's ability to understand complex PHP code. Alongside this huge new feature, this release continues our focus on polishing the formatter and improving the accuracy of the type system.


✨ Major Feature: By-Reference Analysis

Mago now understands by-reference semantics! This is a foundational feature that replaces previous "unsupported operation" warnings with deep, accurate static analysis.

  • Core Operations Support: The analyzer now correctly processes all major by-reference operations, including assignment ($a = &$b), passing arguments to by-reference parameters, returning by-reference, foreach loops (foreach ($arr as &$value)), and use (&$var) in closures.
  • Context-Aware Constraint Validation: Mago reports detailed, helpful errors for reference constraint violations. The error messages are tailored to the source of the constraint, explaining why a variable's type is restricted (e.g., because it's a global, static, a by-ref parameter, or was passed as an argument to a method).
  • Robust Branch Reconciliation: The logic for merging variable states from if/else branches has been completely rewritten to accurately handle by-reference variables in conditional scopes.

💅 Formatter Fixes

We've continued to refine the formatter for more stable and predictable output.

  • Idempotent Comment Handling: Fixed several bugs where comments could be lost or mangled during formatting, especially around the closing parenthesis ) of function calls and between if/else blocks separated by blank lines. The formatter is now more robust and idempotent.
  • Whitespace Consistency: The formatter now correctly handles tab characters (\t) as insignificant whitespace, preventing them from causing incorrect layout decisions around trailing comments.

⚙️ Other Fixes & Improvements

  • Analyzer & Codex:
    • Smarter Docblocks: The scanner now intelligently prioritizes tool-specific tags, preferring @psalm-* then @phpstan-* over generic @var and @return tags to ensure the most specific type is always used.
    • Dogfooding: We're now running mago analyze on our own Composer plugin in CI! This has already helped us find and fix a bug in template type inference related to empty arrays.
  • Stubs:
    • Added more stubs for the imagick extension, improving analysis for projects that use it. Thanks, @Bleksak!

Closed Issues

This release closes the following issues:


Full Changelog: 1.0.0-alpha.9...1.0.0-alpha.10

Mago 1.0.0-alpha.9

13 Aug 07:38
b7ad067
Compare
Choose a tag to compare

This release is all about polish! ✨ We've focused heavily on stabilizing and refining the code formatter, squashing a host of community-reported bugs to make the output cleaner, more predictable, and more aesthetically pleasing.

A big thank you to everyone who contributed by reporting issues!


💅 Formatter Polish

This release introduces a significant number of improvements to the code formatter, targeting consistency, comment handling, and smarter line breaking.

  • Smarter Parentheses: The logic for adding parentheses has been completely overhauled. The formatter now uses a more robust, precedence-based system instead of a complex set of specific rules. This change eliminates many unnecessary parentheses that were previously added around binary, ternary, and unary cast expressions, leading to cleaner, more minimal code.

  • Improved Argument Lists & Comments: Formatting for function calls, especially those containing comments or nested calls, is now much more reliable. We've fixed issues with "dangling" comments (e.g., those between a ( and the first argument) and improved the inlining logic to prevent ugly, deeply-indented code for nested function calls.

  • Consistent Line Breaking: Several bugs that caused inconsistent or awkward line breaks have been fixed. Binary expressions used as values in assignments (e.g., 'key' => $a ?: match{...}) and multi-line logical expressions with comments are now formatted correctly and predictably.

  • Idempotency Fixes: Formatting the same file multiple times is now more stable. We've resolved issues with inconsistent spacing before closing tags (?>) and incorrect formatting of comments between if/else blocks to ensure the output is the same every time.


⚙️ Other Fixes & Improvements

Beyond the formatter, this release also includes fixes for the type system and internal refactoring for better maintainability.

  • Type Syntax:

    • Union types with negated literals (e.g., @param -1|string $a) are now parsed correctly.
    • Type error messages have been enhanced to include the original raw type string that caused the error, making it significantly easier to find and debug invalid docblock annotations.
  • Analyzer Internal Refactoring:

    • We've done some internal housekeeping by centralizing context creation in the analyzer and removing duplicated code, improving the overall health and maintainability of the project.

Closed Issues

This release closes the following issues:


Full Changelog: 1.0.0-alpha.8...1.0.0-alpha.9

Mago 1.0.0-alpha.8

12 Aug 07:57
71be327
Compare
Choose a tag to compare

This release is packed with major improvements to the static analyzer, significantly boosting its accuracy and intelligence. We've fixed a whole class of tricky type resolution bugs and introduced a powerful new feature for inferring closure parameter types. We also fixed a critical bug that had disabled all linter rules in the previous version.


🚀 Features

  • Precise Closure Parameter Type Inference: The analyzer is now much smarter when dealing with closures passed as arguments (e.g., to array_map). It can now infer the precise type of a closure's parameters from the call site, eliminating a wide range of false positive errors that occurred within closures. ( Closes #281 )

🐛 Fixes & Improvements

This release focuses heavily on correctness, with numerous fixes to the linter and analyzer.

Linter

  • Linter Plugins Re-enabled: Fixed a critical bug from a recent refactoring that inadvertently disabled all linter plugins. All rules are now active again. (Closes #284)
  • override-attribute Rule: The override rule has been corrected to ignore methods inside traits, preventing false positives.

Analyzer

  • Union & Derived Types in Assertions: Fixed two critical bugs where the analyzer would fail to correctly resolve or compare complex types in @psalm-assert tags. Assertions with union types (e.g., int|string) and derived types (e.g., value-of<T>) are now handled correctly.
  • readonly Property Writes: The analyzer's rules for readonly properties have been aligned with the PHP engine. A public readonly property is now correctly treated as having protected(set) visibility, and writing to it outside of a constructor is now correctly flagged as an error.
  • Inherited @require Annotations: Fixed a bug where @require-implements and @require-extends annotations were not being checked when inherited from parent classes or traits.

Formatter

  • Comment Placement: Corrected an issue where comments placed before ternary operators (?, :) or match arm arrows (=>) were being misplaced during formatting. (Closes #280)

🛠️ Chores & Refactoring

  • Immutability Annotations Temporarily Removed: Support for @immutable, @mutation-free, and related annotations has been temporarily removed. The previous implementation was not robust enough, and we plan to re-introduce these features with a more thorough design after the stable release.
  • Docs: Corrected a reference to mago fix in the documentation. (Thanks, @sasezaki! 🎉)

🤝 New Contributors


Full Changelog: 1.0.0-alpha.7...1.0.0-alpha.8

Mago 1.0.0-alpha.7

11 Aug 06:03
f7c6e33
Compare
Choose a tag to compare

This release introduces a major architectural refactoring, replacing the tokio asynchronous runtime with a CPU-bound parallelism model powered by rayon. This change simplifies the entire concurrency model, improves robustness, and makes the application significantly more efficient for its core analysis tasks.

🚀 Performance & Memory Improvements

The new rayon-based pipeline has resulted in significant optimizations. Benchmarks comparing this release with the previous version show substantial speedups on small-to-medium projects and dramatic memory reductions on large codebases.

PSL Project (Small-to-Medium Codebase)

Command Metric Before After Improvement
mago analyze Speed 152.0 ms 110.2 ms +38% Faster
Memory (RSS) 156.52 MB 127.69 MB -18.4% Memory
Memory (Peak) 146.49 MB 117.16 MB -20.0% Memory
mago format Speed 32.7 ms 30.1 ms +8.6% Faster
Memory (RSS) 44.05 MB 38.39 MB -12.8% Memory
Memory (Peak) 40.85 MB 35.00 MB -14.3% Memory
mago lint Speed 106.8 ms 86.8 ms +23.0% Faster
Memory (RSS) 126.31 MB 126.69 MB ~ Neutral
Memory (Peak) 118.63 MB 119.78 MB ~ Neutral
mago lint -c Speed 87.0 ms 86.3 ms ~ Neutral
Memory (RSS) 122.77 MB 124.67 MB ~ Neutral
Memory (Peak) 115.91 MB 117.60 MB ~ Neutral
mago lint -s Speed 25.5 ms 24.5 ms +4.1% Faster
Memory (RSS) 34.86 MB 29.11 MB -16.5% Memory
Memory (Peak) 31.57 MB 25.64 MB -18.8% Memory

WordPress Project (Large Codebase)

Command Metric Before After Improvement
mago analyze Speed 6.511 s 6.489 s ~ Neutral
Memory (RSS) 1709.19 MB 1375.25 MB -19.5% Memory
Memory (Peak) 1503.40 MB 1168.82 MB -22.2% Memory
mago format Speed 2.049 s 2.068 s ~ Neutral
Memory (RSS) 306.52 MB 293.14 MB -4.4% Memory
Memory (Peak) 218.21 MB 219.52 MB ~ Neutral
mago lint -s Speed 322.6 ms 324.9 ms ~ Neutral
Memory (RSS) 208.25 MB 192.06 MB -7.8% Memory
Memory (Peak) 200.22 MB 186.08 MB -7.1% Memory

🛠️ Architectural Overhaul: Tokio to Rayon

The most significant change in this version is the complete removal of the tokio runtime. While tokio is excellent for I/O-bound tasks, Mago's workload (parsing, type inference, linting) is fundamentally CPU-bound. The switch to rayon's data-parallel model is better suited for this and has several key benefits:

  • Simplified Concurrency: The complex async/await logic and manual task management have been replaced with a cleaner, more direct parallel iterator pattern. All command execute functions are now synchronous.
  • New pipeline Abstraction: The core logic for the analyze, lint, and format commands has been extracted into a new, reusable pipeline module. This provides two distinct orchestrators for different needs:
    • ParallelPipeline for complex, multi-phase tasks that require a global CodebaseMetadata.
    • StatelessParallelPipeline for simple, single-phase tasks like formatting.

🐛 Fixes & Improvements

  • Directory Exclusions: Fixed a bug where directory exclusions were not applied correctly. The previous logic performed an exact path match, which failed for files within an excluded directory. The check now correctly uses starts_with to exclude all nested paths.
  • Path Handling: All user-provided paths (for sources, includes, and excludes) are now canonicalized upfront to ensure consistent and correct behavior.
  • File Size Limit: Added a safeguard to prevent the loader from attempting to read files larger than 1GB, providing a clear error instead of a potential crash.

Full Changelog: 1.0.0-alpha.6...1.0.0-alpha.7

Mago 1.0.0-alpha.6

10 Aug 08:10
686b849
Compare
Choose a tag to compare

This is a major performance-focused release, delivering significant improvements to both execution speed and memory usage. Through a series of targeted optimizations, Mago is now substantially faster on small-to-medium codebases and drastically leaner on large ones.


🚀 Performance Improvements

This release cycle has been dedicated to making Mago faster and more efficient. Benchmarks comparing this release (1.0.0-alpha.6) with 1.0.0-alpha.4 show the following results ( mago analyze ):

Project Metric 1.0.0-alpha.4 1.0.0-alpha.6 Improvement
PSL (Small) Speed 143.2 ms 118.6 ms ~21% Faster
Memory 174.80 MB 154.67 MB -11.5% Memory
WordPress (Large) Speed 7.016 s 6.692 s ~5% Faster
Memory 2065.36 MB 1855.00 MB -10.2% Memory

On the large WordPress codebase, these changes resulted in over 210 MB of RAM savings, making the analyzer more capable of handling massive projects.

Key optimizations include:

  • Span Size Reduction: The core Span struct was reduced in size by 50% (from 32 to 16 bytes), dramatically lowering memory usage.
  • Parallel File Loading: The database loader now uses rayon to discover and read files in parallel, providing a significant speedup on small projects.
  • Efficient Data Structures: Core data structures in the codex and algebra crates were migrated from BTreeMap to IndexMap for faster lookups.
  • Memory Sharing with Cow and Arc: The database was refactored to use Copy-on-Write strings and Arc to share file data cheaply across threads.

🐛 Fixes & Improvements

  • CI & Tests: Corrected a build failure and fixed broken tests that affected the 1.0.0-alpha.5 release.
  • Stubs: Added missing public properties to the DateInterval class stub for more accurate analysis. (by @Bleksak in #273)
  • Analyzer: Improved template type inference when converting between list and keyed arrays.
  • Formatter: Fixed an issue where the formatter would incorrectly add newlines for arguments that were already multi-line constructs. (Closes #244)
  • Typos: Corrected various typos across multiple crates. (@szepeviktor in #276)

🛠️ Chores & Refactoring

  • Docs: Improved the formatting of the main README.md file. (by @szepeviktor in #278)
  • The rand dependency has been removed from the mago-algebra crate.
  • Unnecessary #[repr(C)] attributes were removed from AST and span structs.
  • Added new badges to the project README.

New Contributors


Full Changelog: 1.0.0-alpha.4...1.0.0-alpha.6

Mago 1.0.0-alpha.4

08 Aug 23:27
f5f493b
Compare
Choose a tag to compare

This release introduces a major architectural overhaul with the new mago-database crate, significantly improving performance and laying a better foundation for future development. The type analyzer also receives substantial improvements, fixing several key bugs related to generic and loop variable inference.

🚀 Features & Major Changes

  • New mago-database Crate: The legacy mago-source crate has been completely replaced by the new mago-database crate. This is a significant architectural improvement that provides a cleaner, more performant, and thread-safe foundation for file management. It introduces a clear distinction between a mutable Database for building the file set and an immutable, cheap-to-clone ReadDatabase for high-speed concurrent analysis.
  • Template Inference from Default Parameters: The type analyzer can now infer generic template types from the default values of optional parameters. If you call a generic function but omit an argument that has a default, Mago will now correctly resolve the template based on that default value's type.

🐛 Bug Fixes

This release includes numerous fixes to the type analyzer and formatter, improving accuracy and correctness.

Analyzer

  • Redefined Loop Variables: Fixed a critical bug where variable types redefined inside a loop (especially with a break) were not correctly propagated to the parent scope. This resolves many false positive "dead code" and "redundant condition" errors. (Closes #264)
  • @return static<...> Generics: The analyzer now correctly respects generic type parameters in @return static<T, U> docblocks. This fixes incorrect type inference in methods that transform a collection's generic types, like map. (Closes #266)
  • Nested Array Access: The possibly_undefined state is now correctly propagated through nested array access (e.g., $arr[$key1][$key2]). This eliminates false positive "redundant null coalesce" (??) errors. (Closes #267)
  • ::class on Generic object: Correctly infers the type of $variable::class as class-string when $variable is a generic object, instead of incorrectly resolving to mixed. (Closes #271)

Formatter

  • Script-Terminating Statements: Fixed an issue where extra newlines were added after statements that exit PHP mode (e.g., endif ?>). The logic for removing trailing ?> has also been corrected to avoid removing it when required. (Closes #272)
  • Member Access Formatting: Corrected the eligibility score calculation for member access expressions to improve formatting decisions. (Closes #236)

Other

  • count() Stub: The return type for the built-in count() function has been refined to be more precise for empty vs. non-empty arrays. (Closes #269)
  • Type System: The internal any type has been removed in favor of mixed to reduce confusion and align with standard PHP behavior.

🎉 Acknowledgements

A special thank you to the following community members for their detailed bug reports, which were invaluable for this release:


Full Changelog: 1.0.0-alpha.3...1.0.0-alpha.4

Mago 1.0.0-alpha.3

07 Aug 00:28
3aadf22
Compare
Choose a tag to compare

Hello everyone,

This is a significant alpha release that introduces major improvements to the type analyzer's accuracy, especially around conditional type narrowing. We've also added a new linter rule and a debugging feature for better developer experience.

✨ New Features

  • New Linter Rule: best-practices/no-short-opening-tag
    A new rule has been added to discourage the use of short open tags (<?). While valid, this tag's availability depends on the short_open_tag setting in php.ini. This rule encourages using the full <?php tag for better portability and security.
  • Support for @psalm-trace: For easier debugging and compatibility, the analyzer now supports the @psalm-trace docblock tag. When encountered, it will report the inferred type of the specified variable at that point in the code.

🛠️ Fixes & Improvements

  • Analyzer: Major isset Refinement Fix
    The analyzer now correctly refines the types of object properties after isset checks, especially in complex || conditions. This fixes a major bug where the analyzer would fail to determine that a property was non-null after a guard clause, leading to many false positives. (closes #253)
  • Analyzer: Correct Array Shape Reconciliation
    Fixed a critical bug where assertions on array keys were not being applied correctly. After a condition like if (is_bool($data['x'])), the type of $data is now correctly narrowed from array{'x': mixed} to array{'x': bool}. (closes #256)
  • Docblock Parser: Complex Template Constraints
    The @template tag parser has been fixed to correctly handle constraint types that contain spaces, such as generics (Collection<string, int>) and shape arrays (array{name: string}). (closes #262)
  • Formatter: Empty Statement Formatting
    The formatter no longer skips over empty statements (;), ensuring correct spacing and newline placement around consecutive or trailing semicolons. (closes #263)
  • Semantics: Short Open Tag (<?)
    Removed the incorrect semantic error for short open tags. This tag is valid in modern PHP, and the check has been moved to the new, optional linter rule.
  • Analyzer: @var Assignment
    Fixed an issue where @var types were not being applied correctly in some assignment contexts. (closes #261)

This release significantly enhances the analyzer's ability to understand complex conditional logic. Thank you for your contributions and bug reports!


Full Changelog: 1.0.0-alpha.2...1.0.0-alpha.3

Mago 1.0.0-alpha.2

05 Aug 20:47
a0e3386
Compare
Choose a tag to compare

Just a few hours after our first alpha, we're back with another release packed with new features, important bug fixes, and a ton of polish based on early feedback.

This release significantly enhances the analyzer's intelligence, particularly around inheritance and unused code detection.

✨ New & Noteworthy

Comprehensive Trait Validation is Here!

The analyzer is now much smarter about how it handles traits. Previously a todo comment, we now have a full suite of checks for use statements, including:

  • Invalid Usage: Mago will now report an error if you try to use a class or interface.
  • Contract Enforcement: The analyzer now correctly enforces @require-extends and @require-implements annotations on traits.
  • Inheritance Restrictions: Traits using the @inheritors tag are now correctly validated.
  • Mutability Rules: Using a @mutation-free or @external-mutation-free trait in a mutable class will now be flagged as an error.

Support for PHP 8.5's #[NoDiscard] Attribute

Looking ahead, Mago now understands and respects the new #[NoDiscard] attribute proposed for PHP 8.5. If you discard the return value of a function or method marked with this attribute, Mago will report an error, helping you write more robust and future-proof code.

🐞 Fixes & Polish

More Accurate Stubs for Built-in Functions

We've improved the stubs for several standard PHP functions, leading to more accurate type inference in your projects:

  • array_sum: Now correctly returns int if the input is an array<int>, and float otherwise (by @azjezz, closes #257).
  • in_array: The signature has been corrected to use the array's value type (V) for the $needle parameter, not the key type (by @azjezz, closes #259).
  • array_map: The return type now correctly preserves list type when appropriate (by @Nadyita in #254).
  • array_reverse: A typo in the conditional return type has been fixed (by @azjezz, closes #255).

Smarter Useless Statement Detection

The unused-statement check is now more intelligent and produces fewer false positives:

  • @must-use Fixed: The @must-use docblock tag is now parsed correctly.
  • By-Reference Parameters: Mago will no longer flag calls to @pure functions as useless if they have by-reference parameters, as they can produce side effects (by @azjezz, closes #258)

Drastically Improved Error Messages

We've overhauled the error messages for all inheritance-related checks (extends, implements, and use). The new messages are clearer, more consistent, and provide more context to help you fix issues faster.


New Contributors


Full Changelog: 1.0.0-alpha.1...1.0.0-alpha.2