1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Add detection for callable variable use

This commit is contained in:
Matthew Brown 2019-04-23 22:31:38 -04:00
parent c657a45132
commit 3681762a9b
4 changed files with 123 additions and 20 deletions

View File

@ -2157,7 +2157,8 @@ class CallAnalyzer
foreach ($input_type->getTypes() as $input_type_part) {
if ($input_type_part instanceof Type\Atomic\ObjectLike) {
$potential_method_id = TypeAnalyzer::getCallableMethodIdFromObjectLike(
$input_type_part
$input_type_part,
$codebase
);
if ($potential_method_id) {

View File

@ -1141,7 +1141,7 @@ class TypeAnalyzer
return false;
}
} elseif ($input_type_part instanceof ObjectLike) {
$method_id = self::getCallableMethodIdFromObjectLike($input_type_part);
$method_id = self::getCallableMethodIdFromObjectLike($input_type_part, $codebase);
if ($method_id === 'not-callable') {
return false;
@ -1342,7 +1342,7 @@ class TypeAnalyzer
}
}
} elseif ($input_type_part instanceof ObjectLike) {
if ($method_id = self::getCallableMethodIdFromObjectLike($input_type_part)) {
if ($method_id = self::getCallableMethodIdFromObjectLike($input_type_part, $codebase)) {
try {
$method_storage = $codebase->methods->getStorage($method_id);
@ -1361,7 +1361,7 @@ class TypeAnalyzer
}
/** @return ?string */
public static function getCallableMethodIdFromObjectLike(ObjectLike $input_type_part)
public static function getCallableMethodIdFromObjectLike(ObjectLike $input_type_part, Codebase $codebase)
{
if (!isset($input_type_part->properties[0])
|| !isset($input_type_part->properties[1])
@ -1369,20 +1369,28 @@ class TypeAnalyzer
return 'not-callable';
}
if ($input_type_part->properties[1]->hasMixed() || $input_type_part->properties[1]->hasScalar()) {
return null;
}
$lhs = $input_type_part->properties[0];
$rhs = $input_type_part->properties[1];
if (!$input_type_part->properties[1]->hasString()) {
return 'not-callable';
}
if ($rhs->hasMixed()
|| $rhs->hasScalar()
|| !$rhs->isSingleStringLiteral()
) {
if (!$rhs->hasString()) {
return 'not-callable';
}
foreach ($lhs->getTypes() as $lhs_atomic_type) {
if ($lhs_atomic_type instanceof TNamedObject) {
$codebase->analyzer->addMixedMemberName(strtolower($lhs_atomic_type->value) . '::');
}
}
if (!$input_type_part->properties[1]->isSingleStringLiteral()) {
return null;
}
$lhs = $input_type_part->properties[0];
$method_name = $input_type_part->properties[1]->getSingleStringLiteral()->value;
$method_name = $rhs->getSingleStringLiteral()->value;
$class_name = null;
@ -1397,6 +1405,8 @@ class TypeAnalyzer
}
if (!$class_name) {
$codebase->analyzer->addMixedMemberName(strtolower($method_name));
return null;
}

View File

@ -11,14 +11,50 @@ ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
ini_set('memory_limit', '4096M');
gc_collect_cycles();
gc_disable();
$args = array_slice($argv, 1);
$valid_short_options = ['f:', 'm', 'h', 'r:'];
$valid_long_options = [
'help', 'debug', 'debug-by-line', 'config:', 'file:', 'root:',
'plugin:', 'issues:', 'php-version:', 'dry-run', 'safe-types',
'find-unused-code', 'threads:',
];
// get options from command line
$options = getopt(
'f:mhr:',
[
'help', 'debug', 'debug-by-line', 'config:', 'file:', 'root:',
'plugin:', 'issues:', 'php-version:', 'dry-run', 'safe-types',
'find-unused-code', 'threads:',
]
$options = getopt(implode('', $valid_short_options), $valid_long_options);
array_map(
/**
* @param string $arg
*
* @return void
*/
function ($arg) use ($valid_long_options, $valid_short_options) {
if (substr($arg, 0, 2) === '--' && $arg !== '--') {
$arg_name = preg_replace('/=.*$/', '', substr($arg, 2));
if (!in_array($arg_name, $valid_long_options)
&& !in_array($arg_name . ':', $valid_long_options)
&& !in_array($arg_name . '::', $valid_long_options)
) {
echo 'Unrecognised argument "--' . $arg_name . '"' . PHP_EOL
. 'Type --help to see a list of supported arguments'. PHP_EOL;
exit(1);
}
} elseif (substr($arg, 0, 2) === '-' && $arg !== '-' && $arg !== '--') {
$arg_name = preg_replace('/=.*$/', '', substr($arg, 1));
if (!in_array($arg_name, $valid_short_options) && !in_array($arg_name . ':', $valid_short_options)) {
echo 'Unrecognised argument "-' . $arg_name . '"' . PHP_EOL
. 'Type --help to see a list of supported arguments'. PHP_EOL;
exit(1);
}
}
},
$args
);
if (array_key_exists('help', $options)) {
@ -73,7 +109,7 @@ Options:
--issues=IssueType1,IssueType2
If any issues can be fixed automatically, Psalm will update the codebase
--find-dead-code
--find-unused-code
Include unused code as a candidate for removal
--threads=INT

View File

@ -1771,6 +1771,62 @@ class FileManipulationTest extends TestCase
['PossiblyUnusedMethod'],
true,
],
'dontRemovePossiblyUnusedMethodWithVariableCallableCall' => [
'<?php
class A {
public function foo() : void {}
}
function takeCallable(callable $c) : void {}
function foo(A $a, string $var) {
takeCallable([$a, $var]);
}',
'<?php
class A {
public function foo() : void {}
}
function takeCallable(callable $c) : void {}
function foo(A $a, string $var) {
takeCallable([$a, $var]);
}',
'7.1',
['PossiblyUnusedMethod'],
true,
],
'dontRemovePossiblyUnusedMethodWithVariableCallableLhsCall' => [
'<?php
class A {
public function foo() : void {}
public function bar() : void {}
}
function takeCallable(callable $c) : void {}
function foo($a) {
takeCallable([$a, "foo"]);
}
foo(new A);',
'<?php
class A {
public function foo() : void {}
}
function takeCallable(callable $c) : void {}
function foo($a) {
takeCallable([$a, "foo"]);
}
foo(new A);',
'7.1',
['PossiblyUnusedMethod'],
true,
],
'dontRemovePossiblyUnusedMethodWithVariableCallOnParent' => [
'<?php
class A { }