Skip to content

Commit d8d8dca

Browse files
authored
Simpler/cleaner reveal_type() (#20929)
As discussed with @JukkaL, I only select most important things, but make the new behavior default. I will make the bulk of test output updates in a separate PR, here I only update tests where it is easier to update them, than to postpone. Three main changes here: * Strip `builtins.` prefix everywhere. * Simpler type variables (no ids, no repeated defaults). * Cleaned-up, fixed, and unified handling on `Unpack[...]`. Few smaller notes: * In some cases we ignored `options` when formatting types (esp. in tests). * There is a bunch of code duplication, but it looks hard to fix, so I don't. * Single quotes for TypedDict names are redundant (just add to visual noise), so I remove them as well.
1 parent 8cd6d89 commit d8d8dca

23 files changed

Lines changed: 217 additions & 132 deletions

mypy/messages.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2970,11 +2970,20 @@ def [T <: int] f(self, x: int, y: T) -> None
29702970
if tp.arg_kinds[i] == ARG_STAR2:
29712971
s += "**"
29722972
name = tp.arg_names[i]
2973+
if not name and not options.reveal_verbose_types:
2974+
# Avoid ambiguous (and weird) formatting for anonymous args/kwargs.
2975+
if tp.arg_kinds[i] == ARG_STAR and isinstance(tp.arg_types[i], UnpackType):
2976+
name = "args"
2977+
elif tp.arg_kinds[i] == ARG_STAR2 and tp.unpack_kwargs:
2978+
name = "kwargs"
29732979
if name:
29742980
s += name + ": "
29752981
type_str = format_type_bare(tp.arg_types[i], options)
29762982
if tp.arg_kinds[i] == ARG_STAR2 and tp.unpack_kwargs:
2977-
type_str = f"Unpack[{type_str}]"
2983+
if options.reveal_verbose_types:
2984+
type_str = f"Unpack[{type_str}]"
2985+
else:
2986+
type_str = f"**{type_str}"
29782987
s += type_str
29792988
if tp.arg_kinds[i].is_optional():
29802989
s += " = ..."

mypy/options.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ def __init__(self) -> None:
372372
self.show_error_end: bool = False
373373
self.hide_error_codes = False
374374
self.show_error_code_links = False
375+
# This is an internal-only flag to simplify migrating test output.
376+
self.reveal_verbose_types = False
375377
# Use soft word wrap and show trimmed source snippets with error location markers.
376378
self.pretty = False
377379
self.dump_graph = False
@@ -433,7 +435,7 @@ def __init__(self) -> None:
433435
self.mypyc_skip_c_generation = False
434436

435437
def use_star_unpack(self) -> bool:
436-
return self.python_version >= (3, 11)
438+
return self.python_version >= (3, 11) or not self.reveal_verbose_types
437439

438440
def snapshot(self) -> dict[str, object]:
439441
"""Produce a comparable snapshot of this Option"""

mypy/semanal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@
292292
TypeAliasType,
293293
TypedDictType,
294294
TypeOfAny,
295+
TypeStrVisitor,
295296
TypeType,
296297
TypeVarId,
297298
TypeVarLikeType,
@@ -1387,7 +1388,7 @@ def process_deprecated_overload(self, defn: OverloadedFuncDef) -> None:
13871388
elif (deprecated := self.get_deprecated(d)) is not None:
13881389
deprecation = True
13891390
if isinstance(typ := item.func.type, CallableType):
1390-
typestr = f" {typ} "
1391+
typestr = f" {typ.accept(TypeStrVisitor(options=self.options))} "
13911392
else:
13921393
typestr = " "
13931394
item.func.deprecated = (

mypy/test/testcheck.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def run_case_once(
137137
options.use_builtins_fixtures = True
138138
options.show_traceback = True
139139
options.native_parser = bool(os.environ.get("TEST_NATIVE_PARSER"))
140+
options.reveal_verbose_types = not testcase.name.endswith("_no_verbose_reveal")
140141

141142
if options.num_workers:
142143
options.fixed_format_cache = True

mypy/test/testfinegrained.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo
155155
options.export_types = "inspect" in testcase.file
156156
# Treat empty bodies safely for these test cases.
157157
options.allow_empty_bodies = not testcase.name.endswith("_no_empty")
158+
options.reveal_verbose_types = True
158159
if re.search("flags:.*--follow-imports", source) is None:
159160
# Override the default for follow_imports
160161
options.follow_imports = "error"

mypy/test/testmerge.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def build(self, source: str, testcase: DataDrivenTestCase) -> BuildResult | None
102102
options.export_types = True
103103
options.show_traceback = True
104104
options.allow_empty_bodies = True
105+
options.reveal_verbose_types = True
105106
main_path = os.path.join(test_temp_dir, "main")
106107

107108
self.str_conv.options = options
@@ -222,7 +223,8 @@ def dump_types(
222223
key=lambda n: (
223224
n.line,
224225
short_type(n),
225-
n.str_with_options(self.str_conv.options) + str(type_map[n]),
226+
n.str_with_options(self.str_conv.options)
227+
+ type_map[n].accept(self.type_str_conv),
226228
),
227229
):
228230
typ = type_map[expr]

mypy/test/testsemanal.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from mypy.modulefinder import BuildSource
99
from mypy.nodes import TypeInfo
1010
from mypy.options import Options
11+
from mypy.strconv import StrConv
1112
from mypy.test.config import test_temp_dir
1213
from mypy.test.data import DataDrivenTestCase, DataSuite
1314
from mypy.test.helpers import (
@@ -19,8 +20,9 @@
1920
)
2021

2122
# Semantic analyzer test cases: dump parse tree
22-
2323
# Semantic analysis test case description files.
24+
from mypy.types import TypeStrVisitor
25+
2426
semanal_files = find_test_files(
2527
pattern="semanal-*.test",
2628
exclude=[
@@ -38,6 +40,7 @@ def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Opti
3840
options.semantic_analysis_only = True
3941
options.show_traceback = True
4042
options.python_version = PYTHON3_VERSION
43+
options.reveal_verbose_types = True
4144
return options
4245

4346

@@ -154,14 +157,22 @@ class SemAnalTypeInfoSuite(DataSuite):
154157
required_out_section = True
155158
files = ["semanal-typeinfo.test"]
156159

160+
def setup(self) -> None:
161+
super().setup()
162+
self.str_conv = StrConv(options=Options())
163+
self.type_str_conv = TypeStrVisitor(options=Options())
164+
157165
def run_case(self, testcase: DataDrivenTestCase) -> None:
158166
"""Perform a test case."""
167+
src = "\n".join(testcase.input)
168+
options = get_semanal_options(src, testcase)
169+
self.str_conv.options = options
170+
self.type_str_conv.options = options
159171
try:
160172
# Build test case input.
161-
src = "\n".join(testcase.input)
162173
result = build.build(
163174
sources=[BuildSource("main", None, src)],
164-
options=get_semanal_options(src, testcase),
175+
options=options,
165176
alt_lib_path=test_temp_dir,
166177
)
167178
a = result.errors
@@ -179,7 +190,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
179190
typeinfos[n.fullname] = n.node
180191

181192
# The output is the symbol table converted into a string.
182-
a = str(typeinfos).split("\n")
193+
a = typeinfos.dump(self.str_conv, self.type_str_conv).split("\n")
183194
except CompileError as e:
184195
a = e.messages
185196
assert_string_arrays_equal(
@@ -190,10 +201,10 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
190201

191202

192203
class TypeInfoMap(dict[str, TypeInfo]):
193-
def __str__(self) -> str:
204+
def dump(self, str_conv: StrConv, type_str_conv: TypeStrVisitor) -> str:
194205
a: list[str] = ["TypeInfoMap("]
195206
for x, y in sorted(self.items()):
196-
ti = ("\n" + " ").join(str(y).split("\n"))
207+
ti = ("\n" + " ").join(y.dump(str_conv, type_str_conv).split("\n"))
197208
a.append(f" {x} : {ti}")
198209
a[-1] += ")"
199210
return "\n".join(a)

mypy/test/teststubtest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,7 +2799,7 @@ def test_output(self) -> None:
27992799
f'error: {TEST_MODULE_NAME}.bad is inconsistent, stub parameter "number" differs '
28002800
'from runtime parameter "num"\n'
28012801
f"Stub: in file {TEST_MODULE_NAME}.pyi:1\n"
2802-
"def (number: builtins.int, text: builtins.str)\n"
2802+
"def (number: int, text: str)\n"
28032803
f"Runtime: in file {TEST_MODULE_NAME}.py:1\ndef (num, text)\n\n"
28042804
"Found 1 error (checked 1 module)\n"
28052805
)
@@ -2994,7 +2994,7 @@ def myfunction(arg: str, /) -> None: ...
29942994
stub = result.files["__main__"].names["myfunction"].node
29952995
assert isinstance(stub, nodes.OverloadedFuncDef)
29962996
sig = mypy.stubtest.Signature.from_overloadedfuncdef(stub)
2997-
assert str(sig) == "def (arg: builtins.int | builtins.str)"
2997+
assert str(sig) == "def (arg: int | str)"
29982998

29992999
def test_config_file(self) -> None:
30003000
runtime = "temp = 5\n"

mypy/test/testtransform.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None:
3838
options.use_builtins_fixtures = True
3939
options.semantic_analysis_only = True
4040
options.show_traceback = True
41+
options.reveal_verbose_types = True
4142
result = build.build(
4243
sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir
4344
)

mypy/test/testtypegen.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
3535
options.export_types = True
3636
options.preserve_asts = True
3737
options.allow_empty_bodies = True
38+
options.reveal_verbose_types = True
3839
result = build.build(
3940
sources=[BuildSource("main", None, src)],
4041
options=options,

0 commit comments

Comments
 (0)