diff --git a/src/Analyser/ExpressionTypeHolder.php b/src/Analyser/ExpressionTypeHolder.php index 7477dbf3dc..649de05f88 100644 --- a/src/Analyser/ExpressionTypeHolder.php +++ b/src/Analyser/ExpressionTypeHolder.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr; use PHPStan\TrinaryLogic; +use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -47,7 +48,31 @@ public function equals(self $other): bool return false; } - return $this->type === $other->type || $this->type->equals($other->type); + if ($this->type === $other->type) { + return true; + } + + if ($this->type instanceof ObjectType && $other->type instanceof ObjectType) { + if (!$this->type->equals($other->type)) { + return false; + } + + $classReflection = $this->type->getClassReflection(); + $otherClassReflection = $other->type->getClassReflection(); + if ( + $classReflection !== null && $otherClassReflection !== null + && ( + $classReflection->hasFinalByKeywordOverride() + !== $otherClassReflection->hasFinalByKeywordOverride() + ) + ) { + return false; + } + + return true; + } + + return $this->type->equals($other->type); } public function and(self $other): self diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 4425f8aa52..a9cce96ea0 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3382,9 +3382,19 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, $exprString = $this->getNodeKey($expr); $expressionTypes = $scope->expressionTypes; - $expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty); + $newHolder = new ExpressionTypeHolder($expr, $type, $certainty); $nativeTypes = $scope->nativeExpressionTypes; - $nativeTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty); + $newNativeHolder = new ExpressionTypeHolder($expr, $nativeType, $certainty); + + if ( + !isset($scope->expressionTypes[$exprString]) + || !isset($scope->nativeExpressionTypes[$exprString]) + || !$newHolder->equals($scope->expressionTypes[$exprString]) + || !$newNativeHolder->equals($scope->nativeExpressionTypes[$exprString]) + ) { + $expressionTypes[$exprString] = $newHolder; + $nativeTypes[$exprString] = $newNativeHolder; + } $scope = $this->scopeFactory->create( $this->context, diff --git a/tests/PHPStan/Analyser/nsrt/bug-2911.php b/tests/PHPStan/Analyser/nsrt/bug-2911.php index 1b189148b3..b845c0189b 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-2911.php +++ b/tests/PHPStan/Analyser/nsrt/bug-2911.php @@ -134,6 +134,6 @@ private function getResultSettings(array $settings): array function foo(array $array): void { $array['bar'] = 'string'; - assertType("non-empty-array&hasOffsetValue('bar', 'string')", $array); + assertType("non-empty-array&hasOffsetValue('bar', 'string')", $array); } } diff --git a/tests/PHPStan/Analyser/nsrt/superglobals.php b/tests/PHPStan/Analyser/nsrt/superglobals.php index a7be748b26..f636efffae 100644 --- a/tests/PHPStan/Analyser/nsrt/superglobals.php +++ b/tests/PHPStan/Analyser/nsrt/superglobals.php @@ -31,7 +31,7 @@ public function canBeOverwritten(): void public function canBePartlyOverwritten(): void { $GLOBALS['foo'] = 'foo'; - assertType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS); + assertType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS); assertNativeType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS); } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index bdd0d7c73a..a04a2af76b 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1299,7 +1299,7 @@ public function testBug2911(): void { $this->analyse([__DIR__ . '/data/bug-2911.php'], [ [ - 'Parameter #1 $array of function Bug2911\bar expects array{bar: string}, non-empty-array given.', + 'Parameter #1 $array of function Bug2911\bar expects array{bar: string}, non-empty-array given.', 23, ], ]);