Skip to content

Commit 6f30adc

Browse files
authored
ForbidVariableTypeOverwriting: deep generalization, generic SubtractableType (#35)
* ForbidVariableTypeOverwriting: deep generalization, generic SubtractableType * Drop bad copypaste
1 parent 7d161e5 commit 6f30adc

File tree

3 files changed

+29
-6
lines changed

3 files changed

+29
-6
lines changed

src/Rule/ForbidVariableTypeOverwritingRule.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
use PHPStan\Type\IntersectionType;
1717
use PHPStan\Type\MixedType;
1818
use PHPStan\Type\NullType;
19-
use PHPStan\Type\ObjectType;
19+
use PHPStan\Type\SubtractableType;
2020
use PHPStan\Type\Type;
2121
use PHPStan\Type\TypeCombinator;
22+
use PHPStan\Type\TypeTraverser;
2223
use PHPStan\Type\UnionType;
2324
use PHPStan\Type\VerbosityLevel;
2425

@@ -53,8 +54,8 @@ public function processNode(Node $node, Scope $scope): array
5354
return [];
5455
}
5556

56-
$previousVariableType = $this->generalize($scope->getVariableType($variableName));
57-
$newVariableType = $this->generalize($scope->getType($node->expr));
57+
$previousVariableType = $this->generalizeDeeply($scope->getVariableType($variableName));
58+
$newVariableType = $this->generalizeDeeply($scope->getType($node->expr));
5859

5960
if ($this->isTypeToIgnore($previousVariableType) || $this->isTypeToIgnore($newVariableType)) {
6061
return [];
@@ -70,6 +71,13 @@ public function processNode(Node $node, Scope $scope): array
7071
return [];
7172
}
7273

74+
private function generalizeDeeply(Type $type): Type
75+
{
76+
return TypeTraverser::map($type, function (Type $traversedTyped, callable $traverse): Type {
77+
return $traverse($this->generalize($traversedTyped));
78+
});
79+
}
80+
7381
private function generalize(Type $type): Type
7482
{
7583
if (
@@ -113,8 +121,8 @@ private function removeNullAccessoryAndSubtractedTypes(Type $type): Type
113121
$type = TypeCombinator::intersect(...$newInnerTypes);
114122
}
115123

116-
if ($type instanceof ObjectType) {
117-
$type = $type->changeSubtractedType(null);
124+
if ($type instanceof SubtractableType) { // @phpstan-ignore-line ignore bc promise
125+
$type = $type->getTypeWithoutSubtractedType(); // @phpstan-ignore-line ignore bc promise
118126
}
119127

120128
return TypeCombinator::removeNull($type);

tests/Rule/data/ForbidAssignmentNotMatchingVarDocRule/code.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ public function returnArrayShape(): array
126126
return ['id' => 1, 'value' => 'foo'];
127127
}
128128

129+
/**
130+
* @return iterable{ id: int, value: string }
131+
*/
132+
public function returnIterableWithShape(): iterable
133+
{
134+
return ['id' => 1, 'value' => 'foo'];
135+
}
136+
129137
public function returnInt(): int
130138
{
131139
return 0;

tests/Rule/data/ForbidVariableTypeOverwritingRule/code.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,15 @@ function testAdvancedTypesAreIgnored(
119119
$strictArray = ['string' => 'string'];
120120
}
121121

122-
function testSubtractedTypeNotKept(ParentClass $someClass) {
122+
/**
123+
* @param ParentClass $someClass
124+
* @param mixed[] $strings
125+
*/
126+
function testSubtractedTypeNotKept(ParentClass $someClass, array $strings) {
123127
if (!$someClass instanceof ChildClass1) {
124128
$someClass = new ChildClass1();
125129
}
130+
131+
unset($strings[0]);
132+
$strings = array_values($strings);
126133
}

0 commit comments

Comments
 (0)