diff --git a/misc/typeshed_patches/0001-Revert-dict.__or__-typeshed-change.patch b/misc/typeshed_patches/0001-Revert-dict.__or__-typeshed-change.patch new file mode 100644 index 0000000000000..890b2782abedd --- /dev/null +++ b/misc/typeshed_patches/0001-Revert-dict.__or__-typeshed-change.patch @@ -0,0 +1,30 @@ +From d88a4b774fc59ecd0f2d0fdda8e0e61092adafd3 Mon Sep 17 00:00:00 2001 +From: Ivan Levkivskyi +Date: Wed, 8 Apr 2026 00:01:44 +0100 +Subject: [PATCH] Revert dict.__or__ typeshed change + +--- + mypy/typeshed/stdlib/builtins.pyi | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi +index 674142d70..25b21ba97 100644 +--- a/mypy/typeshed/stdlib/builtins.pyi ++++ b/mypy/typeshed/stdlib/builtins.pyi +@@ -1142,7 +1142,13 @@ class dict(MutableMapping[_KT, _VT]): + def __reversed__(self) -> Iterator[_KT]: ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... ++ @overload ++ def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... ++ @overload + def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... ++ @overload ++ def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... ++ @overload + def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + # dict.__ior__ should be kept roughly in line with MutableMapping.update() + @overload # type: ignore[misc] +-- +2.25.1 + diff --git a/mypy/build.py b/mypy/build.py index 88a112625cd08..aa6e839b6f0a0 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -874,7 +874,7 @@ def __init__( ] ) - self.metastore = create_metastore(options, parallel_worker) + self.metastore = create_metastore(options) # a mapping from source files to their corresponding shadow files # for efficient lookup @@ -1613,13 +1613,10 @@ def exclude_from_backups(target_dir: str) -> None: pass -def create_metastore(options: Options, parallel_worker: bool = False) -> MetadataStore: +def create_metastore(options: Options) -> MetadataStore: """Create the appropriate metadata store.""" if options.sqlite_cache: - # We use this flag in both coordinator and workers to speed up commits, - # see mypy.metastore.connect_db() for details. - sync_off = options.num_workers > 0 or parallel_worker - mds: MetadataStore = SqliteMetadataStore(_cache_dir_prefix(options), sync_off=sync_off) + mds: MetadataStore = SqliteMetadataStore(_cache_dir_prefix(options)) else: mds = FilesystemMetadataStore(_cache_dir_prefix(options)) return mds diff --git a/mypy/checker.py b/mypy/checker.py index 7d0b5dbde09d8..4799f255af938 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5393,6 +5393,8 @@ def check_except_handler_test(self, n: Expression, is_star: bool) -> Type: if isinstance(ttype, AnyType): all_types.append(ttype) continue + if isinstance(ttype, UninhabitedType): + continue if isinstance(ttype, FunctionLike): item = ttype.items[0] @@ -6674,8 +6676,8 @@ def comparison_type_narrowing_helper(self, node: ComparisonExpr) -> tuple[TypeMa # If we have found non-trivial restrictions from the regular comparisons, # then return soon. Otherwise try to infer restrictions involving `len(x)`. # TODO: support regular and len() narrowing in the same chain. - if any(m != ({}, {}) for m in partial_type_maps): - return reduce_conditional_maps(partial_type_maps) + if any(len(m[0]) or len(m[1]) for m in partial_type_maps): + return reduce_conditional_maps(partial_type_maps, use_meet=True) else: # Use meet for `and` maps to get correct results for chained checks # like `if 1 < len(x) < 4: ...` @@ -6803,11 +6805,14 @@ def narrow_type_by_identity_equality( # It is correct to always narrow here. It improves behaviour on tests and # detects many inaccurate type annotations on primer. # However, because mypy does not currently check unreachable code, it feels - # risky to narrow to unreachable without --warn-unreachable. + # risky to narrow to unreachable without --warn-unreachable or not + # at module level # See also this specific primer comment, where I force primer to run with # --warn-unreachable to see what code we would stop checking: # https://github.com/python/mypy/pull/20660#issuecomment-3865794148 - if self.options.warn_unreachable or not is_unreachable_map(if_map): + if ( + self.options.warn_unreachable and len(self.scope.stack) != 1 + ) or not is_unreachable_map(if_map): all_if_maps.append(if_map) # Handle narrowing for operands with custom __eq__ methods specially @@ -6896,6 +6901,14 @@ def narrow_type_by_identity_equality( expr_in_type_expr = type_expr.expr else: continue + + p_expr_type = get_proper_type(operand_types[i]) + if isinstance(p_expr_type, TypeType) and isinstance(p_expr_type.item, TypeVarType): + # This mirrors logic in comparison_type_narrowing_helper + # In theory, this is like `i not in narrowable_indices`, except that + # narrowable_indices filters all type(x) narrowing as it's a call + continue + for j in expr_indices: if i == j: continue @@ -8731,7 +8744,13 @@ def reduce_and_conditional_type_maps(ms: list[TypeMap], *, use_meet: bool) -> Ty return result -BUILTINS_CUSTOM_EQ_CHECKS: Final = {"builtins.bytearray", "builtins.memoryview"} +BUILTINS_CUSTOM_EQ_CHECKS: Final = { + "builtins.bytearray", + "builtins.memoryview", + "builtins.frozenset", + "_collections_abc.dict_keys", + "_collections_abc.dict_items", +} def has_custom_eq_checks(t: Type) -> bool: @@ -8741,7 +8760,7 @@ def has_custom_eq_checks(t: Type) -> bool: # custom_special_method has special casing for builtins.* and typing.* that make the # above always return False. So here we return True if the a value of a builtin type # will ever compare equal to value of another type, e.g. a bytes value can compare equal - # to a bytearray value. We also include builtins collections, see testNarrowingCollections + # to a bytearray value. or ( isinstance(pt := get_proper_type(t), Instance) and pt.type.fullname in BUILTINS_CUSTOM_EQ_CHECKS diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 49fc1159856f7..84d8fa67fd177 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -236,8 +236,6 @@ "builtins.frozenset", "typing.KeysView", "typing.ItemsView", - "builtins._dict_keys", - "builtins._dict_items", "_collections_abc.dict_keys", "_collections_abc.dict_items", ] @@ -3709,8 +3707,22 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type: elif operator == "is" or operator == "is not": right_type = self.accept(right) # validate the right operand sub_result = self.bool_type() - if not self.chk.can_skip_diagnostics and self.dangerous_comparison( - left_type, right_type, identity_check=True + if ( + not self.chk.can_skip_diagnostics + and self.dangerous_comparison(left_type, right_type, identity_check=True) + # Allow dangerous identity comparisons with objects explicitly typed as Any + and not ( + isinstance(left, NameExpr) + and isinstance(left.node, Var) + and not left.node.is_inferred + and isinstance(get_proper_type(left.node.type), AnyType) + ) + and not ( + isinstance(right, NameExpr) + and isinstance(right.node, Var) + and not right.node.is_inferred + and isinstance(get_proper_type(right.node.type), AnyType) + ) ): # Show the most specific literal types possible left_type = try_getting_literal(left_type) diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index be6a78a021ba0..53fa75fa5ec38 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -378,9 +378,15 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType: # tuples, so we instead try to narrow the entire type. # TODO: use more precise narrowing when possible (e.g. for identical shapes). new_tuple_type = TupleType(new_inner_types, current_type.partial_fallback) - new_type, rest_type = self.chk.conditional_types_with_intersection( + new_type, _ = self.chk.conditional_types_with_intersection( new_tuple_type, [get_type_range(current_type)], o, default=new_tuple_type ) + if ( + star_position is not None + and required_patterns <= len(inner_types) - 1 + and all(is_uninhabited(rest) for rest in rest_inner_types) + ): + rest_type = UninhabitedType() else: new_inner_type = UninhabitedType() for typ in new_inner_types: @@ -460,7 +466,7 @@ def expand_starred_pattern_types( # so we only restore the type of the star item. res = [] for i, t in enumerate(types): - if i != star_pos: + if i != star_pos or is_uninhabited(t): res.append(t) else: res.append(UnpackType(self.chk.named_generic_type("builtins.tuple", [t]))) diff --git a/mypy/main.py b/mypy/main.py index 14148720269a4..f4d6b18e58b63 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -599,7 +599,6 @@ def add_invertible_flag( add_invertible_flag( "--warn-unused-configs", default=False, - strict_flag=True, help="Warn about unused '[mypy-]' or '[[tool.mypy.overrides]]' config sections", group=config_group, ) diff --git a/mypy/metastore.py b/mypy/metastore.py index 64839bf8a79c0..3d32ba29ae107 100644 --- a/mypy/metastore.py +++ b/mypy/metastore.py @@ -154,21 +154,20 @@ def close(self) -> None: """ -def connect_db(db_file: str, sync_off: bool = False) -> sqlite3.Connection: +def connect_db(db_file: str) -> sqlite3.Connection: import sqlite3.dbapi2 db = sqlite3.dbapi2.connect(db_file) - if sync_off: - # This is a bit unfortunate (as we may get corrupt cache after e.g. Ctrl + C), - # but without this flag, commits are *very* slow, especially when using HDDs, - # see https://www.sqlite.org/faq.html#q19 for details. - db.execute("PRAGMA synchronous=OFF") + # This is a bit unfortunate (as we may get corrupt cache after e.g. Ctrl + C), + # but without this flag, commits are *very* slow, especially when using HDDs, + # see https://www.sqlite.org/faq.html#q19 for details. + db.execute("PRAGMA synchronous=OFF") db.executescript(SCHEMA) return db class SqliteMetadataStore(MetadataStore): - def __init__(self, cache_dir_prefix: str, sync_off: bool = False) -> None: + def __init__(self, cache_dir_prefix: str) -> None: # We check startswith instead of equality because the version # will have already been appended by the time the cache dir is # passed here. @@ -177,7 +176,7 @@ def __init__(self, cache_dir_prefix: str, sync_off: bool = False) -> None: return os.makedirs(cache_dir_prefix, exist_ok=True) - self.db = connect_db(os_path_join(cache_dir_prefix, "cache.db"), sync_off=sync_off) + self.db = connect_db(os_path_join(cache_dir_prefix, "cache.db")) def _query(self, name: str, field: str) -> Any: # Raises FileNotFound for consistency with the file system version diff --git a/mypy/typeshed/stdlib/builtins.pyi b/mypy/typeshed/stdlib/builtins.pyi index 674142d709a29..25b21ba971540 100644 --- a/mypy/typeshed/stdlib/builtins.pyi +++ b/mypy/typeshed/stdlib/builtins.pyi @@ -1142,7 +1142,13 @@ class dict(MutableMapping[_KT, _VT]): def __reversed__(self) -> Iterator[_KT]: ... __hash__: ClassVar[None] # type: ignore[assignment] def __class_getitem__(cls, item: Any, /) -> GenericAlias: ... + @overload + def __or__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload def __or__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... + @overload + def __ror__(self, value: dict[_KT, _VT], /) -> dict[_KT, _VT]: ... + @overload def __ror__(self, value: dict[_T1, _T2], /) -> dict[_KT | _T1, _VT | _T2]: ... # dict.__ior__ should be kept roughly in line with MutableMapping.update() @overload # type: ignore[misc] diff --git a/mypy/version.py b/mypy/version.py index 52dd26b9d0ef0..1da397d90a3ae 100644 --- a/mypy/version.py +++ b/mypy/version.py @@ -8,7 +8,7 @@ # - Release versions have the form "1.2.3". # - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440). # - Before 1.0 we had the form "0.NNN". -__version__ = "1.20.0" +__version__ = "1.20.1" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) diff --git a/mypyc/test-data/run-base64.test b/mypyc/test-data/run-base64.test index 12fb191809e86..a14676a019d40 100644 --- a/mypyc/test-data/run-base64.test +++ b/mypyc/test-data/run-base64.test @@ -146,9 +146,10 @@ def test_decode_with_extra_data_after_padding() -> None: check_decode(b"====", encoded=True) check_decode(b"eA===", encoded=True) check_decode(b"eHk==", encoded=True) - check_decode(b"eA==x", encoded=True) - check_decode(b"eHk=x", encoded=True) - check_decode(b"eA==abc=======efg", encoded=True) + # TODO: behavior in these cases changed in Python 3.14.4, we should match that. + # check_decode(b"eA==x", encoded=True) + # check_decode(b"eHk=x", encoded=True) + # check_decode(b"eA==abc=======efg", encoded=True) def test_decode_wrappers() -> None: funcs: list[Any] = [b64decode, urlsafe_b64decode] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 17ce4cd596590..77d034902dc62 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -2380,6 +2380,51 @@ if 1 in ('x', 'y'): # E: Non-overlapping container check (element type: "int", [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] +[case testStrictEqualityWithAnySentinel] +# flags: --strict-equality +from __future__ import annotations +from typing import Any, cast + +class A: ... +class B: ... + +sentinel: Any = object() + +def f1(a: A = sentinel, b: B = sentinel): + if a is sentinel and b is sentinel: + raise + + +def f2(a: A | None = sentinel, b: B | None = sentinel): + if a is sentinel and b is sentinel: + raise + + +sentinel_strict = object() + +def f3(a: A = sentinel_strict, b: B = sentinel_strict): # E: Incompatible default for parameter "a" (default has type "object", parameter has type "A") \ + # E: Incompatible default for parameter "b" (default has type "object", parameter has type "B") + if a is sentinel_strict and b is sentinel_strict: # E: Non-overlapping identity check (left operand type: "B", right operand type: "A") + raise + + +def f4(a: A | None = sentinel_strict, b: B | None = sentinel_strict): # E: Incompatible default for parameter "a" (default has type "object", parameter has type "A | None") \ + # E: Incompatible default for parameter "b" (default has type "object", parameter has type "B | None") + if a is sentinel_strict and b is sentinel_strict: # E: Non-overlapping identity check (left operand type: "B | None", right operand type: "A | None") + raise + + +sentinel_inferred = cast(Any, object()) + +def f5(a: A = sentinel_inferred, b: B = sentinel_inferred): + if a is sentinel_inferred and b is sentinel_inferred: # E: Non-overlapping identity check (left operand type: "B", right operand type: "A") + raise + +def f6(a: A | None = sentinel_inferred, b: B | None = sentinel_inferred): + if a is sentinel_inferred and b is sentinel_inferred: # E: Non-overlapping identity check (left operand type: "B | None", right operand type: "A | None") + raise +[builtins fixtures/bool.pyi] + [case testOverlappingAnyTypeWithoutStrictOptional] # flags: --no-strict-optional --strict-equality from typing import Any, Optional diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 0b20d5911151f..310145dfc4efe 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -2134,27 +2134,22 @@ else: # flags: --warn-unreachable from typing import List, Optional -x: List[str] -y: Optional[int] - -if y in x: - reveal_type(y) # E: Statement is unreachable -else: - reveal_type(y) # N: Revealed type is "builtins.int | None" +def f(x: List[str], y: Optional[int]) -> None: + if y in x: + reveal_type(y) # E: Statement is unreachable + else: + reveal_type(y) # N: Revealed type is "builtins.int | None" [builtins fixtures/list.pyi] [case testNarrowTypeAfterInListNested] # flags: --warn-unreachable from typing import List, Optional, Any -x: Optional[int] -lst: Optional[List[int]] -nested_any: List[List[Any]] - -if lst in nested_any: - reveal_type(lst) # N: Revealed type is "builtins.list[builtins.int]" -if x in nested_any: - reveal_type(x) # E: Statement is unreachable +def f(x: Optional[int], lst: Optional[List[int]], nested_any: List[List[Any]]) -> None: + if lst in nested_any: + reveal_type(lst) # N: Revealed type is "builtins.list[builtins.int]" + if x in nested_any: + reveal_type(x) # E: Statement is unreachable [builtins fixtures/list.pyi] [case testNarrowTypeAfterInTuple] diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 93a7207288761..154e283daa2d3 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2802,13 +2802,13 @@ while x is not None and b(): [case testAvoidFalseNonOverlappingEqualityCheckInLoop1] # flags: --allow-redefinition-new --local-partial-types --strict-equality --warn-unreachable -x = 1 -while True: - if x == str(): - break - x = str() - if x == int(): # E: Non-overlapping equality check (left operand type: "str", right operand type: "int") - break # E: Statement is unreachable +def f(x: int) -> None: + while True: + if x == str(): + break + x = str() + if x == int(): # E: Non-overlapping equality check (left operand type: "str", right operand type: "int") + break # E: Statement is unreachable [builtins fixtures/primitives.pyi] [case testAvoidFalseNonOverlappingEqualityCheckInLoop2] @@ -2818,11 +2818,11 @@ class A: ... class B: ... class C: ... -x = A() -while True: - if x == C(): # E: Non-overlapping equality check (left operand type: "A | B", right operand type: "C") - break # E: Statement is unreachable - x = B() +def f(x: A) -> None: + while True: + if x == C(): # E: Non-overlapping equality check (left operand type: "A | B", right operand type: "C") + break # E: Statement is unreachable + x = B() [builtins fixtures/primitives.pyi] [case testAvoidFalseNonOverlappingEqualityCheckInLoop3] @@ -3264,6 +3264,39 @@ def bad_but_should_pass(has_key: bool, key: bool, s: tuple[bool, ...]) -> None: reveal_type(key) # N: Revealed type is "builtins.bool" [builtins fixtures/primitives.pyi] +[case testNarrowChainedComparisonMeet] +# flags: --strict-equality --warn-unreachable +from __future__ import annotations +from typing import Any + +def f1(a: str | None, b: str | None) -> None: + if None is not a == b: + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(b) # N: Revealed type is "builtins.str | None" + + if (None is not a) and (a == b): + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(b) # N: Revealed type is "builtins.str" + +def f2(a: Any | None, b: str | None) -> None: + if None is not a == b: + reveal_type(a) # N: Revealed type is "Any" + reveal_type(b) # N: Revealed type is "builtins.str | None" + + if (None is not a) and (a == b): + reveal_type(a) # N: Revealed type is "Any" + reveal_type(b) # N: Revealed type is "builtins.str | None" + +def f3(a: str | None, b: Any | None) -> None: + if None is not a == b: + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(b) # N: Revealed type is "Any | builtins.str | None" + + if (None is not a) and (a == b): + reveal_type(a) # N: Revealed type is "builtins.str" + reveal_type(b) # N: Revealed type is "Any | builtins.str" +[builtins fixtures/primitives.pyi] + [case testNarrowTypeObject] # flags: --strict-equality --warn-unreachable from typing import Any @@ -3362,6 +3395,30 @@ def f(x: str, y: Any, z: object): reveal_type(z) # N: Revealed type is "builtins.str" [builtins fixtures/primitives.pyi] +[case testTypeEqualsCheckWideningSelf] +# flags: --strict-equality --warn-unreachable +from typing import Any +from typing_extensions import Self + +class A: + def f(self: Self, y: Any, z: object): + if type(self) is type(y): + reveal_type(self) # N: Revealed type is "Self`0" + reveal_type(y) # N: Revealed type is "Self`0" + + if type(self) is type(z): + reveal_type(self) # N: Revealed type is "Self`0" + reveal_type(z) # N: Revealed type is "Self`0" + + if type(self) == type(y): + reveal_type(self) # N: Revealed type is "Self`0" + reveal_type(y) # N: Revealed type is "Self`0" + + if type(self) == type(z): + reveal_type(self) # N: Revealed type is "Self`0" + reveal_type(z) # N: Revealed type is "Self`0" +[builtins fixtures/primitives.pyi] + [case testTypeEqualsCheckUsingIs] # flags: --strict-equality --warn-unreachable from typing import Any diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index ff334ff6c692b..d984edd623f73 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -87,11 +87,11 @@ b: int import b class A: ... -m: A -match m: - case b.b: - reveal_type(m) # E: Statement is unreachable +def f(m: A) -> None: + match m: + case b.b: + reveal_type(m) # E: Statement is unreachable [file b.py] class B: ... b: B @@ -100,11 +100,10 @@ b: B # flags: --strict-equality --warn-unreachable import b -m: int - -match m: - case b.b: - reveal_type(m) # E: Statement is unreachable +def f(m: int) -> None: + match m: + case b.b: + reveal_type(m) # E: Statement is unreachable [file b.py] b: str [builtins fixtures/primitives.pyi] @@ -3006,6 +3005,36 @@ match m3: reveal_type(c3) # N: Revealed type is "builtins.int" [builtins fixtures/tuple.pyi] +[case testMatchSequencePatternVariadicTuple] +# flags: --strict-equality --warn-unreachable +from typing_extensions import Unpack + +def f1(m: tuple[int, Unpack[tuple[str, ...]], int]) -> None: + match m: + case (a1, b1): + reveal_type(m) # N: Revealed type is "tuple[builtins.int, builtins.int]" + case (a2, b2, c2): + reveal_type(m) # N: Revealed type is "tuple[builtins.int, builtins.str, builtins.int]" + case (a3, b3, c3, d3): + reveal_type(m) # N: Revealed type is "tuple[builtins.int, builtins.str, builtins.str, builtins.int]" + case (a4, *b4, c4): + reveal_type(m) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.str, ...]], builtins.int]" + case _: + reveal_type(m) # E: Statement is unreachable + + +def f2(m: tuple[int] | tuple[str, str] | tuple[int, Unpack[tuple[str, ...]], int]): + match m: + case (x,): + reveal_type(m) # N: Revealed type is "tuple[builtins.int]" + case (x, y): + reveal_type(m) # N: Revealed type is "tuple[builtins.str, builtins.str] | tuple[builtins.int, builtins.int]" + case (x, y, z): + reveal_type(m) # N: Revealed type is "tuple[builtins.int, builtins.str, builtins.int]" + case _: + reveal_type(m) # N: Revealed type is "tuple[builtins.int, Unpack[builtins.tuple[builtins.str, ...]], builtins.int]" +[builtins fixtures/tuple.pyi] + [case testMatchSequencePatternTypeVarTupleNotTooShort] from typing import Tuple from typing_extensions import Unpack, TypeVarTuple @@ -3151,13 +3180,14 @@ def nested_in_dict(d: dict[str, Any]) -> int: # flags: --warn-unreachable from typing import Literal -def x() -> tuple[Literal["test"]]: ... +def f() -> None: + def x() -> tuple[Literal["test"]]: ... -match x(): - case (x,) if x == "test": # E: Incompatible types in capture pattern (pattern captures type "Literal['test']", variable has type "Callable[[], tuple[Literal['test']]]") - reveal_type(x) # E: Statement is unreachable - case foo: - foo + match x(): + case (x,) if x == "test": # E: Incompatible types in capture pattern (pattern captures type "Literal['test']", variable has type "Callable[[], tuple[Literal['test']]]") + reveal_type(x) # E: Statement is unreachable + case foo: + foo [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 0b768908a4487..242a60be50ff2 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -802,7 +802,7 @@ def error_in_variadic(exc: Tuple[int, ...]) -> None: [builtins fixtures/tuple.pyi] [case testExceptWithMultipleTypes5] -from typing import Tuple, Type, Union +from typing import Tuple, Type, Union, Never class E1(BaseException): pass class E2(BaseException): pass @@ -849,7 +849,38 @@ def error_in_tuple_union(exc: Tuple[Union[Type[E1], Type[E2]], Union[Type[E3], i pass except exc as e: # E: Exception type must be derived from BaseException (or be a tuple of exception classes) pass +[builtins fixtures/tuple.pyi] + +[case testExceptWithMultipleTypes6_no_parallel] +# For no_parallel, see: +# https://github.com/mypyc/ast_serialize/issues/50 +from typing import Never + +def random() -> bool: ... + +def error_in_empty_tuple() -> None: + try: + pass + except () as e: # E: Need type annotation for "e" + pass +def error_in_empty_tuple_annotated(exc: tuple[()]) -> None: + try: + pass + except exc as e: # E: Need type annotation for "e" + pass + +def error_in_conditional_empty_tuple() -> None: + try: + pass + except (BaseException if random() else ()) as e: + pass + +def error_in_never(exc: Never) -> None: + try: + pass + except exc as e: # E: Need type annotation for "e" + pass [builtins fixtures/tuple.pyi] [case testExceptWithAnyTypes] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 2d3f867d8dfdf..837bd905f3666 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -2229,3 +2229,17 @@ def f(x: int, y: list[str]): x in y [out] _testStrictEqualityWithList.py:3: error: Non-overlapping container check (element type: "int", container item type: "str") + +[case testDictOrUnionEdgeCases] +from typing import Mapping, Sequence, Union + +d: dict[str, list[str]] +m: Mapping[str, Sequence[str]] = d | d + +A = dict[str, Union[A, int]] +d1: A +d2: A +d3: A = d1 | d2 +reveal_type(d1 | d2) +[out] +_testDictOrUnionEdgeCases.py:10: note: Revealed type is "dict[str, dict[str, ... | int] | int]"