Fix #10698: False positive argument.type (… expects Foo<string|null>, Foo<string|null> given.)
#4903
+58
−1
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
Fixes a false positive
argument.typeerror where passing a generic object with a nullable type argument (e.g.,Foo<string|null>) to a parameter expecting the exact same type would report "expectsFoo<string|null>,Foo<string|null>given" at rule levels 5-7 (but not at level 8+).Changes
GenericObjectType::isSuperTypeOfInternal()insrc/Type/Generic/GenericObjectType.phpto handle the accepts context for invariant template type parameters: when the invariance check fails, verify whether the mismatch is only due to asymmetric null stripping byRuleLevelHelper::transformAcceptedTypetests/PHPStan/Rules/Methods/data/bug-10698.phpand test method intests/PHPStan/Rules/Methods/CallMethodsRuleTest.phpRoot cause
RuleLevelHelper::transformAcceptedType()usesTypeTraverser::mapto stripnullfrom union types whencheckNullablesis false (rule levels < 8). This traversal recurses intoGenericObjectType's inner type arguments, transformingFoo<string|null>(accepted) intoFoo<string>. However, the accepting typeFoo<string|null>retains its null. The invariance check inTemplateTypeVariance::isValidVariance()then comparesstring|null(from accepting) vsstring(from accepted) usingequals(), which fails, producing the false positive.The fix adds a fallback check in
GenericObjectType::isSuperTypeOfInternal(): when in accepts context and the invariance check fails, it verifies whether the accepting type without null equals the accepted type (i.e., the mismatch was solely due to null stripping). If so, the result is treated as a match.Test
Added a regression test that reproduces the exact scenario from the issue: a generic class
Foo<T>passed to a method expectingFoo<T>, where the type argument isstring|null. The test runs withcheckNullables=falseandcheckUnionTypes=true(simulating level 7) and verifies no error is reported.Fixes phpstan/phpstan#10698