From 61ff8a511bb87dae9b2da1a79f67ad782882a521 Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 03:33:47 +0300 Subject: [PATCH 01/11] Add check for `None in list/set/tuple` and refine the types of the variables to not include None --- mypy/checker.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9c10cd2fc30d..80aff0f10aa2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -133,6 +133,7 @@ WithStmt, YieldExpr, is_final_node, + SetExpr, ) from mypy.operators import flip_ops, int_op_to_method, neg_ops from mypy.options import PRECISE_TUPLE_TYPES, Options @@ -5928,20 +5929,36 @@ def has_no_custom_eq_checks(t: Type) -> bool: ): if_map[operands[left_index]] = remove_optional(item_type) + right = operands[right_index] if right_index in narrowable_operand_index_to_hash: if_type, else_type = self.conditional_types_for_iterable( item_type, iterable_type ) - expr = operands[right_index] if if_type is None: if_map = None else: - if_map[expr] = if_type + if_map[right] = if_type if else_type is None: else_map = None else: - else_map[expr] = else_type + else_map[right] = else_type + # check for `None in ` + if ( + isinstance(item_type, NoneType) + # if the iterable has `None` in it then the condition is worthless + and not any(is_literal_none(i) for i in right.items) + and isinstance(right, (ListExpr, TupleExpr, SetExpr)) + ): + for item_in_right_collection in right.items: + item_in_right_collection_type = self.lookup_type( + item_in_right_collection + ) + # Remove the option of the current item to be `None` for the entire else scope + if is_overlapping_none(item_in_right_collection_type): + else_map[item_in_right_collection] = remove_optional( + item_in_right_collection_type + ) else: if_map = {} else_map = {} From af0ae37984db2323347e7a5f9b1de4fccafe6f30 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 00:42:47 +0000 Subject: [PATCH 02/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 80aff0f10aa2..e6c4aca9b549 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -115,6 +115,7 @@ RaiseStmt, RefExpr, ReturnStmt, + SetExpr, StarExpr, Statement, StrExpr, @@ -133,7 +134,6 @@ WithStmt, YieldExpr, is_final_node, - SetExpr, ) from mypy.operators import flip_ops, int_op_to_method, neg_ops from mypy.options import PRECISE_TUPLE_TYPES, Options From f7905a094fd898cd079a04eed07a9825346aa58c Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 13:47:16 +0300 Subject: [PATCH 03/11] change the `any(is_literal_none(i) for i in right.items)` to be checked at the end of the if condition (preventing exceptions if right has no attribute "items") --- mypy/checker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 80aff0f10aa2..d12c16512201 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3413,8 +3413,8 @@ def check_final(self, s: AssignmentStmt | OperatorAssignmentStmt | AssignmentExp if ( lv.node.final_unset_in_class and not lv.node.final_set_in_init - and not self.is_stub - and # It is OK to skip initializer in stub files. + and not self.is_stub # It is OK to skip initializer in stub files. + and # Avoid extra error messages, if there is no type in Final[...], # then we already reported the error about missing r.h.s. isinstance(s, AssignmentStmt) @@ -5946,9 +5946,9 @@ def has_no_custom_eq_checks(t: Type) -> bool: # check for `None in ` if ( isinstance(item_type, NoneType) + and isinstance(right, (ListExpr, TupleExpr, SetExpr)) # if the iterable has `None` in it then the condition is worthless and not any(is_literal_none(i) for i in right.items) - and isinstance(right, (ListExpr, TupleExpr, SetExpr)) ): for item_in_right_collection in right.items: item_in_right_collection_type = self.lookup_type( From d5348c4d32e06e1e636cc26738019f6d9ba7f5ba Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 13:48:09 +0300 Subject: [PATCH 04/11] renaming and slight formatting --- mypy/checker.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d12c16512201..751af3081e9c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5950,15 +5950,11 @@ def has_no_custom_eq_checks(t: Type) -> bool: # if the iterable has `None` in it then the condition is worthless and not any(is_literal_none(i) for i in right.items) ): - for item_in_right_collection in right.items: - item_in_right_collection_type = self.lookup_type( - item_in_right_collection - ) + for i in right.items: + i_type = self.lookup_type(i) # Remove the option of the current item to be `None` for the entire else scope - if is_overlapping_none(item_in_right_collection_type): - else_map[item_in_right_collection] = remove_optional( - item_in_right_collection_type - ) + if is_overlapping_none(i_type): + else_map[i] = remove_optional(i_type) else: if_map = {} else_map = {} From c09f73a5cf0e0f577784a345be3935f8c84990ac Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 14:15:14 +0300 Subject: [PATCH 05/11] Add tests for the new feature --- m_my_check.py | 53 +++++++++++++++++++++++++++ test-data/unit/check-optional.test | 57 ++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 m_my_check.py diff --git a/m_my_check.py b/m_my_check.py new file mode 100644 index 000000000000..33625ac5bf33 --- /dev/null +++ b/m_my_check.py @@ -0,0 +1,53 @@ +from typing import Optional, reveal_type + + +# def refine_types(a: Optional[int], b: Optional[str], c: Optional[float]) -> str: +# if None in [a, b, c]: +# return "One or more are None" +# else: +# reveal_type(a) # N: Revealed type is 'builtins.int' +# reveal_type(b) # N: Revealed type is 'builtins.str' +# reveal_type(c) # N: Revealed type is 'builtins.float' +# return f"{a}, {b}, {c}" +# +# +# a: Optional[int] = 5 +# b: Optional[str] = "hello" +# c: Optional[float] = None +# +# result = refine_types(a, b, c) +# reveal_type(result) # N: Revealed type is 'builtins.str' + + +# def refine_complex(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: +# # Test with complex conditions mixed with `None in` +# if None not in [a, b, c] and b + 3 != 5.5: +# reveal_type(a) # N: Revealed type is 'builtins.int' +# reveal_type(b) # N: Revealed type is 'builtins.float' +# reveal_type(c) # N: Revealed type is 'builtins.str' +# return f"{a}, {b}, {c}" +# reveal_type(a) # N: Revealed type is 'Union[builtins.int, None]' +# reveal_type(b) # N: Revealed type is 'Union[builtins.float, None]' +# reveal_type(c) # N: Revealed type is 'Union[builtins.str, None]' +# return "All are valid" +# +# +# a: Optional[int] = 5 +# b: Optional[float] = 10.3 +# c: Optional[str] = "cCCCc" +# +# result_complex = refine_complex(a, b, c) +# reveal_type(result_complex) # N: Revealed type is 'builtins.str' + + +def check_failure(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: + if None in [a, b, c]: + print(a + 3) # E: Unsupported operand types for + ("None" and "int") [operator] + print(b.is_integer()) # E: Item "None" of "float | None" has no attribute "is_integer" [union-attr] + print(c.upper()) # E: Item "None" of "str | None" has no attribute "upper" [union-attr] + return "None is present" + else: + print(a + 3) + print(b.is_integer()) + print(c.upper()) + return "All are valid" diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 70f3c4486e14..ca46fd3254e5 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -93,6 +93,63 @@ else: reveal_type(x) # N: Revealed type is "Any" [builtins fixtures/bool.pyi] +[case testRefinementAfterNoneInCheck] +from typing import Optional, List + +def refine_types(a: Optional[int], b: Optional[str], c: Optional[float]) -> str: + if None in [a, b, c]: + return "One or more are None" + else: + reveal_type(a) # N: Revealed type is 'builtins.int' + reveal_type(b) # N: Revealed type is 'builtins.str' + reveal_type(c) # N: Revealed type is 'builtins.float' + return f"{a}, {b}, {c}" + +a: Optional[int] = 5 +b: Optional[str] = "hello" +c: Optional[float] = None + +result = refine_types(a, b, c) +reveal_type(result) # N: Revealed type is 'builtins.str' + +[case testRefinementWithComplexCondition] +from typing import Optional, List + +def refine_complex(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: + # Test with complex conditions mixed with `None in` + if None not in [a, b, c] and b + 3 != 5.5: + reveal_type(a) # N: Revealed type is 'builtins.int' + reveal_type(b) # N: Revealed type is 'builtins.float' + reveal_type(c) # N: Revealed type is 'builtins.str' + return f"{a}, {b}, {c}" + reveal_type(a) # N: Revealed type is 'Union[builtins.int, None]' + reveal_type(b) # N: Revealed type is 'Union[builtins.float, None]' + reveal_type(c) # N: Revealed type is 'Union[builtins.str, None]' + return "All are valid" + +a: Optional[int] = 5 +b: Optional[float] = 10.3 +c: Optional[str] = "cCCCc" + +result_complex = refine_complex(a, b, c) +reveal_type(result_complex) # N: Revealed type is 'builtins.str' + +[case testRefinementFailureWhenNonePresent] +from typing import Optional, List + +def check_failure(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: + if None in [a, b, c]: + print(a + 3) # E: Unsupported operand types for + ("None" and "int") [operator] + print(b.is_integer()) # E: Item "None" of "float | None" has no attribute "is_integer" [union-attr] + print(c.upper()) # E: Item "None" of "str | None" has no attribute "upper" [union-attr] + return "None is present" + else: + print(a + 3) + print(b.is_integer()) + print(c.upper()) + return "All are valid" + + [case testOrCases] from typing import Optional x = None # type: Optional[str] From dbc86d2b8905947631d1b7c8db8bdb50b526d867 Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 14:15:47 +0300 Subject: [PATCH 06/11] Remove the small test file that I used --- m_my_check.py | 53 --------------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 m_my_check.py diff --git a/m_my_check.py b/m_my_check.py deleted file mode 100644 index 33625ac5bf33..000000000000 --- a/m_my_check.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Optional, reveal_type - - -# def refine_types(a: Optional[int], b: Optional[str], c: Optional[float]) -> str: -# if None in [a, b, c]: -# return "One or more are None" -# else: -# reveal_type(a) # N: Revealed type is 'builtins.int' -# reveal_type(b) # N: Revealed type is 'builtins.str' -# reveal_type(c) # N: Revealed type is 'builtins.float' -# return f"{a}, {b}, {c}" -# -# -# a: Optional[int] = 5 -# b: Optional[str] = "hello" -# c: Optional[float] = None -# -# result = refine_types(a, b, c) -# reveal_type(result) # N: Revealed type is 'builtins.str' - - -# def refine_complex(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: -# # Test with complex conditions mixed with `None in` -# if None not in [a, b, c] and b + 3 != 5.5: -# reveal_type(a) # N: Revealed type is 'builtins.int' -# reveal_type(b) # N: Revealed type is 'builtins.float' -# reveal_type(c) # N: Revealed type is 'builtins.str' -# return f"{a}, {b}, {c}" -# reveal_type(a) # N: Revealed type is 'Union[builtins.int, None]' -# reveal_type(b) # N: Revealed type is 'Union[builtins.float, None]' -# reveal_type(c) # N: Revealed type is 'Union[builtins.str, None]' -# return "All are valid" -# -# -# a: Optional[int] = 5 -# b: Optional[float] = 10.3 -# c: Optional[str] = "cCCCc" -# -# result_complex = refine_complex(a, b, c) -# reveal_type(result_complex) # N: Revealed type is 'builtins.str' - - -def check_failure(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: - if None in [a, b, c]: - print(a + 3) # E: Unsupported operand types for + ("None" and "int") [operator] - print(b.is_integer()) # E: Item "None" of "float | None" has no attribute "is_integer" [union-attr] - print(c.upper()) # E: Item "None" of "str | None" has no attribute "upper" [union-attr] - return "None is present" - else: - print(a + 3) - print(b.is_integer()) - print(c.upper()) - return "All are valid" From 285e883c4bf67d336ca4499612ff03d6a32df79a Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 14:17:46 +0300 Subject: [PATCH 07/11] Return the comment to where it originally was (not relevant for my new feature) --- mypy/checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index affc5280b57e..5dbfd893ca6b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3413,8 +3413,8 @@ def check_final(self, s: AssignmentStmt | OperatorAssignmentStmt | AssignmentExp if ( lv.node.final_unset_in_class and not lv.node.final_set_in_init - and not self.is_stub # It is OK to skip initializer in stub files. - and + and not self.is_stub + and # It is OK to skip initializer in stub files. # Avoid extra error messages, if there is no type in Final[...], # then we already reported the error about missing r.h.s. isinstance(s, AssignmentStmt) From 9fb1a91787b652c32e76f3ee9e7289be961f03fc Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 14:23:54 +0300 Subject: [PATCH 08/11] Add check for None in the iterable --- test-data/unit/check-optional.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index ca46fd3254e5..f469b4fb70eb 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -149,6 +149,14 @@ def check_failure(a: Optional[int], b: Optional[float], c: Optional[str]) -> str print(c.upper()) return "All are valid" +[case testNotRefiningWhenNoneIsInTheIterable] +def not_refine_none_in_iterable(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: + # Test with complex conditions mixed with `None in` + if None not in {a, b, c, None} and b + 3 != 5.5: # E: Unsupported operand types for + ("None" and "int") + reveal_type(a) # N: Revealed type is 'Union[builtins.int, None]' + reveal_type(b) # N: Revealed type is 'Union[builtins.float, None]' + reveal_type(c) # N: Revealed type is 'Union[builtins.str, None]' + return "Bla Bla" [case testOrCases] from typing import Optional From 846cb0d42bf9732cbfd3cba5cf4ca95b86a630f1 Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 14:24:54 +0300 Subject: [PATCH 09/11] Change checks to be with sets, lists, and tuples --- test-data/unit/check-optional.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index f469b4fb70eb..155cbc7289d2 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -117,7 +117,7 @@ from typing import Optional, List def refine_complex(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: # Test with complex conditions mixed with `None in` - if None not in [a, b, c] and b + 3 != 5.5: + if None not in (a, b, c) and b + 3 != 5.5: reveal_type(a) # N: Revealed type is 'builtins.int' reveal_type(b) # N: Revealed type is 'builtins.float' reveal_type(c) # N: Revealed type is 'builtins.str' @@ -138,7 +138,7 @@ reveal_type(result_complex) # N: Revealed type is 'builtins.str' from typing import Optional, List def check_failure(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: - if None in [a, b, c]: + if None in {a, b, c}: print(a + 3) # E: Unsupported operand types for + ("None" and "int") [operator] print(b.is_integer()) # E: Item "None" of "float | None" has no attribute "is_integer" [union-attr] print(c.upper()) # E: Item "None" of "str | None" has no attribute "upper" [union-attr] From 75107f2b4244bc3129ae0859e68f4f57c91f65cc Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 14:40:53 +0300 Subject: [PATCH 10/11] Minor fixes for mypy --- mypy/checker.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5dbfd893ca6b..b6e4090efc01 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3413,8 +3413,8 @@ def check_final(self, s: AssignmentStmt | OperatorAssignmentStmt | AssignmentExp if ( lv.node.final_unset_in_class and not lv.node.final_set_in_init - and not self.is_stub - and # It is OK to skip initializer in stub files. + and not self.is_stub # It is OK to skip initializer in stub files. + and # Avoid extra error messages, if there is no type in Final[...], # then we already reported the error about missing r.h.s. isinstance(s, AssignmentStmt) @@ -5929,7 +5929,7 @@ def has_no_custom_eq_checks(t: Type) -> bool: ): if_map[operands[left_index]] = remove_optional(item_type) - right = operands[right_index] + right_iterable_expr = operands[right_index] if right_index in narrowable_operand_index_to_hash: if_type, else_type = self.conditional_types_for_iterable( item_type, iterable_type @@ -5937,24 +5937,29 @@ def has_no_custom_eq_checks(t: Type) -> bool: if if_type is None: if_map = None else: - if_map[right] = if_type + if_map[right_iterable_expr] = if_type if else_type is None: else_map = None else: - else_map[right] = else_type + else_map[right_iterable_expr] = else_type # check for `None in ` if ( - isinstance(item_type, NoneType) - and isinstance(right, (ListExpr, TupleExpr, SetExpr)) - # if the iterable has `None` in it then the condition is worthless - and not any(is_literal_none(i) for i in right.items) + isinstance(get_proper_type(item_type), NoneType) + and isinstance(right_iterable_expr, (ListExpr, TupleExpr, SetExpr)) + # Ensure the iterable does not inherently contain None literals + and not any( + is_literal_none(iterable_item) + for iterable_item in right_iterable_expr.items + ) ): - for i in right.items: - i_type = self.lookup_type(i) + if else_map is None: + else_map = {} + for iterable_item in right_iterable_expr.items: + proper_item_type = self.lookup_type(iterable_item) # Remove the option of the current item to be `None` for the entire else scope - if is_overlapping_none(i_type): - else_map[i] = remove_optional(i_type) + if is_overlapping_none(proper_item_type): + else_map[iterable_item] = remove_optional(proper_item_type) else: if_map = {} else_map = {} @@ -6019,8 +6024,8 @@ def has_no_custom_eq_checks(t: Type) -> bool: and_conditional_maps(left_else_vars, right_else_vars), ) elif isinstance(node, UnaryExpr) and node.op == "not": - left, right = self.find_isinstance_check(node.expr) - return right, left + left, iterable_expr = self.find_isinstance_check(node.expr) + return iterable_expr, left elif ( literal(node) == LITERAL_TYPE and self.has_type(node) From 7c9afad88e2f5a1331c0614813088ca4f1cc857a Mon Sep 17 00:00:00 2001 From: Elad Kimchi Shoshani Date: Tue, 23 Apr 2024 14:49:05 +0300 Subject: [PATCH 11/11] Minor fixes of messages --- mypy/checker.py | 4 ++-- test-data/unit/check-optional.test | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b6e4090efc01..8ff921996121 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3413,8 +3413,8 @@ def check_final(self, s: AssignmentStmt | OperatorAssignmentStmt | AssignmentExp if ( lv.node.final_unset_in_class and not lv.node.final_set_in_init - and not self.is_stub # It is OK to skip initializer in stub files. - and + and not self.is_stub + and # It is OK to skip initializer in stub files. # Avoid extra error messages, if there is no type in Final[...], # then we already reported the error about missing r.h.s. isinstance(s, AssignmentStmt) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 155cbc7289d2..98d7f93092cf 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -100,9 +100,9 @@ def refine_types(a: Optional[int], b: Optional[str], c: Optional[float]) -> str: if None in [a, b, c]: return "One or more are None" else: - reveal_type(a) # N: Revealed type is 'builtins.int' - reveal_type(b) # N: Revealed type is 'builtins.str' - reveal_type(c) # N: Revealed type is 'builtins.float' + reveal_type(a) # N: Revealed type is "builtins.int" + reveal_type(b) # N: Revealed type is "builtins.str" + reveal_type(c) # N: Revealed type is "builtins.float" return f"{a}, {b}, {c}" a: Optional[int] = 5 @@ -110,7 +110,7 @@ b: Optional[str] = "hello" c: Optional[float] = None result = refine_types(a, b, c) -reveal_type(result) # N: Revealed type is 'builtins.str' +reveal_type(result) # N: Revealed type is "builtins.str" [case testRefinementWithComplexCondition] from typing import Optional, List @@ -118,13 +118,13 @@ from typing import Optional, List def refine_complex(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: # Test with complex conditions mixed with `None in` if None not in (a, b, c) and b + 3 != 5.5: - reveal_type(a) # N: Revealed type is 'builtins.int' - reveal_type(b) # N: Revealed type is 'builtins.float' - reveal_type(c) # N: Revealed type is 'builtins.str' + reveal_type(a) # N: Revealed type is "builtins.int" + reveal_type(b) # N: Revealed type is "builtins.float" + reveal_type(c) # N: Revealed type is "builtins.str" return f"{a}, {b}, {c}" - reveal_type(a) # N: Revealed type is 'Union[builtins.int, None]' - reveal_type(b) # N: Revealed type is 'Union[builtins.float, None]' - reveal_type(c) # N: Revealed type is 'Union[builtins.str, None]' + reveal_type(a) # N: Revealed type is "Union[builtins.int, None]" + reveal_type(b) # N: Revealed type is "Union[builtins.float, None]" + reveal_type(c) # N: Revealed type is "Union[builtins.str, None]" return "All are valid" a: Optional[int] = 5 @@ -132,7 +132,7 @@ b: Optional[float] = 10.3 c: Optional[str] = "cCCCc" result_complex = refine_complex(a, b, c) -reveal_type(result_complex) # N: Revealed type is 'builtins.str' +reveal_type(result_complex) # N: Revealed type is "builtins.str" [case testRefinementFailureWhenNonePresent] from typing import Optional, List @@ -153,9 +153,9 @@ def check_failure(a: Optional[int], b: Optional[float], c: Optional[str]) -> str def not_refine_none_in_iterable(a: Optional[int], b: Optional[float], c: Optional[str]) -> str: # Test with complex conditions mixed with `None in` if None not in {a, b, c, None} and b + 3 != 5.5: # E: Unsupported operand types for + ("None" and "int") - reveal_type(a) # N: Revealed type is 'Union[builtins.int, None]' - reveal_type(b) # N: Revealed type is 'Union[builtins.float, None]' - reveal_type(c) # N: Revealed type is 'Union[builtins.str, None]' + reveal_type(a) # N: Revealed type is "Union[builtins.int, None]" + reveal_type(b) # N: Revealed type is "Union[builtins.float, None]" + reveal_type(c) # N: Revealed type is "Union[builtins.str, None]" return "Bla Bla" [case testOrCases]