-
Notifications
You must be signed in to change notification settings - Fork 548
Normalize specified types before intersection #1016
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
Normalize specified types before intersection #1016
Conversation
1dce3de to
67282d2
Compare
67282d2 to
949e5fa
Compare
|
The failing tests are either unrelated or, in case of webmozart-assert, actual fixes Marking this as ready, but I have the feeling the 2 typespecifier workarounds should be reverted for now, just let me know |
|
Hi, thank you for the research. Can you please describe in more detail how does SpecifiedTypes look before and after the normalization and what does it achieve further down the line? Thanks. |
|
@ondrejmirtes I wrote a short novel hidden behind the Details section in the description. I hope this explains everything. |
|
Perfect explanation, I now understand the problem :) I'm gonna let it sit for a while to think about it, maybe I'll come up even with a better solution. What I'm worried about a bit is a falsy context where the meaning of sureTypes/sureNotTypes is switched and it should be doing the opposite I think. Anyway, it's now my turn to do something about it, thank you. |
…ve hacky workarounds
|
I added another commit and with that one I managed to
It makes use of the I think this change makes PHPStan understand some complex true and false context conditionals that it did not understand before 🎉 At least if I'm not completely mistaken. But I'll think about it a bit more. Maybe it also makes sense to add more complex test cases somewhere |
|
Are any of the new test cases and bugfixes about my concern?
|
There were already test cases that check the type in the else statement and I added some more. And AFAIK and could see via debugger, it uses a falsy context then to create the specified types and filter by them in the scope. So, I think this should be covered :) |
|
Alright, I'm gonna merge it :) Please send a fix to webmozart-assert extension afterwards, thanks. |
|
Thank you! |
|
Finally I can sleep tonight xD But seriously, this kept me thinking, busy and step debugging quite a few hours
In order to do that I need a new phpstan release to specify as minimum supported version, I think. Shall I comment-out the two problematic lines for now and add them back in correctly after a new phpstan version was tagged? |
|
Also, feel free to continue thinking about it and have it in the back of your head, it's often that people come up with improvements afterwards and I love many little PRs :) Generics were implemented over dozens of small PRs :) |
|
great job @herndlm ! |
You don't - put ^1.4.7 or whatever is the next one to composer.json and the current dev-master will get installed. It's not going to have to change after the release and the stable version will be used automatically 😊 |
Closes phpstan/phpstan#6329
This PR is improving type specification with combined BooleanAnd and BooleanIf expressions in 2 ways. Therefore the title does not match anymore :) I think something like "Improve type specification for combined && and || expressions" would fit better.
AccessoryNonEmptyStringTypeorNonEmptyArrayType. Without this, sureNotTypes might not be considered when intersecting instances ofSpecifiedType, which leads to type information loss.Details about the normalization
In order to narrow down the variable
$afrommixedtonon-empty-string|nullthe following code could (it's maybe a bit odd but this is not relevant) be used in an if (truthy context):This can be described with the expression
The interesting parts are the inner BooleanAnd and the outer BooleanOr. If the TypeSpecifier looks at this it will basically recursively determine the
SpecifiedTypes. Without going into too much details, that the relevant people know better than me anyway, the following will be the simplified relevant sureTypes and sureNotTypes for$aof the relevant SpecifiedTypes instances:BooleanAnd
left expression:
StringTypesureTyperight expression:
ConstantStringTypewith value''sureNotTypeResult
BooleanAnd uses
SpecifiedTypes->unionWithfor a truthy context like here which results in a new SpecifiedTypes having both of thoseBooleanOr
left expression: the above union =>
StringTypesureType,ConstantStringTypewith value''sureNotTyperight expression:
NullTypesureTypeResult
=> BooleanOr uses
SpecifiedTypes->intersectWithBefore
intersectWith only looks at the sureTypes and sureNotTypes of both SpecifiedTypes instances if their expression string occurs also in both. Meaning, it will union (for a truthy context like here) the
StringTypeandNullTypebecause both SpecifiedTypes instances have sureTypes for$a. But, it will not use the sureNotType if there is not a sureNotType in the other SpecifiedTypes for$a. Therefore it results in a SpecifiedTypes instance with aStringType|NullTypesureTypes union only and basically looses the sureNotType information that is relevant for the left expression.After
both SpecifiedTypes instances are normalized which "moves" the sureNotType into the sureType resulting in a
StringType&AccessoryNonEmptyStringTypeintersection for the left expression basically. The other SpecifiedTypes instance is not affected here by normalization. The intersectWith logic then unionizes (for a truthy context like here) both instances resulting in a(StringType&AccessoryNonEmptyStringType)|NullTypeunion sureType and therfore does not loose the sureNotType information which is coupled to the left expression but does not make any sense for the right one.Without the BooleanOr the sureNotType is not lost and the MutatingScope can use it to determine that the outcome is
non-empty-string. With BooleanOr we have to normalize this already here IMO before intersecting in order to not loose the information.MutatingScopeinBooleanAndandBooleanOrexpressions to filter by the specified types of the left espression and uses the resulting mutated scope in the right expression. This ensures that information gathered on the left side can be used on the right side. See Normalize specified types before intersection #1016 (comment)