-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
[Reverted] Fixes to union simplification, isinstance and more #3025
Conversation
The main change is that unions containing Any are no longer simplified to just Any. This required changes in various other places to keep the existing semantics, and resulted in some fixes to existing test cases.
Fix #1914.
We generally don't have fully constructed TypeInfos so we can't do proper union simplification. Just implement simple-minded simplifaction that deals with the cases we care about.
mypy/subtypes.py
Outdated
return UnionType.make_union(new_items) | ||
else: | ||
return t | ||
|
||
|
||
def is_proper_subtype(t: Type, s: Type) -> bool: | ||
def is_proper_subtype(left: Type, right: Type) -> bool: |
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.
t
and s
should also be replaced with left
and right
in the docstring below.
reveal_type(u(object(), t_a)) # E: Revealed type is 'builtins.object*' | ||
|
||
# Union between type objects | ||
reveal_type(u(t_o, t_a)) # E: Revealed type is 'Union[Type[Any], Type[builtins.object]]' |
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.
Union[Type[Any], Type[object]]
and Union[type, Type[object]]
should give the same result, since type
is equivalent to Type[Any]
(at least it is what PEP 484 says). Maybe this could be fixed by adding a special case in spirit of #3012 at the end of visit_type_type
.
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.
This is not feasible since Type[Any]
and type
have different internal representations. We can't have two different types being proper subtypes of each other unless they are the same type -- otherwise the subtyping relation doesn't form a lattice.
test-data/unit/check-optional.test
Outdated
reveal_type(u(C(), None)) # E: Revealed type is 'Union[builtins.None, __main__.C*]' | ||
reveal_type(u(None, C())) # E: Revealed type is 'Union[__main__.C*, builtins.None]' | ||
reveal_type(u(a, None)) # E: Revealed type is 'Union[builtins.None, Any]' | ||
reveal_type(u(None, a)) # E: Revealed type is 'Union[Any, builtins.None]' |
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.
These two lines are duplicated here and in the test case above.
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.
Oh, wait, this test case almost entirely duplicates previous one. Is this intentional? (I suppose both cases are run with --strict-optional
)
mypy/subtypes.py
Outdated
return False | ||
|
||
def visit_typeddict_type(self, left: TypedDictType) -> bool: | ||
# TODO: Does it make sense to support TypedDict here? |
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.
If this is supposed to be used only with isinstance
and issubclass
, then no, since they fail at runtime for TypedDict
s.
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.
They're also used for union simplification so we still need to do this.
mypy/subtypes.py
Outdated
if isinstance(right, TypeType): | ||
return is_proper_subtype(left.item, right.item) | ||
if isinstance(right, CallableType): | ||
# This is unsound, we don't check the __init__ signature. |
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.
(And so is the previous case (TypeType).)
mypy/subtypes.py
Outdated
# This is unsound, we don't check the __init__ signature. | ||
return right.is_type_obj() and is_proper_subtype(left.item, right.ret_type) | ||
if isinstance(right, Instance): | ||
if right.type.fullname() in ('builtins.type', 'builtins.object'): |
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.
I've got a feeling this means that issubclass(Type[int], type)
and issubclass(Type[int], Type)
will not be treated the same. (Those expressions aren't valid literally, but I think a similar situation can arise in other ways, or perhaps using isinstance(x, type)
vs. isinstance(x, Type)
.)
mypy/typeanal.py
Outdated
return t | ||
if isinstance(t, NoneTyp): | ||
return t | ||
if isinstance(t, UnionType) and any(isinstance(item, NoneTyp) |
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.
Do we always flatten unions? Otherwise this could be fooled by Union[X, Union[Y, None]]
. (Not that I'm sure it matters.)
mypy/subtypes.py
Outdated
return True | ||
|
||
def visit_erased_type(self, left: ErasedType) -> bool: | ||
return True |
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.
Maybe assert False instead?
mypy/subtypes.py
Outdated
self.right = right | ||
|
||
def visit_unbound_type(self, left: UnboundType) -> bool: | ||
return True |
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.
Maybe assert False instead?
reveal_type(x) | ||
[out] | ||
tmp/b.py:6: error: Revealed type is 'a.X' | ||
tmp/b.py:8: error: Revealed type is 'Tuple[Any, fallback=a.X]' |
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.
Might be worth adding another test that just uses N instead of X everywhere.
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.
This doesn't work because of #3054.
test-data/unit/check-optional.test
Outdated
|
||
[case testOptionalAndAnyBaseClass] | ||
from typing import Any, Optional | ||
class C(Any): |
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.
Should use a value of type Any as base class, since just inheriting from Any could be statically rejected.
test-data/unit/check-optional.test
Outdated
|
||
[case testUnionSimplificationWithStrictOptional] | ||
from typing import Any, TypeVar, Union | ||
class C(Any): pass |
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.
Ditto.
test-data/unit/check-optional.test
Outdated
x = None # type: Optional[C] | ||
x.foo() # E: Some element of union has no attribute "foo" | ||
|
||
[case testUnionSimplificationWithStrictOptional] |
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.
Test name should be more like "certain unions are not simplified"
test-data/unit/check-optional.test
Outdated
[case testIsinstanceAndOptionalAndAnyBase] | ||
from typing import Any, Optional | ||
|
||
class A(Any): pass |
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.
Also use a base of type Any.
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.
I'm afraid you're going to be mostly on your own here, I don't really understand how all this code hangs together. :-(
@@ -911,7 +911,8 @@ def foo() -> Union[int, str, A]: pass | |||
|
|||
def bar() -> None: | |||
x = foo() | |||
x + 1 # E: Unsupported left operand type for + (some union) | |||
x + 1 # E: Unsupported left operand type for + (some union) \ | |||
# E: Unsupported operand types for + (likely involving Union) |
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.
Really? Two errors, both vaguely mentioning "union" without indicating which union item(s) are involved?
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.
Yeah, this is unfortunate, but this wasn't trivial to fix. I'll create a follow-up issue to address the duplicate messages (and maybe generally improve error messages related to union types.
if isinstance(x, type): | ||
reveal_type(x) # E: Revealed type is 'Type[builtins.int]' | ||
else: | ||
reveal_type(x) # Unreachable |
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.
Which reminds me (OT), maybe be should have a debugging flag that warns when a block is deemed unreachable?
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.
That could be nice. There are some cases when this is valid, foe example when type checking AnyStr
some blocks are often unreachable for either str
or bytes
, so the required logic may be somewhat involved.
def visit_instance(self, left: Instance) -> bool: | ||
right = self.right | ||
if isinstance(right, Instance): | ||
for base in left.type.mro: |
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.
This breaks on the code snippet from #3069: when left
refers to a NamedTuple
, left.type.mro
is None
. I'm guessing it can be fixed by adding mro to instances created by NamedTuple
?
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.
If mro
is None
during type checking a lot of things might break. The right fix seems to be to populate mro
always instead of papering over it here. Named tuple initialization is currently broken in some contexts (see #3054, for example).
Whee! Thanks. |
I found a major bug when experimenting with real-world codebases. This reverts commit 67cda6e.
The main change is that unions containing Any are no longer simplified to just Any. Also union simplification now has a deterministic result unlike previously, when result could depend on the order of items in a union (this is true modulo remaining bugs). This required changes in various other places to keep the existing semantics, and resulted in some fixes to existing test cases. I also had to fix some tangentially related minor bugs that were triggered by the other changes. We generally don't have fully constructed TypeInfos so we can't do proper union simplification during semantic analysis. Just implement simple-minded simplification that deals with the cases we care about. Fixes #2978. Fixes #1914.
* Fixes to union simplification, isinstance and more (#3025) The main change is that unions containing Any are no longer simplified to just Any. Also union simplification now has a deterministic result unlike previously, when result could depend on the order of items in a union (this is true modulo remaining bugs). This required changes in various other places to keep the existing semantics, and resulted in some fixes to existing test cases. I also had to fix some tangentially related minor bugs that were triggered by the other changes. We generally don't have fully constructed TypeInfos so we can't do proper union simplification during semantic analysis. Just implement simple-minded simplification that deals with the cases we care about. Fixes #2978. Fixes #1914. * Fix strict optional and partial type special case and fix test * Fix lint * Make is_subtype and is_proper_subtype do promotion the same way Fixes #1850. * Drop reference to ErrorType (recently expunged) * Fix merge mistake * Fix another merge mistake * Update based on review * Add test case * Fix redundant TODO due to copy paste
The main change is that unions containing Any are no longer simplified
to just Any. Use proper subtype check for union simplification and
implement proper subtype checks more fully.
This required changes in various other places to keep the existing
semantics and to avoid regressions and inconsistencies.
Fixes #1914.
Fixes #2978.