Skip to content

Commit

Permalink
Add superglobal variable expressions into scope by default
Browse files Browse the repository at this point in the history
  • Loading branch information
herndlm committed Nov 20, 2022
1 parent 8a18dea commit d784b9b
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 39 deletions.
97 changes: 64 additions & 33 deletions src/Analyser/MutatingScope.php
Expand Up @@ -152,6 +152,9 @@ class MutatingScope implements Scope

private ?self $scopeOutOfFirstLevelStatement = null;

/** @var array<string, ExpressionTypeHolder>|null */
private ?array $superglobals = null;

/**
* @param array<string, ExpressionTypeHolder> $expressionTypes
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
);
}

Expand All @@ -2733,6 +2709,14 @@ public function enterNamespace(string $namespaceName): self
$this->isDeclareStrictTypes(),
null,
$namespaceName,
$this->getSuperglobals(),
[],
null,
null,
true,
[],
[],
$this->getSuperglobals(),
);
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -5008,4 +4992,51 @@ private function getNativeConstantTypes(): array
return $constantTypes;
}

/** @return array<string, ExpressionTypeHolder> */
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,
);
}

}
2 changes: 1 addition & 1 deletion src/Analyser/ScopeFactory.php
Expand Up @@ -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();
}

}
2 changes: 1 addition & 1 deletion src/Testing/TypeInferenceTestCase.php
Expand Up @@ -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),
Expand Down
32 changes: 28 additions & 4 deletions tests/PHPStan/Analyser/data/superglobals.php
Expand Up @@ -2,6 +2,7 @@

namespace Superglobals;

use function PHPStan\Testing\assertNativeType;
use function PHPStan\Testing\assertType;

class Superglobals
Expand All @@ -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<string, mixed>&hasOffsetValue('foo', 'foo')", $_SESSION);
$GLOBALS['foo'] = 'foo';
assertType("non-empty-array<string, mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
assertNativeType("non-empty-array<string, mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
}

public function canBeNarrowed(): void
{
if (isset($GLOBALS['foo'])) {
assertType("array<string, mixed>&hasOffsetValue('foo', mixed~null)", $GLOBALS);
assertNativeType("array<string, mixed>&hasOffset('foo')", $GLOBALS); // https://github.com/phpstan/phpstan/issues/8395
} else {
assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);
}
assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);
}

}

function functionScope() {
assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);
}

assertType('array<string, mixed>', $GLOBALS);
assertNativeType('array<string, mixed>', $GLOBALS);

0 comments on commit d784b9b

Please sign in to comment.