Skip to content

Commit

Permalink
Scope - any variable after extract() call might exist
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 24, 2020
1 parent af60a50 commit d3e7b9c
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/Analyser/DirectScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function __construct(
* @param array<string, true> $currentlyAssignedExpressions
* @param array<string, Type> $nativeExpressionTypes
* @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack
* @param bool $afterExtractCall
* @param Scope|null $parentScope
*
* @return MutatingScope
Expand All @@ -94,6 +95,7 @@ public function create(
array $currentlyAssignedExpressions = [],
array $nativeExpressionTypes = [],
array $inFunctionCallsStack = [],
bool $afterExtractCall = false,
?Scope $parentScope = null
): MutatingScope
{
Expand Down Expand Up @@ -126,6 +128,7 @@ public function create(
$inFunctionCallsStack,
$this->dynamicConstantNames,
$this->treatPhpDocTypesAsCertain,
$afterExtractCall,
$parentScope
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/LazyScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function __construct(
* @param array<string, true> $currentlyAssignedExpressions
* @param array<string, Type> $nativeExpressionTypes
* @param array<\PHPStan\Reflection\FunctionReflection|\PHPStan\Reflection\MethodReflection> $inFunctionCallsStack
* @param bool $afterExtractCall
* @param Scope|null $parentScope
*
* @return MutatingScope
Expand All @@ -66,6 +67,7 @@ public function create(
array $currentlyAssignedExpressions = [],
array $nativeExpressionTypes = [],
array $inFunctionCallsStack = [],
bool $afterExtractCall = false,
?Scope $parentScope = null
): MutatingScope
{
Expand Down Expand Up @@ -98,6 +100,7 @@ public function create(
$inFunctionCallsStack,
$this->dynamicConstantNames,
$this->treatPhpDocTypesAsCertain,
$afterExtractCall,
$parentScope
);
}
Expand Down
53 changes: 49 additions & 4 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ class MutatingScope implements Scope

private bool $treatPhpDocTypesAsCertain;

private bool $afterExtractCall;

private ?Scope $parentScope;

/**
Expand All @@ -181,6 +183,7 @@ class MutatingScope implements Scope
* @param array<MethodReflection|FunctionReflection> $inFunctionCallsStack
* @param string[] $dynamicConstantNames
* @param bool $treatPhpDocTypesAsCertain
* @param bool $afterExtractCall
* @param Scope|null $parentScope
*/
public function __construct(
Expand All @@ -207,6 +210,7 @@ public function __construct(
array $inFunctionCallsStack = [],
array $dynamicConstantNames = [],
bool $treatPhpDocTypesAsCertain = true,
bool $afterExtractCall = false,
?Scope $parentScope = null
)
{
Expand Down Expand Up @@ -237,6 +241,7 @@ public function __construct(
$this->inFunctionCallsStack = $inFunctionCallsStack;
$this->dynamicConstantNames = $dynamicConstantNames;
$this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain;
$this->afterExtractCall = $afterExtractCall;
$this->parentScope = $parentScope;
}

Expand Down Expand Up @@ -335,9 +340,30 @@ private function getVariableTypes(): array
return $this->variableTypes;
}

private function isRootScope(): bool
private function canAnyVariableExist(): bool
{
return ($this->function === null && !$this->isInAnonymousFunction()) || $this->afterExtractCall;
}

public function afterExtractCall(): self
{
return $this->function === null && !$this->isInAnonymousFunction();
return $this->scopeFactory->create(
$this->context,
$this->isDeclareStrictTypes(),
$this->constantTypes,
$this->getFunction(),
$this->getNamespace(),
$this->getVariableTypes(),
$this->moreSpecificTypes,
$this->inClosureBindScopeClass,
$this->anonymousFunctionReflection,
$this->isInFirstLevelStatement(),
$this->currentlyAssignedExpressions,
$this->nativeExpressionTypes,
$this->inFunctionCallsStack,
true,
$this->parentScope
);
}

public function hasVariableType(string $variableName): TrinaryLogic
Expand All @@ -347,7 +373,7 @@ public function hasVariableType(string $variableName): TrinaryLogic
}

if (!isset($this->variableTypes[$variableName])) {
if ($this->isRootScope()) {
if ($this->canAnyVariableExist()) {
return TrinaryLogic::createMaybe();
}

Expand Down Expand Up @@ -1957,6 +1983,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope
$this->inFunctionCallsStack,
$this->dynamicConstantNames,
false,
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -2213,6 +2240,7 @@ public function pushInFunctionCall($reflection): self
$this->currentlyAssignedExpressions,
$this->nativeExpressionTypes,
$stack,
$this->afterExtractCall,
$this->parentScope
);
}
Expand All @@ -2236,6 +2264,7 @@ public function popInFunctionCall(): self
$this->currentlyAssignedExpressions,
$this->nativeExpressionTypes,
$stack,
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -2622,6 +2651,7 @@ public function enterAnonymousFunction(
[],
$nativeTypes,
[],
false,
$this
);
}
Expand Down Expand Up @@ -2668,6 +2698,7 @@ public function enterArrowFunction(Expr\ArrowFunction $arrowFunction): self
[],
[],
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -2782,6 +2813,7 @@ public function enterExpressionAssign(Expr $expr): self
$currentlyAssignedExpressions,
$this->nativeExpressionTypes,
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand All @@ -2806,6 +2838,7 @@ public function exitExpressionAssign(Expr $expr): self
$currentlyAssignedExpressions,
$this->nativeExpressionTypes,
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -2861,6 +2894,7 @@ public function assignVariable(string $variableName, Type $type, ?TrinaryLogic $
$this->currentlyAssignedExpressions,
$nativeTypes,
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -2890,6 +2924,7 @@ public function unsetExpression(Expr $expr): self
[],
$nativeTypes,
[],
$this->afterExtractCall,
$this->parentScope
);
} elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
Expand Down Expand Up @@ -2948,6 +2983,7 @@ public function specifyExpressionType(Expr $expr, Type $type): self
$this->currentlyAssignedExpressions,
$this->nativeExpressionTypes,
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -2979,6 +3015,7 @@ public function specifyExpressionType(Expr $expr, Type $type): self
$this->currentlyAssignedExpressions,
$nativeTypes,
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope
);
} elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
Expand Down Expand Up @@ -3055,6 +3092,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
$this->currentlyAssignedExpressions,
[],
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -3167,6 +3205,7 @@ public function exitFirstLevelStatements(): self
$this->currentlyAssignedExpressions,
$this->nativeExpressionTypes,
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -3202,6 +3241,7 @@ private function addMoreSpecificTypes(array $types): self
$this->currentlyAssignedExpressions,
$this->nativeExpressionTypes,
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand All @@ -3221,7 +3261,7 @@ public function mergeWith(?self $otherScope): self

$ourVariableTypes = $this->getVariableTypes();
$theirVariableTypes = $otherScope->getVariableTypes();
if ($this->isRootScope()) {
if ($this->canAnyVariableExist()) {
foreach (array_keys($theirVariableTypes) as $name) {
if (array_key_exists($name, $ourVariableTypes)) {
continue;
Expand Down Expand Up @@ -3253,6 +3293,7 @@ public function mergeWith(?self $otherScope): self
return $holder->getCertainty()->yes();
})),
[],
$this->afterExtractCall && $otherScope->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -3323,6 +3364,7 @@ public function processFinallyScope(self $finallyScope, self $originalFinallySco
array_map($typeToVariableHolder, $originalFinallyScope->nativeExpressionTypes)
)),
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -3414,6 +3456,7 @@ public function processClosureScope(
[],
$this->nativeExpressionTypes,
$this->inFunctionCallsStack,
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -3462,6 +3505,7 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope
[],
$nativeTypes,
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down Expand Up @@ -3506,6 +3550,7 @@ public function generalizeWith(self $otherScope): self
[],
$nativeTypes,
[],
$this->afterExtractCall,
$this->parentScope
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,10 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
) {
$scope = $scope->assignVariable('http_response_header', new ArrayType(new IntegerType(), new StringType()));
}

if (isset($functionReflection) && $functionReflection->getName() === 'extract') {
$scope = $scope->afterExtractCall();
}
} elseif ($expr instanceof MethodCall) {
$originalScope = $scope;
if (
Expand Down
2 changes: 2 additions & 0 deletions src/Analyser/ScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface ScopeFactory
* @param array<string, true> $currentlyAssignedExpressions
* @param array<string, Type> $nativeExpressionTypes
* @param array<MethodReflection|FunctionReflection> $inFunctionCallsStack
* @param bool $afterExtractCall
* @param Scope|null $parentScope
*
* @return MutatingScope
Expand All @@ -42,6 +43,7 @@ public function create(
array $currentlyAssignedExpressions = [],
array $nativeExpressionTypes = [],
array $inFunctionCallsStack = [],
bool $afterExtractCall = false,
?Scope $parentScope = null
): MutatingScope;

Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10230,6 +10230,11 @@ public function dataArraySliceNonEmpty(): array
return $this->gatherAssertTypes(__DIR__ . '/data/array-slice-non-empty.php');
}

public function dataBug3990(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/bug-3990.php');
}

/**
* @dataProvider dataBug2574
* @dataProvider dataBug2577
Expand Down Expand Up @@ -10318,6 +10323,7 @@ public function dataArraySliceNonEmpty(): array
* @dataProvider dataBug2816Two
* @dataProvider dataBug3985
* @dataProvider dataArraySliceNonEmpty
* @dataProvider dataBug3990
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
21 changes: 21 additions & 0 deletions tests/PHPStan/Analyser/data/bug-3990.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Bug3990;

use PHPStan\TrinaryLogic;
use function PHPStan\Analyser\assertVariableCertainty;

function doFoo(array $config): void
{
extract($config);

assertVariableCertainty(TrinaryLogic::createMaybe(), $a);

if (isset($a)) {
assertVariableCertainty(TrinaryLogic::createYes(), $a);
} else {
assertVariableCertainty(TrinaryLogic::createMaybe(), $a);
}

assertVariableCertainty(TrinaryLogic::createMaybe(), $a);
}

0 comments on commit d3e7b9c

Please sign in to comment.