Skip to content

Commit c381cc0

Browse files
phpstan-botclaude
andauthored
Fix phpstan/phpstan#14138: errors for argument array template types no longer reported (argument.type) (#5300)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f171c0e commit c381cc0

File tree

5 files changed

+109
-24
lines changed

5 files changed

+109
-24
lines changed

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -891,14 +891,6 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
891891
}
892892
$phpDocParameterTypes[$paramName] = $paramTag->getType();
893893
}
894-
foreach ($phpDocParameterTypes as $paramName => $paramType) {
895-
$phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes(
896-
$paramType,
897-
$phpDocBlockClassReflection->getActiveTemplateTypeMap(),
898-
$phpDocBlockClassReflection->getCallSiteVarianceMap(),
899-
TemplateTypeVariance::createContravariant(),
900-
);
901-
}
902894
foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) {
903895
$phpDocParameterOutTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes(
904896
$paramOutTag->getType(),
@@ -914,22 +906,6 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
914906
$isInternal = $resolvedPhpDoc->isInternal();
915907
$isFinal = $resolvedPhpDoc->isFinal();
916908
$isPure ??= $resolvedPhpDoc->isPure();
917-
if ($isPure === null) {
918-
$classResolvedPhpDoc = $phpDocBlockClassReflection->getResolvedPhpDoc();
919-
if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) {
920-
if (
921-
strtolower($methodReflection->getName()) === '__construct'
922-
|| (
923-
($phpDocReturnType === null || !$phpDocReturnType->isVoid()->yes())
924-
&& !$nativeReturnType->isVoid()->yes()
925-
)
926-
) {
927-
$isPure = true;
928-
}
929-
} elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) {
930-
$isPure = false;
931-
}
932-
}
933909
$asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc);
934910
$acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments();
935911
$selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null;
@@ -938,6 +914,32 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
938914
}
939915
}
940916

917+
if ($isPure === null) {
918+
$classResolvedPhpDoc = $phpDocBlockClassReflection->getResolvedPhpDoc();
919+
if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) {
920+
if (
921+
strtolower($methodReflection->getName()) === '__construct'
922+
|| (
923+
($phpDocReturnType === null || !$phpDocReturnType->isVoid()->yes())
924+
&& !$nativeReturnType->isVoid()->yes()
925+
)
926+
) {
927+
$isPure = true;
928+
}
929+
} elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) {
930+
$isPure = false;
931+
}
932+
}
933+
934+
foreach ($phpDocParameterTypes as $paramName => $paramType) {
935+
$phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes(
936+
$paramType,
937+
$phpDocBlockClassReflection->getActiveTemplateTypeMap(),
938+
$phpDocBlockClassReflection->getCallSiteVarianceMap(),
939+
TemplateTypeVariance::createContravariant(),
940+
);
941+
}
942+
941943
return $this->methodReflectionFactory->create(
942944
$actualDeclaringClass,
943945
$declaringTrait,

tests/PHPStan/Rules/Classes/InstantiationRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,4 +615,16 @@ public function testBug11006(): void
615615
$this->analyse([__DIR__ . '/data/bug-11006.php'], []);
616616
}
617617

618+
#[RequiresPhp('>= 8.0')]
619+
public function testBug14138(): void
620+
{
621+
$this->analyse([__DIR__ . '/data/bug-14138.php'], [
622+
[
623+
'Parameter #1 $data of class Bug14138\Foo constructor expects array{foo: int, bar: int}, array{foo: 1} given.',
624+
36,
625+
"Array does not have offset 'bar'.",
626+
],
627+
]);
628+
}
629+
618630
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14138;
6+
7+
/**
8+
* @template T of array
9+
*/
10+
abstract class AbstractApiData
11+
{
12+
public function __construct(
13+
/** @var T */
14+
protected array $data
15+
) {}
16+
17+
/**
18+
* @return T
19+
*/
20+
public function getData(): array
21+
{
22+
return $this->data;
23+
}
24+
}
25+
26+
27+
/**
28+
* @extends AbstractApiData<array{
29+
* foo: int,
30+
* bar: int,
31+
* }>
32+
*/
33+
class Foo extends AbstractApiData {}
34+
35+
function testing(): void {
36+
$a = new Foo(["foo" => 1]);
37+
}

tests/PHPStan/Rules/Pure/PureMethodRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,13 @@ public function testAllMethodsArePure(): void
279279
]);
280280
}
281281

282+
#[RequiresPhp('>= 8.0')]
283+
public function testBug14138Pure(): void
284+
{
285+
$this->treatPhpDocTypesAsCertain = true;
286+
$this->analyse([__DIR__ . '/data/bug-14138-pure.php'], []);
287+
}
288+
282289
public function testBug12382(): void
283290
{
284291
$this->treatPhpDocTypesAsCertain = true;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug14138Pure;
4+
5+
/**
6+
* @phpstan-all-methods-pure
7+
*/
8+
class PureClassWithPromotedProps
9+
{
10+
public function __construct(
11+
protected int $value
12+
) {}
13+
14+
public function getValue(): int
15+
{
16+
return $this->value;
17+
}
18+
}
19+
20+
class TestCaller
21+
{
22+
/** @phpstan-pure */
23+
public function callPureConstructor(): PureClassWithPromotedProps
24+
{
25+
return new PureClassWithPromotedProps(1);
26+
}
27+
}

0 commit comments

Comments
 (0)