Skip to content

Commit c126c48

Browse files
committed
render types: render callables in errors
1 parent 718c4db commit c126c48

File tree

3 files changed

+72
-43
lines changed

3 files changed

+72
-43
lines changed

mypy/messages.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
TypeOfAny,
9393
TypeStrVisitor,
9494
TypeType,
95+
TypeVarLikeType,
9596
TypeVarType,
9697
UnboundType,
9798
UninhabitedType,
@@ -2153,26 +2154,27 @@ def format_callable_args(
21532154
return ", ".join(arg_strings)
21542155

21552156

2156-
def format_type_inner(typ: Type, verbosity: int, fullnames: Optional[Set[str]]) -> str:
2157+
def format_type_inner(
2158+
typ: Type,
2159+
verbosity: int,
2160+
fullnames: Optional[Set[str]],
2161+
disable_own_scope: Sequence[TypeVarLikeType] = (),
2162+
) -> str:
21572163
"""
21582164
Convert a type to a relatively short string suitable for error messages.
21592165
21602166
Args:
21612167
verbosity: a coarse grained control on the verbosity of the type
21622168
fullnames: a set of names that should be printed in full
2169+
disable_own_scope: TypeVars to disable scope names
21632170
"""
21642171

21652172
def format(typ: Type) -> str:
2166-
return format_type_inner(typ, verbosity, fullnames)
2173+
return format_type_inner(typ, verbosity, fullnames, disable_own_scope)
21672174

21682175
def format_list(types: Sequence[Type]) -> str:
21692176
return ", ".join(format(typ) for typ in types)
21702177

2171-
def format_union(types: Sequence[Type]) -> str:
2172-
if not mypy.options._based:
2173-
return format_list(types)
2174-
return " | ".join(format(typ) for typ in types)
2175-
21762178
def format_literal_value(typ: LiteralType) -> str:
21772179
if typ.is_enum_literal():
21782180
underlying_type = format(typ.fallback)
@@ -2220,7 +2222,7 @@ def format_literal_value(typ: LiteralType) -> str:
22202222
return f"{base_str}[{format_list(itype.args)}]"
22212223
elif isinstance(typ, TypeVarType):
22222224
# This is similar to non-generic instance types.
2223-
if mypy.options._based:
2225+
if mypy.options._based and typ.scopename and typ not in disable_own_scope:
22242226
return f"{typ.name}@{typ.scopename}"
22252227
return typ.name
22262228
elif isinstance(typ, ParamSpecType):
@@ -2297,11 +2299,11 @@ def format_literal_value(typ: LiteralType) -> str:
22972299
elif isinstance(typ, DeletedType):
22982300
return "<deleted>"
22992301
elif isinstance(typ, UninhabitedType):
2302+
if mypy.options._based:
2303+
return "Never"
23002304
if typ.is_noreturn:
23012305
return "NoReturn"
23022306
else:
2303-
if mypy.options._based:
2304-
return "Never"
23052307
return "<nothing>"
23062308
elif isinstance(typ, TypeType):
23072309
if not mypy.options._based:
@@ -2314,6 +2316,8 @@ def format_literal_value(typ: LiteralType) -> str:
23142316
# return type (this always works).
23152317
return format(TypeType.make_normalized(erase_type(func.items[0].ret_type)))
23162318
elif isinstance(func, CallableType):
2319+
if func.variables:
2320+
disable_own_scope = func.variables
23172321
if func.type_guard is not None:
23182322
return_type = f"TypeGuard[{format(func.type_guard)}]"
23192323
else:
@@ -2332,7 +2336,8 @@ def format_literal_value(typ: LiteralType) -> str:
23322336
)
23332337
if not mypy.options._based:
23342338
return f"Callable[[{args}], {return_type}]"
2335-
return f"({args}) -> {return_type}"
2339+
args = TypeStrVisitor.render_callable_type_params(func, format) + f"({args})"
2340+
return f"{args} -> {return_type}"
23362341
else:
23372342
# Use a simple representation for function types; proper
23382343
# function types may result in long and difficult-to-read

mypy/types.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import (
77
TYPE_CHECKING,
88
Any,
9+
Callable,
910
ClassVar,
1011
Dict,
1112
Generator,
@@ -3062,34 +3063,42 @@ def visit_callable_type(self, t: CallableType) -> str:
30623063
else:
30633064
s += f" -> {t.ret_type.accept(self)}"
30643065

3065-
if t.variables:
3066-
vs = []
3067-
for var in t.variables:
3068-
if isinstance(var, TypeVarType):
3069-
# We reimplement TypeVarType.__repr__ here in order to support id_mapper.
3070-
if (
3071-
mypy.options._based
3072-
and var.scopename
3073-
and t.name
3074-
and var.scopename != t.name.split(" ")[0]
3075-
):
3076-
name = f"{var.name} (from {var.scopename})"
3077-
else:
3078-
name = var.name
3079-
if var.values:
3080-
vals = f"({', '.join(val.accept(self) for val in var.values)})"
3081-
vs.append(f"{name} in {vals}")
3082-
elif not is_named_instance(var.upper_bound, "builtins.object"):
3083-
vs.append(f"{name} <: {var.upper_bound.accept(self)}")
3084-
else:
3085-
vs.append(name)
3086-
else:
3087-
# For other TypeVarLikeTypes, just use the name
3088-
vs.append(var.name)
3089-
s = f"[{', '.join(vs)}] {s}"
3066+
s = self.render_callable_type_params(t, lambda x: x.accept(self)) + s
30903067

30913068
return f"def {s}"
30923069

3070+
@staticmethod
3071+
def render_callable_type_params(t: CallableType, renderer: Callable[[Type], str]) -> str:
3072+
if not t.variables:
3073+
return ""
3074+
vs = []
3075+
for var in t.variables:
3076+
if isinstance(var, TypeVarType):
3077+
# We reimplement TypeVarType.__repr__ here in order to support id_mapper.
3078+
if (
3079+
mypy.options._based
3080+
and var.scopename
3081+
and t.name
3082+
and var.scopename != t.name.split(" ")[0]
3083+
):
3084+
name = f"{var.name} (from {var.scopename})"
3085+
else:
3086+
name = var.name
3087+
if var.values:
3088+
vals = f"({', '.join(renderer(val) for val in var.values)})"
3089+
vs.append(f"{name} in {vals}")
3090+
elif not is_named_instance(var.upper_bound, "builtins.object"):
3091+
if mypy.options._based:
3092+
vs.append(f"{name}: {renderer(var.upper_bound)}")
3093+
else:
3094+
vs.append(f"{name} <: {renderer(var.upper_bound)}")
3095+
else:
3096+
vs.append(name)
3097+
else:
3098+
# For other TypeVarLikeTypes, just use the name
3099+
vs.append(var.name)
3100+
return f"[{', '.join(vs)}] "
3101+
30933102
def visit_overloaded(self, t: Overloaded) -> str:
30943103
a = []
30953104
for i in t.items:

test-data/unit/check-based-type-render.test

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ class A(Generic[T2]):
1414

1515
reveal_type(A.f) # N: Revealed type is "def [T2 (from A), T] (self: __main__.A[T2], t: T, t2: T2) -> T | T2"
1616
reveal_type(A[int]().f) # N: Revealed type is "def [T] (t: T, t2: int) -> T | int"
17+
A.f = 1 # E: Cannot assign to a method [assignment] \
18+
# E: Incompatible types in assignment (expression has type "int", variable has type "[T2 (from A), T] (A[T2], T, T2) -> T | T2") [assignment]
19+
20+
21+
[case testGenericFunction]
22+
from typing import TypeVar
23+
T = TypeVar("T", bound=int)
24+
25+
def foo(t: T) -> T: ...
26+
reveal_type(foo) # N: Revealed type is "def [T: int] (t: T) -> T"
27+
foo = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "[T: int] (T) -> T") [assignment]
1728

1829

1930
[case testRenderAny]
@@ -47,14 +58,18 @@ from typing import TypeVar
4758
T = TypeVar("T", bound=int)
4859

4960
def foo(t: T): ...
50-
reveal_type(foo) # N: Revealed type is "def[T: int](t: T)"
61+
reveal_type(foo) # N: Revealed type is "def [T: int] (t: T)"
5162

5263

5364
[case testInferredNever]
54-
a: bool
55-
if a: ...
56-
elif not a: ...
57-
else:
58-
reveal_type(a) # N: Revealed type is "Never"
59-
# E: Statement is unreachable [unreachable]
65+
a: int = [] # E: Incompatible types in assignment (expression has type "list[Never]", variable has type "int") [assignment]
66+
reveal_type([]) # N: Revealed type is "list[Never]"
67+
6068

69+
[case testNoReturnAsNever]
70+
from typing import NoReturn
71+
a: NoReturn = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "Never") [assignment]
72+
def b() -> NoReturn: ...
73+
reveal_type(a) # N: Revealed type is "Never"
74+
reveal_type(b) # N: Revealed type is "def () -> Never"
75+
b = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "() -> Never") [assignment]

0 commit comments

Comments
 (0)