8000 Fix make_simplified_union interaction with Any · python/mypy@69b42d6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 69b42d6

Browse files
ddfisherJukkaL
authored andcommitted
Fix make_simplified_union interaction with Any
(This is an updated version of #2197 by ddfisher.) make_simplified_union had two incorrect interactions with Any that this fixes: 1) Any unions containing Any were simplified to Any, which is incorrect. 2) Any classes that inherited from Any would be removed from the union by the subclass simplification (because subclasses of Any are considered subclasses of all other classes due to subtype/compatible with confusion). Note that `Union`s call make_union and not make_simplified_union, so this doesn't change their behavior directly (unless they interact with something else which causes them to be simplified, like being part of an `Optional`).
1 parent 234297c commit 69b42d6

9 files changed

+133
-19
lines changed

mypy/checkmember.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,17 @@ def analyze_member_access(name: str,
107107
elif isinstance(typ, UnionType):
108108
# The base object has dynamic type.
109109
msg.disable_type_names += 1
110+
old_num_messages = msg.num_messages()
110111
results = [analyze_member_access(name, subtype, node, is_lvalue, is_super,
111112
is_operator, builtin_type, not_ready_callback, msg,
112113
original_type=original_type, chk=chk)
113114
for subtype in typ.items]
114115
msg.disable_type_names -= 1
116+
if msg.num_messages() != old_num_messages and any(isinstance(t, AnyType)
117+
for t in results):
118+
# If there was an error, return AnyType to avoid generating multiple messages for the
119+
# same error.
120+
return AnyType()
115121
return UnionType.make_simplified_union(results)
116122
elif isinstance(typ, TupleType):
117123
# Actually look up from the fallback instance type.

mypy/meet.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,19 @@ def meet_types(s: Type, t: Type) -> Type:
2525
return t.accept(TypeMeetVisitor(s))
2626

2727

28-
def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type:
28+
def meet_simple(s: Type, t: Type) -> Type:
29+
"""Return the type s "narrowed down" to t.
30+
31+
Note that this is not symmetric with respect to s and t.
32+
33+
TODO: Explain what this does in more detail and how this is
34+
different from meet_types.
35+
"""
2936
if s == t:
3037
return s
38+
if isinstance(t, AnyType):
39+
# Anything can be narrowed down to Any.
40+
return t
3141
if isinstance(s, UnionType):
3242
return UnionType.make_simplified_union([meet_types(x, t) for x in s.items])
3343
elif not is_overlapping_types(s, t, use_promotions=True):
@@ -36,10 +46,7 @@ def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type:
3646
else:
3747
return NoneTyp()
3848
else:
39-
if default_right:
40-
return t
41-
else:
42-
return s
49+
return t
4350

4451

4552
def is_overlapping_types(t: Type, s: Type, use_promotions: bool = False) -> bool:

mypy/messages.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ def enable_errors(self) -> None:
141141
def is_errors(self) -> bool:
142142
return self.errors.is_errors()
143143

144+
def num_messages(self) -> int:
145+
return self.errors.num_messages()
146+
144147
def report(self, msg: str, context: Context, severity: str,
145148
file: str = None, origin: Context = None) -> None:
146149
"""Report an error or note (unless disabled)."""

mypy/types.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,17 +1030,28 @@ def make_simplified_union(items: List[Type], line: int = -1, column: int = -1) -
10301030
all_items.append(typ)
10311031
items = all_items
10321032

1033-
if any(isinstance(typ, AnyType) for typ in items):
1034-
return AnyType()
1035-
10361033
from mypy.subtypes import is_subtype
1034+
from mypy.sametypes import is_same_type
1035+
1036+
def is_any_like(typ: Type) -> bool:
1037+
return (isinstance(typ, AnyType) or
1038+
(isinstance(typ, Instance) and typ.type.fallback_to_any))
1039+
10371040
removed = set() # type: Set[int]
10381041
for i, ti in enumerate(items):
10391042
if i in removed: continue
10401043
# Keep track of the truishness info for deleted subtypes which can be relevant
10411044
cbt = cbf = False
10421045
for j, tj in enumerate(items):
1043-
if i != j and is_subtype(tj, ti):
1046+
# Attempt to only combine true subtypes by avoiding types containing Any.
1047+
# TODO: Properly exclude generics and functions.
1048+
is_either_anylike = is_any_like(ti) or is_any_like(tj)
1049+
if (i != j
1050+
and is_subtype(tj, ti)
1051+
and (not is_either_anylike or
1052+
is_same_type(ti, tj) or
1053+
(isinstance(tj, NoneTyp)
1054+
and not experiments.STRICT_OPTIONAL))):
10441055
removed.add(j)
10451056
cbt = cbt or tj.can_be_true
10461057
cbf = cbf or tj.can_be_false

test-data/unit/check-dynamic-typing.test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ n = 0
7979
d in a # E: Unsupported right operand type for in ("A")
8080
d and a
8181
d or a
82-
c = d and b # Unintuitive type inference?
83-
c = d or b # Unintuitive type inference?
82+
c = d and b # E: Incompatible types in assignment (expression has type "Union[Any, bool]", variable has type "C")
83+
c = d or b # E: Incompatible types in assignment (expression has type "Union[Any, bool]", variable has type "C")
8484

8585
c = d + a
8686
c = d - a
@@ -123,8 +123,8 @@ n = 0
123123
a and d
124124
a or d
125125
c = a in d
126-
c = b and d # Unintuitive type inference?
127-
c = b or d # Unintuitive type inference?
126+
c = b and d # E: Incompatible types in assignment (expression has type "Union[bool, Any]", variable has type "C")
127+
c = b or d # E: Incompatible types in assignment (expression has type "Union[bool, Any]", variable has type "C")
128128
b = a + d
129129
b = a / d
130130

test-data/unit/check-generics.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ if not isinstance(s, str):
777777

778778
z = None # type: TNode # Same as TNode[Any]
779779
z.x
780-
z.foo() # Any simplifies Union to Any now. This test should be updated after #2197
780+
z.foo() # E: Some element of union has no attribute "foo"
781781

782782
[builtins fixtures/isinstance.pyi]
783783

test-data/unit/check-optional.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,3 +525,26 @@ f = None # type: Optional[Callable[[int], None]]
525525
f = lambda x: None
526526
f(0)
527527
[builtins fixtures/function.pyi]
528+
529+
[case testOptionalAndAnyBaseClass]
530+
from typing import Any, Optional
531+
class C(Any):
532+
pass
533+
x = None # type: Optional[C]
534+
x.foo() # E: Some element of union has no attribute "foo"
535+
536+
[case testUnionSimplificationWithStrictOptional]
537+
from typing import Any, TypeVar, Union
538+
class C(Any): pass
539+
T = TypeVar('T')
540+
S = TypeVar('S')
541+
def u(x: T, y: S) -> Union[S, T]: pass
542+
a = None # type: Any
543+
544+
# Test both orders
545+
reveal_type(u(C(), None)) # E: Revealed type is 'Union[builtins.None, __main__.C*]'
546+
reveal_type(u(None, C())) # E: Revealed type is 'Union[__main__.C*, builtins.None]'
547+
reveal_type(u(a, None)) # E: Revealed type is 'Union[builtins.None, Any]'
548+
reveal_type(u(None, a)) # E: Revealed type is 'Union[Any, builtins.None]'
549+
reveal_type(u(1, None)) # E: Revealed type is 'Union[builtins.None, builtins.int*]'
550+
reveal_type(u(None, 1)) # E: Revealed type is 'Union[builtins.int*, builtins.None]'

test-data/unit/check-statements.test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -630,11 +630,11 @@ try:
630630
except BaseException as e1:
631631
reveal_type(e1) # E: Revealed type is 'builtins.BaseException'
632632
except (E1, BaseException) as e2:
633-
reveal_type(e2) # E: Revealed type is 'Any'
633+
reveal_type(e2) # E: Revealed type is 'Union[Any, builtins.BaseException]'
634634
except (E1, E2) as e3:
635-
reveal_type(e3) # E: Revealed type is 'Any'
635+
reveal_type(e3) # E: Revealed type is 'Union[Any, __main__.E2]'
636636
except (E1, E2, BaseException) as e4:
637-
reveal_type(e4) # E: Revealed type is 'Any'
637+
reveal_type(e4) # E: Revealed type is 'Union[Any, builtins.BaseException]'
638638

639639
try: pass
640640
except E1 as e1:

test-data/unit/check-unions.test

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def f(x: Union[int, str]) -> None:
3737
[case testUnionAnyIsInstance]
3838
from typing import Any, Union
3939

40-
def func(v:Union[int, Any]) -> None:
40+
def func(v: Union[int, Any]) -> None:
4141
if isinstance(v, int):
4242
reveal_type(v) # E: Revealed type is 'builtins.int'
4343
else:
@@ -61,7 +61,8 @@ y = w.y
6161
z = w.y # E: Incompatible types in assignment (expression has type "int", variable has type "str")
6262
w.y = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
6363
y = x.y # E: Some element of union has no attribute "y"
64-
z = x.y # E: Some element of union has no attribute "y"
64+
v = x.y # E: Some element of union has no attribute "y"
65+
reveal_type(v) # E: Revealed type is 'Any'
6566

6667
[builtins fixtures/isinstance.pyi]
6768

@@ -168,3 +169,66 @@ if foo():
168169
def g(x: Union[int, str, bytes]) -> None: pass
169170
else:
170171
def g(x: Union[int, str]) -> None: pass # E: All conditional function variants must have identical signatures
172+
173+
[case testUnionSimplificationSpecialCases]
174+
from typing import Any, TypeVar, Union
175+
176+
class C(Any): pass
177+
178+
T = TypeVar('T')
179+
S = TypeVar('S')
180+
def u(x: T, y: S) -> Union[S, T]: pass
181+
182+
a = None # type: Any
183+
184+
# Base-class-Any and None, simplify
185+
reveal_type(u(C(), None)) # E: Revealed type is '__main__.C*'
186+
reveal_type(u(None, C())) # E: Revealed type is '__main__.C*'
187+
188+
# Normal instance type and None, simplify
189+
reveal_type(u(1, None)) # E: Revealed type is 'builtins.int*'
190+
reveal_type(u(None, 1)) # E: Revealed type is 'builtins.int*'
191+
192+
# Normal instance type and base-class-Any, no simplification
193+
reveal_type(u(C(), 1)) # E: Revealed type is 'Union[builtins.int*, __main__.C*]'
194+
reveal_type(u(1, C())) # E: Revealed type is 'Union[__main__.C*, builtins.int*]'
195+
196+
# Normal instance type and Any, no simplification
197+
reveal_type(u(1, a)) # E: Revealed type is 'Union[Any, builtins.int*]'
198+
reveal_type(u(a, 1)) # E: Revealed type is 'Union[builtins.int*, Any]'
199+
200+
# Any and base-class-Any, no simplificaiton
201+
reveal_type(u(C(), a)) # E: Revealed type is 'Union[Any, __main__.C*]'
202+
reveal_type(u(a, C())) # E: Revealed type is 'Union[__main__.C*, Any]'
203+
204+
# Two normal instance types, simplify
205+
reveal_type(u(1, object())) # E: Revealed type is 'builtins.object*'
206+
reveal_type(u(object(), 1)) # E: Revealed type is 'builtins.object*'
207+
208+
# Two normal instance types, no simplification
209+
reveal_type(u(1, '')) # E: Revealed type is 'Union[builtins.str*, builtins.int*]'
210+
reveal_type(u('', 1)) # E: Revealed type is 'Union[builtins.int*, builtins.str*]'
211+
212+
[case testUnionSimplificationWithDuplicateItems]
213+
from typing import Any, TypeVar, Union
214+
215+
class C(Any): pass
216+
217+
T = TypeVar('T')
218+
S = TypeVar('S')
219+
R = TypeVar('R')
220+
def u(x: T, y: S, z: R) -> Union[R, S, T]: pass
221+
222+
a = None # type: Any
223+
224+
reveal_type(u(1, 1, 1)) # E: Revealed type is 'builtins.int*'
225+
reveal_type(u(C(), C(), None)) # E: Revealed type is '__main__.C*'
226+
reveal_type(u(a, a, 1)) # E: Revealed type is 'Union[builtins.int*, Any]'
227+
reveal_type(u(a, C(), a)) # E: Revealed type is 'Union[Any, __main__.C*]'
228+
reveal_type(u('', 1, 1)) # E: Revealed type is 'Union[builtins.int*, builtins.str*]'
229+
230+
[case testUnionAndBinaryOperation]
231+
from typing import Union
232+
class A: pass
233+
def f(x: Union[int, str, A]):
234+
x + object() # E: Unsupported left operand type for + (some union)

0 commit comments

Comments
 (0)
0