From 85b5063c6f57b3dc777a3fd18b3e417a33470fe8 Mon Sep 17 00:00:00 2001 From: staabm <120441+staabm@users.noreply.github.com> Date: Sat, 9 May 2026 15:08:41 +0000 Subject: [PATCH 1/3] Assign proper types for `$argc` and `$argv` in `global` statements - NodeScopeResolver now assigns `int<1, max>` for `$argc` and `non-empty-list` for `$argv` when processing `global` statements, instead of unconditionally assigning `MixedType` - Extract `getGlobalVariableType()` helper in NodeScopeResolver to determine the correct type for a global variable name - Update cli-globals.php test assertions to expect the correct types and add test cases for methods, static methods, closures, and reassignment after `global` --- src/Analyser/NodeScopeResolver.php | 25 +++++++++++++- tests/PHPStan/Analyser/nsrt/cli-globals.php | 36 +++++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 07466466f39..12f4e571bf4 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -139,12 +139,17 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; +use PHPStan\Type\Accessory\AccessoryArrayListType; +use PHPStan\Type\Accessory\NonEmptyArrayType; +use PHPStan\Type\ArrayType; use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; +use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -153,6 +158,7 @@ use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\ResourceType; use PHPStan\Type\StaticType; +use PHPStan\Type\StringType; use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -2181,7 +2187,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 +4860,20 @@ private function inferForLoopExpressions(For_ $stmt, Expr $lastCondExpr, Mutatin return $bodyScope; } + private function getGlobalVariableType(string $variableName): Type + { + if ($variableName === 'argc') { + return IntegerRangeType::fromInterval(1, null); + } + if ($variableName === 'argv') { + return new IntersectionType([ + new ArrayType(IntegerRangeType::createAllGreaterThanOrEqualTo(0), new StringType()), + new NonEmptyArrayType(), + new AccessoryArrayListType(), + ]); + } + + return new MixedType(); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/cli-globals.php b/tests/PHPStan/Analyser/nsrt/cli-globals.php index 9dc930c395d..b4bc55dba98 100644 --- a/tests/PHPStan/Analyser/nsrt/cli-globals.php +++ b/tests/PHPStan/Analyser/nsrt/cli-globals.php @@ -19,15 +19,45 @@ 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() { - // user created local variable $argc = 'hallo'; $argv = 'welt'; 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); + }; +} From 6d7eb952535bb5d1dc0a4f93f8c79ce7d15a0f4e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 9 May 2026 17:21:51 +0200 Subject: [PATCH 2/3] refactor type-definition into StaticTypeFactory --- src/Analyser/MutatingScope.php | 8 ++------ src/Analyser/NodeScopeResolver.php | 15 +++------------ src/Type/StaticTypeFactory.php | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 310c280c2a5..e71ed1ab546 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 12f4e571bf4..16c1aa8d1dd 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -139,17 +139,12 @@ use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider; use PHPStan\ShouldNotHappenException; use PHPStan\TrinaryLogic; -use PHPStan\Type\Accessory\AccessoryArrayListType; -use PHPStan\Type\Accessory\NonEmptyArrayType; -use PHPStan\Type\ArrayType; use PHPStan\Type\ClosureType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\TemplateTypeHelper; use PHPStan\Type\Generic\TemplateTypeMap; -use PHPStan\Type\IntegerRangeType; -use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; @@ -158,7 +153,7 @@ use PHPStan\Type\ParserNodeTypeToPHPStanType; use PHPStan\Type\ResourceType; use PHPStan\Type\StaticType; -use PHPStan\Type\StringType; +use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -4863,14 +4858,10 @@ private function inferForLoopExpressions(For_ $stmt, Expr $lastCondExpr, Mutatin private function getGlobalVariableType(string $variableName): Type { 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(); } return new MixedType(); diff --git a/src/Type/StaticTypeFactory.php b/src/Type/StaticTypeFactory.php index b727a386d66..822165a3965 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; From 07e7da90a4039610d4326adfe787a7fed473fd99 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 9 May 2026 17:22:51 +0200 Subject: [PATCH 3/3] Update cli-globals.php --- tests/PHPStan/Analyser/nsrt/cli-globals.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Analyser/nsrt/cli-globals.php b/tests/PHPStan/Analyser/nsrt/cli-globals.php index b4bc55dba98..5a7b71a5318 100644 --- a/tests/PHPStan/Analyser/nsrt/cli-globals.php +++ b/tests/PHPStan/Analyser/nsrt/cli-globals.php @@ -24,6 +24,7 @@ function h() { } function i() { + // user created local variable $argc = 'hallo'; $argv = 'welt';