-
Notifications
You must be signed in to change notification settings - Fork 534
Fix chaining nullsafe operator #752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
b4eea66
to
a700279
Compare
a700279
to
052dcca
Compare
src/Rules/RuleLevelHelper.php
Outdated
string $unknownClassErrorPattern, | ||
callable $unionTypeCriteriaCallback | ||
callable $unionTypeCriteriaCallback, | ||
bool $shouldShortCircuitNullSafeOperator = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default required in order to not break rules in phpstan-strict-rules and others
src/Rules/RuleLevelHelper.php
Outdated
} | ||
|
||
if ($shouldShortCircuitNullSafeOperator && TypeCombinator::containsNull($type)) { | ||
$type = $scope->getType(NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($var)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, can you please explain to me how is it possible that this fixes the bug? In current dev-master, we first call NullsafeOperatorHelper::getNullsafeShortcircuitedExpr($node->var)
and RuleLevelHelper then gets the type:
phpstan-src/src/Rules/RuleLevelHelper.php
Line 141 in 95754d1
$type = $scope->getType($var); |
In your version you basically do the same thing but getNullsafeShortcircuitedExpr is called inside findTypeToCheck
. Maybe I'm tired, but I don't see the difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, it's hard to grasp, I tried to explain it in phpstan/phpstan#5868 (comment)
The problem is that running NullsafeOperatorHelper::getNullsafeShortcircuitedExpr on $expr modifies the expression in such a way that it does not match the one saved in $scope->moreSpecificTypes.
There is a difference in how $method?->get()?->get() and $method?->get()->get().
One relies only on the moreSpecificTypes whereas the other needs the NullsafeOperatorHelper as it does not have the moreSpecificTypes information available.
So the previous version for expression $method?->get()?->getExisting()->get()
worked like this:
$method?->get()
returns ObjectType|NULL, $var == $method which is ObjectType($method?->get())?->getExisting()
returns ObjectType|NULL, $var == $method?->get() which is ObjectType|NULL
Current fix works like this:
$method?->get()
returns ObjectType (thanks to $scope->moreSpecificType), $var == $method which is ObjectType($method?->get())?->getExisting()
returns ObjectType|NULL, $var == $method?->get() which is ObjectType (due to$scope->moreSpecificType
)($method?->get()?->getExisting())->get()
return ObjectType|NULL, $var == $method?->get()?->getExisting() and this is where$scope->moreSpecificType
fails, for regular MethodCall (and not NullsafeMethodCall) and NullsafeOperatorHelper is required.
I'll try to come up with real examples to make it more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who saves it to moreSpecificTypes
?
The fix I'm thinking of is that NullsafeOperatorHelper would have two public methods: getNullsafeShortcircuitedExpr and a new one with a different name, just so that we avoid boolean parameters.
People would still call NullsafeOperatorHelper in their specific rules when passing $var
into RuleLevelHelper. Where you're passing false
in your PR, the old getNullsafeShortcircuitedExpr
method would be called.
Where you're passing true
, the new method would be called. What it would do is it'd inspect the last part of the chain, exchanged it for MethodCall
and PropertyFetch
, but it would note in the node's attributes that this used to be a nullsafe node.
RuleLevelHelper would inspect $var
for this attribute and it it would find it, it would remove NullType
from the var type.
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NodeScopeResolver::2025, I don't really understand the logic here though yet, I just came to this conclusion by step-by-step debugging.
I don't like that we have to deal with "shallow non-nullability" on two levels (in NodeScopeResolver && specific rules). I'd say it should be possible to modify nested scope similarly to how it's done for NullsafeMethodCalls for MethodCall->var as well 🤔
Regarding your proposed solution, in the case of passing false
, the expression does not have to be modified I think. Yes, I think we can do that if you're OK with saving extra node attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please try it. Right now it looks like that it's only luck that it works so I'd like something more explicit :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ondrejmirtes Check it now please, I did not do it the "attribute-way" because the decision is based on the top-level expression and not the $expr->var (or other, which is passed to the NullsafeOperatorHelper).
I'd prefer to come up with a different name for the NullsafeOperatorHelper methods though 🤔 Any ideas?
052dcca
to
75e9aba
Compare
Thank you, this is great! |
Resolves: phpstan/phpstan#5868