Skip to content

Commit

Permalink
Add detection for callable variable use
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Apr 24, 2019
1 parent c657a45 commit 3681762
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2157,7 +2157,8 @@ public static function checkFunctionArgumentType(
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) {
Expand Down
32 changes: 21 additions & 11 deletions src/Psalm/Internal/Analyzer/TypeAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -1141,7 +1141,7 @@ public static function isAtomicContainedBy(
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;
Expand Down Expand Up @@ -1342,7 +1342,7 @@ public static function getCallableFromAtomic(Codebase $codebase, Type\Atomic $in
}
}
} 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);

Expand All @@ -1361,28 +1361,36 @@ public static function getCallableFromAtomic(Codebase $codebase, Type\Atomic $in
}

/** @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])
) {
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;

Expand All @@ -1397,6 +1405,8 @@ public static function getCallableMethodIdFromObjectLike(ObjectLike $input_type_
}

if (!$class_name) {
$codebase->analyzer->addMixedMemberName(strtolower($method_name));

return null;
}

Expand Down
52 changes: 44 additions & 8 deletions src/psalter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,50 @@
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)) {
Expand Down Expand Up @@ -73,7 +109,7 @@
--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
Expand Down
56 changes: 56 additions & 0 deletions tests/FileManipulationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,62 @@ function foo(A $a, string $var) {
['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 { }
Expand Down

0 comments on commit 3681762

Please sign in to comment.