diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0c0af430e8..541263638a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -548,17 +548,20 @@ public function getVariableType(string $variableName): Type } } - if ($this->isGlobalVariable($variableName)) { - return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true)); - } + $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()); @@ -2893,18 +2896,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 +3299,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 +3313,8 @@ public function enterNamespace(string $namespaceName): self $this->isDeclareStrictTypes(), null, $namespaceName, + $this->getSuperglobalTypes(), + $this->getNativeSuperglobalTypes(), ); } @@ -3588,8 +3591,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 +5990,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/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);