Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,12 @@ def add_invertible_flag(
help="Show links to error code documentation",
group=error_group,
)
add_invertible_flag(
"--reveal-simple-types",
default=False,
help="Use compact (but potentially ambiguous) type representation in reveal_type()",
group=error_group,
)
add_invertible_flag(
"--pretty",
default=False,
Expand Down
2 changes: 2 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,7 @@ def reveal_type(self, typ: Type, context: Context) -> None:

# Nothing special here; just create the note:
visitor = TypeStrVisitor(options=self.options)
visitor.reveal_simple_types = self.options.reveal_simple_types
self.note(f'Revealed type is "{typ.accept(visitor)}"', context)

def reveal_locals(self, type_map: dict[str, Type | None], context: Context) -> None:
Expand All @@ -1754,6 +1755,7 @@ def reveal_locals(self, type_map: dict[str, Type | None], context: Context) -> N
self.note("Revealed local types are:", context)
for k, v in sorted_locals.items():
visitor = TypeStrVisitor(options=self.options)
visitor.reveal_simple_types = self.options.reveal_simple_types
self.note(f" {k}: {v.accept(visitor) if v is not None else None}", context)
else:
self.note("There are no locals to reveal", context)
Expand Down
1 change: 1 addition & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ def __init__(self) -> None:
self.show_error_end: bool = False
self.hide_error_codes = False
self.show_error_code_links = False
self.reveal_simple_types = False
# Use soft word wrap and show trimmed source snippets with error location markers.
self.pretty = False
self.dump_graph = False
Expand Down
41 changes: 34 additions & 7 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3719,6 +3719,9 @@ def __init__(self, id_mapper: IdMapper | None = None, *, options: Options) -> No
self.id_mapper = id_mapper
self.options = options
self.dotted_aliases: set[TypeAliasType] | None = None
# This visitor is used in other contexts (not just reveal_type()), so only
# use this option when explicitly set by the caller.
self.reveal_simple_types = False

def visit_unbound_type(self, t: UnboundType, /) -> str:
s = t.name + "?"
Expand Down Expand Up @@ -3759,6 +3762,8 @@ def visit_instance(self, t: Instance, /) -> str:
# Instances with a literal fallback should never be generic. If they are,
# something went wrong so we fall back to showing the full Instance repr.
s = f"{t.last_known_value.accept(self)}?"
elif self.reveal_simple_types:
s = t.type.name or "<???>"
else:
s = t.type.fullname or t.type.name or "<???>"

Expand All @@ -3775,10 +3780,13 @@ def visit_instance(self, t: Instance, /) -> str:
return s

def visit_type_var(self, t: TypeVarType, /) -> str:
s = f"{t.name}`{t.id}"
if self.reveal_simple_types:
s = t.name
else:
s = f"{t.name}`{t.id}"
if self.id_mapper and t.upper_bound:
s += f"(upper_bound={t.upper_bound.accept(self)})"
if t.has_default():
if t.has_default() and not self.reveal_simple_types:
s += f" = {t.default.accept(self)}"
return s

Expand All @@ -3787,7 +3795,10 @@ def visit_param_spec(self, t: ParamSpecType, /) -> str:
s = ""
if t.prefix.arg_types:
s += f"[{self.list_str(t.prefix.arg_types)}, **"
s += f"{t.name_with_suffix()}`{t.id}"
if self.reveal_simple_types:
s += t.name_with_suffix()
else:
s += f"{t.name_with_suffix()}`{t.id}"
if t.prefix.arg_types:
s += "]"
if t.has_default():
Expand Down Expand Up @@ -3824,8 +3835,11 @@ def visit_parameters(self, t: Parameters, /) -> str:
return f"[{s}]"

def visit_type_var_tuple(self, t: TypeVarTupleType, /) -> str:
s = f"{t.name}`{t.id}"
if t.has_default():
if self.reveal_simple_types:
s = t.name
else:
s = f"{t.name}`{t.id}"
if t.has_default() and not self.reveal_simple_types:
s += f" = {t.default.accept(self)}"
return s

Expand Down Expand Up @@ -3854,7 +3868,10 @@ def visit_callable_type(self, t: CallableType, /) -> str:
s += name + ": "
type_str = t.arg_types[i].accept(self)
if t.arg_kinds[i] == ARG_STAR2 and t.unpack_kwargs:
type_str = f"Unpack[{type_str}]"
if self.reveal_simple_types:
type_str = f"**{type_str}"
else:
type_str = f"Unpack[{type_str}]"
s += type_str
if t.arg_kinds[i].is_optional():
s += " ="
Expand Down Expand Up @@ -3933,7 +3950,10 @@ def item_str(name: str, typ: str) -> str:
prefix = ""
if t.fallback and t.fallback.type:
if t.fallback.type.fullname not in TPDICT_FB_NAMES:
prefix = repr(t.fallback.type.fullname) + ", "
if self.reveal_simple_types:
prefix = t.fallback.type.name + ", "
else:
prefix = repr(t.fallback.type.fullname) + ", "
return f"TypedDict({prefix}{s})"

def visit_raw_expression_type(self, t: RawExpressionType, /) -> str:
Expand Down Expand Up @@ -3967,6 +3987,11 @@ def visit_placeholder_type(self, t: PlaceholderType, /) -> str:
def visit_type_alias_type(self, t: TypeAliasType, /) -> str:
if t.alias is None:
return "<alias (unfixed)>"
if self.reveal_simple_types:
type_str = t.alias.name
if t.args:
type_str += f"[{self.list_str(t.args)}]"
return type_str
if not t.is_recursive:
return get_proper_type(t).accept(self)
if self.dotted_aliases is None:
Expand All @@ -3979,6 +4004,8 @@ def visit_type_alias_type(self, t: TypeAliasType, /) -> str:
return type_str

def visit_unpack_type(self, t: UnpackType, /) -> str:
if self.reveal_simple_types:
return f"*{t.type.accept(self)}"
return f"Unpack[{t.type.accept(self)}]"

def list_str(self, a: Iterable[Type], *, use_or_syntax: bool = False) -> str:
Expand Down
27 changes: 27 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -2737,3 +2737,30 @@ def f() -> None:
[file mypy.ini]
\[mypy]
disallow_redefinition = true

[case testRevealSimpleTypes]
# flags: --reveal-simple-types
from typing import Generic, TypeVar
from typing_extensions import TypeVarTuple, Unpack, TypedDict

T = TypeVar("T", bound=int)
S = TypeVar("S", default=list[T])

class A(Generic[T, S]): ...
reveal_type(A) # N: Revealed type is "def [T <: int, S = list[T]] () -> A[T, S]"

Ts = TypeVarTuple("Ts")
def foo(x: int, *xs: Unpack[Ts]) -> tuple[int, Unpack[Ts]]: ...
reveal_type(foo) # N: Revealed type is "def [Ts] (x: int, *xs: *Ts) -> tuple[int, *Ts]"

class User(TypedDict):
age: int
name: str

def bar(x: int, **kwargs: Unpack[User]) -> None: ...
reveal_type(bar) # N: Revealed type is "def (x: int, **kwargs: **TypedDict(User, {'age': int, 'name': str}))"

Alias = tuple[T, S]
x: Alias[int, str]
reveal_type(x) # N: Revealed type is "Alias[int, str]"
[builtins fixtures/tuple.pyi]