From 98f40eb19f754308f10fdfb795186727a1e32e44 Mon Sep 17 00:00:00 2001 From: Martin Herndl Date: Sun, 20 Nov 2022 01:27:25 +0100 Subject: [PATCH] Add superglobal variable expressions into scope by default --- src/Analyser/MutatingScope.php | 98 +++++++++++++------- src/Analyser/ScopeFactory.php | 2 +- src/Testing/TypeInferenceTestCase.php | 2 +- tests/PHPStan/Analyser/data/superglobals.php | 32 ++++++- 4 files changed, 95 insertions(+), 39 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d13db71e0ee..eeba8902397 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -467,10 +467,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()) { @@ -507,10 +503,6 @@ public function getVariableType(string $variableName): Type $varExprString = '$' . $variableName; if (!array_key_exists($varExprString, $this->expressionTypes)) { - if ($this->isGlobalVariable($variableName)) { - return new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables)); - } - return new MixedType(); } @@ -538,21 +530,6 @@ public function getDefinedVariables(): array return $variables; } - private function isGlobalVariable(string $variableName): bool - { - return in_array($variableName, [ - 'GLOBALS', - '_SERVER', - '_GET', - '_POST', - '_FILES', - '_COOKIE', - '_SESSION', - '_REQUEST', - '_ENV', - ], true); - } - /** @api */ public function hasConstant(Name $name): bool { @@ -2465,23 +2442,21 @@ public function enterClass(ClassReflection $classReflection): self { $thisHolder = ExpressionTypeHolder::createYes(new Variable('this'), new ThisType($classReflection)); + $superglobals = $this->getSuperglobals(); + return $this->scopeFactory->create( $this->context->enterClass($classReflection), $this->isDeclareStrictTypes(), null, $this->getNamespace(), - array_merge($this->getConstantTypes(), [ - '$this' => $thisHolder, - ]), + array_merge($superglobals, $this->getConstantTypes(), ['$this' => $thisHolder]), [], null, null, true, [], [], - array_merge($this->getNativeConstantTypes(), [ - '$this' => $thisHolder, - ]), + array_merge($superglobals, $this->getNativeConstantTypes(), ['$this' => $thisHolder]), [], false, $classReflection->isAnonymous() ? $this : null, @@ -2709,30 +2684,42 @@ private function enterFunctionLike( $nativeExpressionTypes['$this'] = $this->nativeExpressionTypes['$this']; } + $superglobals = $this->getSuperglobals(); + return $this->scopeFactory->create( $this->context, $this->isDeclareStrictTypes(), $functionReflection, $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), + array_merge($superglobals, $this->getConstantTypes(), $expressionTypes), [], null, null, true, [], [], - array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes), + array_merge($superglobals, $this->getNativeConstantTypes(), $nativeExpressionTypes), ); } /** @api */ public function enterNamespace(string $namespaceName): self { + $superglobals = $this->getSuperglobals(); + return $this->scopeFactory->create( $this->context->beginFile(), $this->isDeclareStrictTypes(), null, $namespaceName, + $superglobals, + [], + null, + null, + true, + [], + [], + $superglobals, ); } @@ -2931,19 +2918,21 @@ private function enterAnonymousFunctionWithoutReflection( $nativeTypes['$this'] = ExpressionTypeHolder::createYes($node, $this->getNativeType($node)); } + $superglobals = $this->getSuperglobals(); + return $this->scopeFactory->create( $this->context, $this->isDeclareStrictTypes(), $this->getFunction(), $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), + array_merge($superglobals, $this->getConstantTypes(), $expressionTypes), [], $this->inClosureBindScopeClass, new TrivialParametersAcceptor(), true, [], [], - array_merge($this->getNativeConstantTypes(), $nativeTypes), + array_merge($superglobals, $this->getNativeConstantTypes(), $nativeTypes), [], false, $this, @@ -5008,4 +4997,47 @@ private function getNativeConstantTypes(): array return $constantTypes; } + /** @return array */ + public function getSuperglobals(): array + { + $superglobalType = new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables)); + + return [ + '$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), + ]; + } + + public function addSuperglobals(): self + { + $expressionTypes = $this->expressionTypes; + $nativeExpressionTypes = $this->nativeExpressionTypes; + foreach ($this->getSuperglobals() as $exprStr => $typeHolder) { + $expressionTypes[$exprStr] = $typeHolder; + $nativeExpressionTypes[$exprStr] = $typeHolder; + } + + return $this->scopeFactory->create( + $this->context, + $this->declareStrictTypes, + $this->function, + $this->namespace, + $expressionTypes, + $this->conditionalExpressions, + $this->inClosureBindScopeClass, + $this->anonymousFunctionReflection, + $this->inFirstLevelStatement, + $this->currentlyAssignedExpressions, + $this->currentlyAllowedUndefinedExpressions, + $nativeExpressionTypes, + ); + } + } diff --git a/src/Analyser/ScopeFactory.php b/src/Analyser/ScopeFactory.php index c2401d375c2..a4eb7c7be0f 100644 --- a/src/Analyser/ScopeFactory.php +++ b/src/Analyser/ScopeFactory.php @@ -12,7 +12,7 @@ public function __construct(private InternalScopeFactory $internalScopeFactory) public function create(ScopeContext $context): MutatingScope { - return $this->internalScopeFactory->create($context); + return $this->internalScopeFactory->create($context)->addSuperglobals(); } } diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 070fbd9292c..c2305ff4813 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -65,7 +65,7 @@ public function processFile( $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], $this->getAdditionalAnalysedFiles()))); $scopeFactory = $this->createScopeFactory($reflectionProvider, $typeSpecifier, $dynamicConstantNames); - $scope = $scopeFactory->create(ScopeContext::create($file)); + $scope = $scopeFactory->create(ScopeContext::create($file))->addSuperglobals(); $resolver->processNodes( $this->getParser()->parseFile($file), diff --git a/tests/PHPStan/Analyser/data/superglobals.php b/tests/PHPStan/Analyser/data/superglobals.php index d70d1a30ad6..2885d59f044 100644 --- a/tests/PHPStan/Analyser/data/superglobals.php +++ b/tests/PHPStan/Analyser/data/superglobals.php @@ -2,6 +2,7 @@ namespace Superglobals; +use function PHPStan\Testing\assertNativeType; use function PHPStan\Testing\assertType; class Superglobals @@ -22,14 +23,37 @@ public function originalTypes(): void public function canBeOverwritten(): void { - $_SESSION = []; - assertType('array{}', $_SESSION); + $GLOBALS = []; + assertType('array{}', $GLOBALS); + assertNativeType('array{}', $GLOBALS); } public function canBePartlyOverwritten(): void { - $_SESSION['foo'] = 'foo'; - assertType("non-empty-array&hasOffsetValue('foo', 'foo')", $_SESSION); + $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("array&hasOffsetValue('foo', mixed~null)", $GLOBALS); + assertNativeType("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);