Skip to content

Commit 9f0b709

Browse files
phpstan-botclaude
andcommitted
Move HasOffset type stripping from isValidVariance to generalizeInferredTemplateType
Instead of working around the equals check in TemplateTypeVariance::isValidVariance(), strip HasOffsetValueType and HasOffsetType during template type generalization in TemplateTypeHelper::generalizeInferredTemplateType(). This normalizes inferred template types before they reach the invariance check, so equals() naturally returns true without needing special handling. Changing IntersectionType::equals() globally was investigated but breaks scope tracking and type narrowing (introduces false positives in PHPStan self-analysis). The generalization approach is more targeted: it only affects inferred template type arguments for non-covariant templates, which is the correct normalization point for this precision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7b29df6 commit 9f0b709

2 files changed

Lines changed: 11 additions & 21 deletions

File tree

src/Type/Generic/TemplateTypeHelper.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
namespace PHPStan\Type\Generic;
44

55
use PHPStan\Reflection\ParametersAcceptor;
6+
use PHPStan\Type\Accessory\HasOffsetType;
7+
use PHPStan\Type\Accessory\HasOffsetValueType;
68
use PHPStan\Type\ErrorType;
79
use PHPStan\Type\GeneralizePrecision;
10+
use PHPStan\Type\MixedType;
811
use PHPStan\Type\NonAcceptingNeverType;
912
use PHPStan\Type\Type;
1013
use PHPStan\Type\TypeTraverser;
@@ -147,6 +150,14 @@ public static function generalizeInferredTemplateType(TemplateType $templateType
147150
} elseif ($type->isConstantValue()->yes() && (!$templateType->getBound()->isScalar()->yes() || $isArrayKey)) {
148151
$type = $type->generalize(GeneralizePrecision::templateArgument());
149152
}
153+
154+
$type = TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
155+
if ($type instanceof HasOffsetValueType || $type instanceof HasOffsetType) {
156+
return new MixedType();
157+
}
158+
159+
return $traverse($type);
160+
});
150161
}
151162

152163
return $type;

src/Type/Generic/TemplateTypeVariance.php

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@
55
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
66
use PHPStan\ShouldNotHappenException;
77
use PHPStan\TrinaryLogic;
8-
use PHPStan\Type\Accessory\HasOffsetType;
9-
use PHPStan\Type\Accessory\HasOffsetValueType;
108
use PHPStan\Type\BenevolentUnionType;
119
use PHPStan\Type\IsSuperTypeOfResult;
1210
use PHPStan\Type\MixedType;
1311
use PHPStan\Type\NeverType;
1412
use PHPStan\Type\Type;
15-
use PHPStan\Type\TypeTraverser;
1613
use function sprintf;
1714

1815
/**
@@ -180,13 +177,6 @@ public function isValidVariance(TemplateType $templateType, Type $a, Type $b): I
180177

181178
if ($this->invariant()) {
182179
$result = $a->equals($b);
183-
if (!$result) {
184-
$strippedA = self::stripHasOffsetTypes($a);
185-
$strippedB = self::stripHasOffsetTypes($b);
186-
if ($strippedA !== $a || $strippedB !== $b) {
187-
$result = $strippedA->equals($strippedB);
188-
}
189-
}
190180
$reasons = [];
191181
if (!$result) {
192182
if (
@@ -269,15 +259,4 @@ public function toPhpDocNodeVariance(): string
269259
throw new ShouldNotHappenException();
270260
}
271261

272-
private static function stripHasOffsetTypes(Type $type): Type
273-
{
274-
return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
275-
if ($type instanceof HasOffsetValueType || $type instanceof HasOffsetType) {
276-
return new MixedType();
277-
}
278-
279-
return $traverse($type);
280-
});
281-
}
282-
283262
}

0 commit comments

Comments
 (0)