Skip to content

Commit f60ac61

Browse files
committed
Merge branch '1.9.x' into 1.10.x
2 parents 436e6d3 + e1c8380 commit f60ac61

9 files changed

+377
-9
lines changed

Diff for: src/Analyser/MutatingScope.php

+22-9
Original file line numberDiff line numberDiff line change
@@ -2419,18 +2419,18 @@ public function isInFunctionExists(string $functionName): bool
24192419
public function enterClass(ClassReflection $classReflection): self
24202420
{
24212421
$thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection));
2422+
$constantTypes = $this->getConstantTypes();
2423+
$constantTypes['$this'] = $thisHolder;
2424+
$nativeConstantTypes = $this->getNativeConstantTypes();
2425+
$nativeConstantTypes['$this'] = $thisHolder;
24222426

24232427
return $this->scopeFactory->create(
24242428
$this->context->enterClass($classReflection),
24252429
$this->isDeclareStrictTypes(),
24262430
null,
24272431
$this->getNamespace(),
2428-
array_merge($this->getConstantTypes(), [
2429-
'$this' => $thisHolder,
2430-
]),
2431-
array_merge($this->getNativeConstantTypes(), [
2432-
'$this' => $thisHolder,
2433-
]),
2432+
$constantTypes,
2433+
$nativeConstantTypes,
24342434
[],
24352435
[],
24362436
null,
@@ -2451,13 +2451,26 @@ public function enterTrait(ClassReflection $traitReflection): self
24512451
if (count($traitNameParts) > 1) {
24522452
$namespace = implode('\\', array_slice($traitNameParts, 0, -1));
24532453
}
2454+
2455+
$traitContext = $this->context->enterTrait($traitReflection);
2456+
$classReflection = $traitContext->getClassReflection();
2457+
if ($classReflection === null) {
2458+
throw new ShouldNotHappenException();
2459+
}
2460+
2461+
$thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection, null, $traitReflection));
2462+
$expressionTypes = $this->expressionTypes;
2463+
$expressionTypes['$this'] = $thisHolder;
2464+
$nativeExpressionTypes = $this->nativeExpressionTypes;
2465+
$nativeExpressionTypes['$this'] = $thisHolder;
2466+
24542467
return $this->scopeFactory->create(
2455-
$this->context->enterTrait($traitReflection),
2468+
$traitContext,
24562469
$this->isDeclareStrictTypes(),
24572470
$this->getFunction(),
24582471
$namespace,
2459-
$this->expressionTypes,
2460-
$this->nativeExpressionTypes,
2472+
$expressionTypes,
2473+
$nativeExpressionTypes,
24612474
[],
24622475
$this->inClosureBindScopeClasses,
24632476
$this->anonymousFunctionReflection,

Diff for: src/Type/ObjectType.php

+25
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,31 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
302302
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
303303
}
304304

305+
if ($type instanceof ThisType && $type->isInTrait()) {
306+
if ($type->getSubtractedType() !== null) {
307+
$isSuperType = $type->getSubtractedType()->isSuperTypeOf($this);
308+
if ($isSuperType->yes()) {
309+
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
310+
}
311+
}
312+
313+
if ($this->getClassReflection() === null) {
314+
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
315+
}
316+
317+
$thisClassReflection = $this->getClassReflection();
318+
if ($thisClassReflection->isTrait()) {
319+
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
320+
}
321+
322+
$traitReflection = $type->getTraitReflection();
323+
if ($thisClassReflection->isFinal() && !$thisClassReflection->hasTraitUse($traitReflection->getName())) {
324+
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
325+
}
326+
327+
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
328+
}
329+
305330
$transformResult = static fn (TrinaryLogic $result) => $result;
306331
if ($this->subtractedType !== null) {
307332
$isSuperType = $this->subtractedType->isSuperTypeOf($type);

Diff for: src/Type/ThisType.php

+14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class ThisType extends StaticType
1717
public function __construct(
1818
ClassReflection $classReflection,
1919
?Type $subtractedType = null,
20+
private ?ClassReflection $traitReflection = null,
2021
)
2122
{
2223
parent::__construct($classReflection, $subtractedType);
@@ -57,6 +58,19 @@ public function changeSubtractedType(?Type $subtractedType): Type
5758
return $type;
5859
}
5960

61+
/**
62+
* @phpstan-assert-if-true !null $this->getTraitReflection()
63+
*/
64+
public function isInTrait(): bool
65+
{
66+
return $this->traitReflection !== null;
67+
}
68+
69+
public function getTraitReflection(): ?ClassReflection
70+
{
71+
return $this->traitReflection;
72+
}
73+
6074
public function traverse(callable $cb): Type
6175
{
6276
$subtractedType = $this->getSubtractedType() !== null ? $cb($this->getSubtractedType()) : null;

Diff for: tests/PHPStan/Analyser/NodeScopeResolverTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,7 @@ public function dataFileAsserts(): iterable
11971197
yield from $this->gatherAssertTypes(__DIR__ . '/data/callsite-cast-narrowing.php');
11981198
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8775.php');
11991199
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8752.php');
1200+
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-instance-of.php');
12001201
}
12011202

12021203
/**

Diff for: tests/PHPStan/Analyser/data/trait-instance-of.php

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace TraitInstanceOf;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
trait Trait1 {
8+
public function test(): string {
9+
assertType('$this(TraitInstanceOf\ATrait1Class)', $this);
10+
if ($this instanceof WithoutFoo) {
11+
assertType('$this(TraitInstanceOf\ATrait1Class)&TraitInstanceOf\WithoutFoo', $this);
12+
return 'hello world';
13+
}
14+
15+
if ($this instanceof FinalOther) {
16+
assertType('*NEVER*', $this);
17+
return 'hello world';
18+
}
19+
20+
assertType('$this(TraitInstanceOf\ATrait1Class)', $this);
21+
if ($this instanceof Trait2) {
22+
assertType('*NEVER*', $this);
23+
return 'hello world';
24+
}
25+
26+
if ($this instanceof FinalTrait2Class) {
27+
assertType('*NEVER*', $this);
28+
return 'hello world';
29+
}
30+
31+
assertType('$this(TraitInstanceOf\ATrait1Class)', $this);
32+
throw new \Error();
33+
}
34+
}
35+
36+
trait Trait2 {
37+
public function test(): string {
38+
assertType('$this(TraitInstanceOf\FinalTrait2Class)', $this);
39+
40+
if ($this instanceof FinalTrait2Class) {
41+
assertType('$this(TraitInstanceOf\FinalTrait2Class)&TraitInstanceOf\FinalTrait2Class', $this);
42+
return 'hello world';
43+
}
44+
45+
if ($this instanceof ATrait1Class) {
46+
assertType('*NEVER*', $this);
47+
return 'hello world';
48+
}
49+
50+
if ($this instanceof FinalOther) {
51+
assertType('*NEVER*', $this);
52+
return 'hello world';
53+
}
54+
55+
return 'hello world';
56+
}
57+
}
58+
59+
final class FinalOther {
60+
}
61+
62+
final class FinalTrait2Class {
63+
use Trait2;
64+
}
65+
66+
class WithoutFoo {}
67+
68+
class ATrait1Class {
69+
use Trait1;
70+
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,14 @@ public function testBug7720(): void
7070
]);
7171
}
7272

73+
public function testTraitInstanceOf(): void
74+
{
75+
$this->analyse([__DIR__ . '/../../Analyser/data/trait-instance-of.php'], [
76+
[
77+
'Instanceof between $this(TraitInstanceOf\ATrait1Class) and trait TraitInstanceOf\Trait2 will always evaluate to false.',
78+
21,
79+
],
80+
]);
81+
}
82+
7383
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -541,4 +541,11 @@ public function testBug4689(): void
541541
$this->analyse([__DIR__ . '/data/bug-4689.php'], []);
542542
}
543543

544+
public function testBug3632(): void
545+
{
546+
$this->checkAlwaysTrueInstanceOf = true;
547+
$this->treatPhpDocTypesAsCertain = false;
548+
$this->analyse([__DIR__ . '/data/bug-3632.php'], []);
549+
}
550+
544551
}

Diff for: tests/PHPStan/Rules/Classes/data/bug-3632.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug3632;
4+
5+
trait Foo {
6+
public function test(): string {
7+
if ($this instanceof HelloWorld) {
8+
return 'hello world';
9+
}
10+
if ($this instanceof OtherClass) {
11+
return 'other class';
12+
}
13+
14+
return 'no';
15+
}
16+
}
17+
18+
class HelloWorld
19+
{
20+
use Foo;
21+
22+
function bar(): string {
23+
return $this->test();
24+
}
25+
}
26+
27+
class OtherClass {
28+
use Foo;
29+
30+
function bar(): string {
31+
return $this->test();
32+
}
33+
}

0 commit comments

Comments
 (0)