diff --git a/mypy/main.py b/mypy/main.py index acc27f1806b7..c469715fc401 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -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, diff --git a/mypy/messages.py b/mypy/messages.py index 59b1b3db07cc..ff2388802fb1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -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: @@ -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) diff --git a/mypy/options.py b/mypy/options.py index 3dc72c7e3051..fe8090d28452 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -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 diff --git a/mypy/types.py b/mypy/types.py index db8fd4605659..e687ecd28795 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -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 + "?" @@ -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 "" @@ -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 @@ -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(): @@ -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 @@ -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 += " =" @@ -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: @@ -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 "" + 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: @@ -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: diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 5e9c7f4be8dc..6660a828bceb 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -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]