From c703aa0f2480860062da694a37918df9d8c75976 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Wed, 4 Mar 2026 07:48:49 +0000 Subject: [PATCH] Fix phpstan/phpstan#14206: @var PDOStatement incorrectly reported as unresolvable - When TypeNodeResolver creates an intersection of a non-generic iterable class with user-provided type arguments (e.g. PDOStatement), TypeCombinator::intersect may collapse the result to NeverType if the iterable types are incompatible - This was triggered by the more precise getIterator() return type in the PDOStatement stub (Iterator> instead of plain Iterator) - The fix preserves the IntersectionType directly when the intersection would otherwise collapse to NeverType, maintaining @var override semantics - New regression test in tests/PHPStan/Rules/PhpDoc/data/bug-14206.php --- .claude/worktrees/agent-a624340d | 1 + .claude/worktrees/agent-af2804bc | 1 + src/PhpDoc/TypeNodeResolver.php | 23 ++++++++++++------- .../InvalidPhpDocVarTagTypeRuleTest.php | 5 ++++ tests/PHPStan/Rules/PhpDoc/data/bug-14206.php | 17 ++++++++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) create mode 160000 .claude/worktrees/agent-a624340d create mode 160000 .claude/worktrees/agent-af2804bc create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-14206.php diff --git a/.claude/worktrees/agent-a624340d b/.claude/worktrees/agent-a624340d new file mode 160000 index 0000000000..13a2f02edb --- /dev/null +++ b/.claude/worktrees/agent-a624340d @@ -0,0 +1 @@ +Subproject commit 13a2f02edb0f763049973ae926dc10b6899a7cff diff --git a/.claude/worktrees/agent-af2804bc b/.claude/worktrees/agent-af2804bc new file mode 160000 index 0000000000..1734058bef --- /dev/null +++ b/.claude/worktrees/agent-af2804bc @@ -0,0 +1 @@ +Subproject commit 1734058bef811c84970b19806bbd2daee38786ab diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 8af74bf926..3cbc9321c1 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -86,6 +86,7 @@ use PHPStan\Type\IterableType; use PHPStan\Type\KeyOfType; use PHPStan\Type\MixedType; +use PHPStan\Type\NeverType; use PHPStan\Type\NewObjectType; use PHPStan\Type\NonAcceptingNeverType; use PHPStan\Type\NonexistentParentClassType; @@ -935,17 +936,23 @@ static function (string $variance): TemplateTypeVariance { try { if (count($genericTypes) === 1) { // Foo - return TypeCombinator::intersect( - $mainType, - new IterableType(new MixedType(true), $genericTypes[0]), - ); + $iterableType = new IterableType(new MixedType(true), $genericTypes[0]); + $result = TypeCombinator::intersect($mainType, $iterableType); + if (!$result instanceof NeverType) { + return $result; + } + + return new IntersectionType([$mainType, $iterableType]); } if (count($genericTypes) === 2) { // Foo - return TypeCombinator::intersect( - $mainType, - new IterableType($genericTypes[0], $genericTypes[1]), - ); + $iterableType = new IterableType($genericTypes[0], $genericTypes[1]); + $result = TypeCombinator::intersect($mainType, $iterableType); + if (!$result instanceof NeverType) { + return $result; + } + + return new IntersectionType([$mainType, $iterableType]); } } finally { if ($mainTypeClassName !== null) { diff --git a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php index 0c911449d2..a4bef3c6fe 100644 --- a/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/InvalidPhpDocVarTagTypeRuleTest.php @@ -170,6 +170,11 @@ public function testBug6348(): void $this->analyse([__DIR__ . '/data/bug-6348.php'], []); } + public function testBug14206(): void + { + $this->analyse([__DIR__ . '/data/bug-14206.php'], []); + } + public function testBug9055(): void { $this->analyse([__DIR__ . '/data/bug-9055.php'], [ diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-14206.php b/tests/PHPStan/Rules/PhpDoc/data/bug-14206.php new file mode 100644 index 0000000000..8f112666a1 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-14206.php @@ -0,0 +1,17 @@ + */ + $statement = $db->prepare('SELECT foo FROM bar'); + $statement->setFetchMode(PDO::FETCH_COLUMN, 0); + $statement->execute(); + } +}