From e14490b17583dc31131939885952bdc6fdd58671 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Mon, 30 Dec 2024 15:37:46 +0100 Subject: [PATCH 1/2] Improve expression resolving of superglobals --- src/Analyser/MutatingScope.php | 63 ++++++++++++------- src/Analyser/ScopeFactory.php | 22 ++++++- tests/PHPStan/Analyser/ScopeTest.php | 2 +- .../Analyser/nsrt/get-defined-vars.php | 20 +++--- tests/PHPStan/Analyser/nsrt/superglobals.php | 59 +++++++++++++++++ .../Rules/Debug/DebugScopeRuleTest.php | 57 ++++++++++++++++- 6 files changed, 187 insertions(+), 36 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/superglobals.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0c0af430e8..75902cef5b 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -513,10 +513,6 @@ public function afterOpenSslCall(string $openSslFunctionName): self /** @api */ public function hasVariableType(string $variableName): TrinaryLogic { - if ($this->isGlobalVariable($variableName)) { - return TrinaryLogic::createYes(); - } - $varExprString = '$' . $variableName; if (!isset($this->expressionTypes[$varExprString])) { if ($this->canAnyVariableExist()) { @@ -548,10 +544,6 @@ public function getVariableType(string $variableName): Type } } - if ($this->isGlobalVariable($variableName)) { - return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); - } - if ($this->hasVariableType($variableName)->no()) { throw new UndefinedVariableException($this, $variableName); } @@ -606,11 +598,6 @@ public function getMaybeDefinedVariables(): array return $variables; } - private function isGlobalVariable(string $variableName): bool - { - return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true); - } - /** @api */ public function hasConstant(Name $name): bool { @@ -2893,18 +2880,16 @@ public function isInFunctionExists(string $functionName): bool public function enterClass(ClassReflection $classReflection): self { $thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection)); - $constantTypes = $this->getConstantTypes(); - $constantTypes['$this'] = $thisHolder; - $nativeConstantTypes = $this->getNativeConstantTypes(); - $nativeConstantTypes['$this'] = $thisHolder; + $expressionTypes = array_merge($this->getSuperglobalTypes(), $this->getConstantTypes(), ['$this' => $thisHolder]); + $nativeExpressionTypes = array_merge($this->getNativeSuperglobalTypes(), $this->getNativeConstantTypes(), ['$this' => $thisHolder]); return $this->scopeFactory->create( $this->context->enterClass($classReflection), $this->isDeclareStrictTypes(), null, $this->getNamespace(), - $constantTypes, - $nativeConstantTypes, + $expressionTypes, + $nativeExpressionTypes, [], [], null, @@ -3298,8 +3283,8 @@ private function enterFunctionLike( $this->isDeclareStrictTypes(), $functionReflection, $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), - array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes), + array_merge($this->getSuperglobalTypes(), $this->getConstantTypes(), $expressionTypes), + array_merge($this->getNativeSuperglobalTypes(), $this->getNativeConstantTypes(), $nativeExpressionTypes), $conditionalTypes, ); } @@ -3312,6 +3297,8 @@ public function enterNamespace(string $namespaceName): self $this->isDeclareStrictTypes(), null, $namespaceName, + $this->getSuperglobalTypes(), + $this->getNativeSuperglobalTypes(), ); } @@ -3588,8 +3575,8 @@ private function enterAnonymousFunctionWithoutReflection( $this->isDeclareStrictTypes(), $this->getFunction(), $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), - array_merge($this->getNativeConstantTypes(), $nativeTypes), + array_merge($this->getSuperglobalTypes(), $this->getConstantTypes(), $expressionTypes), + array_merge($this->getNativeSuperglobalTypes(), $this->getNativeConstantTypes(), $nativeTypes), [], $this->inClosureBindScopeClasses, new TrivialParametersAcceptor(), @@ -5987,6 +5974,36 @@ public function getConstantReflection(Type $typeWithConstant, string $constantNa return $typeWithConstant->getConstant($constantName); } + /** @return array */ + private function getSuperglobalTypes(): array + { + $superglobalTypes = []; + $exprStrings = ['$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV']; + foreach ($this->expressionTypes as $exprString => $typeHolder) { + if (!in_array($exprString, $exprStrings, true)) { + continue; + } + + $superglobalTypes[$exprString] = $typeHolder; + } + return $superglobalTypes; + } + + /** @return array */ + private function getNativeSuperglobalTypes(): array + { + $superglobalTypes = []; + $exprStrings = ['$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV']; + foreach ($this->nativeExpressionTypes as $exprString => $typeHolder) { + if (!in_array($exprString, $exprStrings, true)) { + continue; + } + + $superglobalTypes[$exprString] = $typeHolder; + } + return $superglobalTypes; + } + /** * @return array */ diff --git a/src/Analyser/ScopeFactory.php b/src/Analyser/ScopeFactory.php index ade6e1d894..9dba3fcb14 100644 --- a/src/Analyser/ScopeFactory.php +++ b/src/Analyser/ScopeFactory.php @@ -2,6 +2,13 @@ namespace PHPStan\Analyser; +use PhpParser\Node\Expr\Variable; +use PHPStan\Type\ArrayType; +use PHPStan\Type\BenevolentUnionType; +use PHPStan\Type\IntegerType; +use PHPStan\Type\MixedType; +use PHPStan\Type\StringType; + /** * @api */ @@ -14,7 +21,20 @@ public function __construct(private InternalScopeFactory $internalScopeFactory) public function create(ScopeContext $context): MutatingScope { - return $this->internalScopeFactory->create($context); + $superglobalType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); + $expressionTypes = [ + '$GLOBALS' => ExpressionTypeHolder::createYes(new Variable('GLOBALS'), $superglobalType), + '$_SERVER' => ExpressionTypeHolder::createYes(new Variable('_SERVER'), $superglobalType), + '$_GET' => ExpressionTypeHolder::createYes(new Variable('_GET'), $superglobalType), + '$_POST' => ExpressionTypeHolder::createYes(new Variable('_POST'), $superglobalType), + '$_FILES' => ExpressionTypeHolder::createYes(new Variable('_FILES'), $superglobalType), + '$_COOKIE' => ExpressionTypeHolder::createYes(new Variable('_COOKIE'), $superglobalType), + '$_SESSION' => ExpressionTypeHolder::createYes(new Variable('_SESSION'), $superglobalType), + '$_REQUEST' => ExpressionTypeHolder::createYes(new Variable('_REQUEST'), $superglobalType), + '$_ENV' => ExpressionTypeHolder::createYes(new Variable('_ENV'), $superglobalType), + ]; + + return $this->internalScopeFactory->create($context, false, null, null, $expressionTypes, $expressionTypes); } } diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index 4612ef1596..775e2d1828 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -261,7 +261,7 @@ public function testDefinedVariables(): void ->assignVariable('a', new ConstantStringType('a'), new StringType(), TrinaryLogic::createYes()) ->assignVariable('b', new ConstantStringType('b'), new StringType(), TrinaryLogic::createMaybe()); - $this->assertSame(['a'], $scope->getDefinedVariables()); + $this->assertSame(['GLOBALS', '_SERVER', '_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_REQUEST', '_ENV', 'a'], $scope->getDefinedVariables()); } public function testMaybeDefinedVariables(): void diff --git a/tests/PHPStan/Analyser/nsrt/get-defined-vars.php b/tests/PHPStan/Analyser/nsrt/get-defined-vars.php index 345d54dbd3..cca98acbbc 100644 --- a/tests/PHPStan/Analyser/nsrt/get-defined-vars.php +++ b/tests/PHPStan/Analyser/nsrt/get-defined-vars.php @@ -10,37 +10,37 @@ function doFoo(int $param) { $local = "foo"; - assertType('array{param: int, local: \'foo\'}', get_defined_vars()); - assertType('array{\'param\', \'local\'}', array_keys(get_defined_vars())); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\'}', get_defined_vars()); + assertType('array{\'GLOBALS\', \'_SERVER\', \'_GET\', \'_POST\', \'_FILES\', \'_COOKIE\', \'_SESSION\', \'_REQUEST\', \'_ENV\', \'param\', \'local\'}', array_keys(get_defined_vars())); } function doBar(int $param) { global $global; $local = "foo"; - assertType('array{param: int, global: mixed, local: \'foo\'}', get_defined_vars()); - assertType('array{\'param\', \'global\', \'local\'}', array_keys(get_defined_vars())); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, global: mixed, local: \'foo\'}', get_defined_vars()); + assertType('array{\'GLOBALS\', \'_SERVER\', \'_GET\', \'_POST\', \'_FILES\', \'_COOKIE\', \'_SESSION\', \'_REQUEST\', \'_ENV\', \'param\', \'global\', \'local\'}', array_keys(get_defined_vars())); } function doConditional(int $param) { $local = "foo"; if(true) { $conditional = "bar"; - assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); } else { $other = "baz"; - assertType('array{param: int, local: \'foo\', other: \'baz\'}', get_defined_vars()); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', other: \'baz\'}', get_defined_vars()); } - assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); } function doRandom(int $param) { $local = "foo"; if(rand(0, 1)) { $random1 = "bar"; - assertType('array{param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars()); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars()); } else { $random2 = "baz"; - assertType('array{param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars()); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars()); } - assertType('array{param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars()); + assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars()); } diff --git a/tests/PHPStan/Analyser/nsrt/superglobals.php b/tests/PHPStan/Analyser/nsrt/superglobals.php new file mode 100644 index 0000000000..7d9f457df9 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/superglobals.php @@ -0,0 +1,59 @@ +', $GLOBALS); + assertType('array', $_SERVER); + assertType('array', $_GET); + assertType('array', $_POST); + assertType('array', $_FILES); + assertType('array', $_COOKIE); + assertType('array', $_SESSION); + assertType('array', $_REQUEST); + assertType('array', $_ENV); + } + + public function canBeOverwritten(): void + { + $GLOBALS = []; + assertType('array{}', $GLOBALS); + assertNativeType('array{}', $GLOBALS); + } + + public function canBePartlyOverwritten(): void + { + $GLOBALS['foo'] = 'foo'; + assertType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS); + assertNativeType("non-empty-array&hasOffsetValue('foo', 'foo')", $GLOBALS); + } + + public function canBeNarrowed(): void + { + if (isset($GLOBALS['foo'])) { + assertType("non-empty-array&hasOffsetValue('foo', mixed~null)", $GLOBALS); + assertNativeType("non-empty-array&hasOffset('foo')", $GLOBALS); // https://github.com/phpstan/phpstan/issues/8395 + } else { + assertType('array', $GLOBALS); + assertNativeType('array', $GLOBALS); + } + assertType('array', $GLOBALS); + assertNativeType('array', $GLOBALS); + } + +} + +function functionScope() { + assertType('array', $GLOBALS); + assertNativeType('array', $GLOBALS); +} + +assertType('array', $GLOBALS); +assertNativeType('array', $GLOBALS); diff --git a/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php b/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php index ff5294eded..4ae7f5a6fb 100644 --- a/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php @@ -21,14 +21,51 @@ public function testRuleInPhpStanNamespace(): void { $this->analyse([__DIR__ . '/data/debug-scope.php'], [ [ - 'Scope is empty', + implode("\n", [ + '$GLOBALS (Yes): array', + '$_SERVER (Yes): array', + '$_GET (Yes): array', + '$_POST (Yes): array', + '$_FILES (Yes): array', + '$_COOKIE (Yes): array', + '$_SESSION (Yes): array', + '$_REQUEST (Yes): array', + '$_ENV (Yes): array', + 'native $GLOBALS (Yes): array', + 'native $_SERVER (Yes): array', + 'native $_GET (Yes): array', + 'native $_POST (Yes): array', + 'native $_FILES (Yes): array', + 'native $_COOKIE (Yes): array', + 'native $_SESSION (Yes): array', + 'native $_REQUEST (Yes): array', + 'native $_ENV (Yes): array', + ]), 7, ], [ implode("\n", [ + '$GLOBALS (Yes): array', + '$_SERVER (Yes): array', + '$_GET (Yes): array', + '$_POST (Yes): array', + '$_FILES (Yes): array', + '$_COOKIE (Yes): array', + '$_SESSION (Yes): array', + '$_REQUEST (Yes): array', + '$_ENV (Yes): array', '$a (Yes): int', '$b (Yes): int', '$debug (Yes): bool', + 'native $GLOBALS (Yes): array', + 'native $_SERVER (Yes): array', + 'native $_GET (Yes): array', + 'native $_POST (Yes): array', + 'native $_FILES (Yes): array', + 'native $_COOKIE (Yes): array', + 'native $_SESSION (Yes): array', + 'native $_REQUEST (Yes): array', + 'native $_ENV (Yes): array', 'native $a (Yes): int', 'native $b (Yes): int', 'native $debug (Yes): bool', @@ -37,10 +74,28 @@ public function testRuleInPhpStanNamespace(): void ], [ implode("\n", [ + '$GLOBALS (Yes): array', + '$_SERVER (Yes): array', + '$_GET (Yes): array', + '$_POST (Yes): array', + '$_FILES (Yes): array', + '$_COOKIE (Yes): array', + '$_SESSION (Yes): array', + '$_REQUEST (Yes): array', + '$_ENV (Yes): array', '$a (Yes): int', '$b (Yes): int', '$debug (Yes): bool', '$c (Maybe): 1', + 'native $GLOBALS (Yes): array', + 'native $_SERVER (Yes): array', + 'native $_GET (Yes): array', + 'native $_POST (Yes): array', + 'native $_FILES (Yes): array', + 'native $_COOKIE (Yes): array', + 'native $_SESSION (Yes): array', + 'native $_REQUEST (Yes): array', + 'native $_ENV (Yes): array', 'native $a (Yes): int', 'native $b (Yes): int', 'native $debug (Yes): bool', From eaac5a5cfd60d3643fdc02bbb8d3f216d01bf05c Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Tue, 7 Jan 2025 12:11:21 +0100 Subject: [PATCH 2/2] Avoid polluting the scope by adding superglobal expressions lazily --- src/Analyser/MutatingScope.php | 20 ++++++- src/Analyser/ScopeFactory.php | 22 +------ tests/PHPStan/Analyser/ScopeTest.php | 2 +- .../Analyser/nsrt/get-defined-vars.php | 20 +++---- .../Rules/Debug/DebugScopeRuleTest.php | 57 +------------------ 5 files changed, 31 insertions(+), 90 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 75902cef5b..541263638a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -513,6 +513,10 @@ public function afterOpenSslCall(string $openSslFunctionName): self /** @api */ public function hasVariableType(string $variableName): TrinaryLogic { + if ($this->isGlobalVariable($variableName)) { + return TrinaryLogic::createYes(); + } + $varExprString = '$' . $variableName; if (!isset($this->expressionTypes[$varExprString])) { if ($this->canAnyVariableExist()) { @@ -544,13 +548,20 @@ public function getVariableType(string $variableName): Type } } + $varExprString = '$' . $variableName; + if ($this->hasVariableType($variableName)->no()) { throw new UndefinedVariableException($this, $variableName); } - $varExprString = '$' . $variableName; if (!array_key_exists($varExprString, $this->expressionTypes)) { - return new MixedType(); + if (!$this->isGlobalVariable($variableName)) { + return new MixedType(); + } + + $superglobalType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); + $this->expressionTypes[$varExprString] = ExpressionTypeHolder::createYes(new Variable($variableName), $superglobalType); + $this->nativeExpressionTypes[$varExprString] = ExpressionTypeHolder::createYes(new Variable($variableName), $superglobalType); } return TypeUtils::resolveLateResolvableTypes($this->expressionTypes[$varExprString]->getType()); @@ -598,6 +609,11 @@ public function getMaybeDefinedVariables(): array return $variables; } + private function isGlobalVariable(string $variableName): bool + { + return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true); + } + /** @api */ public function hasConstant(Name $name): bool { diff --git a/src/Analyser/ScopeFactory.php b/src/Analyser/ScopeFactory.php index 9dba3fcb14..ade6e1d894 100644 --- a/src/Analyser/ScopeFactory.php +++ b/src/Analyser/ScopeFactory.php @@ -2,13 +2,6 @@ namespace PHPStan\Analyser; -use PhpParser\Node\Expr\Variable; -use PHPStan\Type\ArrayType; -use PHPStan\Type\BenevolentUnionType; -use PHPStan\Type\IntegerType; -use PHPStan\Type\MixedType; -use PHPStan\Type\StringType; - /** * @api */ @@ -21,20 +14,7 @@ public function __construct(private InternalScopeFactory $internalScopeFactory) public function create(ScopeContext $context): MutatingScope { - $superglobalType = new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); - $expressionTypes = [ - '$GLOBALS' => ExpressionTypeHolder::createYes(new Variable('GLOBALS'), $superglobalType), - '$_SERVER' => ExpressionTypeHolder::createYes(new Variable('_SERVER'), $superglobalType), - '$_GET' => ExpressionTypeHolder::createYes(new Variable('_GET'), $superglobalType), - '$_POST' => ExpressionTypeHolder::createYes(new Variable('_POST'), $superglobalType), - '$_FILES' => ExpressionTypeHolder::createYes(new Variable('_FILES'), $superglobalType), - '$_COOKIE' => ExpressionTypeHolder::createYes(new Variable('_COOKIE'), $superglobalType), - '$_SESSION' => ExpressionTypeHolder::createYes(new Variable('_SESSION'), $superglobalType), - '$_REQUEST' => ExpressionTypeHolder::createYes(new Variable('_REQUEST'), $superglobalType), - '$_ENV' => ExpressionTypeHolder::createYes(new Variable('_ENV'), $superglobalType), - ]; - - return $this->internalScopeFactory->create($context, false, null, null, $expressionTypes, $expressionTypes); + return $this->internalScopeFactory->create($context); } } diff --git a/tests/PHPStan/Analyser/ScopeTest.php b/tests/PHPStan/Analyser/ScopeTest.php index 775e2d1828..4612ef1596 100644 --- a/tests/PHPStan/Analyser/ScopeTest.php +++ b/tests/PHPStan/Analyser/ScopeTest.php @@ -261,7 +261,7 @@ public function testDefinedVariables(): void ->assignVariable('a', new ConstantStringType('a'), new StringType(), TrinaryLogic::createYes()) ->assignVariable('b', new ConstantStringType('b'), new StringType(), TrinaryLogic::createMaybe()); - $this->assertSame(['GLOBALS', '_SERVER', '_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_REQUEST', '_ENV', 'a'], $scope->getDefinedVariables()); + $this->assertSame(['a'], $scope->getDefinedVariables()); } public function testMaybeDefinedVariables(): void diff --git a/tests/PHPStan/Analyser/nsrt/get-defined-vars.php b/tests/PHPStan/Analyser/nsrt/get-defined-vars.php index cca98acbbc..345d54dbd3 100644 --- a/tests/PHPStan/Analyser/nsrt/get-defined-vars.php +++ b/tests/PHPStan/Analyser/nsrt/get-defined-vars.php @@ -10,37 +10,37 @@ function doFoo(int $param) { $local = "foo"; - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\'}', get_defined_vars()); - assertType('array{\'GLOBALS\', \'_SERVER\', \'_GET\', \'_POST\', \'_FILES\', \'_COOKIE\', \'_SESSION\', \'_REQUEST\', \'_ENV\', \'param\', \'local\'}', array_keys(get_defined_vars())); + assertType('array{param: int, local: \'foo\'}', get_defined_vars()); + assertType('array{\'param\', \'local\'}', array_keys(get_defined_vars())); } function doBar(int $param) { global $global; $local = "foo"; - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, global: mixed, local: \'foo\'}', get_defined_vars()); - assertType('array{\'GLOBALS\', \'_SERVER\', \'_GET\', \'_POST\', \'_FILES\', \'_COOKIE\', \'_SESSION\', \'_REQUEST\', \'_ENV\', \'param\', \'global\', \'local\'}', array_keys(get_defined_vars())); + assertType('array{param: int, global: mixed, local: \'foo\'}', get_defined_vars()); + assertType('array{\'param\', \'global\', \'local\'}', array_keys(get_defined_vars())); } function doConditional(int $param) { $local = "foo"; if(true) { $conditional = "bar"; - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); + assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); } else { $other = "baz"; - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', other: \'baz\'}', get_defined_vars()); + assertType('array{param: int, local: \'foo\', other: \'baz\'}', get_defined_vars()); } - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); + assertType('array{param: int, local: \'foo\', conditional: \'bar\'}', get_defined_vars()); } function doRandom(int $param) { $local = "foo"; if(rand(0, 1)) { $random1 = "bar"; - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars()); + assertType('array{param: int, local: \'foo\', random1: \'bar\'}', get_defined_vars()); } else { $random2 = "baz"; - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars()); + assertType('array{param: int, local: \'foo\', random2: \'baz\'}', get_defined_vars()); } - assertType('array{GLOBALS: array, _SERVER: array, _GET: array, _POST: array, _FILES: array, _COOKIE: array, _SESSION: array, _REQUEST: array, _ENV: array, param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars()); + assertType('array{param: int, local: \'foo\', random2?: \'baz\', random1?: \'bar\'}', get_defined_vars()); } diff --git a/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php b/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php index 4ae7f5a6fb..ff5294eded 100644 --- a/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php @@ -21,51 +21,14 @@ public function testRuleInPhpStanNamespace(): void { $this->analyse([__DIR__ . '/data/debug-scope.php'], [ [ - implode("\n", [ - '$GLOBALS (Yes): array', - '$_SERVER (Yes): array', - '$_GET (Yes): array', - '$_POST (Yes): array', - '$_FILES (Yes): array', - '$_COOKIE (Yes): array', - '$_SESSION (Yes): array', - '$_REQUEST (Yes): array', - '$_ENV (Yes): array', - 'native $GLOBALS (Yes): array', - 'native $_SERVER (Yes): array', - 'native $_GET (Yes): array', - 'native $_POST (Yes): array', - 'native $_FILES (Yes): array', - 'native $_COOKIE (Yes): array', - 'native $_SESSION (Yes): array', - 'native $_REQUEST (Yes): array', - 'native $_ENV (Yes): array', - ]), + 'Scope is empty', 7, ], [ implode("\n", [ - '$GLOBALS (Yes): array', - '$_SERVER (Yes): array', - '$_GET (Yes): array', - '$_POST (Yes): array', - '$_FILES (Yes): array', - '$_COOKIE (Yes): array', - '$_SESSION (Yes): array', - '$_REQUEST (Yes): array', - '$_ENV (Yes): array', '$a (Yes): int', '$b (Yes): int', '$debug (Yes): bool', - 'native $GLOBALS (Yes): array', - 'native $_SERVER (Yes): array', - 'native $_GET (Yes): array', - 'native $_POST (Yes): array', - 'native $_FILES (Yes): array', - 'native $_COOKIE (Yes): array', - 'native $_SESSION (Yes): array', - 'native $_REQUEST (Yes): array', - 'native $_ENV (Yes): array', 'native $a (Yes): int', 'native $b (Yes): int', 'native $debug (Yes): bool', @@ -74,28 +37,10 @@ public function testRuleInPhpStanNamespace(): void ], [ implode("\n", [ - '$GLOBALS (Yes): array', - '$_SERVER (Yes): array', - '$_GET (Yes): array', - '$_POST (Yes): array', - '$_FILES (Yes): array', - '$_COOKIE (Yes): array', - '$_SESSION (Yes): array', - '$_REQUEST (Yes): array', - '$_ENV (Yes): array', '$a (Yes): int', '$b (Yes): int', '$debug (Yes): bool', '$c (Maybe): 1', - 'native $GLOBALS (Yes): array', - 'native $_SERVER (Yes): array', - 'native $_GET (Yes): array', - 'native $_POST (Yes): array', - 'native $_FILES (Yes): array', - 'native $_COOKIE (Yes): array', - 'native $_SESSION (Yes): array', - 'native $_REQUEST (Yes): array', - 'native $_ENV (Yes): array', 'native $a (Yes): int', 'native $b (Yes): int', 'native $debug (Yes): bool',