From c3bbd1cdeca02e63e1102a3274415f056e8d1e43 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Fri, 31 May 2024 11:29:55 -0700 Subject: [PATCH] Use Never in more messages, use ambiguous in join (#17304) Switches the logic from https://github.com/python/mypy/pull/16994 to use ambiguous (since is_noreturn was only meant for error messages) See also https://github.com/python/mypy/pull/15996 --- mypy/copytype.py | 2 +- mypy/join.py | 4 ++-- mypy/messages.py | 7 ++----- mypy/typeanal.py | 2 +- mypy/types.py | 13 ++++--------- test-data/unit/check-dataclasses.test | 4 ++-- test-data/unit/check-flags.test | 4 ++-- test-data/unit/check-literal.test | 6 ++---- test-data/unit/check-plugin-attrs.test | 4 ++-- test-data/unit/check-python310.test | 2 +- test-data/unit/check-type-aliases.test | 2 +- test-data/unit/check-typeddict.test | 6 +++--- 12 files changed, 23 insertions(+), 33 deletions(-) diff --git a/mypy/copytype.py b/mypy/copytype.py index 4ca381c4a8c4..465f06566f54 100644 --- a/mypy/copytype.py +++ b/mypy/copytype.py @@ -53,7 +53,7 @@ def visit_none_type(self, t: NoneType) -> ProperType: return self.copy_common(t, NoneType()) def visit_uninhabited_type(self, t: UninhabitedType) -> ProperType: - dup = UninhabitedType(t.is_noreturn) + dup = UninhabitedType() dup.ambiguous = t.ambiguous return self.copy_common(t, dup) diff --git a/mypy/join.py b/mypy/join.py index 782b4fbebd7b..c711697ec46d 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -108,9 +108,9 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType: # TODO: contravariant case should use meet but pass seen instances as # an argument to keep track of recursive checks. elif type_var.variance in (INVARIANT, CONTRAVARIANT): - if isinstance(ta_proper, UninhabitedType) and not ta_proper.is_noreturn: + if isinstance(ta_proper, UninhabitedType) and ta_proper.ambiguous: new_type = sa - elif isinstance(sa_proper, UninhabitedType) and not sa_proper.is_noreturn: + elif isinstance(sa_proper, UninhabitedType) and sa_proper.ambiguous: new_type = ta elif not is_equivalent(ta, sa): self.seen_instances.pop() diff --git a/mypy/messages.py b/mypy/messages.py index 8f923462c789..53a7f7d97774 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2430,7 +2430,7 @@ def quote_type_string(type_string: str) -> str: """Quotes a type representation for use in messages.""" no_quote_regex = r"^<(tuple|union): \d+ items>$" if ( - type_string in ["Module", "overloaded function", "Never", ""] + type_string in ["Module", "overloaded function", ""] or type_string.startswith("Module ") or re.match(no_quote_regex, type_string) is not None or type_string.endswith("?") @@ -2633,10 +2633,7 @@ def format_literal_value(typ: LiteralType) -> str: elif isinstance(typ, DeletedType): return "" elif isinstance(typ, UninhabitedType): - if typ.is_noreturn: - return "NoReturn" - else: - return "Never" + return "Never" elif isinstance(typ, TypeType): type_name = "type" if options.use_lowercase_names() else "Type" return f"{type_name}[{format(typ.item)}]" diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 31d451b0831a..8f138ab5698f 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -645,7 +645,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Typ return AnyType(TypeOfAny.from_error) return self.anal_type(t.args[0]) elif fullname in NEVER_NAMES: - return UninhabitedType(is_noreturn=True) + return UninhabitedType() elif fullname in LITERAL_TYPE_NAMES: return self.analyze_literal_type(t) elif fullname in ANNOTATED_TYPE_NAMES: diff --git a/mypy/types.py b/mypy/types.py index 0ef3803c5687..2cacc3e44085 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1169,17 +1169,12 @@ class UninhabitedType(ProperType): is_subtype(UninhabitedType, T) = True """ - __slots__ = ("ambiguous", "is_noreturn") + __slots__ = ("ambiguous",) - is_noreturn: bool # Does this come from a NoReturn? Purely for error messages. - # It is important to track whether this is an actual NoReturn type, or just a result - # of ambiguous type inference, in the latter case we don't want to mark a branch as - # unreachable in binder. ambiguous: bool # Is this a result of inference for a variable without constraints? - def __init__(self, is_noreturn: bool = False, line: int = -1, column: int = -1) -> None: + def __init__(self, line: int = -1, column: int = -1) -> None: super().__init__(line, column) - self.is_noreturn = is_noreturn self.ambiguous = False def can_be_true_default(self) -> bool: @@ -1198,12 +1193,12 @@ def __eq__(self, other: object) -> bool: return isinstance(other, UninhabitedType) def serialize(self) -> JsonDict: - return {".class": "UninhabitedType", "is_noreturn": self.is_noreturn} + return {".class": "UninhabitedType"} @classmethod def deserialize(cls, data: JsonDict) -> UninhabitedType: assert data[".class"] == "UninhabitedType" - return UninhabitedType(is_noreturn=data["is_noreturn"]) + return UninhabitedType() class NoneType(ProperType): diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index a055507cdd78..924f9c7bb5be 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2080,8 +2080,8 @@ class B: a_or_b: Union[A[int], B] _ = replace(a_or_b, x=42, y=True, init_var=42) _ = replace(a_or_b, x=42, y=True) # E: Missing named argument "init_var" for "replace" of "Union[A[int], B]" -_ = replace(a_or_b, x=42, y=True, z='42', init_var=42) # E: Argument "z" to "replace" of "Union[A[int], B]" has incompatible type "str"; expected Never -_ = replace(a_or_b, x=42, y=True, w={}, init_var=42) # E: Argument "w" to "replace" of "Union[A[int], B]" has incompatible type "Dict[Never, Never]"; expected Never +_ = replace(a_or_b, x=42, y=True, z='42', init_var=42) # E: Argument "z" to "replace" of "Union[A[int], B]" has incompatible type "str"; expected "Never" +_ = replace(a_or_b, x=42, y=True, w={}, init_var=42) # E: Argument "w" to "replace" of "Union[A[int], B]" has incompatible type "Dict[Never, Never]"; expected "Never" _ = replace(a_or_b, y=42, init_var=42) # E: Argument "y" to "replace" of "Union[A[int], B]" has incompatible type "int"; expected "bool" [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index c90c773e320f..62711d5f0071 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -408,7 +408,7 @@ reveal_type(f() or no_return()) # N: Revealed type is "builtins.int" # flags: --warn-no-return from mypy_extensions import NoReturn -x = 0 # type: NoReturn # E: Incompatible types in assignment (expression has type "int", variable has type "NoReturn") +x = 0 # type: NoReturn # E: Incompatible types in assignment (expression has type "int", variable has type "Never") [builtins fixtures/dict.pyi] [case testNoReturnAsync] @@ -477,7 +477,7 @@ def no_return() -> NoReturn: pass def f() -> NoReturn: no_return() -x: NoReturn = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "NoReturn") +x: NoReturn = 0 # E: Incompatible types in assignment (expression has type "int", variable has type "Never") [builtins fixtures/dict.pyi] [case testShowErrorContextFunction] diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 423ba74eba72..8f8aaf6a3982 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -839,14 +839,13 @@ b: NoReturn c: None fa(lit) -fb(lit) # E: Argument 1 to "fb" has incompatible type "Literal[1]"; expected "NoReturn" +fb(lit) # E: Argument 1 to "fb" has incompatible type "Literal[1]"; expected "Never" fc(lit) # E: Argument 1 to "fc" has incompatible type "Literal[1]"; expected "None" f_lit(a) f_lit(b) f_lit(c) # E: Argument 1 to "f_lit" has incompatible type "None"; expected "Literal[1]" [builtins fixtures/tuple.pyi] -[out] [case testLiteralCheckSubtypingNoStrictOptional] # flags: --no-strict-optional @@ -865,14 +864,13 @@ b: NoReturn c: None fa(lit) -fb(lit) # E: Argument 1 to "fb" has incompatible type "Literal[1]"; expected "NoReturn" +fb(lit) # E: Argument 1 to "fb" has incompatible type "Literal[1]"; expected "Never" fc(lit) # E: Argument 1 to "fc" has incompatible type "Literal[1]"; expected "None" f_lit(a) f_lit(b) f_lit(c) [builtins fixtures/tuple.pyi] -[out] [case testLiteralCallingOverloadedFunction] from typing import overload, Generic, TypeVar, Any diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index 39b266dba50e..b96c00730a74 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -2170,8 +2170,8 @@ class B: a_or_b: A[int] | B a2 = attrs.evolve(a_or_b, x=42, y=True) -a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') # E: Argument "z" to "evolve" of "Union[A[int], B]" has incompatible type "str"; expected Never -a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) # E: Argument "w" to "evolve" of "Union[A[int], B]" has incompatible type "Dict[Never, Never]"; expected Never +a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') # E: Argument "z" to "evolve" of "Union[A[int], B]" has incompatible type "str"; expected "Never" +a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) # E: Argument "w" to "evolve" of "Union[A[int], B]" has incompatible type "Dict[Never, Never]"; expected "Never" [builtins fixtures/plugin_attrs.pyi] diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 8991b65f67b5..5ecc69dc7c32 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1406,7 +1406,7 @@ def f(value: int) -> int: # E: Missing return statement case 2: return 1 case o: - assert_never(o) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "NoReturn" + assert_never(o) # E: Argument 1 to "assert_never" has incompatible type "int"; expected "Never" [case testMatchExhaustiveNoError] from typing import NoReturn, Union, Literal diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index aebb0381d962..f77c3c1c34e2 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -57,7 +57,7 @@ Never = NoReturn a: Never # Used to be an error here def f(a: Never): ... -f(5) # E: Argument 1 to "f" has incompatible type "int"; expected "NoReturn" +f(5) # E: Argument 1 to "f" has incompatible type "int"; expected "Never" [case testImportUnionAlias] import typing from _m import U diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index bd1fbe3f2667..09b86e4afd2d 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1648,7 +1648,7 @@ a.setdefault('y', '') # E: Argument 2 to "setdefault" of "TypedDict" has incompa x = '' a.setdefault(x, 1) # E: Expected TypedDict key to be string literal alias = a.setdefault -alias(x, 1) # E: Argument 1 has incompatible type "str"; expected "NoReturn" +alias(x, 1) # E: Argument 1 has incompatible type "str"; expected "Never" a.update({}) a.update({'x': 1}) @@ -1680,8 +1680,8 @@ b.pop('x') # E: Key "x" of TypedDict "B" cannot be deleted x = '' b.pop(x) # E: Expected TypedDict key to be string literal pop = b.pop -pop('x') # E: Argument 1 has incompatible type "str"; expected "NoReturn" -pop('invalid') # E: Argument 1 has incompatible type "str"; expected "NoReturn" +pop('x') # E: Argument 1 has incompatible type "str"; expected "Never" +pop('invalid') # E: Argument 1 has incompatible type "str"; expected "Never" [builtins fixtures/dict.pyi] [case testTypedDictDel]