Skip to content

Commit

Permalink
Fix false positive with nullsafe operator
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Dec 15, 2020
1 parent d245dab commit 75d00c9
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 0 deletions.
51 changes: 51 additions & 0 deletions src/Rules/RuleLevelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ public function findTypeToCheck(
if (!$this->checkNullables && !$type instanceof NullType) {
$type = \PHPStan\Type\TypeCombinator::removeNull($type);
}

if (TypeCombinator::containsNull($type)) {
$type = $scope->getType($this->getNullsafeShortcircuitedExpr($var));
}

if (
$this->checkExplicitMixed
&& $type instanceof MixedType
Expand Down Expand Up @@ -188,4 +193,50 @@ public function findTypeToCheck(
return new FoundTypeResult($type, $directClassNames, []);
}

private function getNullsafeShortcircuitedExpr(Expr $expr): Expr
{
if ($expr instanceof Expr\NullsafeMethodCall) {
return new Expr\MethodCall($this->getNullsafeShortcircuitedExpr($expr->var), $expr->name, $expr->args);
}

if ($expr instanceof Expr\MethodCall) {
return new Expr\MethodCall($this->getNullsafeShortcircuitedExpr($expr->var), $expr->name, $expr->args);
}

if ($expr instanceof Expr\StaticCall && $expr->class instanceof Expr) {
return new Expr\StaticCall(
$this->getNullsafeShortcircuitedExpr($expr->class),
$expr->name,
$expr->args
);
}

if ($expr instanceof Expr\ArrayDimFetch) {
return new Expr\ArrayDimFetch($this->getNullsafeShortcircuitedExpr($expr->var), $expr->dim);
}

if ($expr instanceof Expr\NullsafePropertyFetch) {
return new Expr\PropertyFetch(
$this->getNullsafeShortcircuitedExpr($expr->var),
$expr->name
);
}

if ($expr instanceof Expr\PropertyFetch) {
return new Expr\PropertyFetch(
$this->getNullsafeShortcircuitedExpr($expr->var),
$expr->name
);
}

if ($expr instanceof Expr\StaticPropertyFetch && $expr->class instanceof Expr) {
return new Expr\StaticPropertyFetch(
$this->getNullsafeShortcircuitedExpr($expr->class),
$expr->name
);
}

return $expr;
}

}
18 changes: 18 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1719,4 +1719,22 @@ public function testNamedArguments(): void
]);
}

public function testBug4199(): void
{
if (!self::$useStaticReflectionProvider && PHP_VERSION_ID < 80000) {
$this->markTestSkipped('Test requires PHP 8.0.');
}

$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;

$this->analyse([__DIR__ . '/data/bug-4199.php'], [
[
'Cannot call method answer() on Bug4199\Baz|null.',
37,
],
]);
}

}
38 changes: 38 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-4199.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php // lint >= 8.0

namespace Bug4199;

class Foo
{
public function getBar(): ?Bar
{
return null;
}
}

class Bar
{
public function getBaz(): Baz
{
return new Baz();
}
public function getBazOrNull(): ?Baz
{
return null;
}
}

class Baz
{
public function answer(): int
{
return 42;
}
}

function (): void {
$foo = new Foo;
$answer = $foo->getBar()?->getBaz()->answer();

$answer2 = $foo->getBar()?->getBazOrNull()->answer();
};

0 comments on commit 75d00c9

Please sign in to comment.