Releases: carthage-software/mago
Mago 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
@vardocblock handling when used on top of an assignment
Codex
- Treat
voidas 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
currentfunction return type - Add missing
DirectoryIterator::isDotmethod
Full Changelog: 1.0.0...1.0.1
Mago 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
@throwsvalidation - 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", "Psl\\**"]
psr = ["Psr\\**"]
framework = ["Symfony\\**", "Doctrine\\**"]
# Defines dependency rules for specific namespaces.
[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Domain"
permit = ["@layer:core"]
[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Application"
permit = ["@layer:core", "@layer:psr"]
[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Infrastructure"
permit = ["@layer:core", "@layer:psr", "@layer:framework"]
[[guard.perimeter.rules]]
namespace = "CarthageSoftware\\Tests"
permit = ["@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:
- Generate a baseline of current issues:
mago analyze --generate-baseline - Ignore existing issues while catching new ones:
mago analyze --baseline baseline.toml - 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 toarray<non-empty-string, mixed>for generic object constraints- Backed properties with auto-generated hooks are now recognized correctly
- Improved
possibly-null-array-indexwarnings for keyed arrays with null key coercion - Comparison between
arrayanditerabletypes now handled correctly
Linter
- Added auto-fixer for the
explicit-octalrule
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.shHomebrew
brew install carthage-software/tap/magoComposer
composer require --dev carthage-software/magoCargo
cargo install magoFor more installation options, see the Installation Guide.
Getting Started
-
Initialize Mago in your project:
mago init
-
Run the linter:
mago lint
-
Run the analyzer:
mago analyze
-
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:
- JetBrains - For supporting open-source development
- Jason R. McNeil
- Vincent Berset
- Bohuslav Šimek
- TicketSwap
- Zuruh
- Yigit Cukuren
- Marcus Müller
- Enzo Innocenzi
- **[Pablo Largo Mohedano](https://devnix.dev/...
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 ofcases()on unit enums
Linter
- New
instanceof-stringablerule: Suggests using$x instanceof Stringableinstead of the verboseis_object($x) && method_exists($x, '__toString')pattern
Bug Fixes
Analyzer
parent::now works in traits with@require-extendsannotation (#732)- Objects with
__toStringmethod 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
$thishandling 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: 1.0.0-rc.12...1.0.0-rc.13
Mago 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 --stagedcommand 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-hookwhen a concrete class inherits the hook implementation from a parent class. is_callable()Narrowing: Added support foris_callable()type narrowing on array callables withclass-stringelements (e.g.,array{class-string<Foo>, 'method'}).isset()on Open Array Shapes: Fixed false positives when usingisset()checks on open array shapes with mixed values.- Dynamic Property Access on
stdClass: Allowed dynamic property access onstdClasswithout 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
@methodTag Parsing: Fixed parsing of@methodtags with leading whitespace.
Formatter
- Heredoc/Nowdoc Comment Preservation: Fixed critical bug where comments before heredoc/nowdoc (e.g.,
/** @lang SQL */) were incorrectly moved after the opening identifier, resulting in invalid code. - Consistent Defaults: Made
empty_line_after_opening_tagdefault behavior consistent across configurations.
New Contributors
- @magic-akari made their first contribution in #713
Full Changelog: 1.0.0-rc.11...1.0.0-rc.12
Mago 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-namerule (#703)
Added a new linter rule to enforce consistent property naming conventions across your codebase. -
New
use-specific-assertionsrule
Introduced a new rule that encourages using specific assertion methods (e.g.,assertTrue,assertNull) instead of genericassertEquals/assertSamewith 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 likefunction_exists(),method_exists(),property_exists(), anddefined(). 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 anon-empty-listwith integer values. -
Fixed list type preservation when narrowing with
is_array()
Usingis_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__callStaticcalls. -
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
@methodtag withstaticreturn type
The docblock parser now correctly handles@methodtags that specifystaticas their return type.
Linter
- Made
strict-assertionsrule less strict
Thestrict-assertionsrule 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: 1.0.0-rc.10...1.0.0-rc.11
Mago 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
Foocan beBarat 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-hookincorrectly reported on interfaces - Infer
Iteratorkey/value types fromkey()andcurrent()method return types - Support
floatandarray-keyin string concatenation with improved__toStringtrait detection - Fixed callable-to-array reconciliation and interface throwable checks in catch blocks
- Added missing
$_COOKIEsuperglobal
Codex
- Resolved false positive redundant condition for integer range identity comparisons (#706)
- Fixed trait
selftype resolution to use intersection with@require-implements/@require-extendsconstraints
Prelude
- Fixed
call_user_funcandsprintfstubs withStringablesupport
Improvements
Composer
- Added PHP 8.5 and 8.6 as compatible versions (#699)
Full Changelog: 1.0.0-rc.9...1.0.0-rc.10
Mago 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: 1.0.0-rc.8...1.0.0-rc.9
Mago 1.0.0-rc.8
Overview
This release brings significant analyzer enhancements including full property hooks support, unused template parameter detection, and improved type assertion analysis. The linter gains a new variable-name rule, and the prelude is updated with MongoDB, Redis, and URI extension stubs.
Features
Analyzer
- Unused Template Parameter Detection: The analyzer now detects and reports unused template parameters on classes, interfaces, traits, functions, and methods
- Impossible Type Assertion Detection: Added detection for impossible type assertions in
@assertannotations, helping catch logical errors in assertion docblocks - Negation Operator Validation: The analyzer now validates operand types for the negation (
-) operator, catching type errors early
Codex
- Full Property Hooks Support: Complete implementation of PHP 8.4 property hooks, including type inference, visibility checks, and backed property reference validation
- Full
properties-of<T>Support: Theproperties-of<T>type is now fully implemented with proper visibility filtering (public-properties-of<T>,protected-properties-of<T>,private-properties-of<T>)
Linter
- New
variable-nameRule: Enforces consistent variable naming conventions (snake_case or camelCase) with configurable options for parameter checking and promoted property exclusion (#695)
Bug Fixes
Analyzer
- Fixed handling of by-reference values in array literals
- Prevented duplicate catch type errors by inlining the avoid-catching-error heuristic check (#696)
- Redundant negated type assertions (e.g.,
!is_string()on anint) are now reported as warnings instead of impossible errors - Removed incorrect assertion from
fclose()stub
Linter
- The
literal-named-argumentrule now skips the first argument by default, as it's typically self-explanatory. A newcheck-first-argumentoption preserves the original behavior (#694)
Playground
- Fixed an issue where shared playground URLs would immediately lose their state upon loading
Documentation
- Replaced outdated PSR-12 references with PER Coding Style (PER-CS) (#698)
Prelude Updates
- New Extension Stubs: Added stubs for MongoDB, Redis, and URI extensions
- Updated Stubs: Synced DOM, Intl, Reflection, and Standard extension stubs with phpstorm-stubs
Full Changelog: 1.0.0-rc.7...1.0.0-rc.8
Mago 1.0.0-rc.7
Features
Type System
- Added support for
int-mask<1, 2, 3>andint-mask-of<Foo::*>types - Added support for
literal-floattype
Analyzer
- Added support for exception filtering
Playground
- Added online playground for trying Mago in the browser ( mago.carthage.software/playground )
Bug Fixes
Formatter
- Fixed indentation for flattened binary chains with trailing comments
- Fixed idempotence issue with elvis operator in arrow function bodies
Analyzer
- Fixed template constraint resolution to use inferred types instead of declared constraints (#676)
- Fixed type assertions for mixed variables in if statements
- Fixed yoda style handling in assertion scrapper
Codex
- Fixed expansion state not being reset after expanding
Linter
- Fixed
literal-named-argumentrule to ignore variadic functions
Prelude
- Fixed multiple PHP stub docblocks
- Added return type to
ReflectionNamedType::getNamemethod (#673)
CLI
- Updated
initcommand to match the latest configuration schema
WASM
- Fixed duplicate issues reported by both linter and analyzer
- Fixed timing being enabled in wasm builds
Improvements
Performance
- Improved type expander and comparator performance
- Improved metadata populator performance
- Refactored codex to use bitflags for TUnion properties
CLI
- Standardized exit codes
Full Changelog: 1.0.0-rc.6...1.0.0-rc.7
Mago 1.0.0-rc.6
Bug Fixes
Analyzer
- Use constant
REQUEST_TIMEfor$_SERVERto ensure deterministic error messages for baseline matching (#669) - Consider template constraints when checking int/float disjointness (#659)
- Mark
$_SESSIONsuperglobal as possibly undefined (#670) - Allow array destructuring on
ArrayAccessimplementations (#671) - Consider
possibly_undefinedwhen checking redundant nullsafe operator (#672) - Resolve array access type using only matching keys from union index type (#666)
- Check parent classes for pseudo methods to prevent false positive
missing-magic-method(#667)
Codex
- Sort docblock inheritance by hierarchy depth to ensure parents are processed first (#663)
- Resolve templates when inheriting assertions from parent methods (#660)
- Infer arithmetic operations as
int|floatinstead ofmixed(#664) - Treat arrays with required keys as non-empty in containment check (#665)
Semantics & Formatter
- Require arrow functions on RHS of
|>to be parenthesized (#662)
Improvements
Baseline
- Introduced a new loose baseline variant as the default format, which is more resilient to code changes. The loose format matches issues by
(file, code, message)tuple with occurrence counts, making baselines stable even when line numbers shift. The previous strict format (exact line matching) remains available via configuration. (#492)
Full Changelog: 1.0.0-rc.5...1.0.0-rc.6