Skip to content

Update all non-major dependencies#143

Open
renovate[bot] wants to merge 1 commit intomainfrom
renovate/all-minor-patch
Open

Update all non-major dependencies#143
renovate[bot] wants to merge 1 commit intomainfrom
renovate/all-minor-patch

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate bot commented Jan 29, 2026

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
carthage-software/mago ^1.14.0^1.22.0 age adoption passing confidence
ergebnis/composer-normalize ^2.50.0^2.51.0 age adoption passing confidence
php-standard-library/phpstan-extension ^2.0.3^2.1.0 age adoption passing confidence
phpstan/phpstan ^2.1.40^2.1.50 age adoption passing confidence
phpunit/phpunit (source) ^11.5.50^11.5.55 age adoption passing confidence

Warning

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.0

Compare 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-behavior linter rule ships an auto-fix, mago init seeds check-throws with sensible Error / LogicException defaults, 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-behavior auto-fix: The rule now attaches a PotentiallyUnsafe fix that inserts , strict: true (or strict: true when a trailing comma is already present) before the closing paren of calls to in_array, array_search, base64_decode, and array_keys that rely on loose comparison. Not offered when allow_loose_behavior is enabled or when the call already has an explicit non-true strict: value, so deliberate opt-outs stay intact (#​1656)
Init
  • Seed unchecked-exceptions with Error and LogicException: mago init now writes check-throws.unchecked-exceptions = ["Error", "LogicException"] into the generated mago.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-int keyword: New type keyword that lowers to negative-int | positive-int, commonly used to express "any non-zero integer".
  • Reserved keywords as class-constant member names: 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.
  • Trailing | in unions: A dangling pipe at the end of a union (common in hand-written or machine-concatenated docblocks) now parses as Type::TrailingPipe instead of erroring; the docblock tag splitter no longer swallows the whitespace after such an operator.
  • FOO_* / *_BAR global-constant wildcards: Global-constant wildcard patterns now parse as Type::GlobalWildcardReference, lowered to a new TReference::Global selector that resolves against global constants. Covers int-mask-of<*_BAR> / value-of<FOO_*> idioms.
Reporting
  • Dead-entry count in stale-baseline warning: When mago lint / mago analyze detects 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
  • Guard against empty key union in TArray::Keyed parameters: get_array_parameters tripped a combine() received an empty Vec debug assertion when a keyed array's parameters held an empty key union and known_items was 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]). The Keyed arm now mirrors the List arm's safety push of TAtomic::Never in the empty case
  • Preserve source casing of class / function / method names in diagnostics: Diagnostics previously lowercased symbol names because the analyzer carried them around as atoms normalised to lowercase. A method reported as app\http\controllers\usercontroller::getprofile was actually declared as App\Http\Controllers\UserController::getProfile. The renderer now re-derives the original casing from the source file when emitting issue messages and annotations (#​1660)
Codex
  • Never panic on empty unions in combine() or filter helpers: Two additional sites that could construct an empty atomic vector before calling combine() (the combiner itself and the union-filter helper) now return a well-formed never-typed union instead of hitting the debug_assert!. Belt-and-braces hardening around the same class of bug the analyzer fix above addresses
Syntax
  • Heredoc/nowdoc closing-label terminator accepts full identifier alphabet: The lexer used 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 uses is_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.1

Compare 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-style fixer no longer emits invalid PHP for chains that aren't rooted in a variable, the prefer-explode-over-preg-split fixer 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 like value-of<Enum> before comparing class-constant values and property defaults, the type parser accepts new as a class-constant name after :: despite the new new<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-style fixer only interpolates chains rooted in a variable: Fixed the concat-to-interpolation auto-fix for the string-style rule to correctly reject chains that aren't rooted in a $variable. Previously, "Hello " . SomeEnum::World->value was 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, returning true only when the chain's root is a variable (#​1658)
  • prefer-explode-over-preg-split fixer 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. So preg_split("/\n\n/", $s) now becomes explode("\n\n", $s) instead of explode('<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
  • Expand declared type before comparing constant values and property defaults: The invalid-constant-value and invalid-property-default-value checks introduced in 1.21.0 did not expand derived types like value-of<Enum> before comparing against the initializer's type, producing false positives on patterns such as /** @&#8203;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
  • Allow new as a member-name identifier after ::: The new<T> type construct introduced in 1.21.0 made new a reserved keyword in the type grammar, which in turn broke class-constant and enum-case references like Action::NEW or Foo::new. The parser now treats new as an identifier in every post-:: context (plain, qualified, and fully-qualified class references; plus the array{Foo::NEW: int} shape-key pattern) when it isn't followed by <. Top-level new<T> still parses as the NewType construct unchanged (#​1654)
Reporting
  • Strip Win32 verbatim prefix from OSC 8 editor-URL hyperlinks: On Windows, file paths surfaced to the terminal through OSC 8 editor hyperlinks (--editor-url) carried the \\?\ verbatim prefix that std::fs::canonicalize adds, which editors and file:// handlers don't accept. The prefix is now stripped before the path is templated into the URL — \\?\C:\… becomes C:\…, 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.0

Compare 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 new prefer-explode-over-preg-split linter rule, an opt-in formatter setting that preserves author-written parentheses around logical sub-expressions, auto-fixes for the string-style rule in both directions, an array_flip return-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
  • Detect invalid default values for parameters, properties, and class constants: Three new checks — invalid-parameter-default-value, invalid-property-default-value, invalid-constant-value — flag declarations whose default value doesn't match the declared type. Catches classics like function f(SomeClass $x = []), public string $s = 1, and public const int C = 'x', including narrower @var/@param docblock types such as non-empty-string $x = '' or positive-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]) infers array{1: 'foo'}, array_flip(list<string>) infers array<string, int>, and array_flip(array<K, V>) infers array<V, K> when V is an array-key subtype. Narrowing under array_key_exists now works on flipped arrays (#​1633)
  • Accept wider index types for narrow-keyed arrays: Indexing array<1, T> with an int no longer reports mismatched-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 (never key type) still reject any index (#​1633)
Linter
  • prefer-explode-over-preg-split rule: Flags preg_split() calls whose pattern has no regex meta-characters and no modifiers, suggesting explode() instead. The rule fires only when the pattern is a literal with symmetric delimiters (/ # ~ ! @&#8203; % , ; |), the inner content has no regex special chars, and the flags argument (if present) is literal 0. Ships with an auto-fix that rewrites the pattern as a plain string, renames the call to explode, and drops a trailing flags = 0 argument (#​1554)
  • Auto-fix for string-style rule (both directions): The string-style rule now offers auto-fixes for both interpolation (converting "Hello, " . $name to "Hello, {$name}") and concatenation (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-parentheses option: New opt-in formatter setting (default false) 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) || $c stays 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)
  • Unused-pragma auto-fix no longer deletes whole PHPDocs: When removing an unfulfilled @mago-expect from 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_keys helper: 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
  • Identifier end offsets and new ResolvedNames APIs: ResolvedNames now 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. Adds at_offset(offset) for precise range-based lookup and iter() for allocation-free iteration over (start, end, fqn, imported) tuples. The existing all() method stays around but is now #[deprecated] in favor of the new API.

🐛 Bug Fixes

Analyzer
  • Fixed false mixed-operand in loops and preserved keyed-array shapes on append: Appending to a keyed array in a loop no longer produces spurious mixed-operand errors, and the keyed-array shape (array{x: int, y: int}) is preserved across appends instead of being widened to a generic array<array-key, mixed> (#​1639)
  • Produce literal union for interpolated strings with union parts: Interpolated strings whose parts are themselves unions (e.g. "x=$a" with $a: 'foo'|'bar') now produce a literal union of concrete strings ('x=foo'|'x=bar') instead of a general string, letting downstream narrowing continue to work on the result (#​1651)
  • Keep closure's declared parameter type when inferred type is incompatible: When a closure is passed to a parameter with an incompatible callable signature, 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)
  • Narrow union array access to iterable branch when reconciling assertions: Inside if (!empty($vd['key'])) where $vd is false|array{key: string}, the narrowed $vd['key'] type is now correctly non-empty-string rather than truthy-mixed. The reconciler's get_value_for_key helper no longer short-circuits to mixed when 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)
  • Handle non-terminal default cases in switch fall-through analysis: The fall-through detection now correctly handles default cases that appear mid-switch instead of last, so switches that break out of a non-terminal default no longer report spurious unreachable-arm diagnostics.
Formatter
  • Align member-access chain breaking across base kinds and keep commented chains broken: Chains starting with a static method call (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, so Writer::default()->u8(1) // version no longer collapses onto a single line when it technically fits (#​1623)
  • Keep interpolated string chains inline: Interpolated strings that are part of a concatenation chain are no longer broken onto separate lines by the chain-breaking heuristics, matching user expectations for template-style code (#​1643)
Codex
  • class-string is not contained by known string literals: Fixed a containment check where a general class-string type was incorrectly treated as a subtype of a specific literal string, producing false positives on comparisons and narrowing (#​1638)
Orchestrator
  • Stop excluding original source paths when CLI overrides paths: When a user runs mago <command> some/path.php, the original source.paths from 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)
  • Deduplicate context paths against active source paths: Follow-up fix for the same CLI-override scenario. When a CLI-specified path matches an original config path exactly, the same pattern used to appear in both paths and includes, 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)
  • Track owned keys in incremental analysis to avoid cross-file removals: The incremental analysis service now tracks which codex keys were contributed by which file, so re-analyzing one file can only remove that file's own symbols from the shared metadata. Previously a re-analysis could accidentally evict prelude or other-file symbols, leaving downstream analyses looking at partial codebase metadata.
Database
  • Only bypass watcher exclusions for paths that match a glob themselves: Watcher-triggered incremental scans no longer bypass exclusion matching for every file in a glob-selected directory — only files that match an explicit glob pattern skip the exclusion pass, keeping vendor/ and generated paths out of the hot incremental loop as intended.
Playground
  • Stop Vue from clobbering CodeJar's 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

  • Migrate mago ast --names to ResolvedNames::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 deprecated all().

🙏 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.1

Compare 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
  • Faster issue reporting on every output format: Mago used to copy its entire list of issues before formatting them — even when no filtering or sorting was needed, and even for a format like count that 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 snappier analyze runs on large codebases, no behavior change.

🐛 Bug Fixes

Analyzer
  • Consistent issue code for unary operators on mixed: Using ++, --, or unary - on a value of type mixed was being reported as invalid-operand or possibly-invalid-operand, the same codes used for genuinely incompatible operands. Those operations are now reported under the dedicated mixed-operand code instead, matching how every other operator in Mago handles mixed and 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.0

Compare 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> and template-type<Object, ClassName, TemplateName> utility types, detection of @param docblocks that silently drop native union branches, a new find-overly-wide-return-types analyzer check, six new linter rules, glob-pattern support across both analyzer ignore.in and 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_eq fast paths on every TType impl, Rc<TUnion> plumbing through the analyzer hot path, saturate_clauses fast 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 ?->, strict in_array narrowing, property-hook variance, self-referencing class constants, and more.

✨ Features

Analyzer
  • new<X> and template-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, and template-type<O, C, N> extracts a named template parameter from a given object/class, matching the PHPStan semantics (#​1217)
  • find-overly-wide-return-types check: 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 new OverlyWideReturnType issue when declared branches are never produced. Skipped for generators, abstract/unchecked methods, overriding methods, and mixed/void/never/templated returns (#​1446, #​1553)
  • @param docblock narrowing check: Flags when a @param docblock type silently drops a branch of the native parameter union (e.g. annotating int|string as @param int), which would otherwise collapse the parameter to never inside the body (#​1487)
  • Side-effects-in-condition check: New side-effects-in-condition diagnostic and matching allow-side-effects-in-conditions setting (on by default) that warns when an if/while/for/ternary/match condition calls a function or method that isn't marked @pure, @mutation-free, or @external-mutation-free (#​1604)
  • Glob patterns in ignore.in: The analyzer's ignore configuration now accepts full glob patterns (e.g. src/**/*.php, modules/*/Generated/*.php) in in = [...] alongside plain directory/file prefixes, routed through the shared ExclusionMatcher (#​1619)
Linter
  • no-literal-namespace-string rule: Flags string literals that look like fully-qualified PHP class names and suggests ::class notation. Disabled by default, warning level (#​1386)
  • no-null-property-init rule: Flags untyped public $foo = null; property declarations, since untyped properties already default to null. Ships with an auto-fix to drop the redundant initializer. Disabled by default, help level (#​1315)
  • no-side-effects-with-declarations rule: Flags files that mix top-level declarations (class, interface, trait, enum, function) with side-effecting statements. Configurable to allow class_alias, class_exists top-level calls, and conditional declarations (#​1560)
  • no-service-state-mutation rule: Flags in-place mutation of injected service state so long-lived workers (RoadRunner, FrankenPHP, Swoole) don't accidentally leak state between requests (#​1582)
  • string-style rule: Enforces a configurable preferred string style (interpolation vs concatenation), powered by a new ancestors tracking API on the lint context (get_parent, get_parent_kind, get_nth_parent, get_nth_parent_kind) (#​1614)
  • disallowed-type-instantiation rule: New security rule that flags direct new Foo() on classes listed in the rule configuration, intended to enforce factory/provider patterns. The types array accepts either plain strings or {name, help} objects so users can attach custom help text per disallowed class (#​1621)
  • Glob patterns in per-rule exclude lists: The linter's per-rule excludes configuration now accepts full glob syntax via a new mago_database::matcher::ExclusionMatcher, classifying each pattern as either a glob or a plain directory prefix (#​1453, #​1619)
Formatter
  • align-parameters and align-named-arguments options: Two new opt-in alignment settings (both default false) 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-line option: When enabled alongside preserve-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)
  • Opt-in arithmetic and bitwise paren omission: Two new settings, omit-redundant-arithmetic-binary-expression-parentheses and omit-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 --staged works with unstaged changes: mago format --staged no longer refuses to run on files with partial staging. It now reads each staged blob via git cat-file, formats it in memory, and writes the formatted version back to the index via git hash-object -w + git update-index --cacheinfo, leaving unstaged worktree changes untouched (#​1629)
CLI
  • Project version pinning: New top-level version key in mago.toml (exact, major.minor, or major) is validated on every run — patch or minor drift logs a warning, major drift fails hard. A matching mago self-update --to-project-version flag reads the pin and installs the exact tag (or the latest release matching the constraint) (#​1618)
Orchestrator
  • Trace-gated hang watcher: When MAGO_LOG=trace is set, a dedicated watcher thread tracks in-flight analyzer workers and emits tracing::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
  • Detailed trace-level pipeline diagnostics: New analyzer/orchestrator telemetry 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 a measure! macro that compiles to a branch-predicted zero-cost path when trace is off.

🐛 Bug Fixes

Analyzer
  • Fixed spurious impossibility on chained AND-clauses: When evaluating a conjunction of assertions against a running narrowed type, the reconciler now re-probes the original pre-narrowing type before reporting an assertion as impossible, so later clauses in a chain no longer fire false redundant/impossible diagnostics on already-narrowed types (#​1627)
  • Preserved narrowing through redundant ?->: For a null-safe access $obj?->prop where $obj is already known non-null, the analyzer now looks up the equivalent non-null-safe property id in block_context.locals and reuses its narrowed type, so the result of the ?-> retains the earlier narrowing instead of falling back to the widest declared type (#​1627)
  • Strict in_array narrowing: When in_array(..., strict: true) returns true, the synthesized needle assertion is now Assertion::IsIdentical rather than Assertion::IsEqual, letting the analyzer narrow the needle to the haystack's element type (e.g. string|int|null to string against a string[] haystack). The stub also gained a @template T on the needle so its type is independent of the haystack value type (#​1628)
  • Narrowing preserved across ->sub->method() calls and readonly props: clear_object_property_narrowings now threads through the receiver variable and only wipes $this->... narrowings when the method call is actually on $this. Additionally, narrowings for readonly $this properties are preserved across self-calls and @suspends-fiber calls (#​1625)
  • Fixed stack overflow on self-referencing class constants: The codex type expander now detects cycles like const int B = self::B via a thread-local EXPANDING_CONSTANTS set and RAII guard, pushing TAtomic::Never on re-entry. The class-constant analyzer surfaces this as a new UnresolvableClassConstant issue (#​1624)
  • Variance allowed for hook-only property types: Replaced the strict invariance check for overridden property types with a rule that permits covariance on virtual properties with only a get hook and contravariance on properties with only a set hook, so read-only/write-only hooked properties no longer emit false type-incompatibility errors in subclasses (#​1615)
  • Invalidated narrowing after by-reference mutations in &&: $x === 0 && f($x) && $x !== 0 (where f takes $x by reference) now correctly drops the stale narrowing on $x from the left side by propagating parent_conflicting_clause_variables through both branches (#​1608)
  • Skipped docblock-parameter-narrowing on overrides: The new @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
  • Fixed set => expr shorthand treated as virtual: A property hook like public string $test { set => strtolower($value); } was wrongly classified as virtual (and therefore write-only), because the shorthand walker only looked for explicit $this->propertyName references and missed the implicit $this->prop = expr desugaring. The walker now also tracks whether any assignment appears in the hook body: a shorthand set with no assignment in its body is correctly recognized as referencing the backing field (#​1632)
Orchestrator
  • Vendor paths under source dirs no longer linted/formatted: When include_externals is false (the default for lint and format), configured includes patterns are now also compiled into excludes and merged with the user-configured excludes, so directories like vendor/ nested under a source path are correctly skipped (#​1630, #​913)
Database
  • Exclude globs match workspace-relative paths: dir_prune_globs and file-level exclude globs now match against the path relative to the canonical workspace rather than the absolute path, so workspace-anchored patterns such as src/*/Test/** and vendor/**/tests/** actually exclude matching files. Includes a new workspace_relative_str helper with Windows backslash normalization (#​1143)
  • Legacy absolute-prefix exclude patterns still work: Both file and directory exclude matching now try the absolute path in addition to the relative path, so existing patterns like */packages/**/vendor/* that anchor on the workspace's absolute prefix continue to function.
Reporting
  • NO_COLOR=0 honored by the issue reporter: The color detection used by the issue reporter now treats any non-empty NO_COLOR value (including the literal "0") as disabling colors, and treats FORCE_COLOR=0 as explicitly disabling colors, aligning with the logger and the no-color.org spec (#​1599)
Config
  • Relative baseline paths resolved against workspace: Relative baseline paths configured for the analyzer, linter, and guard are now joined against source.workspace at 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: The reset() stub's @param-out now preserves non-empty-list/non-empty-array shapes and its @return narrows to V (dropping false) when the array is statically known non-empty, instead of always returning V|false (#​1611)
  • array_replace and array_replace_recursive template parameters: Added @template K of array-key and @template V to the stubs so they return array<K, V> matching the inputs instead of a lossy array<array-key, mixed> (#​1605)
  • DateTime::getLastErrors() array shape: Replaced the loose array<string, int|array>|false return type with a precise array{warning_count, warnings, error_count, errors}|false shape on both DateTime and DateTimeImmutable (#​1607)
  • DateTimeZone stub improvements: Filled in the real bitmask values for the timezone constants (AFRICA = 1 through PER_COUNTRY = 4096), tightened return types for getTransitions, getLocation, listAbbreviations, and listIdentifiers with non-empty-string/list<...>/concrete array{...} 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: Threaded Rc<TUnion> through assignment, binary logical, unary, variable access, invocation, loop handling, and static-statement analysis, alongside new add_optional_union_type_rc / combine_union_types_rc helpers in the codex ttype module that reuse Rc pointers when inputs are pointer-equal. Cuts redundant TUnion clones by roughly a third on Psl.
  • Loop fixed-point depth cap + bounded keyed-array combining: New loop-assignment-depth-threshold setting (default 1) caps how deep get_assignment_map_depth will recurse through loop-carried assignment chains, short-circuiting fixed-point iteration that used to re-analyse loop bodies many times. The codex combiner's DEFAULT_ARRAY_COMBINATION_THRESHOLD was also lowered from 128 to 32 so very wide keyed-array unions collapse to a general shape sooner.
  • Incremental match-arm reconciliation: analyze_expression_arm now returns only the clauses newly negated by that arm, and the match driver feeds only those into find_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 in PartialEq: Replaced the derived PartialEq on TAtomic and 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. TUnion atoms 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.
  • Bounded sealed keyed arrays in the combiner: Once the combiner accumulates more sealed keyed arrays than array-combination-threshold, the excess is flushed into a single merged keyed-array entry instead of tracking each shape separately. adjust_keyed_array_parameters also short-circuits entries whose key type cannot match, preventing quadratic blowup when unioning many mixed shapes (#​1610)
Algebra
  • saturate_clauses fast paths for single-literal clauses: Pre-builds a (var, possibility-hash) HashSet index 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
  • Walkdir directory pruning: The loader now derives a secondary GlobSet of directory-level prune patterns from the user's file-level globs (by stripping trailing /*, /**, /**/*) and applies it (plus the path-excludes) inside WalkDir::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
  • Zero-alloc AST visitor: New Node::visit_children method drives the same match logic as Node::children but delivers each child to a caller-supplied closure instead of allocating a Vec<Node>. The linter walk, filter_map_internal, prefer_static_closure's contains_this_reference, and the cyclomatic_complexity/halstead/kan_defect metric rules all migrated to it; the linter walk is also rewritten to use an explicit Op stack instead of recursion, avoiding stack overflows on deeply nested ASTs (#​1606)
Config
  • Dropped the defaults seed from Config::builder: Removed the eager Configuration::from_workspace seed that was being serialized and re-fed into Config::builder(), letting the final try_deserialize::<Configuration> fall back to serde defaults. SourceConfiguration gained #[serde(default)] plus a Default impl so the workspace still resolves correctly. Saves roughly 20 ms on startup.

📖 Documentation

  • Diagnosing slow runs: New analyzer configuration-reference section that explains the "if Mago takes more than 30 seconds to analyze your project, something is wrong" rule of thumb, documents MAGO_LOG=trace, and covers what the hang watcher, slowest-files report, and per-phase durations give you.
  • Removed consulting section: Stripped the consulting/services marketing block from the homepage and the corresponding footer link.

🏗️ Internal

  • Init template fix: The mago init template now includes the missing workspace = "." line so the generated mago.toml matches 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)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 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.


  • If you want to rebase/retry this PR, check this box

Renovate Bot

@renovate
Copy link
Copy Markdown
Contributor Author

renovate bot commented Jan 29, 2026

automergeComment

@renovate renovate bot force-pushed the renovate/all-minor-patch branch from c4883b8 to 702f079 Compare January 30, 2026 17:29
@renovate renovate bot changed the title Update dependency carthage-software/mago to ^1.3.0 Update all non-major dependencies Jan 30, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 2 times, most recently from 07dc643 to 19adc6e Compare February 3, 2026 21:09
@renovate renovate bot changed the title Update all non-major dependencies Update all non-major dependencies - autoclosed Feb 4, 2026
@renovate renovate bot closed this Feb 4, 2026
@renovate renovate bot deleted the renovate/all-minor-patch branch February 4, 2026 14:28
@renovate renovate bot changed the title Update all non-major dependencies - autoclosed Update dependency carthage-software/mago to ^1.5.0 Feb 5, 2026
@renovate renovate bot reopened this Feb 5, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 3 times, most recently from f0930dd to 79cda5a Compare February 5, 2026 08:57
@renovate renovate bot changed the title Update dependency carthage-software/mago to ^1.5.0 Update all non-major dependencies Feb 5, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 3 times, most recently from 5fce0b8 to 4ebd3bd Compare February 8, 2026 21:04
@renovate renovate bot changed the title Update all non-major dependencies Update dependency phpunit/phpunit to ^11.5.52 Feb 8, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch from 4ebd3bd to 2c4252f Compare February 9, 2026 01:35
@renovate renovate bot changed the title Update dependency phpunit/phpunit to ^11.5.52 Update all non-major dependencies Feb 9, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 9 times, most recently from 0d1f9b5 to 3a1748c Compare February 17, 2026 02:34
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 7 times, most recently from 30d51ad to 8442d6f Compare March 11, 2026 09:29
@renovate renovate bot changed the title Update all non-major dependencies Update dependency phpunit/phpunit to ^11.5.55 Mar 11, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch from 8442d6f to 6282980 Compare March 14, 2026 01:43
@renovate renovate bot changed the title Update dependency phpunit/phpunit to ^11.5.55 Update all non-major dependencies Mar 14, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch from 6282980 to 759633b Compare March 15, 2026 12:51
@renovate renovate bot changed the title Update all non-major dependencies Update dependency phpunit/phpunit to ^11.5.55 Mar 15, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch from 759633b to f1f5ec9 Compare March 15, 2026 16:41
@renovate renovate bot changed the title Update dependency phpunit/phpunit to ^11.5.55 Update all non-major dependencies Mar 15, 2026
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 8 times, most recently from e543c70 to c23f26f Compare March 23, 2026 14:06
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 5 times, most recently from 26cae27 to 8e93294 Compare March 31, 2026 21:02
@renovate renovate bot force-pushed the renovate/all-minor-patch branch 2 times, most recently from a55fc12 to e18fe51 Compare April 2, 2026 19:00
| 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 |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants