Skip to content

Callable arguments of methods/functions are checked if they are valid callbacks - #716

Closed
MartinMystikJonas wants to merge 4 commits into
phpstan:masterfrom
MartinMystikJonas:callable-check
Closed

MartinMystikJonas wants to merge 4 commits into
phpstan:masterfrom
MartinMystikJonas:callable-check

Conversation

@MartinMystikJonas

Copy link
Copy Markdown
Contributor

No description provided.

@ondrejmirtes ondrejmirtes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a first preliminary review. I probably didn't catch everything, but pointed out the obvious, easy, broken stuff.

Comment thread src/Rules/CallableExistsCheck.php Outdated
public function checkCallableArgument(
Arg $argument,
Scope $scope,
string $msgPrefix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use abbreviations.

Comment thread src/Rules/CallableExistsCheck.php Outdated
public function checkCallableArgument(
Arg $argument,
Scope $scope,
string $msgPrefix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use abbreviations.

Comment thread src/Rules/CallableExistsCheck.php Outdated
return [$msgPrefix . "array doesn't have required 2 items."];
}
if ($arrayItemsCount > 2) {
return [$msgPrefix . 'array has too much items, only two items expected.'];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has too many

Comment thread src/Rules/CallableExistsCheck.php Outdated
$targetType = new ObjectType($item1->value);
$targetValue = $item1->value;
$targetIsStaticMethod = true;
} elseif ($item1 instanceof Variable) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not have to be variable, e.g. [$this->object, 'method']. This should be the fallback else branch.

Comment thread src/Rules/CallableExistsCheck.php Outdated
$targetType = new ObjectType($item1->value);
$targetValue = $item1->value;
$targetIsStaticMethod = true;
} elseif ($item1 instanceof Variable) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not have to be variable, e.g. [$this->object, 'method']. This should be the fallback else branch.

Comment thread src/Rules/CallableExistsCheck.php Outdated
try {
$this->broker->getFunction(new Name($targetMethod), $scope);
} catch (\PHPStan\Broker\FunctionNotFoundException $e) {
return [$msgPrefix . sprintf('%s is not a existing function name.', $targetMethod)];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function %s not found. is enough.

continue;
}

$msgPrefix = sprintf('Argument #%d of %s should be callable, but passed ', $i + 1, $node->name);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the argument is also added, for example: 'Parameter #%d %s of method ' . $messagesMethodName . ' expects %s, %s given.', (see usages of FunctionCallParametersCheck).

$typeResult = $this->ruleLevelHelper->findTypeToCheck($scope, $node->var, '');
$type = $typeResult->getType();
if ($type instanceof ErrorType) {
return [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return $typeResult->getUnknownClassErrors()

$lineOffset2 = 74;

$this->analyse([__DIR__ . '/../data/callable-exists.php'], [
[

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test is really weirdly formatted, please look at it on GitHub.

$this->analyse([__DIR__ . '/../data/callable-exists.php'], [
[
'Argument #1 of call_user_func should be callable, but passed array has too much items, only two items expected.',
$lineOffset, //109

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use this, write the literal line where the error occurs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change it in final version, I often add new test cases whoch changes line numbers so i create offset to fix tests more easily.

@ondrejmirtes ondrejmirtes added this to the 0.10 milestone Dec 27, 2017
@ondrejmirtes

ondrejmirtes commented Dec 27, 2017 via email

Copy link
Copy Markdown
Member

@MartinMystikJonas

Copy link
Copy Markdown
Contributor Author

New version commited:

  • updated error messages
  • support for ::class
  • support for properties and static properties in args
  • support for expressions in args (only check types)
  • fixed formatting
  • expanded tests to cover more edge cases

@MartinMystikJonas

Copy link
Copy Markdown
Contributor Author

I'm working on new version which will support wider variety of parametr combinations (class constant as method name, ...) and I also try to add support for concatenated values like:

  call_user_func(self::class . "::someMethod");

@ondrejmirtes

ondrejmirtes commented Dec 28, 2017 via email

Copy link
Copy Markdown
Member

@MartinMystikJonas

Copy link
Copy Markdown
Contributor Author

Commited new version with refactored CallableExistsCheck nad better support for value resolving.

@ondrejmirtes Method resolveValue is candidate for moving to Scope class if it would be made more universal. Currenty in only supports strings but can be extended to other scalars. This method resolves value of Expr to scalar if possible (const, class const, literal, concatenation, ...) or return Type if not (variable, function call, operator, ...).

@MartinMystikJonas

Copy link
Copy Markdown
Contributor Author

@ondrejmirtes Any idea how to get list of all core PHP functions that accepts callable as argument?

@iluuu1994

Copy link
Copy Markdown
Contributor

@MartinMystikJonas Stubs will be needed for that.

https://github.com/JetBrains/phpstorm-stubs

@ondrejmirtes

Copy link
Copy Markdown
Member

@MartinMystikJonas Yep, just list a few, add support for functions/methods with callable argument (userland ones) and it will automatically work once we add these stubs in some form or another. (Do not attempt to add the stubs here, that would be out of scope of this PR.)

@MartinMystikJonas

MartinMystikJonas commented Dec 28, 2017 •

Copy link
Copy Markdown
Contributor Author

@ondrejmirtes Listed all main functions i can find. So for now this feature is complete from my side. Please review it and let me know is anything more is required. Then I will clean up tests code, squash commits and prepare final version.

@MartinMystikJonas

Copy link
Copy Markdown
Contributor Author

Should I prepare final version or shpuld I wait fir review? This afternoon could be last I could work on this for few months.

['array_uintersect_assoc', 4],
['array_intersect_uassoc', 4],
['array_uintersect_uassoc', 4],
['array_diff_ukey', 4],

@mavimo mavimo Jan 21, 2018 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand we are indicate that parameter 4 into function should be a callable, but php doc indicate that the callable should be the last item of the function, so it call be the 3, the 4 or the N-th element. Same issue on array_udiff, ..

$a = [1, 2, 3];
$b = [4, 5, 6];
$c = [7, 8, 9];
$d = [10, 11, 12];
$e = [13, 14, 15];
$res = array_udiff($a, $b, $c, $d, $e, function (int $first, int $second): int {
  return $first - $second;
});

http://php.net/array_diff_ukey

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MartinMystikJonas Maybe should consider to get last element of args and process it when needed?

'Argument #1 callableParam of funcWithCallableParam should be callable, but passed non-static method CallableExists\Bar::knownMethod as static.',
$lineOffset2 + 64,
],
[

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong indentation.

$targetIsStaticMethod = true;
} elseif ($item1 instanceof ObjectType || $item1 instanceof ThisType) {
$targetType = $item1;
$targetIsStaticMethod = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider the case where the static method is called on an object? See: https://3v4l.org/HQ1cp

@MartinMystikJonas MartinMystikJonas Feb 10, 2018 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will result in error:
Argument #1 callableParam of funcWithCallableParam should be callable, but passed static method CallableExists\Foo::knownStaticMethod as non-static.

Should I change this? From my PoW this type of call is propably a mistake.

if (is_string($item1)) {
$targetType = new ObjectType($item1);
$targetIsStaticMethod = true;
} elseif ($item1 instanceof ObjectType || $item1 instanceof ThisType) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should be a different condition. What about not having the else branch and just asking the Type about hasMethod/getMethod?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will solve union/intersection types, StaticType...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean here. How do you think this conditionshould look like?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this:

if (is_string($item1)) {
   ...
} elseif ($item1 instanceof MixedType || $item1 instanceof StringType) {
   ...
} else {
   // fallback for all other types - make sure to call $item1->hasMethod() + getMethod somewhere lower in this file
}

@ondrejmirtes

Copy link
Copy Markdown
Member

Please note that I updated master to PHP 7.1, adding nullable and void typehints, so be careful when rebasing 😊 Run vendor/bin/phing cs to find out anything you might have missed.

@ondrejmirtes ondrejmirtes reopened this Feb 3, 2018
@ondrejmirtes

Copy link
Copy Markdown
Member

This might require some rework and/or simplification in light of #828 😊 isCallable now works more universally, see for example ConstantStringType::isCallable().

Note: #828 will be merged soon.

@ondrejmirtes

Copy link
Copy Markdown
Member

Well, it's even more advanced than I suspected, #828 now fully recognizes arrays and strings as callables. It does not have such nice error messages as this pull request but gets the job done 😊 Don't close this yet, I'll go through it to find out whether we forgot something.

With function stubs of native functions that are also in the works, this feature will be completely covered 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants