Skip to content

Commit

Permalink
Use Never in more messages, use ambiguous in join (#17304)
Browse files Browse the repository at this point in the history
Switches the logic from #16994 to use
ambiguous (since is_noreturn was only meant for error messages)

See also #15996
  • Loading branch information
hauntsaninja authored May 31, 2024
1 parent 77cfb98 commit c3bbd1c
Show file tree
Hide file tree
Showing 12 changed files with 23 additions and 33 deletions.
2 changes: 1 addition & 1 deletion mypy/copytype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 2 additions & 5 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "<deleted>"]
type_string in ["Module", "overloaded function", "<deleted>"]
or type_string.startswith("Module ")
or re.match(no_quote_regex, type_string) is not None
or type_string.endswith("?")
Expand Down Expand Up @@ -2633,10 +2633,7 @@ def format_literal_value(typ: LiteralType) -> str:
elif isinstance(typ, DeletedType):
return "<deleted>"
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)}]"
Expand Down
2 changes: 1 addition & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
13 changes: 4 additions & 9 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
6 changes: 2 additions & 4 deletions test-data/unit/check-literal.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-plugin-attrs.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-python310.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-type-aliases.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down Expand Up @@ -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]
Expand Down

0 comments on commit c3bbd1c

Please sign in to comment.