From adefaea0cdc36de91d02c797818ed014f370db0c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 22 May 2018 12:23:26 -0700 Subject: [PATCH 1/2] Don't always infer unions for conditional expressions Use a join for type of a conditional expression unless the type context is a union type. A recent change (#5041) modified the type to always use a union, but this caused many problems in internal Dropbox repos that we aren't ready to fix yet. --- mypy/checkexpr.py | 9 ++++++++- test-data/unit/check-expressions.test | 19 ++++++++++++++----- test-data/unit/check-functions.test | 4 ++-- test-data/unit/check-optional.test | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0e349a436dab..d7905ddb348b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2357,7 +2357,14 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type: # branch's type. else_type = self.analyze_cond_branch(else_map, e.else_expr, context=if_type) - res = UnionType.make_simplified_union([if_type, else_type]) + # Only create a union type if the type context is a union to be mostly + # compatibile with older mypy versions where always did a join. + # + # TODO: Always create a union or at least in more cases? + if isinstance(self.type_context[-1], UnionType): + res = UnionType.make_simplified_union([if_type, else_type]) + else: + res = join.join_types(if_type, else_type) return res diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 193b6cd5233f..5772238e3fa3 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1449,8 +1449,11 @@ x = ['x'] # E: List item 0 has incompatible type "str"; expected "int" [builtins fixtures/list.pyi] [case testConditionalExpressionUnion] +from typing import Union reveal_type(1 if bool() else 2) # E: Revealed type is 'builtins.int' -reveal_type(1 if bool() else '') # E: Revealed type is 'Union[builtins.int, builtins.str]' +reveal_type(1 if bool() else '') # E: Revealed type is 'builtins.object' +x: Union[int, str] = reveal_type(1 if bool() else '') \ + # E: Revealed type is 'Union[builtins.int, builtins.str]' class A: pass class B(A): @@ -1464,12 +1467,18 @@ b = B() c = C() d = D() reveal_type(a if bool() else b) # E: Revealed type is '__main__.A' -reveal_type(b if bool() else c) # E: Revealed type is 'Union[__main__.B, __main__.C]' -reveal_type(c if bool() else b) # E: Revealed type is 'Union[__main__.C, __main__.B]' -reveal_type(c if bool() else a) # E: Revealed type is 'Union[__main__.C, __main__.A]' -reveal_type(d if bool() else b) # E: Revealed type is 'Union[__main__.D, __main__.B]' +reveal_type(b if bool() else c) # E: Revealed type is 'builtins.object' +reveal_type(c if bool() else b) # E: Revealed type is 'builtins.object' +reveal_type(c if bool() else a) # E: Revealed type is 'builtins.object' +reveal_type(d if bool() else b) # E: Revealed type is '__main__.A' [builtins fixtures/bool.pyi] +[case testConditionalExpressionUnionWithAny] +from typing import Union, Any +a: Any +x: Union[int, str] = reveal_type(a if int() else 1) # E: Revealed type is 'Union[Any, builtins.int]' +reveal_type(a if int() else 1) # E: Revealed type is 'Any' + -- Special cases -- ------------- diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index cbad4baa95a2..6817b110b3e0 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -2064,8 +2064,8 @@ def f() -> None: def g(x: int) -> None: pass h = f if bool() else g -reveal_type(h) # E: Revealed type is 'Union[def (), def (x: builtins.int)]' -h(7) # E: Too many arguments for "f" +reveal_type(h) # E: Revealed type is 'builtins.function' +h(7) # E: Cannot call function of unknown type [builtins fixtures/bool.pyi] -- Positional-only arguments diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 44c83f973a5e..55345d11b996 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -385,7 +385,7 @@ def lookup_field(name, obj): attr = None [case testTernaryWithNone] -reveal_type(None if bool() else 0) # E: Revealed type is 'Union[None, builtins.int]' +reveal_type(None if bool() else 0) # E: Revealed type is 'Union[builtins.int, None]' [builtins fixtures/bool.pyi] [case testListWithNone] From 0280a6494b8c5831ae4131074284f62a1970fda8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Tue, 22 May 2018 13:41:49 -0700 Subject: [PATCH 2/2] Update comment --- mypy/checkexpr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d7905ddb348b..463d274b02df 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2357,8 +2357,8 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type: # branch's type. else_type = self.analyze_cond_branch(else_map, e.else_expr, context=if_type) - # Only create a union type if the type context is a union to be mostly - # compatibile with older mypy versions where always did a join. + # Only create a union type if the type context is a union, to be mostly + # compatible with older mypy versions where we always did a join. # # TODO: Always create a union or at least in more cases? if isinstance(self.type_context[-1], UnionType):