From 84cd9639b543d2ebdaf1212d4b569f013689718b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Jun 2022 19:05:08 +0200 Subject: [PATCH 1/9] specify types on get_parent_class() --- src/Analyser/TypeSpecifier.php | 43 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 3 ++ tests/PHPStan/Analyser/data/bug-5552.php | 43 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-5552.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 4deacb787e4..a4fc0792ddc 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -32,6 +32,7 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; +use PHPStan\Type\ClassStringType; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; @@ -326,6 +327,48 @@ public function specifyTypesInCondition( return $this->create($exprNode->getArgs()[0]->value, $type, $context, false, $scope, $rootExpr); } } + + if ( + !$context->null() + && $exprNode instanceof FuncCall + && $exprNode->name instanceof Name + && strtolower((string) $exprNode->name) === 'get_parent_class' + && isset($exprNode->getArgs()[0]) + && $constantType instanceof ConstantStringType + && $constantType->getValue() !== '' + ) { + $argType = $scope->getType($exprNode->getArgs()[0]->value); + $objectType = new ObjectType($constantType->getValue()); + $classStringType = new GenericClassStringType($objectType); + + if ($argType->isString()->yes()) { + return $this->create( + $exprNode->getArgs()[0]->value, + $classStringType, + $context, + false, + $scope, + ); + } + + if ((new ObjectWithoutClassType())->isSuperTypeOf($argType)->yes()) { + return $this->create( + $exprNode->getArgs()[0]->value, + $objectType, + $context, + false, + $scope, + ); + } + + return $this->create( + $exprNode->getArgs()[0]->value, + TypeCombinator::union($objectType, $classStringType), + $context, + false, + $scope, + ); + } } $rightType = $scope->getType($expr->right); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b7f0425f681..837bbf98b69 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1050,6 +1050,9 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array-offset-unset.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8008.php'); + + require_once __DIR__ . '/data/bug-5552.php'; + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5552.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-5552.php b/tests/PHPStan/Analyser/data/bug-5552.php new file mode 100644 index 00000000000..d68c3f801c1 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-5552.php @@ -0,0 +1,43 @@ +', $mixed); + } + if (A::class === get_parent_class($mixed)) { + assertType('Bug5552\A|class-string', $mixed); + } + if (get_parent_class($mixed) === 'Bug5552\A') { + assertType('Bug5552\A|class-string', $mixed); + } + if ('Bug5552\A' === get_parent_class($mixed)) { + assertType('Bug5552\A|class-string', $mixed); + } + assertType('mixed', $mixed); + + if (get_parent_class($o) === A::class) { + assertType('Bug5552\A', $o); + } + if (A::class === get_parent_class($o)) { + assertType('Bug5552\A', $o); + } + + if (get_parent_class($s) === A::class) { + assertType('class-string', $s); + } + if (A::class === get_parent_class($s)) { + assertType('class-string', $s); + } + } +} + +class A {} + +class B extends A {} From 3b1407a808c2bc56fd40b2ca2c4701ad054698ad Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Jun 2022 19:06:49 +0200 Subject: [PATCH 2/9] cs --- src/Analyser/TypeSpecifier.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index a4fc0792ddc..6166627537e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -32,7 +32,6 @@ use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; -use PHPStan\Type\ClassStringType; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; From 6d71b8d9aa26d5d91ac42b203df0756036fa77f3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 1 Jun 2022 19:08:19 +0200 Subject: [PATCH 3/9] fix --- tests/PHPStan/Analyser/data/bug-5552.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/data/bug-5552.php b/tests/PHPStan/Analyser/data/bug-5552.php index d68c3f801c1..005918f2a73 100644 --- a/tests/PHPStan/Analyser/data/bug-5552.php +++ b/tests/PHPStan/Analyser/data/bug-5552.php @@ -6,7 +6,7 @@ class HelloWorld { - public function happyPath($mixed, object $o, string $s, B $b): void + public function happyPath($mixed, object $o, string $s): void { if (get_parent_class($mixed) === A::class) { assertType('Bug5552\A|class-string', $mixed); @@ -39,5 +39,3 @@ public function happyPath($mixed, object $o, string $s, B $b): void } class A {} - -class B extends A {} From 6846eac1ef4ae6a48ef13541e2e778ce15016967 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 2 Jun 2022 09:39:33 +0200 Subject: [PATCH 4/9] fix --- src/Analyser/TypeSpecifier.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6166627537e..0d215789eba 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -360,13 +360,15 @@ public function specifyTypesInCondition( ); } - return $this->create( - $exprNode->getArgs()[0]->value, - TypeCombinator::union($objectType, $classStringType), - $context, - false, - $scope, - ); + if ($argType instanceof MixedType) { + return $this->create( + $exprNode->getArgs()[0]->value, + TypeCombinator::union($objectType, $classStringType), + $context, + false, + $scope, + ); + } } } From 45378fb86c2070b793d5161d99a373b9df089c56 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 4 Jun 2022 11:31:51 +0200 Subject: [PATCH 5/9] Update TypeSpecifier.php --- src/Analyser/TypeSpecifier.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 0d215789eba..1383c3a47ec 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -360,15 +360,14 @@ public function specifyTypesInCondition( ); } - if ($argType instanceof MixedType) { - return $this->create( - $exprNode->getArgs()[0]->value, - TypeCombinator::union($objectType, $classStringType), - $context, - false, - $scope, - ); - } + return $this->create( + $exprNode->getArgs()[0]->value, + TypeCombinator::union($objectType, $classStringType), + $context, + false, + $scope, + ); + } } From 26aef2d12d5528e08ad18a337b44c83dc6741aff Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 4 Jun 2022 11:41:31 +0200 Subject: [PATCH 6/9] cs --- 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 1383c3a47ec..a00d81026e7 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -360,7 +360,7 @@ public function specifyTypesInCondition( ); } - return $this->create( + return $this->create( $exprNode->getArgs()[0]->value, TypeCombinator::union($objectType, $classStringType), $context, From ef1ccd28967d29b72d60ae4300e64c3622bb9720 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 26 Sep 2022 17:36:08 +0200 Subject: [PATCH 7/9] fix --- src/Analyser/TypeSpecifier.php | 3 ++- tests/PHPStan/Analyser/data/bug-5552.php | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index a00d81026e7..fcb7791f433 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -334,7 +334,8 @@ public function specifyTypesInCondition( && strtolower((string) $exprNode->name) === 'get_parent_class' && isset($exprNode->getArgs()[0]) && $constantType instanceof ConstantStringType - && $constantType->getValue() !== '' + // the is_string() check should not be necessary, since ConstantStringType->getValue() returns 'string' + && is_string($constantType->getValue()) ) { $argType = $scope->getType($exprNode->getArgs()[0]->value); $objectType = new ObjectType($constantType->getValue()); diff --git a/tests/PHPStan/Analyser/data/bug-5552.php b/tests/PHPStan/Analyser/data/bug-5552.php index 005918f2a73..7a78d97f022 100644 --- a/tests/PHPStan/Analyser/data/bug-5552.php +++ b/tests/PHPStan/Analyser/data/bug-5552.php @@ -11,12 +11,18 @@ public function happyPath($mixed, object $o, string $s): void if (get_parent_class($mixed) === A::class) { assertType('Bug5552\A|class-string', $mixed); } + assertType('mixed', $mixed); + if (A::class === get_parent_class($mixed)) { assertType('Bug5552\A|class-string', $mixed); } + assertType('mixed', $mixed); + if (get_parent_class($mixed) === 'Bug5552\A') { assertType('Bug5552\A|class-string', $mixed); } + assertType('mixed', $mixed); + if ('Bug5552\A' === get_parent_class($mixed)) { assertType('Bug5552\A|class-string', $mixed); } From 6ccb24ed9954f3b2c169e5787e1222f6bdba193d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 26 Sep 2022 17:52:48 +0200 Subject: [PATCH 8/9] add more assertions --- src/Analyser/TypeSpecifier.php | 2 +- tests/PHPStan/Analyser/data/bug-5552.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index fcb7791f433..6af6656459c 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -328,7 +328,7 @@ public function specifyTypesInCondition( } if ( - !$context->null() + $context->true() && $exprNode instanceof FuncCall && $exprNode->name instanceof Name && strtolower((string) $exprNode->name) === 'get_parent_class' diff --git a/tests/PHPStan/Analyser/data/bug-5552.php b/tests/PHPStan/Analyser/data/bug-5552.php index 7a78d97f022..4b05d1b053f 100644 --- a/tests/PHPStan/Analyser/data/bug-5552.php +++ b/tests/PHPStan/Analyser/data/bug-5552.php @@ -10,21 +10,29 @@ public function happyPath($mixed, object $o, string $s): void { if (get_parent_class($mixed) === A::class) { assertType('Bug5552\A|class-string', $mixed); + } else { + assertType('mixed', $mixed); } assertType('mixed', $mixed); if (A::class === get_parent_class($mixed)) { assertType('Bug5552\A|class-string', $mixed); + } else { + assertType('mixed', $mixed); } assertType('mixed', $mixed); if (get_parent_class($mixed) === 'Bug5552\A') { assertType('Bug5552\A|class-string', $mixed); + } else { + assertType('mixed', $mixed); } assertType('mixed', $mixed); if ('Bug5552\A' === get_parent_class($mixed)) { assertType('Bug5552\A|class-string', $mixed); + } else { + assertType('mixed', $mixed); } assertType('mixed', $mixed); From 0d04ca6632210f35d58fee2b44a51b69f256ade3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 27 Sep 2022 11:19:51 +0200 Subject: [PATCH 9/9] factor out logic into private methods --- src/Analyser/TypeSpecifier.php | 397 ++++++++++-------- .../Analyser/NodeScopeResolverTest.php | 2 - 2 files changed, 212 insertions(+), 187 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 6af6656459c..d73a9b7984b 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -183,192 +183,10 @@ public function specifyTypesInCondition( $exprNode = $expressions[0]; /** @var ConstantScalarType $constantType */ $constantType = $expressions[1]; - if (!$context->null() && $constantType->getValue() === false) { - $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - return $types->unionWith($this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createFalse()->negate(), - $rootExpr, - )); - } - - if (!$context->null() && $constantType->getValue() === true) { - $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - return $types->unionWith($this->specifyTypesInCondition( - $scope, - $exprNode, - $context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate(), - $rootExpr, - )); - } - - if ($constantType->getValue() === null) { - return $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - } - - if ( - !$context->null() - && $exprNode instanceof FuncCall - && count($exprNode->getArgs()) === 1 - && $exprNode->name instanceof Name - && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) - && $constantType instanceof ConstantIntegerType - ) { - if ($context->truthy() || $constantType->getValue() === 0) { - $newContext = $context; - if ($constantType->getValue() === 0) { - $newContext = $newContext->negate(); - } - $argType = $scope->getType($exprNode->getArgs()[0]->value); - if ($argType->isArray()->yes()) { - $funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - $valueTypes = $this->create($exprNode->getArgs()[0]->value, new NonEmptyArrayType(), $newContext, false, $scope, $rootExpr); - return $funcTypes->unionWith($valueTypes); - } - } - } - - if ( - !$context->null() - && $exprNode instanceof FuncCall - && count($exprNode->getArgs()) === 1 - && $exprNode->name instanceof Name - && strtolower((string) $exprNode->name) === 'strlen' - && $constantType instanceof ConstantIntegerType - ) { - if ($context->truthy() || $constantType->getValue() === 0) { - $newContext = $context; - if ($constantType->getValue() === 0) { - $newContext = $newContext->negate(); - } - $argType = $scope->getType($exprNode->getArgs()[0]->value); - if ($argType->isString()->yes()) { - $funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - - $accessory = new AccessoryNonEmptyStringType(); - if ($constantType->getValue() >= 2) { - $accessory = new AccessoryNonFalsyStringType(); - } - $valueTypes = $this->create($exprNode->getArgs()[0]->value, $accessory, $newContext, false, $scope, $rootExpr); - - return $funcTypes->unionWith($valueTypes); - } - } - } - - if ( - $context->truthy() - && $exprNode instanceof FuncCall - && $exprNode->name instanceof Name - && in_array(strtolower($exprNode->name->toString()), ['substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'mb_strtolower', 'mb_strtoupper', 'ucfirst', 'lcfirst', 'ucwords'], true) - && isset($exprNode->getArgs()[0]) - && $constantType instanceof ConstantStringType - && $constantType->getValue() !== '' - ) { - $argType = $scope->getType($exprNode->getArgs()[0]->value); - - if ($argType->isString()->yes()) { - if ($constantType->getValue() !== '0') { - return $this->create( - $exprNode->getArgs()[0]->value, - TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()), - $context, - false, - $scope, - ); - } - - return $this->create( - $exprNode->getArgs()[0]->value, - TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()), - $context, - false, - $scope, - ); - } - } - - if ( - $exprNode instanceof FuncCall - && $exprNode->name instanceof Name - && strtolower($exprNode->name->toString()) === 'gettype' - && isset($exprNode->getArgs()[0]) - && $constantType instanceof ConstantStringType - ) { - $type = null; - if ($constantType->getValue() === 'string') { - $type = new StringType(); - } - if ($constantType->getValue() === 'array') { - $type = new ArrayType(new MixedType(), new MixedType()); - } - if ($constantType->getValue() === 'boolean') { - $type = new BooleanType(); - } - if ($constantType->getValue() === 'resource' || $constantType->getValue() === 'resource (closed)') { - $type = new ResourceType(); - } - if ($constantType->getValue() === 'integer') { - $type = new IntegerType(); - } - if ($constantType->getValue() === 'double') { - $type = new FloatType(); - } - if ($constantType->getValue() === 'NULL') { - $type = new NullType(); - } - if ($constantType->getValue() === 'object') { - $type = new ObjectWithoutClassType(); - } - - if ($type !== null) { - return $this->create($exprNode->getArgs()[0]->value, $type, $context, false, $scope, $rootExpr); - } - } - - if ( - $context->true() - && $exprNode instanceof FuncCall - && $exprNode->name instanceof Name - && strtolower((string) $exprNode->name) === 'get_parent_class' - && isset($exprNode->getArgs()[0]) - && $constantType instanceof ConstantStringType - // the is_string() check should not be necessary, since ConstantStringType->getValue() returns 'string' - && is_string($constantType->getValue()) - ) { - $argType = $scope->getType($exprNode->getArgs()[0]->value); - $objectType = new ObjectType($constantType->getValue()); - $classStringType = new GenericClassStringType($objectType); - - if ($argType->isString()->yes()) { - return $this->create( - $exprNode->getArgs()[0]->value, - $classStringType, - $context, - false, - $scope, - ); - } - - if ((new ObjectWithoutClassType())->isSuperTypeOf($argType)->yes()) { - return $this->create( - $exprNode->getArgs()[0]->value, - $objectType, - $context, - false, - $scope, - ); - } - - return $this->create( - $exprNode->getArgs()[0]->value, - TypeCombinator::union($objectType, $classStringType), - $context, - false, - $scope, - ); + $specifiedType = $this->specifyTypesForConstantBinaryExpression($exprNode, $constantType, $context, $scope, $rootExpr); + if ($specifiedType !== null) { + return $specifiedType; } } @@ -1078,6 +896,215 @@ public function specifyTypesInCondition( return new SpecifiedTypes([], [], false, [], $rootExpr); } + private function specifyTypesForConstantBinaryExpression( + Expr $exprNode, + ConstantScalarType $constantType, + TypeSpecifierContext $context, + Scope $scope, + ?Expr $rootExpr, + ): ?SpecifiedTypes + { + if (!$context->null() && $constantType->getValue() === false) { + $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + return $types->unionWith($this->specifyTypesInCondition( + $scope, + $exprNode, + $context->true() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createFalse()->negate(), + $rootExpr, + )); + } + + if (!$context->null() && $constantType->getValue() === true) { + $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + return $types->unionWith($this->specifyTypesInCondition( + $scope, + $exprNode, + $context->true() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createTrue()->negate(), + $rootExpr, + )); + } + + if ($constantType->getValue() === null) { + return $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + } + + if ( + !$context->null() + && $exprNode instanceof FuncCall + && count($exprNode->getArgs()) === 1 + && $exprNode->name instanceof Name + && in_array(strtolower((string) $exprNode->name), ['count', 'sizeof'], true) + && $constantType instanceof ConstantIntegerType + ) { + if ($context->truthy() || $constantType->getValue() === 0) { + $newContext = $context; + if ($constantType->getValue() === 0) { + $newContext = $newContext->negate(); + } + $argType = $scope->getType($exprNode->getArgs()[0]->value); + if ($argType->isArray()->yes()) { + $funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + $valueTypes = $this->create($exprNode->getArgs()[0]->value, new NonEmptyArrayType(), $newContext, false, $scope, $rootExpr); + return $funcTypes->unionWith($valueTypes); + } + } + } + + if ( + !$context->null() + && $exprNode instanceof FuncCall + && count($exprNode->getArgs()) === 1 + && $exprNode->name instanceof Name + && strtolower((string) $exprNode->name) === 'strlen' + && $constantType instanceof ConstantIntegerType + ) { + if ($context->truthy() || $constantType->getValue() === 0) { + $newContext = $context; + if ($constantType->getValue() === 0) { + $newContext = $newContext->negate(); + } + $argType = $scope->getType($exprNode->getArgs()[0]->value); + if ($argType->isString()->yes()) { + $funcTypes = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); + + $accessory = new AccessoryNonEmptyStringType(); + if ($constantType->getValue() >= 2) { + $accessory = new AccessoryNonFalsyStringType(); + } + $valueTypes = $this->create($exprNode->getArgs()[0]->value, $accessory, $newContext, false, $scope, $rootExpr); + + return $funcTypes->unionWith($valueTypes); + } + } + + } + + if ($constantType instanceof ConstantStringType) { + return $this->specifyTypesForConstantStringBinaryExpression($exprNode, $constantType, $context, $scope, $rootExpr); + } + + return null; + } + + private function specifyTypesForConstantStringBinaryExpression( + Expr $exprNode, + ConstantStringType $constantType, + TypeSpecifierContext $context, + Scope $scope, + ?Expr $rootExpr, + ): ?SpecifiedTypes + { + if ( + $context->truthy() + && $exprNode instanceof FuncCall + && $exprNode->name instanceof Name + && in_array(strtolower($exprNode->name->toString()), ['substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'mb_strtolower', 'mb_strtoupper', 'ucfirst', 'lcfirst', 'ucwords'], true) + && isset($exprNode->getArgs()[0]) + && $constantType->getValue() !== '' + ) { + $argType = $scope->getType($exprNode->getArgs()[0]->value); + + if ($argType->isString()->yes()) { + if ($constantType->getValue() !== '0') { + return $this->create( + $exprNode->getArgs()[0]->value, + TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()), + $context, + false, + $scope, + ); + } + + return $this->create( + $exprNode->getArgs()[0]->value, + TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()), + $context, + false, + $scope, + ); + } + } + + if ( + $exprNode instanceof FuncCall + && $exprNode->name instanceof Name + && strtolower($exprNode->name->toString()) === 'gettype' + && isset($exprNode->getArgs()[0]) + ) { + $type = null; + if ($constantType->getValue() === 'string') { + $type = new StringType(); + } + if ($constantType->getValue() === 'array') { + $type = new ArrayType(new MixedType(), new MixedType()); + } + if ($constantType->getValue() === 'boolean') { + $type = new BooleanType(); + } + if ($constantType->getValue() === 'resource' || $constantType->getValue() === 'resource (closed)') { + $type = new ResourceType(); + } + if ($constantType->getValue() === 'integer') { + $type = new IntegerType(); + } + if ($constantType->getValue() === 'double') { + $type = new FloatType(); + } + if ($constantType->getValue() === 'NULL') { + $type = new NullType(); + } + if ($constantType->getValue() === 'object') { + $type = new ObjectWithoutClassType(); + } + + if ($type !== null) { + return $this->create($exprNode->getArgs()[0]->value, $type, $context, false, $scope, $rootExpr); + } + } + + if ( + $context->true() + && $exprNode instanceof FuncCall + && $exprNode->name instanceof Name + && strtolower((string) $exprNode->name) === 'get_parent_class' + && isset($exprNode->getArgs()[0]) + ) { + $argType = $scope->getType($exprNode->getArgs()[0]->value); + $objectType = new ObjectType($constantType->getValue()); + $classStringType = new GenericClassStringType($objectType); + + if ($argType->isString()->yes()) { + return $this->create( + $exprNode->getArgs()[0]->value, + $classStringType, + $context, + false, + $scope, + ); + } + + if ((new ObjectWithoutClassType())->isSuperTypeOf($argType)->yes()) { + return $this->create( + $exprNode->getArgs()[0]->value, + $objectType, + $context, + false, + $scope, + ); + } + + return $this->create( + $exprNode->getArgs()[0]->value, + TypeCombinator::union($objectType, $classStringType), + $context, + false, + $scope, + ); + } + + return null; + } + private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $context, ?Expr $rootExpr, Expr $expr, Scope $scope): SpecifiedTypes { if ($context->null()) { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 837bbf98b69..ce44abdc565 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1050,8 +1050,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array-offset-unset.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8008.php'); - - require_once __DIR__ . '/data/bug-5552.php'; yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5552.php'); }