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:
parent
c657a45132
commit
3681762a9b
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 { }
|
||||
|
Loading…
Reference in New Issue
Block a user