Skip to content

Commit 8cd663f

Browse files
committed
Regression test
1 parent 796c9d5 commit 8cd663f

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,13 @@ public function testBug7068(): void
721721
$this->assertNoErrors($errors);
722722
}
723723

724+
public function testDiscussion6993(): void
725+
{
726+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-6993.php');
727+
$this->assertCount(1, $errors);
728+
$this->assertSame('Parameter #1 $specificable of method Bug6993\AndSpecificationValidator<Bug6993\TestSpecification,Bug6993\Foo>::isSatisfiedBy() expects Bug6993\Foo, Bug6993\Bar given.', $errors[0]->getMessage());
729+
}
730+
724731
/**
725732
* @param string[]|null $allAnalysedFiles
726733
* @return Error[]

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,7 @@ public function dataFileAsserts(): iterable
882882
require_once __DIR__ . '/data/constant-phpdoc-type.php';
883883
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-phpdoc-type.php');
884884
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7068.php');
885+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6993.php');
885886
}
886887

887888
/**
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Bug6993;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @template T
9+
*
10+
* Generic specification interface
11+
*/
12+
interface SpecificationInterface {
13+
/**
14+
* @param T $specificable
15+
*/
16+
public function isSatisfiedBy($specificable): bool;
17+
}
18+
19+
/**
20+
* @template-extends SpecificationInterface<Foo>
21+
*/
22+
interface FooSpecificationInterface extends SpecificationInterface
23+
{
24+
}
25+
26+
/**
27+
* Class-conctrete specification
28+
*/
29+
class TestSpecification implements FooSpecificationInterface
30+
{
31+
public function isSatisfiedBy($specificable): bool
32+
{
33+
return true;
34+
}
35+
}
36+
37+
/**
38+
* @template TSpecifications of SpecificationInterface<TValue>
39+
* @template TValue
40+
* @template-implements SpecificationInterface<TValue>
41+
*/
42+
class AndSpecificationValidator implements SpecificationInterface
43+
{
44+
/**
45+
* @param array<TSpecifications> $specifications
46+
*/
47+
public function __construct(private array $specifications)
48+
{
49+
}
50+
51+
public function isSatisfiedBy($specificable): bool
52+
{
53+
foreach ($this->specifications as $specification) {
54+
if (!$specification->isSatisfiedBy($specificable)) {
55+
return false;
56+
}
57+
}
58+
59+
return true;
60+
}
61+
}
62+
63+
/**
64+
* Admitted value for FooSpecificationInterface instances
65+
*/
66+
class Foo
67+
{
68+
}
69+
70+
/**
71+
* Value not admitted for FooSpecificationInterface instances
72+
*/
73+
class Bar
74+
{
75+
}
76+
77+
function (): void {
78+
$and = (new AndSpecificationValidator([new TestSpecification()]));
79+
assertType('Bug6993\AndSpecificationValidator<Bug6993\TestSpecification, Bug6993\Foo>', $and);
80+
$and->isSatisfiedBy(new Foo());
81+
$and->isSatisfiedBy(new Bar());
82+
};

0 commit comments

Comments
 (0)