Skip to content

Replace SpecifiedTypes::normalize() with symbolic alternative-form entries - #6133

Merged
ondrejmirtes merged 2 commits into
2.2.xfrom
specified-types-alternative-forms
Jul 28, 2026
Merged

Replace SpecifiedTypes::normalize() with symbolic alternative-form entries#6133
ondrejmirtes merged 2 commits into
2.2.xfrom
specified-types-alternative-forms

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Extracted from the resolve-type-rewrite-2 branch (follows #6125, #6129, #6130, #6131, #6132) — re-authored against 2.2.x rather than cherry-picked, since the branch version is written against its callback world.

SpecifiedTypes::normalize() eagerly folded sure-not entries into sure entries by subtracting from $scope->getType() at composition time — baking the composing scope's view into the narrowing and producing artifacts when the merge result is applied elsewhere (bug-3991's fixture documents one). intersectWith() now emits symbolic alternative-form entries — (sure ?? current) minus subtract terms, united — and MutatingScope::filterBySpecifiedTypes() evaluates them at the application point, separately for the PHPDoc and native views. normalize() is deleted (public but not @api).

  • Same-kind merges stay exact (sure → union, sure-not → intersect); vacuous never-subtractions collapse, so ['$foo' => 'mixed']-style no-op entries disappear.
  • Composition-time consumers of concrete sure types (conditional-holder building, decided boolean operands) use the new eager ConditionalExpressionHolderHelper::toSureTypes() — behavior-preserving at those sites.
  • The disjunction union recovery skips keys carrying an alternative entry, as it skipped keys with an eager merge entry before.
  • Expectation changes: five TypeSpecifierTest rows (vacuous-mixed entries gone, symbolic falsey forms) and the bug-3991 native type, which now matches what the fixture's own comment always said it should be.

Validation: full test suite green (17772 tests), self-analysis clean (one baseline count bump for the new (string) cast), code style clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_019wqGgaD7iqL44t1KgpJS7b

ondrejmirtes and others added 2 commits July 28, 2026 17:56
…tries

normalize() eagerly folded sure-not entries into sure entries by
subtracting from $scope->getType() at composition time, baking the
composition scope's view into the narrowing. intersectWith() now
produces symbolic alternative-form entries instead - lists of
(sure, subtract) terms read as `(sure ?? current) minus subtract`,
united - and filterBySpecifiedTypes() evaluates them against the
subject's type at the application point, separately for the PHPDoc and
native views. Same-kind constraints still merge exactly; vacuous
never-subtractions collapse to no entry.

Consumers that need concrete sure types at composition time
(conditional-holder building, decided boolean operands) use the new
eager ConditionalExpressionHolderHelper::toSureTypes(). Result rebuilds
carry the entries via withAlternativeTypesOf(), and the disjunction
union recovery treats an alternative entry as already-constrained, as
it treated the eager merge entry before.

The bug-3991 fixture now infers the native type its own comment always
documented as correct.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wqGgaD7iqL44t1KgpJS7b
Infection flagged the $context->true() gates in the in_array item
combiner and the nullsafe fallback merge as escaped true()->truthy()
mutants. The gates only diverge under negated masks like
negate(createTrue()) - the "not exactly true" context produced by
!== true comparisons - which no row exercised.

New rows cover the plain truthy/falsey narrowing of both shapes and
the !== true form. The in_array !== true row kills the mutant: under
truthy() the combiner would take the positive either-branch merge for
a negative context and lose '$string' => ~'bar'|'foo'. The nullsafe
mutant is behaviorally equivalent - both merge arms coincide for every
constructible input - so its rows pin the current sound behavior
without being able to kill it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019wqGgaD7iqL44t1KgpJS7b
@ondrejmirtes
ondrejmirtes merged commit 3152f6d into 2.2.x Jul 28, 2026
413 of 414 checks passed
@ondrejmirtes
ondrejmirtes deleted the specified-types-alternative-forms branch July 28, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant