Releases: carthage-software/mago
Mago 1.0.0-alpha.12
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
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
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)
), anduse (&$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-refparameter
, or was passed as anargument
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 betweenif/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.
- Smarter Docblocks: The scanner now intelligently prioritizes tool-specific tags, preferring
- Stubs:
- Added more stubs for the
imagick
extension, improving analysis for projects that use it. Thanks, @Bleksak!
- Added more stubs for the
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
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 betweenif/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.
- Union types with negated literals (e.g.,
-
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
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 forreadonly
properties have been aligned with the PHP engine. Apublic readonly
property is now correctly treated as havingprotected(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
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 commandexecute
functions are now synchronous. - New
pipeline
Abstraction: The core logic for theanalyze
,lint
, andformat
commands has been extracted into a new, reusablepipeline
module. This provides two distinct orchestrators for different needs:ParallelPipeline
for complex, multi-phase tasks that require a globalCodebaseMetadata
.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
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 coreSpan
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
andalgebra
crates were migrated fromBTreeMap
toIndexMap
for faster lookups. - Memory Sharing with
Cow
andArc
: The database was refactored to use Copy-on-Write strings andArc
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 themago-algebra
crate. - Unnecessary
#[repr(C)]
attributes were removed from AST and span structs. - Added new badges to the project
README
.
New Contributors
- @szepeviktor made their first contribution in #276
- @Bleksak made their first contribution in #273
Full Changelog: 1.0.0-alpha.4...1.0.0-alpha.6
Mago 1.0.0-alpha.4
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 newmago-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 mutableDatabase
for building the file set and an immutable, cheap-to-cloneReadDatabase
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
asclass-string
when$variable
is a genericobject
, instead of incorrectly resolving tomixed
. (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-incount()
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 ofmixed
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
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 afterisset
checks, especially in complex||
conditions. This fixes a major bug where the analyzer would fail to determine that a property wasnon-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 likeif (is_bool($data['x']))
, the type of$data
is now correctly narrowed fromarray{'x': mixed}
toarray{'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
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 returnsint
if the input is anarray<int>
, andfloat
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