Repository navigation
Callable arguments of methods/functions are checked if they are valid callbacks - #716
MartinMystikJonas wants to merge 4 commits into
Conversation
8aae1ba to
5ea0d4c
Compare
ondrejmirtes
left a comment
There was a problem hiding this comment.
This is a first preliminary review. I probably didn't catch everything, but pointed out the obvious, easy, broken stuff.
| public function checkCallableArgument( | ||
| Arg $argument, | ||
| Scope $scope, | ||
| string $msgPrefix |
There was a problem hiding this comment.
Please don't use abbreviations.
| public function checkCallableArgument( | ||
| Arg $argument, | ||
| Scope $scope, | ||
| string $msgPrefix |
There was a problem hiding this comment.
Please don't use abbreviations.
| return [$msgPrefix . "array doesn't have required 2 items."]; | ||
| } | ||
| if ($arrayItemsCount > 2) { | ||
| return [$msgPrefix . 'array has too much items, only two items expected.']; |
| $targetType = new ObjectType($item1->value); | ||
| $targetValue = $item1->value; | ||
| $targetIsStaticMethod = true; | ||
| } elseif ($item1 instanceof Variable) { |
There was a problem hiding this comment.
Does not have to be variable, e.g. [$this->object, 'method']. This should be the fallback else branch.
| $targetType = new ObjectType($item1->value); | ||
| $targetValue = $item1->value; | ||
| $targetIsStaticMethod = true; | ||
| } elseif ($item1 instanceof Variable) { |
There was a problem hiding this comment.
Does not have to be variable, e.g. [$this->object, 'method']. This should be the fallback else branch.
| try { | ||
| $this->broker->getFunction(new Name($targetMethod), $scope); | ||
| } catch (\PHPStan\Broker\FunctionNotFoundException $e) { | ||
| return [$msgPrefix . sprintf('%s is not a existing function name.', $targetMethod)]; |
There was a problem hiding this comment.
Function %s not found. is enough.
| continue; | ||
| } | ||
|
|
||
| $msgPrefix = sprintf('Argument #%d of %s should be callable, but passed ', $i + 1, $node->name); |
There was a problem hiding this comment.
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 []; |
There was a problem hiding this comment.
return $typeResult->getUnknownClassErrors()
| $lineOffset2 = 74; | ||
|
|
||
| $this->analyse([__DIR__ . '/../data/callable-exists.php'], [ | ||
| [ |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Please don't use this, write the literal line where the error occurs.
There was a problem hiding this comment.
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.
|
I see, please also add support for ::class (ClassConstFetch).
On Wed, 27 Dec 2017 at 13:09, Martin Mystik Jonáš ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/Rules/CallableExistsCheck.php
<#716 (comment)>:
> + }
+ $item1 = $argument->value->items[0]->value;
+ $item2 = $argument->value->items[1]->value;
+
+ if ($item1 instanceof String_) {
+ $targetType = new ObjectType($item1->value);
+ $targetValue = $item1->value;
+ $targetIsStaticMethod = true;
+ } elseif ($item1 instanceof Variable) {
+ $targetType = $scope->getType($item1);
+ $targetIsStaticMethod = false;
+ } elseif ($item1 instanceof ConstFetch) {
+ $targetType = new ObjectType(constant($item1->name->toString()));
+ $targetValue = constant($item1->name->toString());
+ $targetIsStaticMethod = true;
+ } elseif ($item1 instanceof Class_) {
This should catch *CLASS* (PhpParser\Node\Scalar\MagicConst\Class_) and
it works in tests.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#716 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAGZuBv9kKtZM3aMi_rIbNZQd_jeoRhWks5tEjNpgaJpZM4RNBbd>
.
--
Ondřej Mirtes
|
|
New version commited:
|
|
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"); |
|
Please don’t make this over-ambitious, the current feature set is fine. The
other cases will probably start working automatically once @JanTvrdik
implements constant values into the typesystem.
On Thu, 28 Dec 2017 at 10:33, Martin Mystik Jonáš ***@***.***> wrote:
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");
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#716 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAGZuMg6293UNhuwwr73Ngy27gOtrPQkks5tE2BYgaJpZM4RNBbd>
.
--
Ondřej Mirtes
|
|
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, ...). |
|
@ondrejmirtes Any idea how to get list of all core PHP functions that accepts callable as argument? |
|
@MartinMystikJonas Stubs will be needed for that. |
|
@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.) |
|
@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. |
|
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], |
There was a problem hiding this comment.
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;
});There was a problem hiding this comment.
Good point.
There was a problem hiding this comment.
@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, | ||
| ], | ||
| [ |
| $targetIsStaticMethod = true; | ||
| } elseif ($item1 instanceof ObjectType || $item1 instanceof ThisType) { | ||
| $targetType = $item1; | ||
| $targetIsStaticMethod = false; |
There was a problem hiding this comment.
Did you consider the case where the static method is called on an object? See: https://3v4l.org/HQ1cp
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
I feel like this should be a different condition. What about not having the else branch and just asking the Type about hasMethod/getMethod?
There was a problem hiding this comment.
This will solve union/intersection types, StaticType...
There was a problem hiding this comment.
I'm not sure what you mean here. How do you think this conditionshould look like?
There was a problem hiding this comment.
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
}
|
Please note that I updated master to PHP 7.1, adding nullable and void typehints, so be careful when rebasing 😊 Run |
|
This might require some rework and/or simplification in light of #828 😊 Note: #828 will be merged soon. |
|
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 😊 |
No description provided.