Skip to content

Commit e029220

Browse files
committed
Add FQCN support to implementsInterface
1 parent c8cd404 commit e029220

File tree

7 files changed

+95
-9
lines changed

7 files changed

+95
-9
lines changed

src/Type/WebMozartAssert/AssertTypeSpecifyingExtension.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -447,14 +447,14 @@ private static function getExpressionResolvers(): array
447447
},
448448
'implementsInterface' => static function (Scope $scope, Arg $expr, Arg $class): ?Expr {
449449
$classType = $scope->getType($class->value);
450-
if (!$classType instanceof ConstantStringType) {
451-
return null;
450+
if ($classType instanceof ConstantStringType) {
451+
$classReflection = (new ObjectType($classType->getValue()))->getClassReflection();
452+
if ($classReflection === null || !$classReflection->isInterface()) {
453+
return new ConstFetch(new Name('false'));
454+
}
452455
}
453456

454-
return new Instanceof_(
455-
$expr->value,
456-
new Name($classType->getValue())
457-
);
457+
return self::$resolvers['subclassOf']($scope, $expr, $class);
458458
},
459459
'keyExists' => static function (Scope $scope, Arg $array, Arg $key): Expr {
460460
return new FuncCall(

tests/Type/WebMozartAssert/AssertTypeSpecifyingExtensionTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public function dataFileAsserts(): iterable
1818
yield from $this->gatherAssertTypes(__DIR__ . '/data/object.php');
1919
yield from $this->gatherAssertTypes(__DIR__ . '/data/string.php');
2020
yield from $this->gatherAssertTypes(__DIR__ . '/data/type.php');
21+
22+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-18.php');
2123
}
2224

2325
/**

tests/Type/WebMozartAssert/ImpossibleCheckTypeMethodCallRuleTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ public function testExtension(): void
8484
'Call to static method Webmozart\Assert\Assert::allContains() with array<non-empty-string> and \'foo\' will always evaluate to true.',
8585
98,
8686
],
87+
[
88+
'Call to static method Webmozart\Assert\Assert::implementsInterface() with class-string<WebmozartAssertImpossibleCheck\Bar>|WebmozartAssertImpossibleCheck\Bar and \'WebmozartAssertImpossibleCheck\\\Bar\' will always evaluate to true.',
89+
105,
90+
],
91+
[
92+
'Call to static method Webmozart\Assert\Assert::implementsInterface() with class-string<WebmozartAssertImpossibleCheck\Bar> and \'WebmozartAssertImpossibleCheck\\\Bar\' will always evaluate to true.',
93+
108,
94+
],
95+
[
96+
'Call to static method Webmozart\Assert\Assert::implementsInterface() with mixed and \'WebmozartAssertImpossibleCheck\\\Unknown\' will always evaluate to false.',
97+
110,
98+
],
99+
[
100+
'Call to static method Webmozart\Assert\Assert::implementsInterface() with mixed and \'WebmozartAssertImpossibleCheck\\\Foo\' will always evaluate to false.',
101+
111,
102+
],
87103
]);
88104
}
89105

@@ -175,6 +191,21 @@ public function testBug8(): void
175191
]);
176192
}
177193

194+
public function testBug17(): void
195+
{
196+
$this->analyse([__DIR__ . '/data/bug-17.php'], [
197+
[
198+
'Call to static method Webmozart\Assert\Assert::implementsInterface() with \'DateTime\' and \'DateTimeInterface\' will always evaluate to true.',
199+
9,
200+
],
201+
]);
202+
}
203+
204+
public function testBug18(): void
205+
{
206+
$this->analyse([__DIR__ . '/data/bug-18.php'], []);
207+
}
208+
178209
public function testBug32(): void
179210
{
180211
$this->analyse([__DIR__ . '/data/bug-32.php'], []);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug17;
4+
5+
use DateTimeInterface;
6+
use Webmozart\Assert\Assert;
7+
8+
(function () {
9+
Assert::implementsInterface(\DateTime::class, DateTimeInterface::class);
10+
Assert::implementsInterface(\DateTimeZone::class, DateTimeInterface::class);
11+
})();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Bug18;
4+
5+
use Webmozart\Assert\Assert;
6+
use function PHPStan\Testing\assertType;
7+
8+
class MyThingFactory
9+
{
10+
public function make(string $thing)
11+
{
12+
Assert::implementsInterface($thing, SomeDto::class);
13+
14+
assertType('class-string<Bug18\SomeDto>', $thing);
15+
}
16+
}
17+
18+
interface SomeDto {}

tests/Type/WebMozartAssert/data/impossible-check.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,18 @@ public function nonEmptyStringAndSomethingUnknownNarrow($a, string $b, array $c,
9999
Assert::allContains($d, 'bar');
100100
}
101101

102+
public function implementsInterface($a, string $b, $c): void
103+
{
104+
Assert::implementsInterface($a, Bar::class);
105+
Assert::implementsInterface($a, Bar::class); // only this should report
106+
107+
Assert::implementsInterface($b, Bar::class);
108+
Assert::implementsInterface($b, Bar::class); // only this should report
109+
110+
Assert::implementsInterface($c, Unknown::class);
111+
Assert::implementsInterface($c, self::class);
112+
}
113+
102114
}
103115

104116
interface Bar {};

tests/Type/WebMozartAssert/data/object.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,25 @@ public function interfaceExists($a, $b): void
3535
assertType('class-string|null', $b);
3636
}
3737

38-
public function implementsInterface($a, $b): void
38+
public function implementsInterface($a, $b, string $c, object $d, $e, $f): void
3939
{
4040
Assert::implementsInterface($a, ObjectFoo::class);
41-
assertType('PHPStan\Type\WebMozartAssert\ObjectFoo', $a);
41+
assertType('class-string<PHPStan\Type\WebMozartAssert\ObjectFoo>|PHPStan\Type\WebMozartAssert\ObjectFoo', $a);
4242

4343
Assert::nullOrImplementsInterface($b, ObjectFoo::class);
44-
assertType('PHPStan\Type\WebMozartAssert\ObjectFoo|null', $b);
44+
assertType('class-string<PHPStan\Type\WebMozartAssert\ObjectFoo>|PHPStan\Type\WebMozartAssert\ObjectFoo|null', $b);
45+
46+
Assert::implementsInterface($c, ObjectFoo::class);
47+
assertType('class-string<PHPStan\Type\WebMozartAssert\ObjectFoo>', $c);
48+
49+
Assert::implementsInterface($d, ObjectFoo::class);
50+
assertType('PHPStan\Type\WebMozartAssert\ObjectFoo', $d);
51+
52+
Assert::implementsInterface($e, self::class);
53+
assertType('mixed', $e);
54+
55+
Assert::implementsInterface($f, Unknown::class);
56+
assertType('mixed', $f);
4557
}
4658

4759
public function propertyExists(object $a): void

0 commit comments

Comments
 (0)