Skip to content

Commit 3019d39

Browse files
committed
Understand that new Foo() cannot be a subclass
1 parent 8873cdc commit 3019d39

15 files changed

+316
-49
lines changed

Diff for: src/Analyser/MutatingScope.php

+37-10
Original file line numberDiff line numberDiff line change
@@ -5599,6 +5599,10 @@ private function exactInstantiation(New_ $node, string $className): ?Type
55995599
}
56005600

56015601
$classReflection = $this->reflectionProvider->getClass($resolvedClassName);
5602+
$nonFinalClassReflection = $classReflection;
5603+
if (!$isStatic) {
5604+
$classReflection = $classReflection->asFinal();
5605+
}
56025606
if ($classReflection->hasConstructor()) {
56035607
$constructorMethod = $classReflection->getConstructor();
56045608
} else {
@@ -5648,7 +5652,7 @@ private function exactInstantiation(New_ $node, string $className): ?Type
56485652
return $methodResult;
56495653
}
56505654

5651-
$objectType = $isStatic ? new StaticType($classReflection) : new ObjectType($resolvedClassName);
5655+
$objectType = $isStatic ? new StaticType($classReflection) : new ObjectType($resolvedClassName, null, $classReflection);
56525656
if (!$classReflection->isGeneric()) {
56535657
return $objectType;
56545658
}
@@ -5674,7 +5678,8 @@ private function exactInstantiation(New_ $node, string $className): ?Type
56745678

56755679
if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
56765680
$propertyType = TypeCombinator::removeNull($this->getType($assignedToProperty));
5677-
if ($objectType->isSuperTypeOf($propertyType)->yes()) {
5681+
$nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, null, $nonFinalClassReflection);
5682+
if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) {
56785683
return $propertyType;
56795684
}
56805685
}
@@ -5689,9 +5694,13 @@ private function exactInstantiation(New_ $node, string $className): ?Type
56895694
[],
56905695
);
56915696
}
5697+
5698+
$types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds());
56925699
return new GenericObjectType(
56935700
$resolvedClassName,
5694-
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5701+
$types,
5702+
null,
5703+
$classReflection->withTypes($types)->asFinal(),
56955704
);
56965705
}
56975706

@@ -5706,9 +5715,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type
57065715
);
57075716
}
57085717

5718+
$types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds());
57095719
return new GenericObjectType(
57105720
$resolvedClassName,
5711-
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5721+
$types,
5722+
null,
5723+
$classReflection->withTypes($types)->asFinal(),
57125724
);
57135725
}
57145726
$newType = new GenericObjectType($resolvedClassName, $classReflection->typeMapToList($classReflection->getTemplateTypeMap()));
@@ -5723,9 +5735,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type
57235735
);
57245736
}
57255737

5738+
$types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds());
57265739
return new GenericObjectType(
57275740
$resolvedClassName,
5728-
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5741+
$types,
5742+
null,
5743+
$classReflection->withTypes($types)->asFinal(),
57295744
);
57305745
}
57315746
$ancestorClassReflections = $ancestorType->getObjectClassReflections();
@@ -5739,9 +5754,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type
57395754
);
57405755
}
57415756

5757+
$types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds());
57425758
return new GenericObjectType(
57435759
$resolvedClassName,
5744-
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5760+
$types,
5761+
null,
5762+
$classReflection->withTypes($types)->asFinal(),
57455763
);
57465764
}
57475765

@@ -5758,9 +5776,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type
57585776
);
57595777
}
57605778

5779+
$types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds());
57615780
return new GenericObjectType(
57625781
$resolvedClassName,
5763-
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()->resolveToBounds()),
5782+
$types,
5783+
null,
5784+
$classReflection->withTypes($types)->asFinal(),
57645785
);
57655786
}
57665787
$newParentTypeClassReflection = $newParentTypeClassReflections[0];
@@ -5803,9 +5824,12 @@ private function exactInstantiation(New_ $node, string $className): ?Type
58035824
);
58045825
}
58055826

5827+
$types = $classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap));
58065828
return new GenericObjectType(
58075829
$resolvedClassName,
5808-
$classReflection->typeMapToList(new TemplateTypeMap($resolvedTypeMap)),
5830+
$types,
5831+
null,
5832+
$classReflection->withTypes($types)->asFinal(),
58095833
);
58105834
}
58115835

@@ -5817,14 +5841,17 @@ private function exactInstantiation(New_ $node, string $className): ?Type
58175841
);
58185842

58195843
$resolvedTemplateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap();
5844+
$types = $classReflection->typeMapToList($classReflection->getTemplateTypeMap());
58205845
$newGenericType = new GenericObjectType(
58215846
$resolvedClassName,
5822-
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()),
5847+
$types,
5848+
null,
5849+
$classReflection->withTypes($types)->asFinal(),
58235850
);
58245851
if ($isStatic) {
58255852
$newGenericType = new GenericStaticType(
58265853
$classReflection,
5827-
$classReflection->typeMapToList($classReflection->getTemplateTypeMap()),
5854+
$types,
58285855
null,
58295856
[],
58305857
);

Diff for: src/Reflection/ClassReflection.php

+51
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ public function __construct(
175175
private array $universalObjectCratesClasses,
176176
private ?string $extraCacheKey = null,
177177
private ?TemplateTypeVarianceMap $resolvedCallSiteVarianceMap = null,
178+
private ?bool $finalByKeywordOverride = null,
178179
)
179180
{
180181
}
@@ -306,6 +307,10 @@ public function getCacheKey(): string
306307
$cacheKey .= '<' . implode(',', $templateTypes) . '>';
307308
}
308309

310+
if ($this->hasFinalByKeywordOverride()) {
311+
$cacheKey .= '-f=' . ($this->isFinalByKeyword() ? 't' : 'f');
312+
}
313+
309314
if ($this->extraCacheKey !== null) {
310315
$cacheKey .= '-' . $this->extraCacheKey;
311316
}
@@ -1276,12 +1281,21 @@ public function acceptsNamedArguments(): bool
12761281
return $this->acceptsNamedArguments;
12771282
}
12781283

1284+
public function hasFinalByKeywordOverride(): bool
1285+
{
1286+
return $this->isClass() && $this->finalByKeywordOverride !== null;
1287+
}
1288+
12791289
public function isFinalByKeyword(): bool
12801290
{
12811291
if ($this->isAnonymous()) {
12821292
return true;
12831293
}
12841294

1295+
if ($this->isClass() && $this->finalByKeywordOverride !== null) {
1296+
return $this->finalByKeywordOverride;
1297+
}
1298+
12851299
return $this->reflection->isFinal();
12861300
}
12871301

@@ -1543,6 +1557,7 @@ public function withTypes(array $types): self
15431557
$this->universalObjectCratesClasses,
15441558
null,
15451559
$this->resolvedCallSiteVarianceMap,
1560+
$this->finalByKeywordOverride,
15461561
);
15471562
}
15481563

@@ -1573,6 +1588,42 @@ public function withVariances(array $variances): self
15731588
$this->universalObjectCratesClasses,
15741589
null,
15751590
$this->varianceMapFromList($variances),
1591+
$this->finalByKeywordOverride,
1592+
);
1593+
}
1594+
1595+
public function asFinal(): self
1596+
{
1597+
if ($this->getNativeReflection()->isFinal()) {
1598+
return $this;
1599+
}
1600+
if ($this->finalByKeywordOverride === true) {
1601+
return $this;
1602+
}
1603+
1604+
return new self(
1605+
$this->reflectionProvider,
1606+
$this->initializerExprTypeResolver,
1607+
$this->fileTypeMapper,
1608+
$this->stubPhpDocProvider,
1609+
$this->phpDocInheritanceResolver,
1610+
$this->phpVersion,
1611+
$this->signatureMapProvider,
1612+
$this->attributeReflectionFactory,
1613+
$this->propertiesClassReflectionExtensions,
1614+
$this->methodsClassReflectionExtensions,
1615+
$this->allowedSubTypesClassReflectionExtensions,
1616+
$this->requireExtendsPropertiesClassReflectionExtension,
1617+
$this->requireExtendsMethodsClassReflectionExtension,
1618+
$this->displayName,
1619+
$this->reflection,
1620+
$this->anonymousFilename,
1621+
$this->resolvedTemplateTypeMap,
1622+
$this->stubPhpDocBlock,
1623+
$this->universalObjectCratesClasses,
1624+
null,
1625+
$this->resolvedCallSiteVarianceMap,
1626+
true,
15761627
);
15771628
}
15781629

Diff for: src/Type/Constant/ConstantStringType.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ public function isCallable(): TrinaryLogic
223223
return TrinaryLogic::createYes();
224224
}
225225

226-
if (!$classRef->getNativeReflection()->isFinal()) {
226+
if (!$classRef->isFinalByKeyword()) {
227227
return TrinaryLogic::createMaybe();
228228
}
229229

@@ -265,7 +265,7 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope)
265265
return FunctionCallableVariant::createFromVariants($method, $method->getVariants());
266266
}
267267

268-
if (!$classReflection->getNativeReflection()->isFinal()) {
268+
if (!$classReflection->isFinalByKeyword()) {
269269
return [new TrivialParametersAcceptor()];
270270
}
271271
}

Diff for: src/Type/ObjectType.php

+31-11
Original file line numberDiff line numberDiff line change
@@ -377,21 +377,37 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
377377
throw new ShouldNotHappenException();
378378
}
379379

380-
if ($thatClassNames[0] === $thisClassName) {
381-
return $transformResult(IsSuperTypeOfResult::createYes());
382-
}
383-
384-
$reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
385380
$thisClassReflection = $this->getClassReflection();
381+
$thatClassReflections = $type->getObjectClassReflections();
382+
if (count($thatClassReflections) === 1) {
383+
$thatClassReflection = $thatClassReflections[0];
384+
} else {
385+
$thatClassReflection = null;
386+
}
386387

387-
if ($thisClassReflection === null || !$reflectionProvider->hasClass($thatClassNames[0])) {
388+
if ($thisClassReflection === null || $thatClassReflection === null) {
389+
if ($thatClassNames[0] === $thisClassName) {
390+
return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
391+
}
388392
return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
389393
}
390394

391-
$thatClassReflection = $reflectionProvider->getClass($thatClassNames[0]);
395+
if ($thatClassNames[0] === $thisClassName) {
396+
if ($thisClassReflection->getNativeReflection()->isFinal()) {
397+
return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
398+
}
399+
400+
if ($thisClassReflection->hasFinalByKeywordOverride()) {
401+
if (!$thatClassReflection->hasFinalByKeywordOverride()) {
402+
return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createMaybe());
403+
}
404+
}
405+
406+
return self::$superTypes[$thisDescription][$description] = $transformResult(IsSuperTypeOfResult::createYes());
407+
}
392408

393409
if ($thisClassReflection->isTrait() || $thatClassReflection->isTrait()) {
394-
return IsSuperTypeOfResult::createNo();
410+
return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createNo();
395411
}
396412

397413
if ($thisClassReflection->getName() === $thatClassReflection->getName()) {
@@ -406,11 +422,11 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
406422
return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
407423
}
408424

409-
if ($thisClassReflection->isInterface() && !$thatClassReflection->getNativeReflection()->isFinal()) {
425+
if ($thisClassReflection->isInterface() && !$thatClassReflection->isFinalByKeyword()) {
410426
return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
411427
}
412428

413-
if ($thatClassReflection->isInterface() && !$thisClassReflection->getNativeReflection()->isFinal()) {
429+
if ($thatClassReflection->isInterface() && !$thisClassReflection->isFinalByKeyword()) {
414430
return self::$superTypes[$thisDescription][$description] = IsSuperTypeOfResult::createMaybe();
415431
}
416432

@@ -550,6 +566,10 @@ private function describeCache(): string
550566
$description .= '-';
551567
$description .= (string) $reflection->getNativeReflection()->getStartLine();
552568
$description .= '-';
569+
570+
if ($reflection->hasFinalByKeywordOverride()) {
571+
$description .= 'f=' . ($reflection->isFinalByKeyword() ? 't' : 'f');
572+
}
553573
}
554574

555575
return $this->cachedDescription = $description;
@@ -1331,7 +1351,7 @@ private function findCallableParametersAcceptors(): ?array
13311351
);
13321352
}
13331353

1334-
if (!$classReflection->getNativeReflection()->isFinal()) {
1354+
if (!$classReflection->isFinalByKeyword()) {
13351355
return [new TrivialParametersAcceptor()];
13361356
}
13371357

Diff for: tests/PHPStan/Analyser/nsrt/get-debug-type.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function doFoo(bool $b, int $i, float $f, $d, $r, string $s, array $a, $intOrStr
3333
assertType("'float'", get_debug_type($d));
3434
assertType("'string'", get_debug_type($s));
3535
assertType("'array'", get_debug_type($a));
36-
assertType("string", get_debug_type($o));
36+
assertType("'stdClass'", get_debug_type($o));
3737
assertType("string", get_debug_type($std));
3838
assertType("'GetDebugType\\\\A'", get_debug_type($A));
3939
assertType("string", get_debug_type($r));

Diff for: tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php

+11
Original file line numberDiff line numberDiff line change
@@ -504,4 +504,15 @@ public function testBug3632(): void
504504
]);
505505
}
506506

507+
public function testNewIsAlwaysFinalClass(): void
508+
{
509+
$this->treatPhpDocTypesAsCertain = true;
510+
$this->analyse([__DIR__ . '/data/impossible-instanceof-new-is-always-final.php'], [
511+
[
512+
'Instanceof between ImpossibleInstanceofNewIsAlwaysFinal\Bar and ImpossibleInstanceofNewIsAlwaysFinal\Foo will always evaluate to false.',
513+
17,
514+
],
515+
]);
516+
}
517+
507518
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace ImpossibleInstanceofNewIsAlwaysFinal;
4+
5+
interface Foo
6+
{
7+
8+
}
9+
10+
class Bar
11+
{
12+
13+
}
14+
15+
function (): void {
16+
$bar = new Bar();
17+
if ($bar instanceof Foo) {
18+
19+
}
20+
};
21+
22+
function (Bar $bar): void {
23+
if ($bar instanceof Foo) {
24+
25+
}
26+
};

Diff for: tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ public function testImpossibleCheckTypeFunctionCall(): void
9292
'Call to function is_string() with string will always evaluate to true.',
9393
140,
9494
],
95+
[
96+
'Call to function method_exists() with CheckTypeFunctionCall\Foo and \'test\' will always evaluate to false.',
97+
176,
98+
],
9599
[
96100
'Call to function method_exists() with CheckTypeFunctionCall\Foo and \'doFoo\' will always evaluate to true.',
97101
179,
@@ -172,6 +176,10 @@ public function testImpossibleCheckTypeFunctionCall(): void
172176
'Call to function method_exists() with CheckTypeFunctionCall\MethodExists and \'testWithNewObjectIn…\' will always evaluate to true.',
173177
620,
174178
],
179+
[
180+
'Call to function method_exists() with CheckTypeFunctionCall\MethodExists and \'undefinedMethod\' will always evaluate to false.',
181+
623,
182+
],
175183
[
176184
'Call to function method_exists() with CheckTypeFunctionCall\MethodExists and \'testWithNewObjectIn…\' will always evaluate to true.',
177185
635,

0 commit comments

Comments
 (0)