Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/Type/Constant/ConstantStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
11 changes: 1 addition & 10 deletions src/Type/Generic/GenericClassStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3394,4 +3394,9 @@ private function getBug15487Errors(): array
];
}

public function testBug10076(): void
{
$this->analyse([__DIR__ . '/data/bug-10076.php'], []);
}

}
22 changes: 22 additions & 0 deletions tests/PHPStan/Rules/Functions/data/bug-10076.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Bug10076;

/**
* @template T
*
* @param 'object'|'array'|class-string<T> $type
*
* @return list<mixed>
*/
function result($type = 'object'): array
{
return [];
}

function () {
result();
result('object');
result('array');
result(\DateTime::class);
};
10 changes: 10 additions & 0 deletions tests/PHPStan/Type/Constant/ConstantStringTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
],
];
}

Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
],
];
}

Expand Down
54 changes: 54 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<T (function foo(), parameter)>",
];

yield [
[
new ConstantStringType(Exception::class),
new GenericClassStringType(TemplateTypeFactory::create(
TemplateTypeScope::createWithFunction('foo'),
'T',
null,
TemplateTypeVariance::createInvariant(),
)),
],
GenericClassStringType::class,
'class-string<T (function foo(), parameter)>',
];
}

/**
Expand Down Expand Up @@ -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'",
];
}

/**
Expand Down
12 changes: 8 additions & 4 deletions turbo-ext/src/ConstantStringType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,19 +256,23 @@ 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 */
zv::Val isSuperTypeOf(zval *type) const
{
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) {
Expand Down
21 changes: 8 additions & 13 deletions turbo-ext/src/GenericClassStringType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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);
Expand Down Expand Up @@ -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));
Expand Down
Loading