Skip to content

Commit 9a46610

Browse files
rvanvelzenclaude
andcommitted
Do not accept raw ObjectType in intersection as GenericObjectType<TemplateType>
Closes phpstan/phpstan#13190 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5bcff71 commit 9a46610

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

phpstan-baseline.neon

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,12 +1449,24 @@ parameters:
14491449
count: 1
14501450
path: src/Type/IntersectionType.php
14511451

1452+
-
1453+
rawMessage: Doing instanceof PHPStan\Type\Generic\GenericObjectType is error-prone and deprecated.
1454+
identifier: phpstanApi.instanceofType
1455+
count: 3
1456+
path: src/Type/IntersectionType.php
1457+
14521458
-
14531459
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
14541460
identifier: phpstanApi.instanceofType
14551461
count: 3
14561462
path: src/Type/IntersectionType.php
14571463

1464+
-
1465+
rawMessage: 'Doing instanceof PHPStan\Type\ObjectType is error-prone and deprecated. Use Type::isObject() or Type::getObjectClassNames() instead.'
1466+
identifier: phpstanApi.instanceofType
1467+
count: 1
1468+
path: src/Type/IntersectionType.php
1469+
14581470
-
14591471
rawMessage: 'Method PHPStan\Type\IntersectionType::getConstantArrays() should return list<PHPStan\Type\Constant\ConstantArrayType> but returns array{PHPStan\Type\Type}.'
14601472
identifier: return.type

src/Type/IntersectionType.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use PHPStan\Type\Constant\ConstantIntegerType;
4545
use PHPStan\Type\Constant\ConstantStringType;
4646
use PHPStan\Type\Enum\EnumCaseObjectType;
47+
use PHPStan\Type\Generic\GenericObjectType;
4748
use PHPStan\Type\Generic\TemplateArrayType;
4849
use PHPStan\Type\Generic\TemplateType;
4950
use PHPStan\Type\Generic\TemplateTypeMap;
@@ -339,6 +340,28 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsRes
339340
if ($isSuperType->no()) {
340341
return $isSuperType->toAcceptsResult();
341342
}
343+
// A raw (non-generic) ObjectType in the intersection cannot satisfy a TemplateType
344+
// argument — when isSuperTypeOf says Maybe, trust it over lazyMaxMin's Yes.
345+
if ($isSuperType->maybe() && $acceptingType instanceof GenericObjectType) {
346+
foreach ($acceptingType->getTypes() as $typeArg) {
347+
if (!$typeArg instanceof TemplateType) {
348+
continue;
349+
}
350+
foreach ($this->types as $innerType) {
351+
if (
352+
$innerType instanceof GenericObjectType
353+
|| $innerType instanceof TemplateType
354+
|| !$innerType instanceof ObjectType
355+
) {
356+
continue;
357+
}
358+
$ancestor = $innerType->getAncestorWithClassName($acceptingType->getClassName());
359+
if ($ancestor !== null && !$ancestor instanceof GenericObjectType) {
360+
return $isSuperType->toAcceptsResult();
361+
}
362+
}
363+
}
364+
}
342365
}
343366

344367
if ($this->isOversizedArray()->yes()) {

tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,12 @@ public function testBug13190(): void
455455
{
456456
$this->checkNullables = true;
457457
$this->checkExplicitMixed = false;
458-
$this->analyse([__DIR__ . '/data/bug-13190.php'], []);
458+
$this->analyse([__DIR__ . '/data/bug-13190.php'], [
459+
[
460+
'Function Bug13190\inbox() should return Bug13190\Box<T> but returns (Bug13190\Box&T)|Bug13190\Box<T>.',
461+
51,
462+
],
463+
]);
459464
}
460465

461466
}

0 commit comments

Comments
 (0)