Skip to content

Commit 6f9e24a

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix phpstan/phpstan#11844: false property.unusedType for nullable generic property assigned inside null check
- Used declared property type from reflection instead of scope-narrowed type in NewHandler - When `new WeakMap()` was assigned inside `if ($this->map === null)`, scope returned narrowed `null` type - TypeCombinator::removeNull on `null` yielded `never`, making TooWideTypeCheck think WeakMap was never assigned - New regression test in tests/PHPStan/Rules/TooWideTypehints/data/bug-11844.php
1 parent d8f5be7 commit 6f9e24a

3 files changed

Lines changed: 82 additions & 4 deletions

File tree

src/Analyser/ExprHandler/NewHandler.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,13 @@ private function exactInstantiation(MutatingScope $scope, New_ $node, Name $clas
416416
$classTemplateTypes = $traverser->getClassTemplateTypes();
417417

418418
if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
419-
$propertyType = TypeCombinator::removeNull($scope->getType($assignedToProperty));
420-
$nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection);
421-
if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) {
422-
return $propertyType;
419+
$propertyType = $this->resolveAssignedPropertyDeclaredType($assignedToProperty, $scope);
420+
if ($propertyType !== null) {
421+
$propertyType = TypeCombinator::removeNull($propertyType);
422+
$nonFinalObjectType = $isStatic ? new StaticType($nonFinalClassReflection) : new ObjectType($resolvedClassName, classReflection: $nonFinalClassReflection);
423+
if ($nonFinalObjectType->isSuperTypeOf($propertyType)->yes()) {
424+
return $propertyType;
425+
}
423426
}
424427
}
425428
}
@@ -590,4 +593,25 @@ classReflection: $classReflection->withTypes($types)->asFinal(),
590593
return TypeTraverser::map($newGenericType, new GenericTypeTemplateTraverser($resolvedTemplateTypeMap));
591594
}
592595

596+
private function resolveAssignedPropertyDeclaredType(Node\Expr $propertyFetch, MutatingScope $scope): ?Type
597+
{
598+
if ($propertyFetch instanceof Node\Expr\PropertyFetch && $propertyFetch->name instanceof Node\Identifier) {
599+
$holderType = $scope->getType($propertyFetch->var);
600+
$propertyName = $propertyFetch->name->name;
601+
if ($holderType->hasInstanceProperty($propertyName)->yes()) {
602+
return $holderType->getInstanceProperty($propertyName, $scope)->getReadableType();
603+
}
604+
} elseif ($propertyFetch instanceof Node\Expr\StaticPropertyFetch && $propertyFetch->name instanceof Node\VarLikeIdentifier && $propertyFetch->class instanceof Name) {
605+
$className = $scope->resolveName($propertyFetch->class);
606+
if ($this->reflectionProvider->hasClass($className)) {
607+
$classRefl = $this->reflectionProvider->getClass($className);
608+
$propertyName = $propertyFetch->name->name;
609+
if ($classRefl->hasStaticProperty($propertyName)) {
610+
return $classRefl->getStaticProperty($propertyName)->getReadableType();
611+
}
612+
}
613+
}
614+
return null;
615+
}
616+
593617
}

tests/PHPStan/Rules/TooWideTypehints/TooWidePropertyTypeRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,10 @@ public function testBug13624(): void
138138
$this->analyse([__DIR__ . '/data/bug-13624.php'], []);
139139
}
140140

141+
#[RequiresPhp('>= 8.0')]
142+
public function testBug11844(): void
143+
{
144+
$this->analyse([__DIR__ . '/data/bug-11844.php'], []);
145+
}
146+
141147
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug11844;
6+
7+
class StaticPropertyCase
8+
{
9+
/**
10+
* @var \WeakMap<object, string>|null
11+
*/
12+
private static ?\WeakMap $map = null;
13+
14+
public static function init(): void
15+
{
16+
if (self::$map === null) {
17+
self::$map = new \WeakMap();
18+
}
19+
}
20+
}
21+
22+
class InstancePropertyCase
23+
{
24+
/**
25+
* @var \WeakMap<object, string>|null
26+
*/
27+
private ?\WeakMap $map = null;
28+
29+
public function init(): void
30+
{
31+
if ($this->map === null) {
32+
$this->map = new \WeakMap();
33+
}
34+
}
35+
}
36+
37+
class DirectAssignCase
38+
{
39+
/**
40+
* @var \WeakMap<object, string>|null
41+
*/
42+
private ?\WeakMap $map = null;
43+
44+
public function initAlways(): void
45+
{
46+
$this->map = new \WeakMap();
47+
}
48+
}

0 commit comments

Comments
 (0)