Skip to content

Commit 21dc52a

Browse files
committed
Merge remote-tracking branch 'origin/2.1.x' into merge-21
2 parents 4f2b04d + c381cc0 commit 21dc52a

5 files changed

Lines changed: 108 additions & 24 deletions

File tree

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -893,14 +893,6 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
893893
}
894894
$phpDocParameterTypes[$paramName] = $paramTag->getType();
895895
}
896-
foreach ($phpDocParameterTypes as $paramName => $paramType) {
897-
$phpDocParameterTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes(
898-
$paramType,
899-
$phpDocBlockClassReflection->getActiveTemplateTypeMap(),
900-
$phpDocBlockClassReflection->getCallSiteVarianceMap(),
901-
TemplateTypeVariance::createContravariant(),
902-
);
903-
}
904896
foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) {
905897
$phpDocParameterOutTypes[$paramName] = TemplateTypeHelper::resolveTemplateTypes(
906898
$paramOutTag->getType(),
@@ -916,22 +908,6 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
916908
$isInternal = $resolvedPhpDoc->isInternal();
917909
$isFinal = $resolvedPhpDoc->isFinal();
918910
$isPure ??= $resolvedPhpDoc->isPure();
919-
if ($isPure === null) {
920-
$classResolvedPhpDoc = $phpDocBlockClassReflection->getResolvedPhpDoc();
921-
if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) {
922-
if (
923-
strtolower($methodReflection->getName()) === '__construct'
924-
|| (
925-
($phpDocReturnType === null || !$phpDocReturnType->isVoid()->yes())
926-
&& !$nativeReturnType->isVoid()->yes()
927-
)
928-
) {
929-
$isPure = true;
930-
}
931-
} elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) {
932-
$isPure = false;
933-
}
934-
}
935911
$asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc);
936912
$acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments();
937913
$selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null;
@@ -940,6 +916,32 @@ public function createUserlandMethodReflection(ClassReflection $fileDeclaringCla
940916
}
941917
}
942918

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

tests/PHPStan/Rules/Classes/InstantiationRuleTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,4 +646,15 @@ public function testConstantParameterCheckInstantiation(): void
646646
]);
647647
}
648648

649+
public function testBug14138(): void
650+
{
651+
$this->analyse([__DIR__ . '/data/bug-14138.php'], [
652+
[
653+
'Parameter #1 $data of class Bug14138\Foo constructor expects array{foo: int, bar: int}, array{foo: 1} given.',
654+
36,
655+
"Array does not have offset 'bar'.",
656+
],
657+
]);
658+
}
659+
649660
}
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)