1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Fix #129 - emit PossiblyNullFuntionCall when encountering such a thing

This commit is contained in:
Matthew Brown 2017-04-08 11:38:06 -04:00
parent 2d454d6e10
commit 32efdfd0f7
5 changed files with 87 additions and 12 deletions

View File

@ -137,6 +137,7 @@
<xs:element name="NonStaticSelfCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullArgument" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullArrayAccess" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullFunctionCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullOperand" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullPropertyAssignment" type="IssueHandlerType" minOccurs="0" />
<xs:element name="NullPropertyFetch" type="IssueHandlerType" minOccurs="0" />
@ -147,6 +148,7 @@
<xs:element name="PossiblyInvalidArgument" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyNullArgument" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyNullArrayAccess" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyNullFunctionCall" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyNullOperand" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyNullPropertyAssignment" type="IssueHandlerType" minOccurs="0" />
<xs:element name="PossiblyNullPropertyFetch" type="IssueHandlerType" minOccurs="0" />

View File

@ -27,10 +27,12 @@ use Psalm\Issue\InvalidScope;
use Psalm\Issue\MixedArgument;
use Psalm\Issue\MixedMethodCall;
use Psalm\Issue\NullArgument;
use Psalm\Issue\NullFunctionCall;
use Psalm\Issue\NullReference;
use Psalm\Issue\ParentNotFound;
use Psalm\Issue\PossiblyInvalidArgument;
use Psalm\Issue\PossiblyNullArgument;
use Psalm\Issue\PossiblyNullFunctionCall;
use Psalm\Issue\PossiblyNullReference;
use Psalm\Issue\TooFewArguments;
use Psalm\Issue\TooManyArguments;
@ -142,6 +144,32 @@ class CallChecker
}
if (isset($stmt->name->inferredType)) {
if ($stmt->name->inferredType->isNull()) {
if (IssueBuffer::accepts(
new NullFunctionCall(
'Cannot call function on null value',
new CodeLocation($statements_checker->getSource(), $stmt)
),
$statements_checker->getSuppressedIssues()
)) {
return false;
}
return;
}
if ($stmt->name->inferredType->isNullable()) {
if (IssueBuffer::accepts(
new PossiblyNullFunctionCall(
'Cannot call function on possibly null value',
new CodeLocation($statements_checker->getSource(), $stmt)
),
$statements_checker->getSuppressedIssues()
)) {
// fall through
}
}
foreach ($stmt->name->inferredType->types as $var_type_part) {
if ($var_type_part instanceof Type\Atomic\Fn) {
$function_params = $var_type_part->params;
@ -158,10 +186,15 @@ class CallChecker
}
$function_exists = true;
} elseif (!$var_type_part instanceof TMixed &&
(!$var_type_part instanceof TNamedObject || $var_type_part->value !== 'Closure') &&
!$var_type_part instanceof TCallable &&
(!$var_type_part instanceof TNamedObject ||
} elseif ($var_type_part instanceof TMixed) {
// @todo maybe emit issue here
} elseif (($var_type_part instanceof TNamedObject && $var_type_part->value === 'Closure') ||
$var_type_part instanceof TCallable
) {
// this is fine
} elseif ($var_type_part instanceof TNull) {
// handled above
} elseif (!$var_type_part instanceof TNamedObject ||
!ClassLikeChecker::classOrInterfaceExists(
$var_type_part->value,
$statements_checker->getFileChecker()
@ -170,7 +203,6 @@ class CallChecker
$var_type_part->value . '::__invoke',
$statements_checker->getFileChecker()
)
)
) {
$var_id = ExpressionChecker::getVarId(
$stmt->name,

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class NullFunctionCall extends CodeIssue
{
}

View File

@ -0,0 +1,6 @@
<?php
namespace Psalm\Issue;
class PossiblyNullFunctionCall extends CodeIssue
{
}

View File

@ -251,6 +251,35 @@ class ClosureTest extends PHPUnit_Framework_TestCase
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage PossiblyNullFunctionCall
* @return void
*/
public function testPossiblyNullFunctionCall()
{
$stmts = self::$parser->parse('<?php
/**
* @var Closure|null $foo
*/
$foo = null;
$foo = function ($bar) use (&$foo) : string
{
if (is_array($bar)) {
return $foo($bar);
}
return $bar;
};
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context();
$file_checker->visitAndAnalyzeMethods($context);
$this->assertEquals('int', (string) $context->vars_in_scope['$a']);
}
/**