From e6cf9e96dfada0065ae71f7e092c9a7e79f8a62f Mon Sep 17 00:00:00 2001 From: Brown Date: Wed, 17 Oct 2018 13:22:57 -0400 Subject: [PATCH] Add support for class concats when checking for callables --- .../Statements/Expression/CallChecker.php | 20 ++++++++- tests/CallableTest.php | 42 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Checker/Statements/Expression/CallChecker.php b/src/Psalm/Checker/Statements/Expression/CallChecker.php index 34f87e7fa..0eba84246 100644 --- a/src/Psalm/Checker/Statements/Expression/CallChecker.php +++ b/src/Psalm/Checker/Statements/Expression/CallChecker.php @@ -1238,6 +1238,7 @@ class CallChecker if (!$closure_type instanceof Type\Atomic\Fn) { if (!$closure_arg->value instanceof PhpParser\Node\Scalar\String_ && !$closure_arg->value instanceof PhpParser\Node\Expr\Array_ + && !$closure_arg->value instanceof PhpParser\Node\Expr\BinaryOp\Concat ) { return; } @@ -1720,6 +1721,7 @@ class CallChecker } } elseif ($input_expr instanceof PhpParser\Node\Scalar\String_ || $input_expr instanceof PhpParser\Node\Expr\Array_ + || $input_expr instanceof PhpParser\Node\Expr\BinaryOp\Concat ) { foreach ($param_type->getTypes() as $param_type_part) { if ($param_type_part instanceof TClassString @@ -1921,7 +1923,8 @@ class CallChecker } /** - * @param PhpParser\Node\Scalar\String_|PhpParser\Node\Expr\Array_ $callable_arg + * @param PhpParser\Node\Scalar\String_|PhpParser\Node\Expr\Array_|PhpParser\Node\Expr\BinaryOp\Concat + * $callable_arg * * @return string[] */ @@ -1929,6 +1932,21 @@ class CallChecker \Psalm\FileSource $file_source, $callable_arg ) { + if ($callable_arg instanceof PhpParser\Node\Expr\BinaryOp\Concat) { + if ($callable_arg->left instanceof PhpParser\Node\Expr\ClassConstFetch + && $callable_arg->left->class instanceof PhpParser\Node\Name + && $callable_arg->left->name instanceof PhpParser\Node\Identifier + && strtolower($callable_arg->left->name->name) === 'class' + && !in_array(strtolower($callable_arg->left->class->parts[0]), ['self', 'static', 'parent']) + && $callable_arg->right instanceof PhpParser\Node\Scalar\String_ + && preg_match('/^::[A-Za-z0-9]+$/', $callable_arg->right->value) + ) { + return [(string) $callable_arg->left->class->getAttribute('resolvedName') . $callable_arg->right->value]; + } + + return []; + } + if ($callable_arg instanceof PhpParser\Node\Scalar\String_) { return [preg_replace('/^\\\/', '', $callable_arg->value)]; } diff --git a/tests/CallableTest.php b/tests/CallableTest.php index 14135f948..11bde3bb2 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -129,6 +129,7 @@ class CallableTest extends TestCase function foo(callable $c): void {} foo("A::bar"); + foo(A::class . "::bar"); foo(["A", "bar"]); foo([A::class, "bar"]); $a = new A(); @@ -625,7 +626,7 @@ class CallableTest extends TestCase 'error_message' => 'InvalidFunctionCall', 'error_levels' => ['UndefinedClass'], ], - 'undefinedCallableMethod' => [ + 'undefinedCallableMethodFullString' => [ ' 'UndefinedMethod', ], + 'undefinedCallableMethodClassConcat' => [ + ' 'UndefinedMethod', + ], + 'undefinedCallableMethodArray' => [ + ' 'UndefinedMethod', + ], + 'undefinedCallableMethodArrayWithoutClass' => [ + ' 'UndefinedMethod', + ], 'undefinedCallableMethodClass' => [ '