Skip to content
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

Do not merge BenevolentUnionType with UnionType #2058

Draft
wants to merge 6 commits into
base: 1.10.x
Choose a base branch
from

Conversation

herndlm
Copy link
Contributor

@herndlm herndlm commented Dec 6, 2022

This was inspired by phpstan/phpstan#8458 (comment)

Closes phpstan/phpstan#7049
Closes phpstan/phpstan#7279
Closes phpstan/phpstan#7423
Closes phpstan/phpstan#8268

Regarding the performance impact (#2058 (comment))

before:

Benchmark 1: make phpstan
  Time (mean ± σ):     56.978 s ± 10.578 s    [User: 338.941 s, System: 26.704 s]
  Range (min … max):   49.723 s … 82.109 s    10 runs

after:

Benchmark 1: make phpstan
  Time (mean ± σ):     51.790 s ±  5.459 s    [User: 330.922 s, System: 25.508 s]
  Range (min … max):   48.548 s … 66.973 s    10 runs

not sure if that is enough, the system is a bit trashy I guess. but looks like things don't really change perf.-wise. I'm pretty sure things also don't get faster, the variance is just too big on my system, I think we can go with 49s before and after.

Regarding the 3 failures

all of those are expected IMO, but I can create PRs for Prestashop and PHPUnit to add PHPDoc types to the collection if wanted

@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch 2 times, most recently from 175fd36 to 7c3ff7b Compare December 6, 2022 21:52
@herndlm
Copy link
Contributor Author

herndlm commented Dec 6, 2022

OK close, better than I thought. I guess I deleted too much and am loosing the benevolent template object..

@staabm
Copy link
Contributor

staabm commented Dec 7, 2022

You should measure whether these changes have a perf impact

@VincentLanglet
Copy link
Contributor

Since A|(B|C) would be now a possible type, does it require a test to check that a benevolent union
(A|(B|C)|D) is simplified to (A|B|C|D) ?

@herndlm
Copy link
Contributor Author

herndlm commented Dec 7, 2022

Good question, I'm not sure if it should be tbh. If we explicitly want a union, shouldn't it be non-benevolent? 🤔 I guess what I'm saying is, are we expecting this to be simplified if we call TypeCombinator::union()?
UPDATE: ah sorry, got your point now. Those should be simplified I suppose, yeah

@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch from 7c3ff7b to ff51d9f Compare December 12, 2022 09:52
@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch 3 times, most recently from c2c716e to 8c77918 Compare December 12, 2022 10:49
Comment on lines +82 to +86
assertType('int|null', findKey($unknownList, $callback));
assertType('string|null', findKey($unknownMap, $callback));

assertType('int|null', findKey($nonEmptyList, $callback));
assertType('string|null', findKey($nonEmptyMap, $callback));
Copy link
Contributor Author

@herndlm herndlm Dec 12, 2022

Choose a reason for hiding this comment

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

both L83 and L86 are different to the snippet where 'id'|'name'|null was asserted/expected. but this is either not supported or something different than the main problem that was fixed here IMO

@herndlm herndlm marked this pull request as ready for review December 12, 2022 12:09
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch 2 times, most recently from 3515799 to 3e7fff1 Compare December 13, 2022 10:49
@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch 2 times, most recently from ecb2fc4 to 23bee84 Compare January 3, 2023 01:49
@ondrejmirtes
Copy link
Member

I'm struggling a bit with this one. What is your thinking behind this PR? I see that it fixes a bunch of issues, but to me it looks like it's just a matter of luck, not an intention (please take no offense :)).

What's a typical situation that this PR improves/fixes?

Also, I think we need some more tests around this. Let's say for example we have (int|string) and we want to add some types into it by calling TypeCombinator::union. What's the expected result?

  • When we add a ConstantStringType like '1'.
  • When we add a StringType.
  • When we add ConstantBooleanType like true.

@herndlm
Copy link
Contributor Author

herndlm commented Jan 8, 2023

The main intention behind this is that I believe, and I think the linked issues confirm it, that union with a benevolent union should not suddenly get rid of the benevolence. E.g. https://phpstan.org/r/498cd1d0-4568-4cbf-8b0b-11ed722552e8

Calling TypeCombinator::union() will always add a real union and not get rid of or extend the benevolence. More tests make sense for sure :) some already existed though I believe, and I just adapted their result. Your mentioned cases are good ones that worry me that the edges are still rough here hehe

But it's basically what you suggested at phpstan/phpstan#8268 (comment) (which I just found later via issue bot).

@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch from 23bee84 to 322a89a Compare January 8, 2023 20:41
@herndlm
Copy link
Contributor Author

herndlm commented Jan 8, 2023

Added the test cases for union with (int|string) you requested, we get the following

When we add a ConstantStringType like '1'.

(int|string)

When we add a StringType.

(int|string)

When we add ConstantBooleanType like true.

(int|string)|true

that looks OK to me 😊

@ondrejmirtes
Copy link
Member

I'd argue that TypeCombinator::union of (int|string) and string should result in non-benevolent union type.

Another idea for a test - if we have (1|2) and string, it should result in string (which it probably will).

Also we can test what happens with (1|2|3) and int<2, 3>.

Also (InvalidArgumentException|LengthException|stdClass) and LogicException.

@herndlm
Copy link
Contributor Author

herndlm commented Jan 8, 2023

Ok, added your mentioned cases again, the result is a bit different, I got

  • (1|2)|string UPDATE: but ('1'|'2')|string does result in string, not sure if I misunderstood that or not, anyway, also added it as a test
  • (1|2|3)
  • (InvalidArgumentException|LengthException|stdClass)|LogicException

so most likely there's still work needed / it can be improved? I pushed them in this state, but need to think about this another time. Such benevolent cases are still hard for me to think through properly

@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch from c99a035 to 6e10519 Compare January 8, 2023 21:09
@VincentLanglet
Copy link
Contributor

What's a typical situation that this PR improves/fixes?

https://phpstan.org/r/9be20c04-429f-4dc0-b245-9d23628ae2db

Both $key and $bool are accepted by the method, but the union of both type is not accepted.
The current behavior doesn't feel natural.

I'd argue that TypeCombinator::union of (int|string) and string should result in non-benevolent union type.

Technically, it should be (int)|string which is not really a possible type currently.

Because if it's transformed into int|string

		$key = current(array_keys($array));
		$this->acceptString($key); // valid
		$this->acceptString($string); // valid
		
		$keyOrString = rand(0,1) ? $key : $string;
		$this->acceptString($keyOrString); // won't be but should still be valid

@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch 2 times, most recently from c9865e5 to 3251d3d Compare January 13, 2023 09:36
@drupol
Copy link
Contributor

drupol commented Jan 13, 2023

Hello @herndlm,

I don't think this is closing #742, the output is different for sure, but it's not fixed actually.

@herndlm
Copy link
Contributor Author

herndlm commented Jan 13, 2023

@drupol are you referring to the bot message you got in your issue (which is unrelated to this PR) or https://github.com/phpstan/phpstan-src/actions/runs/3910108845? :)

@drupol
Copy link
Contributor

drupol commented Jan 13, 2023

@drupol are you referring to the bot message you got in your issue (which is unrelated to this PR) or https://github.com/phpstan/phpstan-src/actions/runs/3910108845? :)

Sorry, here's the proper link: phpstan/phpstan#7423 I also fixed it in my previous message.

@herndlm
Copy link
Contributor Author

herndlm commented Jan 13, 2023

Sorry, here's the proper link: phpstan/phpstan#7423 I also fixed it in my previous message.

yeah and why do you mean this PR does not fix it? the bot message there is unrelated. but in https://github.com/phpstan/phpstan-src/actions/runs/3910108845 I can see no errors for PHP 8+ because the type should be correct now? and that is also checked via regression test here.

@drupol
Copy link
Contributor

drupol commented Jan 13, 2023

Oh if it's unrelated, please ignore my message then.

Sorry for the noise.

@herndlm herndlm changed the base branch from 1.9.x to 1.10.x January 20, 2023 16:53
@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch 2 times, most recently from f6fb159 to aad632d Compare January 22, 2023 09:15
@herndlm
Copy link
Contributor Author

herndlm commented Jan 24, 2023

Did we come to a conclusion about this one? :) I rebased it on 1.10.x in the meantime. Thought the behavior change might be better there. In case it gets merged. So far it still makes sense to me

@herndlm herndlm force-pushed the do-not-merge-benevolent-union-with-union branch 3 times, most recently from 70d77ae to 9d76dfa Compare March 3, 2023 17:10
@herndlm
Copy link
Contributor Author

herndlm commented May 23, 2023

Going to rebase this one tomorrow morning. Is there anything else I can do here? This still makes sense to me :)

@ondrejmirtes
Copy link
Member

@herndlm I have some issues with it. For example I've seen a type (X)|Y mentioned somewhere and I don't think that one-item union type makes sense... So I'm not sure how you're handling it. Last time I looked at this PR it felt a bit undertested.

For example if you compare int|stdClass|string and (int|string)|stdClass - when is the first one going to be accepted and the second one won't?

@herndlm herndlm marked this pull request as draft June 11, 2023 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants