Skip to content

Commit 1a693bb

Browse files
jrmajorondrejmirtes
authored andcommitted
Fix class-string<*> being supertype of any string literal
Amp-Thread-ID: https://ampcode.com/threads/T-01a19219-4619-76d7-89f1-3dc38b076ad6
1 parent 091c5a7 commit 1a693bb

9 files changed

Lines changed: 122 additions & 30 deletions

File tree

‎src/Type/Constant/ConstantStringType.php‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@ private function export(string $value): string
162162
public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
163163
{
164164
if ($type instanceof GenericClassStringType) {
165-
$genericType = $type->getGenericType();
166-
if ($genericType instanceof MixedType) {
167-
return IsSuperTypeOfResult::createMaybe();
165+
if (!$this->isClassString()->yes()) {
166+
return IsSuperTypeOfResult::createNo();
168167
}
168+
169+
$genericType = $type->getGenericType();
169170
if ($genericType instanceof StaticType) {
170171
$genericType = $genericType->getStaticObjectType();
171172
}

‎src/Type/Generic/GenericClassStringType.php‎

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use PHPStan\Type\InstanceofDeprecated;
1616
use PHPStan\Type\IntersectionType;
1717
use PHPStan\Type\IsSuperTypeOfResult;
18-
use PHPStan\Type\MixedType;
1918
use PHPStan\Type\NeverType;
2019
use PHPStan\Type\ObjectType;
2120
use PHPStan\Type\ObjectWithoutClassType;
@@ -119,10 +118,6 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
119118

120119
if ($type instanceof ConstantStringType) {
121120
$genericType = $this->type;
122-
if ($genericType instanceof MixedType) {
123-
return IsSuperTypeOfResult::createYes();
124-
}
125-
126121
if ($genericType instanceof StaticType) {
127122
$genericType = $genericType->getStaticObjectType();
128123
}
@@ -139,11 +134,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
139134
$isSuperType = $genericType->isSuperTypeOf($objectType);
140135
}
141136

142-
if (!$type->isClassString()->yes()) {
143-
$isSuperType = $isSuperType->and(IsSuperTypeOfResult::createMaybe());
144-
}
145-
146-
return $isSuperType;
137+
return $isSuperType->and(IsSuperTypeOfResult::createFromTrinaryLogic($type->isClassString()));
147138
} elseif ($type instanceof self) {
148139
return $this->type->isSuperTypeOf($type->type);
149140
} elseif ($type instanceof ClassStringType) {

‎tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3394,4 +3394,9 @@ private function getBug15487Errors(): array
33943394
];
33953395
}
33963396

3397+
public function testBug10076(): void
3398+
{
3399+
$this->analyse([__DIR__ . '/data/bug-10076.php'], []);
3400+
}
3401+
33973402
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Bug10076;
4+
5+
/**
6+
* @template T
7+
*
8+
* @param 'object'|'array'|class-string<T> $type
9+
*
10+
* @return list<mixed>
11+
*/
12+
function result($type = 'object'): array
13+
{
14+
return [];
15+
}
16+
17+
function () {
18+
result();
19+
result('object');
20+
result('array');
21+
result(\DateTime::class);
22+
};

‎tests/PHPStan/Type/Constant/ConstantStringTypeTest.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ public static function dataIsSuperTypeOf(): array
134134
new GenericClassStringType(new StaticType($reflectionProvider->getClass(Throwable::class))),
135135
TrinaryLogic::createMaybe(),
136136
],
137+
16 => [
138+
new ConstantStringType('array'),
139+
new GenericClassStringType(TemplateTypeFactory::create(
140+
TemplateTypeScope::createWithFunction('foo'),
141+
'T',
142+
null,
143+
TemplateTypeVariance::createInvariant(),
144+
)),
145+
TrinaryLogic::createNo(),
146+
],
137147
];
138148
}
139149

‎tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ public static function dataIsSuperTypeOf(): array
171171
new ClassStringType(),
172172
TrinaryLogic::createMaybe(),
173173
],
174+
21 => [
175+
new GenericClassStringType(TemplateTypeFactory::create(
176+
TemplateTypeScope::createWithFunction('foo'),
177+
'T',
178+
null,
179+
TemplateTypeVariance::createInvariant(),
180+
)),
181+
new ConstantStringType('array'),
182+
TrinaryLogic::createNo(),
183+
],
174184
];
175185
}
176186

‎tests/PHPStan/Type/TypeCombinatorTest.php‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3244,6 +3244,33 @@ public static function dataUnion(): iterable
32443244
ConstantArrayType::class,
32453245
'array{}',
32463246
];
3247+
yield [
3248+
[
3249+
new ConstantStringType('array'),
3250+
new GenericClassStringType(TemplateTypeFactory::create(
3251+
TemplateTypeScope::createWithFunction('foo'),
3252+
'T',
3253+
null,
3254+
TemplateTypeVariance::createInvariant(),
3255+
)),
3256+
],
3257+
UnionType::class,
3258+
"'array'|class-string<T (function foo(), parameter)>",
3259+
];
3260+
3261+
yield [
3262+
[
3263+
new ConstantStringType(Exception::class),
3264+
new GenericClassStringType(TemplateTypeFactory::create(
3265+
TemplateTypeScope::createWithFunction('foo'),
3266+
'T',
3267+
null,
3268+
TemplateTypeVariance::createInvariant(),
3269+
)),
3270+
],
3271+
GenericClassStringType::class,
3272+
'class-string<T (function foo(), parameter)>',
3273+
];
32473274
}
32483275

32493276
/**
@@ -5823,6 +5850,33 @@ public static function dataIntersect(): iterable
58235850
NeverType::class,
58245851
'*NEVER*=implicit',
58255852
];
5853+
yield [
5854+
[
5855+
new ConstantStringType('array'),
5856+
new GenericClassStringType(TemplateTypeFactory::create(
5857+
TemplateTypeScope::createWithFunction('foo'),
5858+
'T',
5859+
null,
5860+
TemplateTypeVariance::createInvariant(),
5861+
)),
5862+
],
5863+
NeverType::class,
5864+
'*NEVER*=implicit',
5865+
];
5866+
5867+
yield [
5868+
[
5869+
new ConstantStringType(Exception::class),
5870+
new GenericClassStringType(TemplateTypeFactory::create(
5871+
TemplateTypeScope::createWithFunction('foo'),
5872+
'T',
5873+
null,
5874+
TemplateTypeVariance::createInvariant(),
5875+
)),
5876+
],
5877+
ConstantStringType::class,
5878+
"'Exception'",
5879+
];
58265880
}
58275881

58285882
/**

‎turbo-ext/src/ConstantStringType.cpp‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,19 +256,23 @@ class ConstantStringType
256256
return exported;
257257
}
258258

259-
/* maybe/no for a GenericClassStringType by whether its generic type (a
260-
* StaticType's object type, a TemplateType's bound; mixed is maybe) is
261-
* a supertype of this value's ObjectType; maybe/no for a
259+
/* no for a GenericClassStringType unless this is a class-string, else
260+
* maybe/no by whether its generic type (a StaticType's object type, a
261+
* TemplateType's bound) is a supertype of this value's ObjectType;
262+
* maybe/no for a
262263
* ClassStringType by whether this is a class-string; yes/no against
263264
* another ConstantStringType's value; maybe for any other StringType;
264265
* the CompoundType callback; no otherwise; UNDEF = pending exception */
265266
zv::Val isSuperTypeOf(zval *type) const
266267
{
267268
zend_class_entry *typeCe = Z_OBJCE_P(type);
268269
if (instanceof_function(typeCe, pt_ce_generic_class_string_type)) {
270+
zend_long isClass = thisIsClassString();
271+
if (UNEXPECTED(isClass < 0)) return zv::Val();
272+
if (isClass != PT_TRI_YES) return pt_type_is_super_type_of_result(PT_TRI_NO);
273+
269274
zv::Val genericType = pt_type_call(Z_OBJ_P(type), PT_LC("getgenerictype"), 0, NULL);
270275
if (UNEXPECTED(genericType.isUndef())) return zv::Val();
271-
if (zv::Ref(genericType.raw()).instanceOf(pt_ce_mixed_type)) return pt_type_is_super_type_of_result(PT_TRI_MAYBE);
272276
/* $genericType instanceof StaticType — the shadowing class */
273277
bool isStatic = zv::Ref(genericType.raw()).instanceOf(pt_ce_static_type);
274278
if (isStatic) {

‎turbo-ext/src/GenericClassStringType.cpp‎

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ class GenericClassStringType
152152
return callOnType(PT_LC("accepts"), 2, args);
153153
}
154154

155-
/* the CompoundType callback; for a ConstantStringType yes under a mixed
156-
* generic type, else the generic type's (a StaticType's object type, a
157-
* TemplateType's bound) verdict on its ObjectType, and'ed with maybe
158-
* unless it is a class-string; the generic types' verdict for another
155+
/* the CompoundType callback; for a ConstantStringType the generic
156+
* type's (a StaticType's object type, a TemplateType's bound) verdict on
157+
* its ObjectType, and'ed with whether it is a class-string; the generic
158+
* types' verdict for another
159159
* GenericClassStringType; the verdict on ObjectWithoutClassType for a
160160
* ClassStringType; maybe for a StringType; no otherwise; UNDEF =
161161
* pending exception */
@@ -174,7 +174,6 @@ class GenericClassStringType
174174
zval *ownType = this->type();
175175
if (UNEXPECTED(ownType == NULL)) return zv::Val();
176176
zv::Val genericType = zv::Val::copyOf(zv::Ref(ownType));
177-
if (zv::Ref(genericType.raw()).instanceOf(pt_ce_mixed_type)) return pt_type_is_super_type_of_result(PT_TRI_YES);
178177

179178
/* $genericType instanceof StaticType — the shadowing class */
180179
bool isStatic = zv::Ref(genericType.raw()).instanceOf(pt_ce_static_type);
@@ -203,16 +202,12 @@ class GenericClassStringType
203202
}
204203
if (UNEXPECTED(isSuperType.isUndef())) return zv::Val();
205204

205+
/* $isSuperType->and(IsSuperTypeOfResult::createFromTrinaryLogic($type->isClassString())) */
206206
zend_long isClassString = pt_type_call_trinary(Z_OBJ_P(type), PT_LC("isclassstring"), 0, NULL);
207207
if (UNEXPECTED(isClassString < 0)) return zv::Val();
208-
if (isClassString != PT_TRI_YES) {
209-
/* $isSuperType->and(IsSuperTypeOfResult::createMaybe()) */
210-
zv::Val maybe = pt_type_is_super_type_of_result(PT_TRI_MAYBE);
211-
if (UNEXPECTED(maybe.isUndef())) return zv::Val();
212-
isSuperType = pt_type_op(Z_OBJ_P(isSuperType.raw()), PT_OP_AND, 1, maybe.raw());
213-
}
214-
215-
return isSuperType;
208+
zv::Val classStringResult = pt_type_is_super_type_of_result(isClassString);
209+
if (UNEXPECTED(classStringResult.isUndef())) return zv::Val();
210+
return pt_type_op(Z_OBJ_P(isSuperType.raw()), PT_OP_AND, 1, classStringResult.raw());
216211
}
217212
if (instanceof_function(typeCe, pt_ce_generic_class_string_type)) {
218213
zval *other = typeOf(Z_OBJ_P(type));

0 commit comments

Comments
 (0)