Preserve template bound for enum-case, integer-range, constant-float and constant-bool subtypes in TemplateTypeFactory::create()#5905
Open
phpstan-bot wants to merge 1 commit into
Conversation
…and constant-bool subtypes in `TemplateTypeFactory::create()` - `TemplateTypeFactory::create()` matched bounds with strict `get_class()` checks, so any bound that was a subtype of a handled base type (without its own dedicated `Template*` class) fell through to the final `TemplateMixedType` catch-all, silently widening the bound to `mixed`. - This lost the generic type after narrowing a template variable, e.g. `TFoo of Foo::*` narrowed via `=== Foo::Abc` became `TFoo of mixed`, producing bogus `argument.type` errors. - Reorder the `GenericObjectType` check before the `ObjectType` check and let any remaining `ObjectType` subtype (e.g. `EnumCaseObjectType`) map to `TemplateObjectType`, keeping the precise bound. - Route `IntegerRangeType` to `TemplateIntegerType`, `ConstantFloatType` to `TemplateFloatType`, and constant booleans (`isTrue()`/`isFalse()`) to `TemplateBooleanType`. - Update `TypeCombinatorTest` data set phpstan#67 which was documenting the boolean case as a known bug (`should be T of true`).
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.
Summary
When a template-typed variable was narrowed by a comparison (e.g.
Foo::Abc === $foowhere$fooisTFoo of Foo::*), PHPStan lost the generic type information: the narrowed value becameTFoo of mixedinstead ofTFoo of Foo::Abc, which then caused spuriousargument.typeerrors such as "expects Foo::Abc|Foo::Bcd, TFoo given".The root cause is in
TemplateTypeFactory::create(), which builds theTemplate*wrapper for a narrowed/computed bound. It dispatched on the exact bound class viaget_class(), so any bound that is a subtype of a handled base type but lacks its own dedicatedTemplate*class fell through to the finalTemplateMixedTypecatch-all — silently widening the bound tomixed.Changes
src/Type/Generic/TemplateTypeFactory.php:GenericObjectTypebranch before theObjectTypebranch and broadened the latter to accept anyObjectTypesubtype (notablyEnumCaseObjectType), mapping it toTemplateObjectTypewith the precise bound preserved.IntegerRangeType(subtype ofIntegerType) toTemplateIntegerType.ConstantFloatType(subtype ofFloatType) toTemplateFloatType.isTrue()/isFalse()) toTemplateBooleanType.tests/PHPStan/Type/TypeCombinatorTest.php: updated data set Fixed broken travis config #67, which previously asserted the buggyTemplateMixedType/'T (class Foo, parameter)'result and carried// should be ...comments; it now expectsTemplateBooleanType/'T of true (class Foo, parameter)'.Root cause
TemplateTypeFactory::create()is a dispatch table that maps a boundTypeto the matchingTemplate*type. Each branch guarded with$boundClass === SomeType::classto avoid catching subtypes that need their own wrapper. But several concrete subtypes have no dedicated wrapper:EnumCaseObjectType(←ObjectType)IntegerRangeType(←IntegerType)ConstantFloatType(←FloatType)ConstantBooleanType(←BooleanType)For these, the strict checks skipped every branch and the function fell through to
return new TemplateMixedType(...), discarding the bound. The fix routes each of these subtypes to the appropriate baseTemplate*class (which stores the precise bound), so narrowing a template variable keeps its generic identity and bound.Test
tests/PHPStan/Analyser/nsrt/bug-10083.php— a new regression test that mirrors the issue and also covers the analogous families:TFoo of Foo::*in both branches of anifand through a ternary (the reported case),TInt of int(>= 0 && <= 5→TInt of int<0, 5>),TFloat of 1.0|2.0(=== 1.0→TFloat of 1.0).All assertions fail before the fix (the bound shows as
mixed) and pass after.TypeCombinatorTest::testRemovedata set Fixed broken travis config #67 (removingfalsefrom aT of booltemplate now yieldsT of true).Fixes phpstan/phpstan#10083