From d784b9be728b29f8dd3d964a6fceb6df281cbf3a 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 | 97 +++++++++++++------- src/Analyser/ScopeFactory.php | 2 +- src/Testing/TypeInferenceTestCase.php | 2 +- tests/PHPStan/Analyser/data/superglobals.php | 32 ++++++- 4 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d13db71e0ee..ec9d2dcd81d 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -152,6 +152,9 @@ class MutatingScope implements Scope private ?self $scopeOutOfFirstLevelStatement = null; + /** @var array|null */ + private ?array $superglobals = null; + /** * @param array $expressionTypes * @param array $conditionalExpressions @@ -467,10 +470,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 +506,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 +533,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 { @@ -2470,18 +2450,14 @@ public function enterClass(ClassReflection $classReflection): self $this->isDeclareStrictTypes(), null, $this->getNamespace(), - array_merge($this->getConstantTypes(), [ - '$this' => $thisHolder, - ]), + array_merge($this->getSuperglobals(), $this->getConstantTypes(), ['$this' => $thisHolder]), [], null, null, true, [], [], - array_merge($this->getNativeConstantTypes(), [ - '$this' => $thisHolder, - ]), + array_merge($this->getSuperglobals(), $this->getNativeConstantTypes(), ['$this' => $thisHolder]), [], false, $classReflection->isAnonymous() ? $this : null, @@ -2714,14 +2690,14 @@ private function enterFunctionLike( $this->isDeclareStrictTypes(), $functionReflection, $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), + array_merge($this->getSuperglobals(), $this->getConstantTypes(), $expressionTypes), [], null, null, true, [], [], - array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes), + array_merge($this->getSuperglobals(), $this->getNativeConstantTypes(), $nativeExpressionTypes), ); } @@ -2733,6 +2709,14 @@ public function enterNamespace(string $namespaceName): self $this->isDeclareStrictTypes(), null, $namespaceName, + $this->getSuperglobals(), + [], + null, + null, + true, + [], + [], + $this->getSuperglobals(), ); } @@ -2936,14 +2920,14 @@ private function enterAnonymousFunctionWithoutReflection( $this->isDeclareStrictTypes(), $this->getFunction(), $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), + array_merge($this->getSuperglobals(), $this->getConstantTypes(), $expressionTypes), [], $this->inClosureBindScopeClass, new TrivialParametersAcceptor(), true, [], [], - array_merge($this->getNativeConstantTypes(), $nativeTypes), + array_merge($this->getSuperglobals(), $this->getNativeConstantTypes(), $nativeTypes), [], false, $this, @@ -5008,4 +4992,51 @@ private function getNativeConstantTypes(): array return $constantTypes; } + /** @return array */ + public function getSuperglobals(): array + { + if ($this->superglobals !== null) { + return $this->superglobals; + } + + $superglobalType = new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables)); + + return $this->superglobals = [ + '$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);