diff --git a/src/Type/Constant/ConstantStringType.php b/src/Type/Constant/ConstantStringType.php index c321a84d04c..91a00bd2aeb 100644 --- a/src/Type/Constant/ConstantStringType.php +++ b/src/Type/Constant/ConstantStringType.php @@ -162,10 +162,11 @@ private function export(string $value): string public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { if ($type instanceof GenericClassStringType) { - $genericType = $type->getGenericType(); - if ($genericType instanceof MixedType) { - return IsSuperTypeOfResult::createMaybe(); + if (!$this->isClassString()->yes()) { + return IsSuperTypeOfResult::createNo(); } + + $genericType = $type->getGenericType(); if ($genericType instanceof StaticType) { $genericType = $genericType->getStaticObjectType(); } diff --git a/src/Type/Generic/GenericClassStringType.php b/src/Type/Generic/GenericClassStringType.php index 5bc5216e5a8..c3caa57af68 100644 --- a/src/Type/Generic/GenericClassStringType.php +++ b/src/Type/Generic/GenericClassStringType.php @@ -15,7 +15,6 @@ use PHPStan\Type\InstanceofDeprecated; use PHPStan\Type\IntersectionType; use PHPStan\Type\IsSuperTypeOfResult; -use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; @@ -119,10 +118,6 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult if ($type instanceof ConstantStringType) { $genericType = $this->type; - if ($genericType instanceof MixedType) { - return IsSuperTypeOfResult::createYes(); - } - if ($genericType instanceof StaticType) { $genericType = $genericType->getStaticObjectType(); } @@ -139,11 +134,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult $isSuperType = $genericType->isSuperTypeOf($objectType); } - if (!$type->isClassString()->yes()) { - $isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe()); - } - - return $isSuperType; + return $isSuperType->and(IsSuperTypeOfResult::createFromTrinaryLogic($type->isClassString())); } elseif ($type instanceof self) { return $this->type->isSuperTypeOf($type->type); } elseif ($type instanceof ClassStringType) { diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 67b5837fe6d..f1bc6244be2 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -3394,4 +3394,9 @@ private function getBug15487Errors(): array ]; } + public function testBug10076(): void + { + $this->analyse([__DIR__ . '/data/bug-10076.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-10076.php b/tests/PHPStan/Rules/Functions/data/bug-10076.php new file mode 100644 index 00000000000..a218249dbed --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-10076.php @@ -0,0 +1,22 @@ + $type + * + * @return list + */ +function result($type = 'object'): array +{ + return []; +} + +function () { + result(); + result('object'); + result('array'); + result(\DateTime::class); +}; diff --git a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php index 72da17e3c61..7e5ce0776fb 100644 --- a/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php +++ b/tests/PHPStan/Type/Constant/ConstantStringTypeTest.php @@ -134,6 +134,16 @@ public static function dataIsSuperTypeOf(): array new GenericClassStringType(new StaticType($reflectionProvider->getClass(Throwable::class))), TrinaryLogic::createMaybe(), ], + 16 => [ + new ConstantStringType('array'), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant(), + )), + TrinaryLogic::createNo(), + ], ]; } diff --git a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php index 8c52d9c6c95..c49d40e5a5b 100644 --- a/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php +++ b/tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php @@ -171,6 +171,16 @@ public static function dataIsSuperTypeOf(): array new ClassStringType(), TrinaryLogic::createMaybe(), ], + 21 => [ + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant(), + )), + new ConstantStringType('array'), + TrinaryLogic::createNo(), + ], ]; } diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index c4da5fd93ee..301b099f9c4 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -3244,6 +3244,33 @@ public static function dataUnion(): iterable ConstantArrayType::class, 'array{}', ]; + yield [ + [ + new ConstantStringType('array'), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant(), + )), + ], + UnionType::class, + "'array'|class-string", + ]; + + yield [ + [ + new ConstantStringType(Exception::class), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant(), + )), + ], + GenericClassStringType::class, + 'class-string', + ]; } /** @@ -5823,6 +5850,33 @@ public static function dataIntersect(): iterable NeverType::class, '*NEVER*=implicit', ]; + yield [ + [ + new ConstantStringType('array'), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant(), + )), + ], + NeverType::class, + '*NEVER*=implicit', + ]; + + yield [ + [ + new ConstantStringType(Exception::class), + new GenericClassStringType(TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('foo'), + 'T', + null, + TemplateTypeVariance::createInvariant(), + )), + ], + ConstantStringType::class, + "'Exception'", + ]; } /** diff --git a/turbo-ext/src/ConstantStringType.cpp b/turbo-ext/src/ConstantStringType.cpp index fe0955e5409..4e63b56b4ad 100644 --- a/turbo-ext/src/ConstantStringType.cpp +++ b/turbo-ext/src/ConstantStringType.cpp @@ -256,9 +256,10 @@ class ConstantStringType return exported; } - /* maybe/no for a GenericClassStringType by whether its generic type (a - * StaticType's object type, a TemplateType's bound; mixed is maybe) is - * a supertype of this value's ObjectType; maybe/no for a + /* no for a GenericClassStringType unless this is a class-string, else + * maybe/no by whether its generic type (a StaticType's object type, a + * TemplateType's bound) is a supertype of this value's ObjectType; + * maybe/no for a * ClassStringType by whether this is a class-string; yes/no against * another ConstantStringType's value; maybe for any other StringType; * the CompoundType callback; no otherwise; UNDEF = pending exception */ @@ -266,9 +267,12 @@ class ConstantStringType { zend_class_entry *typeCe = Z_OBJCE_P(type); if (instanceof_function(typeCe, pt_ce_generic_class_string_type)) { + zend_long isClass = thisIsClassString(); + if (UNEXPECTED(isClass < 0)) return zv::Val(); + if (isClass != PT_TRI_YES) return pt_type_is_super_type_of_result(PT_TRI_NO); + zv::Val genericType = pt_type_call(Z_OBJ_P(type), PT_LC("getgenerictype"), 0, NULL); if (UNEXPECTED(genericType.isUndef())) return zv::Val(); - if (zv::Ref(genericType.raw()).instanceOf(pt_ce_mixed_type)) return pt_type_is_super_type_of_result(PT_TRI_MAYBE); /* $genericType instanceof StaticType — the shadowing class */ bool isStatic = zv::Ref(genericType.raw()).instanceOf(pt_ce_static_type); if (isStatic) { diff --git a/turbo-ext/src/GenericClassStringType.cpp b/turbo-ext/src/GenericClassStringType.cpp index db25bca8779..13360c6f52e 100644 --- a/turbo-ext/src/GenericClassStringType.cpp +++ b/turbo-ext/src/GenericClassStringType.cpp @@ -152,10 +152,10 @@ class GenericClassStringType return callOnType(PT_LC("accepts"), 2, args); } - /* the CompoundType callback; for a ConstantStringType yes under a mixed - * generic type, else the generic type's (a StaticType's object type, a - * TemplateType's bound) verdict on its ObjectType, and'ed with maybe - * unless it is a class-string; the generic types' verdict for another + /* the CompoundType callback; for a ConstantStringType the generic + * type's (a StaticType's object type, a TemplateType's bound) verdict on + * its ObjectType, and'ed with whether it is a class-string; the generic + * types' verdict for another * GenericClassStringType; the verdict on ObjectWithoutClassType for a * ClassStringType; maybe for a StringType; no otherwise; UNDEF = * pending exception */ @@ -174,7 +174,6 @@ class GenericClassStringType zval *ownType = this->type(); if (UNEXPECTED(ownType == NULL)) return zv::Val(); zv::Val genericType = zv::Val::copyOf(zv::Ref(ownType)); - if (zv::Ref(genericType.raw()).instanceOf(pt_ce_mixed_type)) return pt_type_is_super_type_of_result(PT_TRI_YES); /* $genericType instanceof StaticType — the shadowing class */ bool isStatic = zv::Ref(genericType.raw()).instanceOf(pt_ce_static_type); @@ -203,16 +202,12 @@ class GenericClassStringType } if (UNEXPECTED(isSuperType.isUndef())) return zv::Val(); + /* $isSuperType->and(IsSuperTypeOfResult::createFromTrinaryLogic($type->isClassString())) */ zend_long isClassString = pt_type_call_trinary(Z_OBJ_P(type), PT_LC("isclassstring"), 0, NULL); if (UNEXPECTED(isClassString < 0)) return zv::Val(); - if (isClassString != PT_TRI_YES) { - /* $isSuperType->and(IsSuperTypeOfResult::createMaybe()) */ - zv::Val maybe = pt_type_is_super_type_of_result(PT_TRI_MAYBE); - if (UNEXPECTED(maybe.isUndef())) return zv::Val(); - isSuperType = pt_type_op(Z_OBJ_P(isSuperType.raw()), PT_OP_AND, 1, maybe.raw()); - } - - return isSuperType; + zv::Val classStringResult = pt_type_is_super_type_of_result(isClassString); + if (UNEXPECTED(classStringResult.isUndef())) return zv::Val(); + return pt_type_op(Z_OBJ_P(isSuperType.raw()), PT_OP_AND, 1, classStringResult.raw()); } if (instanceof_function(typeCe, pt_ce_generic_class_string_type)) { zval *other = typeOf(Z_OBJ_P(type));