Skip to content

Commit cd9835f

Browse files
phpstan-botclaude
andcommitted
Fix property access on never type returning ErrorType instead of NeverType
When $case is narrowed to an impossible intersection type (e.g. UnitEnum & BackedEnum = never), property access like $case->value returned ErrorType. Since ErrorType extends MixedType, this corrupted type inference in ternary expressions: union(ErrorType, string) became mixed instead of string, causing false "invalid array key type mixed" errors. Now PropertyFetchHandler returns NeverType for property access on NeverType, so union(NeverType, string) correctly simplifies to string. Also skip InvalidComparisonOperationRule when either operand is NeverType, since comparisons in dead code shouldn't be reported. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8f559bc commit cd9835f

2 files changed

Lines changed: 17 additions & 3 deletions

File tree

src/Analyser/ExprHandler/PropertyFetchHandler.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use PHPStan\Rules\Properties\PropertyReflectionFinder;
2121
use PHPStan\Type\ErrorType;
2222
use PHPStan\Type\MixedType;
23+
use PHPStan\Type\NeverType;
2324
use PHPStan\Type\Type;
2425
use PHPStan\Type\TypeCombinator;
2526
use function array_map;
@@ -91,6 +92,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
9192
public function resolveType(MutatingScope $scope, Expr $expr): Type
9293
{
9394
if ($expr->name instanceof Identifier) {
95+
$fetchedOnType = $scope->getType($expr->var);
96+
if ($fetchedOnType instanceof NeverType) {
97+
return new NeverType();
98+
}
99+
94100
if ($scope->nativeTypesPromoted) {
95101
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $scope);
96102
if ($propertyReflection === null) {
@@ -108,7 +114,7 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type
108114

109115
$returnType = $this->propertyFetchType(
110116
$scope,
111-
$scope->getType($expr->var),
117+
$fetchedOnType,
112118
$expr->name->name,
113119
$expr,
114120
);

src/Rules/Operators/InvalidComparisonOperationRule.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use PHPStan\Type\FloatType;
1818
use PHPStan\Type\IntegerType;
1919
use PHPStan\Type\MixedType;
20+
use PHPStan\Type\NeverType;
2021
use PHPStan\Type\NullType;
2122
use PHPStan\Type\ObjectWithoutClassType;
2223
use PHPStan\Type\Type;
@@ -60,6 +61,13 @@ public function processNode(Node $node, Scope $scope): array
6061
return [];
6162
}
6263

64+
$leftType = $scope->getType($node->left);
65+
$rightType = $scope->getType($node->right);
66+
67+
if ($leftType instanceof NeverType || $rightType instanceof NeverType) {
68+
return [];
69+
}
70+
6371
$isLeftNumberType = $this->isNumberType($scope, $node->left);
6472
$isRightNumberType = $this->isNumberType($scope, $node->right);
6573
if ($isLeftNumberType === $isRightNumberType) {
@@ -68,8 +76,8 @@ public function processNode(Node $node, Scope $scope): array
6876

6977
$result = $this->operatorTypeSpecifyingExtensionRegistryProvider->getRegistry()->callOperatorTypeSpecifyingExtensions(
7078
$node,
71-
$scope->getType($node->left),
72-
$scope->getType($node->right),
79+
$leftType,
80+
$rightType,
7381
);
7482

7583
if ($result !== null) {

0 commit comments

Comments
 (0)