From 691843848e6565216b0cd5f3524c4c78f1b7c332 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 09:13:55 +0100 Subject: [PATCH 01/17] Implement TypeSpecifierContext->getReturnType() --- src/Analyser/TypeSpecifierContext.php | 5 ++ ...peSpecifierContextReturnTypeExtension.neon | 5 ++ ...ypeSpecifierContextReturnTypeExtension.php | 58 +++++++++++++++++++ .../TypeSpecifierContextReturnTypeTest.php | 36 ++++++++++++ .../type-specifier-context-return-type.php | 19 ++++++ 5 files changed, 123 insertions(+) create mode 100644 tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon create mode 100644 tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.php create mode 100644 tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php create mode 100644 tests/PHPStan/Analyser/data/type-specifier-context-return-type.php diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index fe09aa861c..c0928d163b 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -3,6 +3,7 @@ namespace PHPStan\Analyser; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\MixedType; /** * @api @@ -89,4 +90,8 @@ public function null(): bool return $this->value === null; } + public function getReturnType(): Type { + return new MixedType(); + } + } diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon new file mode 100644 index 0000000000..65acbd9e70 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon @@ -0,0 +1,5 @@ +services: + - + class: PHPStan\Analyser\TypeSpecifierContextReturnTypeExtension + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.php b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.php new file mode 100644 index 0000000000..81a77f7075 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.php @@ -0,0 +1,58 @@ +typeSpecifier = $typeSpecifier; + } + + public function getClass(): string + { + return \TypeSpecifierContextReturnTypeTest\ContextReturnType::class; + } + + public function isMethodSupported( + MethodReflection $methodReflection, + MethodCall $node, + TypeSpecifierContext $context, + ): bool + { + return str_starts_with($methodReflection->getName(), 'returns'); + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context, + ): SpecifiedTypes + { + return $this->typeSpecifier->create( + $node->getArgs()[0]->value, + $context->getReturnType(), + $context, + $scope, + ); + } + +} diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php new file mode 100644 index 0000000000..ed5a3bea50 --- /dev/null +++ b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php @@ -0,0 +1,36 @@ +gatherAssertTypes(__DIR__ . '/data/type-specifier-context-return-type.php'); + } + + /** + * @dataProvider dataContextReturnType + * @param mixed ...$args + */ + public function testContextReturnType( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__.'/TypeSpecifierContextReturnTypeExtension.neon' + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/type-specifier-context-return-type.php b/tests/PHPStan/Analyser/data/type-specifier-context-return-type.php new file mode 100644 index 0000000000..6bcfb61fbb --- /dev/null +++ b/tests/PHPStan/Analyser/data/type-specifier-context-return-type.php @@ -0,0 +1,19 @@ +returnsInt($i) > 0) { + assertType('int<1, max>', $i); + } else { + assertType('int', $i); + } + assertType('int', $i); + } +} From 5be8079e5b030123e7f784c078b021eea973208e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 09:22:55 +0100 Subject: [PATCH 02/17] Update TypeSpecifierContext.php --- src/Analyser/TypeSpecifierContext.php | 4 +++- .../TypeSpecifierContextReturnTypeExtension.neon | 2 +- .../Analyser/TypeSpecifierContextReturnTypeTest.php | 7 +++---- .../TypeSpecifierContextReturnTypeExtension.php | 11 ++++------- 4 files changed, 11 insertions(+), 13 deletions(-) rename tests/PHPStan/Analyser/{ => data}/TypeSpecifierContextReturnTypeExtension.php (79%) diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index c0928d163b..77458dba4a 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -4,6 +4,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\MixedType; +use PHPStan\Type\Type; /** * @api @@ -90,7 +91,8 @@ public function null(): bool return $this->value === null; } - public function getReturnType(): Type { + public function getReturnType(): Type + { return new MixedType(); } diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon index 65acbd9e70..6bb9f238df 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon +++ b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.neon @@ -1,5 +1,5 @@ services: - - class: PHPStan\Analyser\TypeSpecifierContextReturnTypeExtension + class: PHPStan\Tests\TypeSpecifierContextReturnTypeExtension tags: - phpstan.typeSpecifier.methodTypeSpecifyingExtension diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php index ed5a3bea50..cf404559b5 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php +++ b/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeTest.php @@ -2,12 +2,11 @@ namespace PHPStan\Analyser; -use PHPStan\ShouldNotHappenException; -use PHPStan\Testing\PHPStanTestCase; use PHPStan\Testing\TypeInferenceTestCase; class TypeSpecifierContextReturnTypeTest extends TypeInferenceTestCase { + public function dataContextReturnType(): iterable { yield from $this->gatherAssertTypes(__DIR__ . '/data/type-specifier-context-return-type.php'); @@ -20,7 +19,7 @@ public function dataContextReturnType(): iterable public function testContextReturnType( string $assertType, string $file, - ...$args, + ...$args, ): void { $this->assertFileAsserts($assertType, $file, ...$args); @@ -29,7 +28,7 @@ public function testContextReturnType( public static function getAdditionalConfigFiles(): array { return [ - __DIR__.'/TypeSpecifierContextReturnTypeExtension.neon' + __DIR__ . '/TypeSpecifierContextReturnTypeExtension.neon', ]; } diff --git a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.php b/tests/PHPStan/Analyser/data/TypeSpecifierContextReturnTypeExtension.php similarity index 79% rename from tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.php rename to tests/PHPStan/Analyser/data/TypeSpecifierContextReturnTypeExtension.php index 81a77f7075..cd57bf603a 100644 --- a/tests/PHPStan/Analyser/TypeSpecifierContextReturnTypeExtension.php +++ b/tests/PHPStan/Analyser/data/TypeSpecifierContextReturnTypeExtension.php @@ -1,6 +1,6 @@ Date: Thu, 13 Mar 2025 10:52:19 +0100 Subject: [PATCH 03/17] Update TypeSpecifierContext.php --- src/Analyser/TypeSpecifierContext.php | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index 77458dba4a..f9fccd436e 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -3,7 +3,6 @@ namespace PHPStan\Analyser; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\MixedType; use PHPStan\Type\Type; /** @@ -23,19 +22,27 @@ final class TypeSpecifierContext /** @var self[] */ private static array $registry; - private function __construct(private ?int $value) + private function __construct( + private ?int $value, + private ?Type $returnType, + ) { } - private static function create(?int $value): self + private static function create(?int $value, ?Type $returnType = null): self { - self::$registry[$value] ??= new self($value); + if ($returnType !== null) { + // return type bound context is unique for each context and therefore not cachable + return new self($value, $returnType); + } + + self::$registry[$value] ??= new self($value, null); return self::$registry[$value]; } - public static function createTrue(): self + public static function createTrue(?Type $returnType = null): self { - return self::create(self::CONTEXT_TRUE); + return self::create(self::CONTEXT_TRUE, $returnType); } public static function createTruthy(): self @@ -43,9 +50,9 @@ public static function createTruthy(): self return self::create(self::CONTEXT_TRUTHY); } - public static function createFalse(): self + public static function createFalse(?Type $returnType = null): self { - return self::create(self::CONTEXT_FALSE); + return self::create(self::CONTEXT_FALSE, $returnType); } public static function createFalsey(): self @@ -91,9 +98,9 @@ public function null(): bool return $this->value === null; } - public function getReturnType(): Type + public function getReturnType(): ?Type { - return new MixedType(); + return $this->returnType; } } From 68817de909ea1f794e835ba248b54452e2fa2ee9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 14:26:38 +0100 Subject: [PATCH 04/17] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 44 ++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index c016b2869e..29941ffc7d 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -331,21 +331,6 @@ public function specifyTypesInCondition( } } - if ( - !$context->null() - && $expr->right instanceof FuncCall - && count($expr->right->getArgs()) >= 3 - && $expr->right->name instanceof Name - && in_array(strtolower((string) $expr->right->name), ['preg_match'], true) - && IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($leftType)->yes() - ) { - return $this->specifyTypesInCondition( - $scope, - new Expr\BinaryOp\NotIdentical($expr->right, new ConstFetch(new Name('false'))), - $context, - )->setRootExpr($expr); - } - if ( !$context->null() && $expr->right instanceof FuncCall @@ -466,6 +451,21 @@ public function specifyTypesInCondition( } } + if ( + !$context->null() + && $expr->right instanceof FuncCall + ) { + $newScope = $scope->filterBySpecifiedTypes($result); + $callType = $newScope->getType($expr->right); + $newContext = $this->createSpecifierContextReturnType($callType, $context); + + return $this->specifyTypesInCondition( + $scope, + $expr->right, + $newContext, + )->setRootExpr($expr); + } + return $result; } elseif ($expr instanceof Node\Expr\BinaryOp\Greater) { @@ -1949,6 +1949,20 @@ private function getTypeSpecifyingExtensionsForType(array $extensions, string $c return array_merge(...$extensionsForClass); } + private function createSpecifierContextReturnType( + Type $contextReturnType, + TypeSpecifierContext $context, + ): TypeSpecifierContext + { + if ($context->true()) { + return TypeSpecifierContext::createTrue($contextReturnType); + } elseif ($context->false()) { + return TypeSpecifierContext::createFalse($contextReturnType); + } + + return $context; + } + public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); From e4cad70a26bdc82e8f50b728164fbe29a1a7b017 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 14:27:52 +0100 Subject: [PATCH 05/17] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 29941ffc7d..de43954f89 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1954,11 +1954,11 @@ private function createSpecifierContextReturnType( TypeSpecifierContext $context, ): TypeSpecifierContext { - if ($context->true()) { - return TypeSpecifierContext::createTrue($contextReturnType); - } elseif ($context->false()) { - return TypeSpecifierContext::createFalse($contextReturnType); - } + if ($context->true()) { + return TypeSpecifierContext::createTrue($contextReturnType); + } elseif ($context->false()) { + return TypeSpecifierContext::createFalse($contextReturnType); + } return $context; } From f38718a8aedae8fe73dc53c2356652478448aea5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 14:35:00 +0100 Subject: [PATCH 06/17] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index de43954f89..6963409dec 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -453,7 +453,7 @@ public function specifyTypesInCondition( if ( !$context->null() - && $expr->right instanceof FuncCall + && $expr->right instanceof Expr\CallLike ) { $newScope = $scope->filterBySpecifiedTypes($result); $callType = $newScope->getType($expr->right); @@ -1954,13 +1954,13 @@ private function createSpecifierContextReturnType( TypeSpecifierContext $context, ): TypeSpecifierContext { - if ($context->true()) { + //if ($context->true()) { return TypeSpecifierContext::createTrue($contextReturnType); - } elseif ($context->false()) { - return TypeSpecifierContext::createFalse($contextReturnType); - } + //} elseif ($context->false()) { + // return TypeSpecifierContext::createFalse($contextReturnType); + //} - return $context; + //return $context; } public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes From 431dca8cb96401e7573fd9ccb8f0c6d6cae65a60 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 14:35:30 +0100 Subject: [PATCH 07/17] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6963409dec..c5febae696 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1954,8 +1954,9 @@ private function createSpecifierContextReturnType( TypeSpecifierContext $context, ): TypeSpecifierContext { - //if ($context->true()) { + if (!$context->null()) { return TypeSpecifierContext::createTrue($contextReturnType); + } //} elseif ($context->false()) { // return TypeSpecifierContext::createFalse($contextReturnType); //} From fca50f7d8edee1293b5f48e5cca7703632323c5a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 14:42:46 +0100 Subject: [PATCH 08/17] Update TypeSpecifierContext.php --- src/Analyser/TypeSpecifierContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index f9fccd436e..20160a1423 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -32,7 +32,7 @@ private function __construct( private static function create(?int $value, ?Type $returnType = null): self { if ($returnType !== null) { - // return type bound context is unique for each context and therefore not cachable + // return type bound context is unique for each context and therefore not cacheable return new self($value, $returnType); } From 58dc99ac1c5bcc30a568dae8fea1b791cbccebfe Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 14:44:24 +0100 Subject: [PATCH 09/17] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index c5febae696..f11579a083 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -457,7 +457,7 @@ public function specifyTypesInCondition( ) { $newScope = $scope->filterBySpecifiedTypes($result); $callType = $newScope->getType($expr->right); - $newContext = $this->createSpecifierContextReturnType($callType, $context); + $newContext = TypeSpecifierContext::createTrue($callType); return $this->specifyTypesInCondition( $scope, @@ -1949,21 +1949,6 @@ private function getTypeSpecifyingExtensionsForType(array $extensions, string $c return array_merge(...$extensionsForClass); } - private function createSpecifierContextReturnType( - Type $contextReturnType, - TypeSpecifierContext $context, - ): TypeSpecifierContext - { - if (!$context->null()) { - return TypeSpecifierContext::createTrue($contextReturnType); - } - //} elseif ($context->false()) { - // return TypeSpecifierContext::createFalse($contextReturnType); - //} - - //return $context; - } - public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes { $expressions = $this->findTypeExpressionsFromBinaryOperation($scope, $expr); From 0b4b6cb87a40c05d5eb6c4d359ba8e8cc2f0bbcd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 14:58:03 +0100 Subject: [PATCH 10/17] fix --- src/Analyser/TypeSpecifier.php | 6 +++--- src/Analyser/TypeSpecifierContext.php | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f11579a083..64dd0687f2 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -457,13 +457,13 @@ public function specifyTypesInCondition( ) { $newScope = $scope->filterBySpecifiedTypes($result); $callType = $newScope->getType($expr->right); - $newContext = TypeSpecifierContext::createTrue($callType); + $newContext = TypeSpecifierContext::createTruthy($callType); - return $this->specifyTypesInCondition( + $result = $result->unionWith($this->specifyTypesInCondition( $scope, $expr->right, $newContext, - )->setRootExpr($expr); + )->setRootExpr($expr)); } return $result; diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index 20160a1423..5ef7629084 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -45,9 +45,9 @@ public static function createTrue(?Type $returnType = null): self return self::create(self::CONTEXT_TRUE, $returnType); } - public static function createTruthy(): self + public static function createTruthy(?Type $returnType = null): self { - return self::create(self::CONTEXT_TRUTHY); + return self::create(self::CONTEXT_TRUTHY, $returnType); } public static function createFalse(?Type $returnType = null): self @@ -55,9 +55,9 @@ public static function createFalse(?Type $returnType = null): self return self::create(self::CONTEXT_FALSE, $returnType); } - public static function createFalsey(): self + public static function createFalsey(?Type $returnType = null): self { - return self::create(self::CONTEXT_FALSEY); + return self::create(self::CONTEXT_FALSEY, $returnType); } public static function createNull(): self From 877cbf67884a01eb763f80df908eb36e2a42808f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 15:02:04 +0100 Subject: [PATCH 11/17] Update type-specifier-context-return-type.php --- .../data/type-specifier-context-return-type.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/type-specifier-context-return-type.php b/tests/PHPStan/Analyser/data/type-specifier-context-return-type.php index 6bcfb61fbb..2303d938f6 100644 --- a/tests/PHPStan/Analyser/data/type-specifier-context-return-type.php +++ b/tests/PHPStan/Analyser/data/type-specifier-context-return-type.php @@ -7,7 +7,7 @@ class ContextReturnType { public function returnsInt(int $specifiedContextReturnType): int {} - public function doFoo(int $i) { + public function doFooLeft(int $i) { assertType('int', $i); if ($this->returnsInt($i) > 0) { assertType('int<1, max>', $i); @@ -16,4 +16,14 @@ public function doFoo(int $i) { } assertType('int', $i); } + + public function doFooRight(int $i) { + assertType('int', $i); + if (0 < $this->returnsInt($i)) { + assertType('int<1, max>', $i); + } else { + assertType('int', $i); + } + assertType('int', $i); + } } From 2587cc7e508295353c82824ad111ce2a7cd46eeb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 15:17:03 +0100 Subject: [PATCH 12/17] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 64dd0687f2..f45068b2d0 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -455,6 +455,9 @@ public function specifyTypesInCondition( !$context->null() && $expr->right instanceof Expr\CallLike ) { + if (!$scope instanceof MutatingScope) { + throw new ShouldNotHappenException(); + } $newScope = $scope->filterBySpecifiedTypes($result); $callType = $newScope->getType($expr->right); $newContext = TypeSpecifierContext::createTruthy($callType); From 2c615a7d4c1053ab13a59735b8b56835a5b54f3d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 15:29:17 +0100 Subject: [PATCH 13/17] pass return type arround when re-creating context --- src/Analyser/TypeSpecifier.php | 4 ++-- src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f45068b2d0..bbcd7e3d12 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1176,7 +1176,7 @@ private function specifyTypesForConstantBinaryExpression( return $types->unionWith($this->specifyTypesInCondition( $scope, $exprNode, - $context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate(), + $context->true() ? TypeSpecifierContext::createTrue($context->getReturnType()) : TypeSpecifierContext::createTrue($context->getReturnType())->negate(), )->setRootExpr($rootExpr)); } @@ -1976,7 +1976,7 @@ public function resolveEqual(Expr\BinaryOp\Equal $expr, Scope $scope, TypeSpecif return $this->specifyTypesInCondition( $scope, $exprNode, - $context->true() ? TypeSpecifierContext::createFalsey() : TypeSpecifierContext::createFalsey()->negate(), + $context->true() ? TypeSpecifierContext::createFalsey($context->getReturnType()) : TypeSpecifierContext::createFalsey($context->getReturnType())->negate(), )->setRootExpr($expr); } diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 997cfea752..a232d25a2b 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -147,7 +147,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create( $node->getArgs()[1]->value, new ArrayType(new MixedType(), $arrayValueType), - TypeSpecifierContext::createTrue(), + TypeSpecifierContext::createTrue($context->getReturnType()), $scope, )); } From 34477172b42329ac5ac44b71db95fb71ab0699dc Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 16:43:16 +0100 Subject: [PATCH 14/17] Update TypeSpecifierContextReturnTypeExtension.php --- .../Analyser/data/TypeSpecifierContextReturnTypeExtension.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Analyser/data/TypeSpecifierContextReturnTypeExtension.php b/tests/PHPStan/Analyser/data/TypeSpecifierContextReturnTypeExtension.php index cd57bf603a..906bd0f304 100644 --- a/tests/PHPStan/Analyser/data/TypeSpecifierContextReturnTypeExtension.php +++ b/tests/PHPStan/Analyser/data/TypeSpecifierContextReturnTypeExtension.php @@ -44,6 +44,10 @@ public function specifyTypes( TypeSpecifierContext $context, ): SpecifiedTypes { + if ($context->null()) { + return new SpecifiedTypes(); + } + return $this->typeSpecifier->create( $node->getArgs()[0]->value, $context->getReturnType(), From e1f4910ad9e2d75f43422f0039c86c3bade1cc1a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 16:45:33 +0100 Subject: [PATCH 15/17] negate type in TypeSpecifierContext --- src/Analyser/TypeSpecifierContext.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index 5ef7629084..4c6c7e2419 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -3,7 +3,9 @@ namespace PHPStan\Analyser; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; /** * @api @@ -70,7 +72,9 @@ public function negate(): self if ($this->value === null) { throw new ShouldNotHappenException(); } - return self::create(~$this->value & self::CONTEXT_BITMASK); + + $baseType = $this->returnType->generalize(GeneralizePrecision::lessSpecific()); + return self::create(~$this->value & self::CONTEXT_BITMASK, TypeCombinator::remove($baseType, $this->returnType)); } public function true(): bool From f4984d253d86c5e3216d77ce7b89e2b7bfd48113 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 17:08:46 +0100 Subject: [PATCH 16/17] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index bbcd7e3d12..7d378afa71 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -460,7 +460,7 @@ public function specifyTypesInCondition( } $newScope = $scope->filterBySpecifiedTypes($result); $callType = $newScope->getType($expr->right); - $newContext = TypeSpecifierContext::createTruthy($callType); + $newContext = $context->true() ? TypeSpecifierContext::createTrue($callType) : TypeSpecifierContext::createTrue($callType)->negate(); $result = $result->unionWith($this->specifyTypesInCondition( $scope, From 35a76411d83f6cb6fda188473d720770dd0a8310 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 13 Mar 2025 17:12:44 +0100 Subject: [PATCH 17/17] Update TypeSpecifierContext.php --- src/Analyser/TypeSpecifierContext.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Analyser/TypeSpecifierContext.php b/src/Analyser/TypeSpecifierContext.php index 4c6c7e2419..4044869c11 100644 --- a/src/Analyser/TypeSpecifierContext.php +++ b/src/Analyser/TypeSpecifierContext.php @@ -73,8 +73,13 @@ public function negate(): self throw new ShouldNotHappenException(); } - $baseType = $this->returnType->generalize(GeneralizePrecision::lessSpecific()); - return self::create(~$this->value & self::CONTEXT_BITMASK, TypeCombinator::remove($baseType, $this->returnType)); + $negatedReturnType = null; + if ($this->returnType !== null) { + $baseType = $this->returnType->generalize(GeneralizePrecision::lessSpecific()); + $negatedReturnType = TypeCombinator::remove($baseType, $this->returnType); + } + + return self::create(~$this->value & self::CONTEXT_BITMASK, $negatedReturnType); } public function true(): bool