Fix recursion issue with nested instances and unions - #9663
Conversation
JukkaL
left a comment
There was a problem hiding this comment.
Thanks for the PR and the detailed explanation!
This seems to regress some supported use cases. I left an alternative idea below.
|
As suggested by JukkaL, I have changed from I have added three additional tests to check if |
JukkaL
left a comment
There was a problem hiding this comment.
Thanks for the updates! Looks good now. It would be good to if you can add a unit test for is_same_type (but not necessary), as explained in my previous comment.
|
Sorry for the delay. I have now added the test in what I think is the best place in the file. I ensured that the test fails on the older solution locally. I hope this is the correct place to add the test, and thank you for helping me find the file in the first place. |
|
Thanks for a well-explained issue report and subsequent fix to a tricky bug! |
Description
Fixes #9657
Test Plan
I have added three new tests to
test-data/unit/check-unions.testtestNestedProtocolUnionsThis is an exact copy of the MVCE I included in the issue.
testNestedProtocolGenericUnionsAt first I 'fixed' the issue by not running the
forifleftis a protocol inmypy.subtypes:is_protocol_implementation. I however noticed that this would still cause the same issue if we changed toIter = Union[Iterator[T], List[T]]. And all I'd have done is add a subtle bug. This is just the same astestNestedProtocolUnionsbut to account for non-protocols.testNestedProtocolGenericUnionsDeepThis tests that we can go 5
Iters deep. This is important as we can fix the bug at 3 levels by adding the following tomypy.sametypes:is_same_type.My changes pass all the tests ran via
./runtests.py.Origin of the Bug
The issue comes from assuming in
mypy.subtypes:is_protocol_implementation.The assumed type is an instance that has a union as its argument.
mypy.sametypes:is_same_typeworks fine for theInstance.We check the
Unions are the same type, again withmypy.sametypes:is_same_type.This falls apart as we then call
simplify_unionwhich goes on to callmypy.subtypes:is_protocol_implementation.In this second call to
is_protocol_implementationwhat we assume the type to be stays the same. This causes problems because we're constantly simplifying what we assume the type to be against itself.To circumvent this semi-infinite loop I've forced the left value to always be assumed to be a simplified union. This is risky as this is a new assumption. It seems to work, but I'm unsure if this is because of a lack of tests that'd put a non-simplified union on the left side.
Alternates
I can think of two different solutions that don't add an assumption to
mypy.sametypes:is_same_type.mypy.subtypes:is_protocol_implementationto correctly handle the recursion. I don't know how to implement this.