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-stringtype support: Addedcallable-string,lowercase-callable-string, anduppercase-callable-stringtypes.function_exists()narrows strings tocallable-string(#1532)range()return type provider: Infers precise return types based on argument values -range(1, 5)returnsnon-empty-list<int<1, 5>>,range(0, 5.0)returnsnon-empty-list<float>,range('a', 'z')returnsnon-empty-list<non-empty-string>(#1510)Psl\Dict\select_keysreturn 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 === falsewhere$visintis now flagged as redundant even insideforeach/for/whileloops, when the types are from fundamentally incompatible categories (#1555)
CLI
--stdin-inputfor 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
--onlyerror for analyze: Instead of a confusing clap suggestion,mago analyze --onlynow explains that the analyzer is not rule-based and suggests--retain-code(#774)
🐛 Bug Fixes
Analyzer
- Fixed callable-string false positives: Resolved
too-many-argumentswhen calling dynamic callable strings,nevernarrowing afterfunction_existsfalse branch, andunreachable-else-clauseforfunction_existschecks. The scalar comparator now properly checksis_callableduring assertion contexts, andcast_atomic_to_callableuses a mixed signature (#1561) - Fixed isset narrowing lost with post-increment index:
$arr[$i++]inside anisset($arr[$i])block now preserves the narrowed type by saving it before the increment side-effect invalidates it (#1556) - Fixed
!emptynot removingfalse/nullfrom unions: When!empty($row['key'])proves a key exists, non-array types likefalseandnullare 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_loopflag when analyzing match arm conditions to fix false non-exhaustive match errors - Fixed
array_mergewith unpacked arguments:array_merge(...$lists)where$listsislist<list<int>>now correctly returnslist<int>instead ofarray<non-negative-int, int>(#1548) - Fixed
net_get_interfacesreturn type: Corrected the prelude stub to match PHP documentation (#1550)
Linter
- Fixed Pest
->notleaking across->and()boundaries: Theuse-specific-expectationsrule no longer carries the->notmodifier 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 lint:halsteadsuppresses 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 withCalculator::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 withinprint-width(#1451) - Fixed
@mago-format-ignore-nextcorrupting 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, andNullTypeHintnow usesnake_casein the generated schema, with PascalCase preserved as aliases for backwards compatibility (#1530)
Docblock
- Support multi-line inline tags:
{@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-asserttemplate narrowing (#1517) - Added
types_share_categoryhelper for loop-aware redundant comparison detection - Added
is_gap_insignificanthelper 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:
- @UweOhse - #1532, #1555, #1556, #1561, #1565
- @innocenzi - #1451, #1452
- @RafaelKr - #774, #1513, #1517
- @bendavies - #1246, #1357
- @mathroc - #1320, #1548
- @ulrichsg - #1562
- @benfreke - #1511
- @KorvinSzanto - #1510
- @kv-codec - #1530
- @steffans - #1459
- @Soean - #1257
Full Changelog: 1.17.0...1.18.0