diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 310c280c2a..e71ed1ab54 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -659,14 +659,10 @@ public function getVariableType(string $variableName): Type if ($hasVariableType->maybe()) { if ($variableName === 'argc') { - return IntegerRangeType::fromInterval(1, null); + return StaticTypeFactory::argc(); } if ($variableName === 'argv') { - return new IntersectionType([ - new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new StringType()), - new NonEmptyArrayType(), - new AccessoryArrayListType(), - ]); + return StaticTypeFactory::argv(); } if ($this->canAnyVariableExist()) { return new MixedType(); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 07466466f3..16c1aa8d1d 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -153,6 +153,7 @@ use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\ResourceType; use PHPStan\Type\StaticType; +use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -2181,7 +2182,8 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch continue; } - $scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes()); + $varType = $this->getGlobalVariableType($var->name); + $scope = $scope->assignVariable($var->name, $varType, $varType, TrinaryLogic::createYes()); $vars[] = $var->name; } $scope = $this->processVarAnnotation($scope, $vars, $stmt); @@ -4853,4 +4855,16 @@ private function inferForLoopExpressions(For_ $stmt, Expr $lastCondExpr, Mutatin return $bodyScope; } + private function getGlobalVariableType(string $variableName): Type + { + if ($variableName === 'argc') { + return StaticTypeFactory::argc(); + } + if ($variableName === 'argv') { + return StaticTypeFactory::argv(); + } + + return new MixedType(); + } + } diff --git a/src/Type/StaticTypeFactory.php b/src/Type/StaticTypeFactory.php index b727a386d6..822165a396 100644 --- a/src/Type/StaticTypeFactory.php +++ b/src/Type/StaticTypeFactory.php @@ -3,6 +3,8 @@ namespace PHPStan\Type; use ArrayAccess; +use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; @@ -42,6 +44,20 @@ public static function truthy(): Type return $truthy; } + public static function argv(): Type + { + return new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new StringType()), + new NonEmptyArrayType(), + new AccessoryArrayListType(), + ]); + } + + public static function argc(): Type + { + return IntegerRangeType::fromInterval(1, null); + } + public static function generalOffsetAccessibleType(): Type { static $generalOffsetAccessible; diff --git a/tests/PHPStan/Analyser/nsrt/cli-globals.php b/tests/PHPStan/Analyser/nsrt/cli-globals.php index 9dc930c395..5a7b71a531 100644 --- a/tests/PHPStan/Analyser/nsrt/cli-globals.php +++ b/tests/PHPStan/Analyser/nsrt/cli-globals.php @@ -19,8 +19,8 @@ function g($argc, $argv) { function h() { global $argc, $argv; - assertType('mixed', $argc); // should be int<1, max> - assertType('mixed', $argv); // should be non-empty-array + assertType('int<1, max>', $argc); + assertType('non-empty-list', $argv); } function i() { @@ -31,3 +31,34 @@ function i() { assertType("'hallo'", $argc); assertType("'welt'", $argv); } + +function j() { + global $argc, $argv; + assertType('int<1, max>', $argc); + assertType('non-empty-list', $argv); + + $argc = 'overridden'; + assertType("'overridden'", $argc); +} + +class Foo { + public function bar(): void { + global $argc, $argv; + assertType('int<1, max>', $argc); + assertType('non-empty-list', $argv); + } + + public static function baz(): void { + global $argc, $argv; + assertType('int<1, max>', $argc); + assertType('non-empty-list', $argv); + } +} + +function withClosure(): void { + $fn = function () { + global $argc, $argv; + assertType('int<1, max>', $argc); + assertType('non-empty-list', $argv); + }; +}