From 690c4d5cc56028bec3b9f3651a384c5630b4d06d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 7 Jul 2016 11:44:14 -0700 Subject: [PATCH 1/3] For `Optional[T] or T`, infer the type `T` instead of `Optional[T]`. Fixes #1817, fixes #1733. --- mypy/checkexpr.py | 7 +++++++ test-data/unit/check-optional.test | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 872684db4df7..f206f13ae9d4 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1090,6 +1090,13 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: mypy.checker.find_isinstance_check(e.left, self.chk.type_map, self.chk.typing_mode_weak()) elif e.op == 'or': + # Special case for `Optional[T] or T` -- this should infer T. + # If the type is a Union containing None, remove the None. + if isinstance(left_type, UnionType): + items = [i for i in left_type.items if not isinstance(i, NoneTyp)] + if len(items) < len(left_type.items): + left_type = UnionType.make_simplified_union(items) + _, right_map = \ mypy.checker.find_isinstance_check(e.left, self.chk.type_map, self.chk.typing_mode_weak()) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 9e842d38aafc..7c77ef299fa9 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -175,3 +175,33 @@ def f(x: None) -> str: pass def f(x: int) -> int: pass reveal_type(f(None)) # E: Revealed type is 'builtins.str' reveal_type(f(0)) # E: Revealed type is 'builtins.int' + +[case testOptionalTypeOrTypePlain] +from typing import Optional +def f(a: Optional[int]) -> int: + return a or 0 +[out] + +[case testOptionalTypeOrTypeTypeVar] +from typing import Optional, TypeVar +T = TypeVar('T') +def f(a: Optional[T], b: T) -> T: + return a or b +[out] + +[case testOptionalTypeOrTypeNoTriggerPlain] +from typing import Optional +def f(a: Optional[int], b: int) -> int: + return b or a +[out] +main: note: In function "f": +main:3: error: Incompatible return value type (got "Optional[int]", expected "int") + +[case testOptionalTypeOrTypeNoTriggerTypeVar] +from typing import Optional, TypeVar +T = TypeVar('T') +def f(a: Optional[T], b: T) -> T: + return b or a +[out] +main: note: In function "f": +main:4: error: Incompatible return value type (got "Optional[T]", expected "T") From 0e388391bd791ec741744febf459cf6a87c08472 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 7 Jul 2016 12:16:53 -0700 Subject: [PATCH 2/3] More test: both operands optional; left operand complex union. --- test-data/unit/check-optional.test | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 7c77ef299fa9..886aecfd2aab 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -189,6 +189,26 @@ def f(a: Optional[T], b: T) -> T: return a or b [out] +[case testOptionalTypeOrTypeBothOptional] +from typing import Optional +def f(a: Optional[int], b: Optional[int]) -> None: + reveal_type(a or b) +def g(a: int, b: Optional[int]) -> None: + reveal_type(a or b) +[out] +main: note: In function "f": +main:3: error: Revealed type is 'Union[builtins.int, builtins.None]' +main: note: In function "g": +main:5: error: Revealed type is 'Union[builtins.int, builtins.None]' + +[case testOptionalTypeOrTypeComplexUnion] +from typing import Union +def f(a: Union[int, str, None]) -> None: + reveal_type(a or 'default') +[out] +main: note: In function "f": +main:3: error: Revealed type is 'Union[builtins.int, builtins.str]' + [case testOptionalTypeOrTypeNoTriggerPlain] from typing import Optional def f(a: Optional[int], b: int) -> int: From d94af2bf28cf75ac9ad00172a52cf58d049f99cc Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 7 Jul 2016 12:20:49 -0700 Subject: [PATCH 3/3] Special-case `None or T` as well (though the code is separate). --- mypy/checkexpr.py | 12 ++++++++++-- test-data/unit/check-optional.test | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f206f13ae9d4..96f2e1e711e2 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1097,6 +1097,10 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: if len(items) < len(left_type.items): left_type = UnionType.make_simplified_union(items) + # Special case for `None or T` -- this should infer T. + if isinstance(left_type, NoneTyp): + left_type = None + _, right_map = \ mypy.checker.find_isinstance_check(e.left, self.chk.type_map, self.chk.typing_mode_weak()) @@ -1110,9 +1114,13 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: right_type = self.accept(e.right, left_type) - self.check_not_void(left_type, context) + if left_type is not None: + self.check_not_void(left_type, context) self.check_not_void(right_type, context) - return UnionType.make_simplified_union([left_type, right_type]) + if left_type is None: + return right_type + else: + return UnionType.make_simplified_union([left_type, right_type]) def check_list_multiply(self, e: OpExpr) -> Type: """Type check an expression of form '[...] * e'. diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 886aecfd2aab..d63dcebe419a 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -225,3 +225,18 @@ def f(a: Optional[T], b: T) -> T: [out] main: note: In function "f": main:4: error: Incompatible return value type (got "Optional[T]", expected "T") + +[case testNoneOrStringIsString] +def f() -> str: + a = None + b = '' + return a or b +[out] + +[case testNoneOrTypeVarIsTypeVar] +from typing import TypeVar +T = TypeVar('T') +def f(b: T) -> T: + a = None + return a or b +[out]