Skip to content

Commit

Permalink
Assertions done on a variable used in a closure should be transferred…
Browse files Browse the repository at this point in the history
… inside the closure
  • Loading branch information
ondrejmirtes committed Nov 9, 2020
1 parent 77157d7 commit 36dac3d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 7 deletions.
29 changes: 22 additions & 7 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2661,20 +2661,35 @@ public function enterAnonymousFunction(
}

$nativeTypes = [];
$moreSpecificTypes = [];
foreach ($closure->uses as $use) {
if (!is_string($use->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}
if ($use->byRef) {
continue;
}
if ($this->hasVariableType($use->var->name)->no()) {
$variableName = $use->var->name;
if ($this->hasVariableType($variableName)->no()) {
$variableType = new ErrorType();
} else {
$variableType = $this->getVariableType($use->var->name);
$nativeTypes[sprintf('$%s', $use->var->name)] = $this->getNativeType($use->var);
$variableType = $this->getVariableType($variableName);
$nativeTypes[sprintf('$%s', $variableName)] = $this->getNativeType($use->var);
}
$variableTypes[$variableName] = VariableTypeHolder::createYes($variableType);
foreach ($this->moreSpecificTypes as $exprString => $moreSpecificType) {
$matches = \Nette\Utils\Strings::matchAll($exprString, '#^\$([a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*)#');
if ($matches === []) {
continue;
}

$matches = array_column($matches, 1);
if (!in_array($variableName, $matches, true)) {
continue;
}

$moreSpecificTypes[$exprString] = $moreSpecificType;
}
$variableTypes[$use->var->name] = VariableTypeHolder::createYes($variableType);
}

if ($this->hasVariableType('this')->yes() && !$closure->static) {
Expand All @@ -2693,7 +2708,7 @@ public function enterAnonymousFunction(
$this->getFunction(),
$this->getNamespace(),
$variableTypes,
[],
$moreSpecificTypes,
$this->inClosureBindScopeClass,
$anonymousFunctionReflection,
true,
Expand Down Expand Up @@ -2740,7 +2755,7 @@ public function enterArrowFunction(Expr\ArrowFunction $arrowFunction): self
$this->getFunction(),
$this->getNamespace(),
$variableTypes,
[],
$this->moreSpecificTypes,
$this->inClosureBindScopeClass,
$anonymousFunctionReflection,
true,
Expand Down Expand Up @@ -3502,7 +3517,7 @@ public function processClosureScope(
$this->getFunction(),
$this->getNamespace(),
$variableTypes,
[],
$this->moreSpecificTypes,
$this->inClosureBindScopeClass,
$this->anonymousFunctionReflection,
$this->inFirstLevelStatement,
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 @@ -10302,6 +10302,11 @@ public function dataNullSafe(): array
return $this->gatherAssertTypes(__DIR__ . '/data/nullsafe.php');
}

public function dataRememberSpecifiedTypesAfterClosureUse(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/specified-types-closure-use.php');
}

/**
* @param string $file
* @return array<string, mixed[]>
Expand Down Expand Up @@ -10475,6 +10480,7 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataBug2378
* @dataProvider dataMatchExpression
* @dataProvider dataNullSafe
* @dataProvider dataRememberSpecifiedTypesAfterClosureUse
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
37 changes: 37 additions & 0 deletions tests/PHPStan/Analyser/data/specified-types-closure-use.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace SpecifiedTypesClosureUse;

use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use function PHPStan\Analyser\assertType;

class Foo
{

public function doFoo(MethodCall $call, MethodCall $bar): void
{
if ($call->name instanceof Identifier && $bar->name instanceof Identifier) {
function () use ($call): void {
assertType('PhpParser\Node\Identifier', $call->name);
assertType('mixed', $bar->name);
};

assertType('PhpParser\Node\Identifier', $call->name);
}
}

public function doBar(MethodCall $call, MethodCall $bar): void
{
if ($call->name instanceof Identifier && $bar->name instanceof Identifier) {
$a = 1;
function () use ($call, &$a): void {
assertType('PhpParser\Node\Identifier', $call->name);
assertType('mixed', $bar->name);
};

assertType('PhpParser\Node\Identifier', $call->name);
}
}

}

0 comments on commit 36dac3d

Please sign in to comment.