Skip to content

Commit 7011ace

Browse files
mglamanclaude
andcommitted
Fix stub @param types not applied inside method bodies
NodeScopeResolver::getPhpDocs() was only consulting FileTypeMapper (the source file's phpDoc) when resolving parameter types inside method bodies, never checking StubPhpDocProvider for stub overrides. This was a regression introduced in 2.1.38 by PR #4829, which rewrote PhpDocInheritanceResolver::resolvePhpDocForMethod() and removed the PhpDocBlock::docBlockToResolvedDocBlock() helper that previously called StubPhpDocProvider::findMethodPhpDoc() first. The fix mirrors the pre-regression behaviour: consult StubPhpDocProvider before falling back to FileTypeMapper in the ClassMethod phpDoc resolution path of getPhpDocs(). Fixes phpstan/phpstan#14118 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 56c1e37 commit 7011ace

10 files changed

+98
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
use PHPStan\Php\PhpVersion;
138138
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
139139
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
140+
use PHPStan\PhpDoc\StubPhpDocProvider;
140141
use PHPStan\PhpDoc\Tag\VarTag;
141142
use PHPStan\Reflection\Assertions;
142143
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
@@ -267,6 +268,7 @@ public function __construct(
267268
#[AutowiredParameter(ref: '@defaultAnalysisParser')]
268269
private readonly Parser $parser,
269270
private readonly FileTypeMapper $fileTypeMapper,
271+
private readonly StubPhpDocProvider $stubPhpDocProvider,
270272
private readonly PhpVersion $phpVersion,
271273
private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver,
272274
private readonly FileHelper $fileHelper,
@@ -7446,12 +7448,20 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
74467448
return $param->var->name;
74477449
}, $node->getParams());
74487450
$currentResolvedPhpDoc = null;
7449-
if ($docComment !== null) {
7451+
if ($class !== null) {
7452+
$currentResolvedPhpDoc = $this->stubPhpDocProvider->findMethodPhpDoc(
7453+
$class,
7454+
$class,
7455+
$functionName,
7456+
$positionalParameterNames,
7457+
);
7458+
}
7459+
if ($currentResolvedPhpDoc === null && $docComment !== null) {
74507460
$currentResolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
74517461
$file,
74527462
$class,
74537463
$trait,
7454-
$node->name->name,
7464+
$functionName,
74557465
$docComment,
74567466
);
74577467
}

src/Testing/RuleTestCase.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use PHPStan\Node\DeepNodeCloner;
2828
use PHPStan\Php\PhpVersion;
2929
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
30+
use PHPStan\PhpDoc\StubPhpDocProvider;
3031
use PHPStan\Reflection\ClassReflectionFactory;
3132
use PHPStan\Reflection\InitializerExprTypeResolver;
3233
use PHPStan\Rules\DirectRegistry as DirectRuleRegistry;
@@ -102,6 +103,7 @@ protected function createNodeScopeResolver(): NodeScopeResolver
102103
self::getContainer()->getByType(ParameterOutTypeExtensionProvider::class),
103104
$this->getParser(),
104105
self::getContainer()->getByType(FileTypeMapper::class),
106+
self::getContainer()->getByType(StubPhpDocProvider::class),
105107
self::getContainer()->getByType(PhpVersion::class),
106108
self::getContainer()->getByType(PhpDocInheritanceResolver::class),
107109
self::getContainer()->getByType(FileHelper::class),

src/Testing/TypeInferenceTestCase.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use PHPStan\Node\InClassNode;
2222
use PHPStan\Php\PhpVersion;
2323
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
24+
use PHPStan\PhpDoc\StubPhpDocProvider;
2425
use PHPStan\PhpDoc\TypeStringResolver;
2526
use PHPStan\Reflection\ClassReflectionFactory;
2627
use PHPStan\Reflection\InitializerExprTypeResolver;
@@ -77,6 +78,7 @@ protected static function createNodeScopeResolver(): NodeScopeResolver
7778
$container->getByType(ParameterOutTypeExtensionProvider::class),
7879
self::getParser(),
7980
$container->getByType(FileTypeMapper::class),
81+
$container->getByType(StubPhpDocProvider::class),
8082
$container->getByType(PhpVersion::class),
8183
$container->getByType(PhpDocInheritanceResolver::class),
8284
$container->getByType(FileHelper::class),

tests/PHPStan/Analyser/AnalyserTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use PHPStan\Parser\RichParser;
2323
use PHPStan\Php\PhpVersion;
2424
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
25+
use PHPStan\PhpDoc\StubPhpDocProvider;
2526
use PHPStan\Reflection\ClassReflectionFactory;
2627
use PHPStan\Reflection\InitializerExprTypeResolver;
2728
use PHPStan\Rules\AlwaysFailRule;
@@ -818,6 +819,7 @@ private function createAnalyser(): Analyser
818819
$container->getByType(ParameterOutTypeExtensionProvider::class),
819820
$this->getParser(),
820821
$fileTypeMapper,
822+
$container->getByType(StubPhpDocProvider::class),
821823
$container->getByType(PhpVersion::class),
822824
$phpDocInheritanceResolver,
823825
$fileHelper,

tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverRuleTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\Node\DeepNodeCloner;
1414
use PHPStan\Php\PhpVersion;
1515
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
16+
use PHPStan\PhpDoc\StubPhpDocProvider;
1617
use PHPStan\Reflection\ClassReflectionFactory;
1718
use PHPStan\Reflection\InitializerExprTypeResolver;
1819
use PHPStan\Rules\IdentifierRuleError;
@@ -122,6 +123,7 @@ protected function createNodeScopeResolver(): NodeScopeResolver
122123
self::getContainer()->getByType(ParameterOutTypeExtensionProvider::class),
123124
$this->getParser(),
124125
self::getContainer()->getByType(FileTypeMapper::class),
126+
self::getContainer()->getByType(StubPhpDocProvider::class),
125127
self::getContainer()->getByType(PhpVersion::class),
126128
self::getContainer()->getByType(PhpDocInheritanceResolver::class),
127129
self::getContainer()->getByType(FileHelper::class),

tests/PHPStan/Analyser/Fiber/FiberNodeScopeResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Node\DeepNodeCloner;
1212
use PHPStan\Php\PhpVersion;
1313
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
14+
use PHPStan\PhpDoc\StubPhpDocProvider;
1415
use PHPStan\Reflection\ClassReflectionFactory;
1516
use PHPStan\Reflection\InitializerExprTypeResolver;
1617
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
@@ -55,6 +56,7 @@ protected static function createNodeScopeResolver(): NodeScopeResolver
5556
$container->getByType(ParameterOutTypeExtensionProvider::class),
5657
self::getParser(),
5758
$container->getByType(FileTypeMapper::class),
59+
$container->getByType(StubPhpDocProvider::class),
5860
$container->getByType(PhpVersion::class),
5961
$container->getByType(PhpDocInheritanceResolver::class),
6062
$container->getByType(FileHelper::class),
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
use PHPUnit\Framework\Attributes\DataProvider;
7+
8+
class StubMethodBodyTypesTest extends TypeInferenceTestCase
9+
{
10+
11+
public static function dataFileAsserts(): iterable
12+
{
13+
yield from self::gatherAssertTypes(__DIR__ . '/data/stub-method-body-types.php');
14+
}
15+
16+
/**
17+
* @param mixed ...$args
18+
*/
19+
#[DataProvider('dataFileAsserts')]
20+
public function testFileAsserts(
21+
string $assertType,
22+
string $file,
23+
...$args,
24+
): void
25+
{
26+
$this->assertFileAsserts($assertType, $file, ...$args);
27+
}
28+
29+
public static function getAdditionalConfigFiles(): array
30+
{
31+
return [
32+
__DIR__ . '/data/stub-method-body-types.neon',
33+
];
34+
}
35+
36+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
stubFiles:
3+
- stub-method-body-types.stub
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
use function PHPStan\Testing\assertType;
4+
5+
class StubMethodBodyTypesClass
6+
{
7+
/**
8+
* Source declares @param string — stub overrides to @param callable-string
9+
* @param string $callback
10+
* @return string
11+
*/
12+
public function process($callback): string
13+
{
14+
// Stub should override the source file's @param type inside the method body
15+
assertType('callable-string', $callback);
16+
return $callback;
17+
}
18+
}
19+
20+
class StubMethodBodyTypesExternalCaller
21+
{
22+
public function test(StubMethodBodyTypesClass $obj): void
23+
{
24+
// Stub should also work for external callers (already worked before regression)
25+
assertType('non-empty-string', $obj->process('strlen'));
26+
}
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
class StubMethodBodyTypesClass
4+
{
5+
/**
6+
* @param callable-string $callback
7+
* @return non-empty-string
8+
*/
9+
public function process($callback): string {}
10+
}

0 commit comments

Comments
 (0)