diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index d13db71e0ee..8d14355ff92 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 { @@ -1948,10 +1925,6 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n if ($result === null) { if ($hasVariable->yes()) { - if ($expr->name === '_SESSION') { - return null; - } - return $typeCallback($this->getVariableType($expr->name)); } @@ -2470,18 +2443,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 +2683,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 +2702,14 @@ public function enterNamespace(string $namespaceName): self $this->isDeclareStrictTypes(), null, $namespaceName, + $this->getSuperglobals(), + [], + null, + null, + true, + [], + [], + $this->getSuperglobals(), ); } @@ -2936,14 +2913,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 +4985,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::createMaybe(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/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index f5389ad7f8f..0969607c82d 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -40,10 +40,6 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, cal if ($error === null) { if ($hasVariable->yes()) { - if ($expr->name === '_SESSION') { - return null; - } - return $this->generateError( $scope->getVariableType($expr->name), sprintf('Variable $%s %s always exists and', $expr->name, $operatorDescription), 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);