From 03adf063ef9c1aa33e2b1829606fda984a755142 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 13 Apr 2025 22:19:33 +0200 Subject: [PATCH 01/13] Fix MixedType->equals(ErrorType) --- src/Type/MixedType.php | 3 +- tests/PHPStan/Type/MixedTypeTest.php | 45 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 10c2b7cccd..13153c0ebc 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -37,6 +37,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use function get_class; use function sprintf; /** @api */ @@ -310,7 +311,7 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) public function equals(Type $type): bool { - if (!$type instanceof self) { + if (get_class($type) !== self::class) { return false; } diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index 5322385963..a4b6f32b0e 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -1164,4 +1164,49 @@ public function testSubtractedHasOffsetValueType(MixedType $mixedType, Type $typ ); } + /** @dataProvider dataEquals */ + public function testEquals(MixedType $mixedType, Type $typeToCompare, bool $expectedResult): void + { + $this->assertSame( + $expectedResult, + $mixedType->equals($typeToCompare), + ); + } + + public function dataEquals(): array + { + return [ + [ + new MixedType(), + new MixedType(), + true, + ], + [ + new MixedType(true), + new MixedType(), + true, + ], + [ + new MixedType(), + new MixedType(true), + true, + ], + [ + new MixedType(), + new MixedType(true, new IntegerType()), + false, + ], + [ + new MixedType(), + new ErrorType(), + false, + ], + [ + new MixedType(true), + new ErrorType(), + false, + ], + ]; + } + } From 9b93701b07378c5bc02edd5f234cee7591ea4923 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 13 Apr 2025 22:25:28 +0200 Subject: [PATCH 02/13] Update MixedType.php --- src/Type/MixedType.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 13153c0ebc..20ec4a691c 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -38,6 +38,7 @@ use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; use function get_class; +use function in_array; use function sprintf; /** @api */ @@ -311,7 +312,7 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) public function equals(Type $type): bool { - if (get_class($type) !== self::class) { + if (!in_array(get_class($type), [self::class, TemplateMixedType::class], true)) { return false; } From 78f4d98d4fa457a032cdc328278bee6b24f26c29 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 13 Apr 2025 22:29:47 +0200 Subject: [PATCH 03/13] simplify --- src/Type/MixedType.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 20ec4a691c..297aabe7d6 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -32,6 +32,7 @@ use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; @@ -312,7 +313,11 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) public function equals(Type $type): bool { - if (!in_array(get_class($type), [self::class, TemplateMixedType::class], true)) { + if (!$type instanceof self) { + return false; + } + + if ($type instanceof ErrorType) { return false; } From 3afdd177203b3b97586783ed95205dbb0028bd2c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 13 Apr 2025 22:36:59 +0200 Subject: [PATCH 04/13] cs --- src/Type/MixedType.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 297aabe7d6..485c7c5270 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -32,14 +32,11 @@ use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; -use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; -use function get_class; -use function in_array; use function sprintf; /** @api */ From 9b1cc2afced679dda8263ce659e0f315f99b6da5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 14 Apr 2025 09:14:02 +0200 Subject: [PATCH 05/13] Update MixedTypeTest.php --- tests/PHPStan/Type/MixedTypeTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Type/MixedTypeTest.php b/tests/PHPStan/Type/MixedTypeTest.php index a4b6f32b0e..c85667c732 100644 --- a/tests/PHPStan/Type/MixedTypeTest.php +++ b/tests/PHPStan/Type/MixedTypeTest.php @@ -1170,6 +1170,7 @@ public function testEquals(MixedType $mixedType, Type $typeToCompare, bool $expe $this->assertSame( $expectedResult, $mixedType->equals($typeToCompare), + sprintf('%s -> equals(%s)', $mixedType->describe(VerbosityLevel::precise()), $typeToCompare->describe(VerbosityLevel::precise())), ); } From 888cb1192a0542f641cb78bd6fb028c9437279e9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 14 Apr 2025 09:29:33 +0200 Subject: [PATCH 06/13] Support arrays in fast-path --- src/Analyser/MutatingScope.php | 2 +- tests/PHPStan/Analyser/nsrt/conditional-vars.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 051d15d960..d46ddac54e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4486,7 +4486,7 @@ public function addTypeToExpression(Expr $expr, Type $type): self if ($originalExprType->equals($nativeType)) { $newType = TypeCombinator::intersect($type, $originalExprType); - if ($newType->isConstantScalarValue()->yes() && $newType->equals($originalExprType)) { + if ($newType->isObject()->no() && $newType->equals($originalExprType)) { // don't add the same type over and over again to improve performance return $this; } diff --git a/tests/PHPStan/Analyser/nsrt/conditional-vars.php b/tests/PHPStan/Analyser/nsrt/conditional-vars.php index 568c6a8b7f..467ca05aae 100644 --- a/tests/PHPStan/Analyser/nsrt/conditional-vars.php +++ b/tests/PHPStan/Analyser/nsrt/conditional-vars.php @@ -10,10 +10,10 @@ class HelloWorld public function conditionalVarInTernary(array $innerHits): void { if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { - assertType('non-empty-array', $innerHits); + assertType('non-empty-array', $innerHits); $x = array_key_exists('nearest_premise', $innerHits) ? assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits) - : assertType('non-empty-array', $innerHits); + : assertType('non-empty-array', $innerHits); assertType('non-empty-array', $innerHits); } @@ -24,11 +24,11 @@ public function conditionalVarInTernary(array $innerHits): void public function conditionalVarInIf(array $innerHits): void { if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { - assertType('non-empty-array', $innerHits); + assertType('non-empty-array', $innerHits); if (array_key_exists('nearest_premise', $innerHits)) { assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits); } else { - assertType('non-empty-array', $innerHits); + assertType('non-empty-array', $innerHits); } assertType('non-empty-array', $innerHits); From 3e91a6ec3f3d1bad3d9cc26f3fb88f3669b08751 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 15 Apr 2025 16:06:30 +0200 Subject: [PATCH 07/13] Update conditional-vars.php --- tests/PHPStan/Analyser/nsrt/conditional-vars.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/conditional-vars.php b/tests/PHPStan/Analyser/nsrt/conditional-vars.php index 467ca05aae..568c6a8b7f 100644 --- a/tests/PHPStan/Analyser/nsrt/conditional-vars.php +++ b/tests/PHPStan/Analyser/nsrt/conditional-vars.php @@ -10,10 +10,10 @@ class HelloWorld public function conditionalVarInTernary(array $innerHits): void { if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { - assertType('non-empty-array', $innerHits); + assertType('non-empty-array', $innerHits); $x = array_key_exists('nearest_premise', $innerHits) ? assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits) - : assertType('non-empty-array', $innerHits); + : assertType('non-empty-array', $innerHits); assertType('non-empty-array', $innerHits); } @@ -24,11 +24,11 @@ public function conditionalVarInTernary(array $innerHits): void public function conditionalVarInIf(array $innerHits): void { if (array_key_exists('nearest_premise', $innerHits) || array_key_exists('matching_premises', $innerHits)) { - assertType('non-empty-array', $innerHits); + assertType('non-empty-array', $innerHits); if (array_key_exists('nearest_premise', $innerHits)) { assertType("non-empty-array&hasOffset('nearest_premise')", $innerHits); } else { - assertType('non-empty-array', $innerHits); + assertType('non-empty-array', $innerHits); } assertType('non-empty-array', $innerHits); From 4b40700ac1edafe81b47b5704dbbcd88f5a72000 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 13 May 2025 16:19:16 +0200 Subject: [PATCH 08/13] fix --- src/Type/MixedType.php | 7 ++----- tests/PHPStan/Generics/TemplateTypeFactoryTest.php | 7 ++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 485c7c5270..75f8972bc1 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -37,6 +37,7 @@ use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait; +use function get_class; use function sprintf; /** @api */ @@ -310,11 +311,7 @@ public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope) public function equals(Type $type): bool { - if (!$type instanceof self) { - return false; - } - - if ($type instanceof ErrorType) { + if (get_class($type) !== static::class) { return false; } diff --git a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php index 58af77cd3b..d044b4b469 100644 --- a/tests/PHPStan/Generics/TemplateTypeFactoryTest.php +++ b/tests/PHPStan/Generics/TemplateTypeFactoryTest.php @@ -55,7 +55,12 @@ public function dataCreate(): array null, TemplateTypeVariance::createInvariant(), ), - new MixedType(), + TemplateTypeFactory::create( + TemplateTypeScope::createWithFunction('a'), + 'U', + null, + TemplateTypeVariance::createInvariant(), + ), ], [ new UnionType([ From 9dcc4e5dab222ad79657d4dc8ec6f7417b671852 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 14 May 2025 09:16:39 +0200 Subject: [PATCH 09/13] Update MutatingScope.php --- src/Analyser/MutatingScope.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d46ddac54e..0a4e85ab55 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4486,7 +4486,7 @@ public function addTypeToExpression(Expr $expr, Type $type): self if ($originalExprType->equals($nativeType)) { $newType = TypeCombinator::intersect($type, $originalExprType); - if ($newType->isObject()->no() && $newType->equals($originalExprType)) { + if (!$newType->isObject()->yes() && $newType->equals($originalExprType)) { // don't add the same type over and over again to improve performance return $this; } From e0b692d7c0163960c98f125750a0300e70594532 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 14 May 2025 09:21:26 +0200 Subject: [PATCH 10/13] Update MutatingScope.php --- src/Analyser/MutatingScope.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0a4e85ab55..299df748d4 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4486,8 +4486,9 @@ public function addTypeToExpression(Expr $expr, Type $type): self if ($originalExprType->equals($nativeType)) { $newType = TypeCombinator::intersect($type, $originalExprType); - if (!$newType->isObject()->yes() && $newType->equals($originalExprType)) { - // don't add the same type over and over again to improve performance + if ($newType->isObject()->no() && $newType->equals($originalExprType)) { + // don't add the same type over and over again to improve performance. + // objects can get narrowed even though ObjectType->equal() will return true (e.g. via impicit "final" via new()) return $this; } return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes()); From b9e54116410ebbe046befe20ce02b205e7b6e30b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 14 May 2025 09:27:30 +0200 Subject: [PATCH 11/13] Update MutatingScope.php --- src/Analyser/MutatingScope.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 299df748d4..12a4d46a42 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4488,7 +4488,7 @@ public function addTypeToExpression(Expr $expr, Type $type): self $newType = TypeCombinator::intersect($type, $originalExprType); if ($newType->isObject()->no() && $newType->equals($originalExprType)) { // don't add the same type over and over again to improve performance. - // objects can get narrowed even though ObjectType->equal() will return true (e.g. via impicit "final" via new()) + // objects can get narrowed even though ObjectType->equal() will return true (e.g. via implicit "final" via new()) return $this; } return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes()); From d2deae68e4b33cf17f903ebfce9b5bbdc745ca1e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 14 May 2025 09:27:47 +0200 Subject: [PATCH 12/13] Update MutatingScope.php --- src/Analyser/MutatingScope.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 12a4d46a42..cda23689c3 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4488,7 +4488,7 @@ public function addTypeToExpression(Expr $expr, Type $type): self $newType = TypeCombinator::intersect($type, $originalExprType); if ($newType->isObject()->no() && $newType->equals($originalExprType)) { // don't add the same type over and over again to improve performance. - // objects can get narrowed even though ObjectType->equal() will return true (e.g. via implicit "final" via new()) + // objects can get narrowed even though ObjectType->equal() will return true (e.g. via implicit "final" via new Foo()) return $this; } return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes()); From 9bfeae2f3e01a2fd52d38ec9b6e17c35f6c7b374 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 16 May 2025 15:05:07 +0200 Subject: [PATCH 13/13] Discard changes to src/Analyser/MutatingScope.php --- src/Analyser/MutatingScope.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index cda23689c3..051d15d960 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4486,9 +4486,8 @@ public function addTypeToExpression(Expr $expr, Type $type): self if ($originalExprType->equals($nativeType)) { $newType = TypeCombinator::intersect($type, $originalExprType); - if ($newType->isObject()->no() && $newType->equals($originalExprType)) { - // don't add the same type over and over again to improve performance. - // objects can get narrowed even though ObjectType->equal() will return true (e.g. via implicit "final" via new Foo()) + if ($newType->isConstantScalarValue()->yes() && $newType->equals($originalExprType)) { + // don't add the same type over and over again to improve performance return $this; } return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes());