Check the subtracted type when a subtracted mixed accepts a value - #6167
Check the subtracted type when a subtracted mixed accepts a value#6167zonuexe wants to merge 3 commits into
Conversation
e8428d1 to
a5e98b4
Compare
|
This pull request has been marked as ready for review. |
| // A subtracted mixed only makes sense in an error message when the subtraction | ||
| // is spelled out. Template bounds are skipped - the subtraction there belongs | ||
| // to the bound, not to the type being described. | ||
| $hasSubtractedMixed = false; | ||
| TypeTraverser::map($acceptingType, static function (Type $type, callable $traverse) use (&$hasSubtractedMixed): Type { | ||
| if ($hasSubtractedMixed || $type instanceof TemplateType) { | ||
| return $type; | ||
| } | ||
|
|
||
| if ( | ||
| ($type instanceof MixedType || $type instanceof StrictMixedType) | ||
| && $type->getSubtractedType() !== null | ||
| ) { | ||
| $hasSubtractedMixed = true; | ||
| return $type; | ||
| } | ||
|
|
||
| return $traverse($type); | ||
| }); | ||
|
|
||
| if ($hasSubtractedMixed) { | ||
| return self::precise(); | ||
| } |
There was a problem hiding this comment.
couldn't this additional type-traversal be prevented by merging this logic into the pre-existing $moreVerboseCallback below?
There was a problem hiding this comment.
Merged in 14d40d3. A depth counter or boolean flag for the template-bound exclusion fails make phpstan: the self-analysis reports identical.alwaysTrue because it doesn't model the callback re-entering through $traverse. So the existing branch block moved into $flagsCallback; the outer callback maps template subtrees with it, escalates on subtracted mixed, and delegates the rest. Single traversal, no extra pass. Side effect: the accepted-type traversal in the invariant-template path now detects subtracted mixed too, so those messages also render the subtraction.
6877410 to
14d40d3
Compare
Closes phpstan/phpstan#15033
MixedType::accepts(andStrictMixedType, reached onceRuleLevelHelperrewrites an explicitMixedTypeforcheckExplicitMixed) accepted every value unconditionally, so a subtracted mixed likenon-empty-mixedrejected nothing at argument or return boundaries, even though the same subtraction already powered narrowing and reachability (identical.alwaysFalse,if.alwaysTrue, etc.).Acceptance now turns to
Noonly on a definite hit (subtractedType->isSuperTypeOf($given)->yes()), so partial overlaps (e.g. a generalstringintonon-empty-mixed, which may or may not be'') stay accepted — this preservesmixed's usual looseness and is why the fix isn'tisSuperTypeOf(...)->toAcceptsResult().NeverTypeis exempted, mirroringMixedType::isSuperTypeOf.RuleLevelHelper::transformCommonTypenow carries the subtraction through when it converts an explicitMixedTypeintoStrictMixedType, instead of discarding it — otherwise level max stayed silent even withMixedTypefixed.VerbosityLevel::getRecommendedLevelByTypeescalates toprecise()when a subtracted (Strict)MixedType is involved, so messages render the subtraction instead of a bare, uninformativemixed.