diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d13db71e0ee..8decced090f 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 { @@ -2470,18 +2447,14 @@ public function enterClass(ClassReflection $classReflection): self $this->isDeclareStrictTypes(), null, $this->getNamespace(), - array_merge($this->getConstantTypes(), [ - '$this' => $thisHolder, - ]), + array_merge($this->getSuperglobalExpressionTypes(), $this->getConstantTypes(), ['$this' => $thisHolder]), [], null, null, true, [], [], - array_merge($this->getNativeConstantTypes(), [ - '$this' => $thisHolder, - ]), + array_merge($this->getSuperglobalExpressionTypes(), $this->getNativeConstantTypes(), ['$this' => $thisHolder]), [], false, $classReflection->isAnonymous() ? $this : null, @@ -2714,14 +2687,14 @@ private function enterFunctionLike( $this->isDeclareStrictTypes(), $functionReflection, $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), + array_merge($this->getSuperglobalExpressionTypes(), $this->getConstantTypes(), $expressionTypes), [], null, null, true, [], [], - array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes), + array_merge($this->getSuperglobalExpressionTypes(), $this->getNativeConstantTypes(), $nativeExpressionTypes), ); } @@ -2733,6 +2706,14 @@ public function enterNamespace(string $namespaceName): self $this->isDeclareStrictTypes(), null, $namespaceName, + $this->getSuperglobalExpressionTypes(), + [], + null, + null, + true, + [], + [], + $this->getSuperglobalExpressionTypes(), ); } @@ -2936,14 +2917,14 @@ private function enterAnonymousFunctionWithoutReflection( $this->isDeclareStrictTypes(), $this->getFunction(), $this->getNamespace(), - array_merge($this->getConstantTypes(), $expressionTypes), + array_merge($this->getSuperglobalExpressionTypes(), $this->getConstantTypes(), $expressionTypes), [], $this->inClosureBindScopeClass, new TrivialParametersAcceptor(), true, [], [], - array_merge($this->getNativeConstantTypes(), $nativeTypes), + array_merge($this->getSuperglobalExpressionTypes(), $this->getNativeConstantTypes(), $nativeTypes), [], false, $this, @@ -5008,4 +4989,61 @@ private function getNativeConstantTypes(): array return $constantTypes; } + private function getSuperglobalExpressionTypes(): array + { + $superglobalExpressionTypes = []; + $exprStrings = ['$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV']; + foreach ($this->expressionTypes as $exprString => $typeHolder) { + if (!in_array($exprString, $exprStrings, true)) { + continue; + } + + $superglobalExpressionTypes[$exprString] = $typeHolder; + } + return $superglobalExpressionTypes; + } + + /** @return array */ + private function createSuperglobalExpressionTypes(): array + { + $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 addSuperglobalExpressionTypes(): self + { + $expressionTypes = $this->expressionTypes; + $nativeExpressionTypes = $this->nativeExpressionTypes; + foreach ($this->createSuperglobalExpressionTypes() 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..f742f974ca6 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)->addSuperglobalExpressionTypes(); } } 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);