Open
Conversation
Contributor
Author
|
automergeComment |
c4883b8 to
702f079
Compare
07dc643 to
19adc6e
Compare
f0930dd to
79cda5a
Compare
5fce0b8 to
4ebd3bd
Compare
4ebd3bd to
2c4252f
Compare
0d1f9b5 to
3a1748c
Compare
30d51ad to
8442d6f
Compare
8442d6f to
6282980
Compare
6282980 to
759633b
Compare
759633b to
f1f5ec9
Compare
e543c70 to
c23f26f
Compare
26cae27 to
8e93294
Compare
a55fc12 to
e18fe51
Compare
| datasource | package | from | to | | ---------- | -------------------------------------- | ------- | ------- | | packagist | carthage-software/mago | 1.14.0 | 1.22.0 | | packagist | ergebnis/composer-normalize | 2.50.0 | 2.51.0 | | packagist | php-standard-library/phpstan-extension | 2.0.3 | 2.1.0 | | packagist | phpstan/phpstan | 2.1.40 | 2.1.50 | | packagist | phpunit/phpunit | 11.5.50 | 11.5.55 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
^1.14.0→^1.22.0^2.50.0→^2.51.0^2.0.3→^2.1.0^2.1.40→^2.1.50^11.5.50→^11.5.55Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
Release Notes
carthage-software/mago (carthage-software/mago)
v1.22.0: Mago 1.22.0Compare Source
Mago 1.22.0
Mago 1.22.0 is a small, high-impact release focused on robustness and ergonomics. The analyzer no longer panics on a combine-empty-Vec edge case in keyed-array parameter derivation, diagnostics preserve the source casing of class/function/method names, and the heredoc/nowdoc lexer accepts the full identifier alphabet when detecting closing labels. On the feature side, the
strict-behaviorlinter rule ships an auto-fix,mago initseedscheck-throwswith sensibleError/LogicExceptiondefaults, the docblock type parser relaxes a handful of common tolerant forms, and the stale-baseline warning now reports the exact dead-entry count.✨ Features
Linter
strict-behaviorauto-fix: The rule now attaches aPotentiallyUnsafefix that inserts, strict: true(orstrict: truewhen a trailing comma is already present) before the closing paren of calls toin_array,array_search,base64_decode, andarray_keysthat rely on loose comparison. Not offered whenallow_loose_behavioris enabled or when the call already has an explicit non-truestrict:value, so deliberate opt-outs stay intact (#1656)Init
unchecked-exceptionswithErrorandLogicException:mago initnow writescheck-throws.unchecked-exceptions = ["Error", "LogicException"]into the generatedmago.toml. These classes represent programmer errors (assertion failures, logic flaws) that should surface during development rather than be caught and recovered from, matching how users treat them in practice (#1661)Type Syntax
non-zero-intkeyword: New type keyword that lowers tonegative-int | positive-int, commonly used to express "any non-zero integer".Foo::NULL,Foo::ARRAY,Foo::INT, and other non-hyphenated reserved keywords now parse as valid class-constant references in type positions after::, matching what PHP itself accepts.|in unions: A dangling pipe at the end of a union (common in hand-written or machine-concatenated docblocks) now parses asType::TrailingPipeinstead of erroring; the docblock tag splitter no longer swallows the whitespace after such an operator.FOO_*/*_BARglobal-constant wildcards: Global-constant wildcard patterns now parse asType::GlobalWildcardReference, lowered to a newTReference::Globalselector that resolves against global constants. Coversint-mask-of<*_BAR>/value-of<FOO_*>idioms.Reporting
mago lint/mago analyzedetects that the baseline file contains entries for issues that no longer exist, the warning now reports the exact count instead of a generic "contains entries for issues that no longer exist" message, so you know how many lines are safe to drop (#1662)🐛 Bug Fixes
Analyzer
TArray::Keyedparameters:get_array_parameterstripped acombine() received an empty Vecdebug assertion when a keyed array'sparametersheld an empty key union andknown_itemswas absent. Reachable through the intermediate shape produced by a narrowing read of a foreach key that is then used to write into a sibling-keyed empty array (['attrs' => []]→$p['attrs'][$name]). TheKeyedarm now mirrors theListarm's safety push ofTAtomic::Neverin the empty caseapp\http\controllers\usercontroller::getprofilewas actually declared asApp\Http\Controllers\UserController::getProfile. The renderer now re-derives the original casing from the source file when emitting issue messages and annotations (#1660)Codex
combine()or filter helpers: Two additional sites that could construct an empty atomic vector before callingcombine()(the combiner itself and the union-filter helper) now return a well-formednever-typed union instead of hitting thedebug_assert!. Belt-and-braces hardening around the same class of bug the analyzer fix above addressesSyntax
is_ascii_alphanumeric()to decide whether a byte following a potential closing label was "still part of the label", which incorrectly excluded_and high-bit UTF-8 bytes. A heredoc whose closing label was followed by a character in either of those classes would be mis-terminated. The check now usesis_part_of_identifier, matching PHP's actual identifier lexing rules🙏 Thank You
Issue Reporters
Thank you to everyone who reported issues that shaped this release:
Full Changelog: carthage-software/mago@1.21.1...1.22.0
v1.21.1: Mago 1.21.1Compare Source
Mago 1.21.1
Mago 1.21.1 is a bug-fix release that cleans up five regressions introduced by the features shipped in 1.21.0. The
string-stylefixer no longer emits invalid PHP for chains that aren't rooted in a variable, theprefer-explode-over-preg-splitfixer emits clean double-quoted escapes for separators containing control characters instead of splatting raw newlines or tabs into the source, the analyzer now expands generics likevalue-of<Enum>before comparing class-constant values and property defaults, the type parser acceptsnewas a class-constant name after::despite the newnew<T>type construct, and OSC 8 editor-URL hyperlinks on Windows drop the\\?\verbatim prefix so terminal-to-editor links work again.🐛 Bug Fixes
Linter
string-stylefixer only interpolates chains rooted in a variable: Fixed the concat-to-interpolation auto-fix for thestring-stylerule to correctly reject chains that aren't rooted in a$variable. Previously,"Hello " . SomeEnum::World->valuewas rewritten to"Hello {SomeEnum::World->value}"which is invalid PHP, since interpolation braces only accept chains rooted in a variable. The check now recurses through property, null-safe property, array, method, and null-safe method accesses, returningtrueonly when the chain's root is a variable (#1658)prefer-explode-over-preg-splitfixer emits proper escapes for control characters: When the extracted separator contains control bytes (\n,\t,\r,\x01, etc.), the fixer now emits a double-quoted PHP string with escape sequences instead of dropping raw control bytes into a single-quoted string. Sopreg_split("/\n\n/", $s)now becomesexplode("\n\n", $s)instead ofexplode('<LF><LF>', $s)which split the call onto multiple lines. Non-ASCII bytes still use single quotes since their UTF-8 encoding round-trips verbatim (#1655)Analyzer
invalid-constant-valueandinvalid-property-default-valuechecks introduced in 1.21.0 did not expand derived types likevalue-of<Enum>before comparing against the initializer's type, producing false positives on patterns such as/** @​var array<value-of<Color>, int> */ const WEIGHTS = [Color::Red->value => 1, ...]. The declared type is now expanded (self/static, generics, templates, conditionals, class constants) before the subtype check runs. The parameter-default path was already correctly expanding (#1657)Type Syntax
newas a member-name identifier after::: Thenew<T>type construct introduced in 1.21.0 madenewa reserved keyword in the type grammar, which in turn broke class-constant and enum-case references likeAction::NEWorFoo::new. The parser now treatsnewas an identifier in every post-::context (plain, qualified, and fully-qualified class references; plus thearray{Foo::NEW: int}shape-key pattern) when it isn't followed by<. Top-levelnew<T>still parses as theNewTypeconstruct unchanged (#1654)Reporting
--editor-url) carried the\\?\verbatim prefix thatstd::fs::canonicalizeadds, which editors andfile://handlers don't accept. The prefix is now stripped before the path is templated into the URL —\\?\C:\…becomesC:\…, and\\?\UNC\server\share\…becomes\\server\share\…. Applies to both the rich and emacs formatters (#1659)🙏 Thank You
Issue Reporters
Thank you to everyone who reported issues that shaped this release:
Full Changelog: carthage-software/mago@1.21.0...1.21.1
v1.21.0Compare Source
Mago 1.21.0
Mago 1.21.0 is a features-and-fixes release focused on diagnostic accuracy, linter ergonomics, and formatter consistency. Highlights include a new
@mago-expect category:code(3)count shorthand so you can say "expect three occurrences" once instead of repeating the code, detection of default values that don't match their declared type on parameters / properties / constants, a newprefer-explode-over-preg-splitlinter rule, an opt-in formatter setting that preserves author-written parentheses around logical sub-expressions, auto-fixes for thestring-stylerule in both directions, anarray_flipreturn-type provider that preserves shape, and a batch of analyzer fixes covering narrowing across union array accesses, keyed-array append type preservation, interpolated strings built from union parts, closure parameter typing in incompatible callable contexts, and more. The formatter also gets member-access chain consistency across four different starting-expression shapes and a new rule that keeps chains with trailing comments broken even when they'd otherwise fit.✨ Features
Analyzer
invalid-parameter-default-value,invalid-property-default-value,invalid-constant-value— flag declarations whose default value doesn't match the declared type. Catches classics likefunction f(SomeClass $x = []),public string $s = 1, andpublic const int C = 'x', including narrower@var/@paramdocblock types such asnon-empty-string $x = ''orpositive-int $n = 0(#1652)array_flip()return-type provider:array_flip()now preserves known-shape information instead of falling back to the generic stub.array_flip(['foo' => 1])infersarray{1: 'foo'},array_flip(list<string>)infersarray<string, int>, andarray_flip(array<K, V>)infersarray<V, K>whenVis anarray-keysubtype. Narrowing underarray_key_existsnow works on flipped arrays (#1633)array<1, T>with anintno longer reportsmismatched-array-index. The access is type-valid — the specific key may just not be present at runtime, which is a separate concern handled by the known-items path. Empty arrays (neverkey type) still reject any index (#1633)Linter
prefer-explode-over-preg-splitrule: Flagspreg_split()calls whose pattern has no regex meta-characters and no modifiers, suggestingexplode()instead. The rule fires only when the pattern is a literal with symmetric delimiters (/ # ~ ! @​ % , ; |), the inner content has no regex special chars, and theflagsargument (if present) is literal0. Ships with an auto-fix that rewrites the pattern as a plain string, renames the call toexplode, and drops a trailingflags = 0argument (#1554)string-stylerule (both directions): Thestring-stylerule now offers auto-fixes for bothinterpolation(converting"Hello, " . $nameto"Hello, {$name}") andconcatenation(the reverse). The fix handles leading/trailing expressions, adjacent expressions, multi-line input, braced and unbraced interpolations, escape preservation, and expression-only strings. Single-quoted literals are intentionally not fixed to avoid changing escape semantics (#1640)Formatter
preserve-redundant-logical-binary-expression-parenthesesoption: New opt-in formatter setting (defaultfalse) that preserves author-written parentheses around a logical binary sub-expression (&&,||,and,or,xor) when its enclosing binary is also logical, even though operator precedence would make them redundant. With the setting on,($a && $b) || $cstays as written instead of being reformatted to$a && $b || $c(#1367)Collector
code(N)count shorthand for@mago-expect/@mago-ignore: Instead of repeating a code to suppress multiple occurrences, you can now write@mago-expect analysis:mixed-operand(3)to suppress up to three matching issues.code(1)is equivalent to a bare code. If fewer issues match than expected, the auto-fix suggests reducing the count (or dropping the suffix entirely when it falls to 1) rather than deleting the directive (#1644)@mago-expectfrom a PHPDoc that carries other content (description lines,@param/@return/ other tags), the auto-fix now deletes only the pragma's own line instead of the entire comment. Single-line pragmas and pragma-only docblocks still get deleted whole as before.Codex
CodebaseMetadata::extract_owned_keyshelper: New method that extracts only the symbol keys owned by a given file's metadata, used by the incremental analysis service to safely prune entries without touching shared/prelude symbols.Names
ResolvedNamesAPIs:ResolvedNamesnow records the end offset of each identifier in addition to its start, enabling direct "what name is at this byte offset?" lookups without rescanning source. Addsat_offset(offset)for precise range-based lookup anditer()for allocation-free iteration over(start, end, fqn, imported)tuples. The existingall()method stays around but is now#[deprecated]in favor of the new API.🐛 Bug Fixes
Analyzer
mixed-operandin loops and preserved keyed-array shapes on append: Appending to a keyed array in a loop no longer produces spuriousmixed-operanderrors, and the keyed-array shape (array{x: int, y: int}) is preserved across appends instead of being widened to a genericarray<array-key, mixed>(#1639)"x=$a"with$a: 'foo'|'bar') now produce a literal union of concrete strings ('x=foo'|'x=bar') instead of a generalstring, letting downstream narrowing continue to work on the result (#1651)callablesignature, the analyzer now preserves the closure's own declared parameter type instead of overwriting it with the incompatible target type. Previously this produced spurious errors inside the closure body even though the closure's own signature was fine — the signature-level mismatch error at the call site is already enough to surface the problem (#1641)if (!empty($vd['key']))where$vdisfalse|array{key: string}, the narrowed$vd['key']type is now correctlynon-empty-stringrather thantruthy-mixed. The reconciler'sget_value_for_keyhelper no longer short-circuits tomixedwhen it sees a non-container atomic (e.g.false,null) in a union: those atomics are skipped so iterable atomics can still contribute their element types (#1653)defaultcases in switch fall-through analysis: The fall-through detection now correctly handlesdefaultcases that appear mid-switch instead of last, so switches that break out of a non-terminaldefaultno longer report spurious unreachable-arm diagnostics.Formatter
Yii::app()->...), a function call (Y()->app()->...), a static property (Yiui::$app->...), or a plain variable ($obj->...) now all break consistently per access when the chain exceeds print width, instead of producing four different partial-break styles depending on the starting expression. A companion rule keeps chains with trailing comments between accesses broken regardless of line width, soWriter::default()->u8(1) // versionno longer collapses onto a single line when it technically fits (#1623)Codex
class-stringis not contained by known string literals: Fixed a containment check where a generalclass-stringtype was incorrectly treated as a subtype of a specific literal string, producing false positives on comparisons and narrowing (#1638)Orchestrator
mago <command> some/path.php, the originalsource.pathsfrom the config are moved into the context (so they still provide analysis context) but no longer added to the exclusion list, which was causing the overridden paths to be silently filtered out (#1648, #1650)pathsandincludes, causing the loader's specificity tiebreaker to reclassify the file as Vendored and hide it from Host-only tools. Context paths are now filtered against the active source paths before being added to includes (#1650)Database
Playground
contenteditable=plaintext-only: The playground editor no longer loses its plaintext-only editing mode on re-renders, keeping caret position stable while typing (#1646)🏗️ Internal
mago ast --namestoResolvedNames::iter(): The CLI's name-dump command now walks resolved names via the new allocation-free iterator instead of cloning the full map, matching how the rest of the codebase should transition away from the deprecatedall().🙏 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: carthage-software/mago@1.20.1...1.21.0
v1.20.1: Mago 1.20.1Compare Source
Mago 1.20.1
A small but high-impact patch release. Reporting got significantly faster across the board — every output format (rich, json, count, github, gitlab, sarif, checkstyle, emacs, ariadne, code-count) now skips a wasteful copy of the issue list it used to make on every run. The bigger your project (and the more issues Mago finds), the more you save: WordPress analysis is ~3% faster end-to-end, Magento ~5%, and the savings in the reporting step itself are around 100 ms on a project of Magento's size.
⚡ Performance
Reporting
countthat just tallies severities. On a small project the cost was barely noticeable, but on a project with hundreds of thousands of issues it could add over 100 ms per run. The reporter now walks the original list directly, only allocating when sorting is meaningful for that format (rich, ariadne, json). End result: noticeably snappieranalyzeruns on large codebases, no behavior change.🐛 Bug Fixes
Analyzer
mixed: Using++,--, or unary-on a value of typemixedwas being reported asinvalid-operandorpossibly-invalid-operand, the same codes used for genuinely incompatible operands. Those operations are now reported under the dedicatedmixed-operandcode instead, matching how every other operator in Mago handlesmixedand making baselines and rule suppressions more predictable (#1635)🙏 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: carthage-software/mago@1.20.0...1.20.1
v1.20.0: Mago 1.20.0Compare Source
Mago 1.20.0
Mago 1.20.0 is a big release focused on analysis accuracy, speed, and diagnostics. Highlights include new
new<X>andtemplate-type<Object, ClassName, TemplateName>utility types, detection of@paramdocblocks that silently drop native union branches, a newfind-overly-wide-return-typesanalyzer check, six new linter rules, glob-pattern support across both analyzerignore.inand per-rule linter excludes, a side-effects-in-condition check, a trace-gated hang watcher for diagnosing pathological inputs, and sweeping performance work — loop fixed-point depth capping, walkdir directory pruning,ptr_eqfast paths on everyTTypeimpl,Rc<TUnion>plumbing through the analyzer hot path,saturate_clausesfast paths for single-literal clauses, sealed-keyed-array bounding in the combiner, and a zero-alloc AST visitor — together dropping end-to-end psl analysis by more than 3x. On the fix side, this release ships a pile of reconciler correctness work covering chained AND-clauses, narrowing through redundant?->, strictin_arraynarrowing, property-hook variance, self-referencing class constants, and more.✨ Features
Analyzer
new<X>andtemplate-type<Object, ClassName, TemplateName>utility types: Two new type-syntax utilities.new<X>resolves a class-string expression to the object type that would result from instantiating it, andtemplate-type<O, C, N>extracts a named template parameter from a given object/class, matching the PHPStan semantics (#1217)find-overly-wide-return-typescheck: New opt-in analyzer pass that compares a function's declared return type against the union of types actually produced by its return statements, reporting a newOverlyWideReturnTypeissue when declared branches are never produced. Skipped for generators, abstract/unchecked methods, overriding methods, and mixed/void/never/templated returns (#1446, #1553)@paramdocblock narrowing check: Flags when a@paramdocblock type silently drops a branch of the native parameter union (e.g. annotatingint|stringas@param int), which would otherwise collapse the parameter toneverinside the body (#1487)side-effects-in-conditiondiagnostic and matchingallow-side-effects-in-conditionssetting (on by default) that warns when anif/while/for/ternary/matchcondition calls a function or method that isn't marked@pure,@mutation-free, or@external-mutation-free(#1604)ignore.in: The analyzer'signoreconfiguration now accepts full glob patterns (e.g.src/**/*.php,modules/*/Generated/*.php) inin = [...]alongside plain directory/file prefixes, routed through the sharedExclusionMatcher(#1619)Linter
no-literal-namespace-stringrule: Flags string literals that look like fully-qualified PHP class names and suggests::classnotation. Disabled by default, warning level (#1386)no-null-property-initrule: Flags untypedpublic $foo = null;property declarations, since untyped properties already default tonull. Ships with an auto-fix to drop the redundant initializer. Disabled by default, help level (#1315)no-side-effects-with-declarationsrule: Flags files that mix top-level declarations (class, interface, trait, enum, function) with side-effecting statements. Configurable to allowclass_alias,class_existstop-level calls, and conditional declarations (#1560)no-service-state-mutationrule: Flags in-place mutation of injected service state so long-lived workers (RoadRunner, FrankenPHP, Swoole) don't accidentally leak state between requests (#1582)string-stylerule: Enforces a configurable preferred string style (interpolation vs concatenation), powered by a newancestorstracking API on the lint context (get_parent,get_parent_kind,get_nth_parent,get_nth_parent_kind) (#1614)disallowed-type-instantiationrule: New security rule that flags directnew Foo()on classes listed in the rule configuration, intended to enforce factory/provider patterns. Thetypesarray accepts either plain strings or{name, help}objects so users can attach custom help text per disallowed class (#1621)excludesconfiguration now accepts full glob syntax via a newmago_database::matcher::ExclusionMatcher, classifying each pattern as either a glob or a plain directory prefix (#1453, #1619)Formatter
align-parametersandalign-named-argumentsoptions: Two new opt-in alignment settings (both defaultfalse) that, on multiline parameter and argument lists, align columns — named arguments by their colon and parameters by the variable column (especially useful for promoted constructor properties) (#1307)preserve-breaking-member-access-chain-first-method-on-same-lineoption: When enabled alongsidepreserve-breaking-member-access-chain, keeps the first->method()call on the same line as its receiver instead of moving the receiver onto its own line (#1319)omit-redundant-arithmetic-binary-expression-parenthesesandomit-redundant-bitwise-binary-expression-parentheses, that drop parentheses around arithmetic/bitwise children under comparison and null-coalesce operators when PHP precedence already preserves meaning (e.g.$i === $retries - 1) (#1620)format --stagedworks with unstaged changes:mago format --stagedno longer refuses to run on files with partial staging. It now reads each staged blob viagit cat-file, formats it in memory, and writes the formatted version back to the index viagit hash-object -w+git update-index --cacheinfo, leaving unstaged worktree changes untouched (#1629)CLI
versionkey inmago.toml(exact, major.minor, or major) is validated on every run — patch or minor drift logs a warning, major drift fails hard. A matchingmago self-update --to-project-versionflag reads the pin and installs the exact tag (or the latest release matching the constraint) (#1618)Orchestrator
MAGO_LOG=traceis set, a dedicated watcher thread tracks in-flight analyzer workers and emitstracing::trace!messages when a file has been analyzing for more than ~5 seconds, with guidance on filing a bug report and anonymizing private code. Zero overhead when tracing is off (#1227)Telemetry
analyzer/orchestratortelemetry modules emit per-phase durations (clap parse, logger/config/rayon init, prelude decode, orchestrator init, database load, compile, codex merge/populate, parallel map/reduce, reduce, analyzer setup/statements/finalize) and report the 20 slowest files seen during the parallel analyze phase, all via ameasure!macro that compiles to a branch-predicted zero-cost path when trace is off.🐛 Bug Fixes
Analyzer
redundant/impossiblediagnostics on already-narrowed types (#1627)?->: For a null-safe access$obj?->propwhere$objis already known non-null, the analyzer now looks up the equivalent non-null-safe property id inblock_context.localsand reuses its narrowed type, so the result of the?->retains the earlier narrowing instead of falling back to the widest declared type (#1627)in_arraynarrowing: Whenin_array(..., strict: true)returnstrue, the synthesized needle assertion is nowAssertion::IsIdenticalrather thanAssertion::IsEqual, letting the analyzer narrow the needle to the haystack's element type (e.g.string|int|nulltostringagainst astring[]haystack). The stub also gained a@template Ton the needle so its type is independent of the haystack value type (#1628)->sub->method()calls and readonly props:clear_object_property_narrowingsnow threads through the receiver variable and only wipes$this->...narrowings when the method call is actually on$this. Additionally, narrowings for readonly$thisproperties are preserved across self-calls and@suspends-fibercalls (#1625)const int B = self::Bvia a thread-localEXPANDING_CONSTANTSset and RAII guard, pushingTAtomic::Neveron re-entry. The class-constant analyzer surfaces this as a newUnresolvableClassConstantissue (#1624)gethook and contravariance on properties with only asethook, so read-only/write-only hooked properties no longer emit false type-incompatibility errors in subclasses (#1615)&&:$x === 0 && f($x) && $x !== 0(whereftakes$xby reference) now correctly drops the stale narrowing on$xfrom the left side by propagatingparent_conflicting_clause_variablesthrough both branches (#1608)@param-narrows-native-type check now skips methods that override a parent, since the docblock narrowing is constrained by the parent signature and would otherwise produce false positives.Codex
set => exprshorthand treated as virtual: A property hook likepublic string $test { set => strtolower($value); }was wrongly classified as virtual (and therefore write-only), because the shorthand walker only looked for explicit$this->propertyNamereferences and missed the implicit$this->prop = exprdesugaring. The walker now also tracks whether any assignment appears in the hook body: a shorthandsetwith no assignment in its body is correctly recognized as referencing the backing field (#1632)Orchestrator
include_externalsis false (the default forlintandformat), configuredincludespatterns are now also compiled into excludes and merged with the user-configured excludes, so directories likevendor/nested under a source path are correctly skipped (#1630, #913)Database
dir_prune_globsand file-level exclude globs now match against the path relative to the canonical workspace rather than the absolute path, so workspace-anchored patterns such assrc/*/Test/**andvendor/**/tests/**actually exclude matching files. Includes a newworkspace_relative_strhelper with Windows backslash normalization (#1143)*/packages/**/vendor/*that anchor on the workspace's absolute prefix continue to function.Reporting
NO_COLOR=0honored by the issue reporter: The color detection used by the issue reporter now treats any non-emptyNO_COLORvalue (including the literal"0") as disabling colors, and treatsFORCE_COLOR=0as explicitly disabling colors, aligning with the logger and the no-color.org spec (#1599)Config
source.workspaceat normalization time, so running mago from a subdirectory no longer resolves the baseline file relative to the current working directory (#1289)Prelude / Stubs
reset()return type preserves non-empty shapes: Thereset()stub's@param-outnow preservesnon-empty-list/non-empty-arrayshapes and its@returnnarrows toV(droppingfalse) when the array is statically known non-empty, instead of always returningV|false(#1611)array_replaceandarray_replace_recursivetemplate parameters: Added@template K of array-keyand@template Vto the stubs so they returnarray<K, V>matching the inputs instead of a lossyarray<array-key, mixed>(#1605)DateTime::getLastErrors()array shape: Replaced the loosearray<string, int|array>|falsereturn type with a precisearray{warning_count, warnings, error_count, errors}|falseshape on bothDateTimeandDateTimeImmutable(#1607)DateTimeZonestub improvements: Filled in the real bitmask values for the timezone constants (AFRICA = 1throughPER_COUNTRY = 4096), tightened return types forgetTransitions,getLocation,listAbbreviations, andlistIdentifierswithnon-empty-string/list<...>/concretearray{...}shapes, and annotated the constructor with@throws DateInvalidTimeZoneException(#1612)⚡ Performance
End-to-end analysis of the Psl monorepo dropped from ~700 ms to ~160 ms in this release thanks to the combined effect of the following changes.
Analyzer
Rc<TUnion>in the hot path: ThreadedRc<TUnion>through assignment, binary logical, unary, variable access, invocation, loop handling, and static-statement analysis, alongside newadd_optional_union_type_rc/combine_union_types_rchelpers in the codex ttype module that reuseRcpointers when inputs are pointer-equal. Cuts redundantTUnionclones by roughly a third on Psl.loop-assignment-depth-thresholdsetting (default1) caps how deepget_assignment_map_depthwill recurse through loop-carried assignment chains, short-circuiting fixed-point iteration that used to re-analyse loop bodies many times. The codex combiner'sDEFAULT_ARRAY_COMBINATION_THRESHOLDwas also lowered from 128 to 32 so very wide keyed-array unions collapse to a general shape sooner.analyze_expression_armnow returns only the clauses newly negated by that arm, and the match driver feeds only those intofind_satisfying_assignments/reconcile_keyed_types, rather than re-saturating and re-reconciling the entire running else-context on every arm.Codex
ptr_eq/cheap-field fast paths inPartialEq: Replaced the derivedPartialEqonTAtomicand on the array / keyed / list variants with a hand-written impl that short-circuits on pointer equality and dispatches only to the matching variant's cheap-field compare.TUnionatoms are also sorted into canonical order in the constructor so unions built from the same set of atoms take the ordered slice-equality path instead of the O(N²) subset fallback.array-combination-threshold, the excess is flushed into a single merged keyed-array entry instead of tracking each shape separately.adjust_keyed_array_parametersalso short-circuits entries whose key type cannot match, preventing quadratic blowup when unioning many mixed shapes (#1610)Algebra
saturate_clausesfast paths for single-literal clauses: Pre-builds a(var, possibility-hash)HashSetindex of every literal in the input so unit propagation can skip the inner O(N) scan whenever no clause contains the negation of a given literal, and adds an all-size-one fast path that skips the strict-subset redundancy walk and the consensus-rule pass entirely when every clause has a single variable. Drops exhaustive match analysis from roughly cubic to linear in arm count.Database
GlobSetof directory-level prune patterns from the user's file-level globs (by stripping trailing/*,/**,/**/*) and applies it (plus the path-excludes) insideWalkDir::filter_entry, so excluded subtrees (vendor/,node_modules/, …) are never descended into. On the psl monorepo the walker now yields 2,975 entries instead of 144,222.Syntax
Node::visit_childrenmethod drives the same match logic asNode::childrenbut delivers each child to a caller-supplied closure instead of allocating aVec<Node>. The linter walk,filter_map_internal,prefer_static_closure'scontains_this_reference, and thecyclomatic_complexity/halstead/kan_defectmetric rules all migrated to it; the linter walk is also rewritten to use an explicitOpstack instead of recursion, avoiding stack overflows on deeply nested ASTs (#1606)Config
Config::builder: Removed the eagerConfiguration::from_workspaceseed that was being serialized and re-fed intoConfig::builder(), letting the finaltry_deserialize::<Configuration>fall back to serde defaults.SourceConfigurationgained#[serde(default)]plus aDefaultimpl so the workspace still resolves correctly. Saves roughly 20 ms on startup.📖 Documentation
MAGO_LOG=trace, and covers what the hang watcher, slowest-files report, and per-phase durations give you.🏗️ Internal
mago inittemplate now includes the missingworkspace = "."line so the generatedmago.tomlmatches the schema expected by the command's tests.🙏 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:
Configuration
📅 Schedule: (in timezone UTC)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
Renovate Bot