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:
parent
2d454d6e10
commit
32efdfd0f7
@ -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" />
|
||||
|
@ -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,
|
||||
|
6
src/Psalm/Issue/NullFunctionCall.php
Normal file
6
src/Psalm/Issue/NullFunctionCall.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class NullFunctionCall extends CodeIssue
|
||||
{
|
||||
}
|
6
src/Psalm/Issue/PossiblyNullFunctionCall.php
Normal file
6
src/Psalm/Issue/PossiblyNullFunctionCall.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Psalm\Issue;
|
||||
|
||||
class PossiblyNullFunctionCall extends CodeIssue
|
||||
{
|
||||
}
|
@ -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']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user