Skip to content

Commit b18a0b0

Browse files
phpstan-botclaude
andcommitted
Add regression tests for trait-related false positives
Add test data files for phpstan/phpstan#7599, #9095, #13474, and #13687 to document and prevent regressions in trait type checking behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ba6e8f3 commit b18a0b0

4 files changed

Lines changed: 177 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug13474;
4+
5+
/**
6+
* @template TValue of mixed
7+
*/
8+
interface ModelInterface {
9+
/**
10+
* @return TValue
11+
*/
12+
public function getValue(): mixed;
13+
}
14+
15+
/**
16+
* @implements ModelInterface<int>
17+
*/
18+
class ModelA implements ModelInterface
19+
{
20+
public function getValue(): int
21+
{
22+
return 0;
23+
}
24+
}
25+
26+
/**
27+
* @implements ModelInterface<string>
28+
*/
29+
class ModelB implements ModelInterface
30+
{
31+
public function getValue(): string
32+
{
33+
return 'foo';
34+
}
35+
}
36+
37+
/**
38+
* @template T of ModelInterface
39+
*/
40+
trait ModelTrait
41+
{
42+
/**
43+
* @return T
44+
*/
45+
abstract function model(): ModelInterface;
46+
47+
/**
48+
* @return template-type<T, ModelInterface, 'TValue'>
49+
*/
50+
public function getValue(): mixed
51+
{
52+
return $this->model()->getValue();
53+
}
54+
55+
public function test(): void
56+
{
57+
if (is_string($this->getValue())) {
58+
echo 'string';
59+
return;
60+
}
61+
62+
echo 'other';
63+
}
64+
}
65+
66+
class TestA
67+
{
68+
/** @use ModelTrait<ModelA> */
69+
use ModelTrait;
70+
71+
function model(): ModelA
72+
{
73+
return new ModelA();
74+
}
75+
}
76+
77+
class TestB
78+
{
79+
/** @use ModelTrait<ModelB> */
80+
use ModelTrait;
81+
82+
function model(): ModelB
83+
{
84+
return new ModelB();
85+
}
86+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13687;
4+
5+
trait MyTrait {
6+
public function foo(): void {
7+
if (method_exists($this, 'bar')) {
8+
$this->bar();
9+
}
10+
11+
if (property_exists($this, 'baz')) {
12+
$a = $this->baz;
13+
}
14+
}
15+
}
16+
17+
class A {
18+
use MyTrait;
19+
20+
public string $baz = 'baz';
21+
}
22+
23+
class B {
24+
use MyTrait;
25+
26+
public function bar(): void {
27+
echo 'bar';
28+
}
29+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug7599;
4+
5+
trait TraitForEnum
6+
{
7+
/**
8+
* @return array<int, string>
9+
*/
10+
public static function fooMethod(): array
11+
{
12+
return array_map(
13+
fn(self $enum): string => method_exists($enum, 'barMethod')
14+
? $enum->barMethod()
15+
: $enum->name,
16+
static::cases()
17+
);
18+
}
19+
}
20+
21+
enum TestEnum: string
22+
{
23+
use TraitForEnum;
24+
25+
case Foo = 'foo';
26+
case Bar = 'bar';
27+
}
28+
29+
enum SecondEnum: string
30+
{
31+
use TraitForEnum;
32+
33+
case Baz = 'baz';
34+
35+
public function barMethod(): string
36+
{
37+
return 'blah';
38+
}
39+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9095;
4+
5+
trait EventTrait
6+
{
7+
public function getCreatedAt(): ?\DateTimeInterface
8+
{
9+
if (
10+
property_exists(static::class, 'createdAt') &&
11+
isset($this->createdAt) &&
12+
$this->createdAt instanceof \DateTimeInterface
13+
) {
14+
return $this->createdAt;
15+
}
16+
return null;
17+
}
18+
}
19+
20+
final class Event
21+
{
22+
use EventTrait;
23+
}

0 commit comments

Comments
 (0)