diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index a71997abed..1079fc1b53 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -30,6 +30,7 @@ use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\HasPropertyType; use PHPStan\Type\Accessory\NonEmptyArrayType; +use PHPStan\Type\ArrayType; use PHPStan\Type\BooleanType; use PHPStan\Type\ConditionalTypeForParameter; use PHPStan\Type\Constant\ConstantArrayType; @@ -52,6 +53,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\ResourceType; use PHPStan\Type\StaticMethodTypeSpecifyingExtension; use PHPStan\Type\StaticType; use PHPStan\Type\StaticTypeFactory; @@ -286,6 +288,44 @@ public function specifyTypesInCondition( ); } } + + 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); + } + } } $rightType = $scope->getType($expr->right); @@ -407,6 +447,17 @@ public function specifyTypesInCondition( $rootExpr, ); } + + if ( + $exprNode instanceof FuncCall + && $exprNode->name instanceof Name + && strtolower($exprNode->name->toString()) === 'gettype' + && isset($exprNode->getArgs()[0]) + && $constantType instanceof ConstantStringType + ) { + return $this->specifyTypesInCondition($scope, new Expr\BinaryOp\Identical($expr->left, $expr->right), $context, $rootExpr); + } + } $leftType = $scope->getType($expr->left); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 152420e240..654cd8b617 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -969,6 +969,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-7417.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-7469.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Variables/data/bug-3391.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6901.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-6901.php b/tests/PHPStan/Analyser/data/bug-6901.php new file mode 100644 index 0000000000..8f818b9035 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6901.php @@ -0,0 +1,97 @@ +|bool $y + * @return integer + */ +function foo($y) +{ + switch (gettype($y)) { + case "integer": + assertType('int', $y); + break; + case "string": + assertType('string', $y); + break; + case "boolean": + assertType('bool', $y); + break; + case "array": + assertType('array', $y); + break; + default: + assertType('*NEVER*', $y); + } + assertType('array|bool|int|string', $y); + return 0; +} + +/** + * @param object|float|null|resource $y + * @return integer + */ +function bar($y) +{ + switch (gettype($y)) { + case "object": + assertType('object', $y); + break; + case "double": + assertType('float', $y); + break; + case "NULL": + assertType('null', $y); + break; + case "resource": + assertType('resource', $y); + break; + default: + assertType('*NEVER*', $y); + } + assertType('float|object|resource|null', $y); + return 0; +} + +/** + * @param int|string|bool $x + * @param int|string|bool $y + */ +function foobarIdentical($x, $y) +{ + if (gettype($x) === 'integer') { + assertType('int', $x); + return; + } + assertType('bool|string', $x); + + if ('boolean' === gettype($x)) { + assertType('bool', $x); + return; + } + + if (gettype($y) === 'string' || gettype($y) === 'integer') { + assertType('int|string', $y); + } +} + +/** + * @param int|string|bool $x + */ +function foobarEqual($x) +{ + if (gettype($x) == 'integer') { + assertType('int', $x); + return; + } + + if ('boolean' == gettype($x)) { + assertType('bool', $x); + return; + } + + assertType('string', $x); +}