From 8b0041db6d7d2ba1ff15ae1cb01393ce0264d38f Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sat, 7 Feb 2026 23:07:53 -0800 Subject: [PATCH 1/6] Enable --pretty by default for better error messages Changed the default value of --pretty from False to True to provide visually nicer output by default. Users can still disable it with --no-pretty if they prefer more concise error messages. Updated defaults in: - mypy/options.py: Changed self.pretty = False to True - mypy/main.py: Changed default=False to default=True for --pretty flag - docs/source/command_line.rst: Added note about default and --no-pretty Fixes #19108 --- docs/source/command_line.rst | 1 + mypy/main.py | 2 +- mypy/options.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index b5081f113f917..5b7f43fce5985 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -937,6 +937,7 @@ in error messages. Use visually nicer output in error messages: use soft word wrap, show source code snippets, and show error location markers. + This is enabled by default. Use :option:`--no-pretty` to disable. .. option:: --no-color-output diff --git a/mypy/main.py b/mypy/main.py index c14bf16929cd9..0e23f6ed87ade 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -1007,7 +1007,7 @@ def add_invertible_flag( ) add_invertible_flag( "--pretty", - default=False, + default=True, help="Use visually nicer output in error messages:" " Use soft word wrap, show source code snippets," " and show error location markers", diff --git a/mypy/options.py b/mypy/options.py index 0a7ddb112e273..46f26b7722d08 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -365,7 +365,7 @@ def __init__(self) -> None: self.hide_error_codes = False self.show_error_code_links = False # Use soft word wrap and show trimmed source snippets with error location markers. - self.pretty = False + self.pretty = True self.dump_graph = False self.dump_deps = False self.logical_deps = False From f08b65f4fa85913a6e5a11d8ebfab7cadd35d664 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Fri, 13 Feb 2026 19:38:20 -0800 Subject: [PATCH 2/6] Fix docs build and test suite failures - Use literal markup for --no-pretty in docs to avoid unknown option warning - Set pretty=False in test infrastructure so existing test expectations remain valid when --pretty is not explicitly passed as a flag --- docs/source/command_line.rst | 2 +- mypy/test/helpers.py | 3 +++ mypy/test/testcmdline.py | 2 ++ mypy/test/testpythoneval.py | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 5b7f43fce5985..a01c9c85515ac 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -937,7 +937,7 @@ in error messages. Use visually nicer output in error messages: use soft word wrap, show source code snippets, and show error location markers. - This is enabled by default. Use :option:`--no-pretty` to disable. + This is enabled by default. Use ``--no-pretty`` to disable. .. option:: --no-color-output diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index fbd3b8f2aac32..0b49498b1ff3f 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -352,11 +352,14 @@ def parse_options( raise RuntimeError("Specifying targets via the flags pragma is not supported.") if "--show-error-codes" not in flag_list: options.hide_error_codes = True + if "--pretty" not in flag_list: + options.pretty = False else: flag_list = [] options = Options() options.error_summary = False options.hide_error_codes = True + options.pretty = False # Allow custom python version to override testfile_pyversion. if all(flag.split("=")[0] != "--python-version" for flag in flag_list): diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index 450f5abc14c34..39b736f409093 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -66,6 +66,8 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None: args.append("--hide-error-codes") if "--disallow-empty-bodies" not in args: args.append("--allow-empty-bodies") + if "--pretty" not in args: + args.append("--no-pretty") # Type check the program. fixed = [python3_path, "-m", "mypy"] env = os.environ.copy() diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index 6d22aca07da7f..b7088e5f93a71 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -52,6 +52,7 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None "--no-error-summary", "--hide-error-codes", "--allow-empty-bodies", + "--no-pretty", "--test-env", # Speeds up some checks ] interpreter = python3_path From 19b91c9f908653c63eefa6ba7a4137a8fff22586 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sat, 14 Feb 2026 23:17:37 -0800 Subject: [PATCH 3/6] Fix test failures from --pretty default change Fix add_invertible_flag to always use store_true for the flag and store_false for its inverse, regardless of the default value. The previous logic inverted the actions when default=True, causing --pretty to disable pretty mode and --no-pretty to enable it. Update all test harnesses to explicitly disable pretty mode for tests that don't opt into it, preventing source code snippets from appearing in test output that expects non-pretty format. --- mypy/main.py | 4 ++-- mypy/stubtest.py | 1 + mypy/test/testdaemon.py | 12 ++++++++++++ mypy/test/testerrorstream.py | 1 + mypy/test/testpep561.py | 2 +- mypyc/test/test_commandline.py | 2 +- 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 0e23f6ed87ade..f60cf59211e61 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -525,12 +525,12 @@ def add_invertible_flag( help += f" (inverse: {inverse})" arg = group.add_argument( - flag, action="store_false" if default else "store_true", dest=dest, help=help + flag, action="store_true", dest=dest, help=help ) dest = arg.dest group.add_argument( inverse, - action="store_true" if default else "store_false", + action="store_false", dest=dest, help=argparse.SUPPRESS, ) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index a6780984c1f54..2ae21a3777885 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -2286,6 +2286,7 @@ def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int: options = Options() options.incremental = False + options.pretty = False options.custom_typeshed_dir = args.custom_typeshed_dir if options.custom_typeshed_dir: options.abs_custom_typeshed_dir = os.path.abspath(options.custom_typeshed_dir) diff --git a/mypy/test/testdaemon.py b/mypy/test/testdaemon.py index 7115e682e60da..96e3396ca4e03 100644 --- a/mypy/test/testdaemon.py +++ b/mypy/test/testdaemon.py @@ -79,6 +79,18 @@ def parse_script(input: list[str]) -> list[list[str]]: def run_cmd(input: str) -> tuple[int, str]: if input[1:].startswith("mypy run --") and "--show-error-codes" not in input: input += " --hide-error-codes" + if "--pretty" not in input: + if input.startswith("dmypy ") and " -- " in input: + # For dmypy commands, mypy flags come after --, so append at end + input += " --no-pretty" + elif input.startswith("mypy ") and " -- " in input: + # For mypy commands, options come before --, so insert before -- + input = input.replace(" -- ", " --no-pretty -- ", 1) + elif input.startswith("dmypy run ") or input.startswith("dmypy start"): + # dmypy commands without -- need the separator added + input += " -- --no-pretty" + elif input.startswith("mypy "): + input += " --no-pretty" if input.startswith("dmypy "): input = sys.executable + " -m mypy." + input if input.startswith("mypy "): diff --git a/mypy/test/testerrorstream.py b/mypy/test/testerrorstream.py index a54a3495ddb2f..6cca7860526fe 100644 --- a/mypy/test/testerrorstream.py +++ b/mypy/test/testerrorstream.py @@ -27,6 +27,7 @@ def test_error_stream(testcase: DataDrivenTestCase) -> None: options = Options() options.show_traceback = True options.hide_error_codes = True + options.pretty = False logged_messages: list[str] = [] diff --git a/mypy/test/testpep561.py b/mypy/test/testpep561.py index 0afb69bc0c998..fdad87463af55 100644 --- a/mypy/test/testpep561.py +++ b/mypy/test/testpep561.py @@ -124,7 +124,7 @@ def test_pep561(testcase: DataDrivenTestCase) -> None: f.write(f"{s}\n") cmd_line.append(program) - cmd_line.extend(["--no-error-summary", "--hide-error-codes"]) + cmd_line.extend(["--no-error-summary", "--hide-error-codes", "--no-pretty"]) if python_executable != sys.executable: cmd_line.append(f"--python-executable={python_executable}") diff --git a/mypyc/test/test_commandline.py b/mypyc/test/test_commandline.py index f66ca2ec8ff02..0b94121b108a8 100644 --- a/mypyc/test/test_commandline.py +++ b/mypyc/test/test_commandline.py @@ -50,7 +50,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: try: # Compile program cmd = subprocess.run( - [sys.executable, "-m", "mypyc", *args], + [sys.executable, "-m", "mypyc", "--no-pretty", *args], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd="tmp", From e36037eff354d7686cbfdf2f245bf4454c8eb00e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Feb 2026 07:20:43 +0000 Subject: [PATCH 4/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/main.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index f60cf59211e61..6c31fe081ab08 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -524,16 +524,9 @@ def add_invertible_flag( if help is not argparse.SUPPRESS: help += f" (inverse: {inverse})" - arg = group.add_argument( - flag, action="store_true", dest=dest, help=help - ) + arg = group.add_argument(flag, action="store_true", dest=dest, help=help) dest = arg.dest - group.add_argument( - inverse, - action="store_false", - dest=dest, - help=argparse.SUPPRESS, - ) + group.add_argument(inverse, action="store_false", dest=dest, help=argparse.SUPPRESS) if strict_flag: assert dest is not None strict_flag_names.append(flag) From cc0e97bcbab164b416e8b43946e903939d044b4a Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Mon, 16 Feb 2026 11:16:41 -0800 Subject: [PATCH 5/6] Fix add_invertible_flag regression and ruff PIE810 violation Restore the original add_invertible_flag logic where the argparse action depends on the default value. The previous commit incorrectly simplified this to always use store_true/store_false, which broke all 7 flags with default=True (--no-namespace-packages, --no-warn-no-return, --no-implicit-reexport, --no-color-output, --no-error-summary, etc). The --pretty default is now controlled solely through Options.pretty=True in options.py, keeping default=False in add_invertible_flag so the flag semantics work correctly (--pretty -> store_true, --no-pretty -> store_false). Also fix ruff PIE810 by merging startswith calls into a tuple. --- mypy/main.py | 13 ++++++++++--- mypy/test/testdaemon.py | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index 6c31fe081ab08..c14bf16929cd9 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -524,9 +524,16 @@ def add_invertible_flag( if help is not argparse.SUPPRESS: help += f" (inverse: {inverse})" - arg = group.add_argument(flag, action="store_true", dest=dest, help=help) + arg = group.add_argument( + flag, action="store_false" if default else "store_true", dest=dest, help=help + ) dest = arg.dest - group.add_argument(inverse, action="store_false", dest=dest, help=argparse.SUPPRESS) + group.add_argument( + inverse, + action="store_true" if default else "store_false", + dest=dest, + help=argparse.SUPPRESS, + ) if strict_flag: assert dest is not None strict_flag_names.append(flag) @@ -1000,7 +1007,7 @@ def add_invertible_flag( ) add_invertible_flag( "--pretty", - default=True, + default=False, help="Use visually nicer output in error messages:" " Use soft word wrap, show source code snippets," " and show error location markers", diff --git a/mypy/test/testdaemon.py b/mypy/test/testdaemon.py index 96e3396ca4e03..b75a01c4eac7f 100644 --- a/mypy/test/testdaemon.py +++ b/mypy/test/testdaemon.py @@ -86,7 +86,7 @@ def run_cmd(input: str) -> tuple[int, str]: elif input.startswith("mypy ") and " -- " in input: # For mypy commands, options come before --, so insert before -- input = input.replace(" -- ", " --no-pretty -- ", 1) - elif input.startswith("dmypy run ") or input.startswith("dmypy start"): + elif input.startswith(("dmypy run ", "dmypy start")): # dmypy commands without -- need the separator added input += " -- --no-pretty" elif input.startswith("mypy "): From f9f7fd6c41c315788f94fb99ca11f81b8dc498da Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Mon, 16 Feb 2026 13:17:14 -0800 Subject: [PATCH 6/6] Fix --no-pretty injection for dmypy commands without -- separator The previous logic incorrectly appended --no-pretty after -- for all dmypy commands (like dmypy check), which caused --no-pretty to be treated as a filename. It also appended "-- --no-pretty" to dmypy run commands without --, which argparse rejected. Fix by: 1. Only injecting --no-pretty for dmypy run/start (not check/recheck) 2. For dmypy run/start without --, properly parse out dmypy-specific flags and reconstruct the command with -- separator so --no-pretty goes into the mypy flags portion. --- mypy/test/testdaemon.py | 66 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/mypy/test/testdaemon.py b/mypy/test/testdaemon.py index b75a01c4eac7f..acac3ee8338c5 100644 --- a/mypy/test/testdaemon.py +++ b/mypy/test/testdaemon.py @@ -8,6 +8,7 @@ from __future__ import annotations import os +import re import subprocess import sys import tempfile @@ -76,19 +77,76 @@ def parse_script(input: list[str]) -> list[list[str]]: return steps +def _add_no_pretty_to_dmypy(input: str) -> str: + """Add --no-pretty to a dmypy run/start command that has no -- separator. + + For dmypy run/start, mypy flags are passed as positional args after --. + When the command has no --, we need to insert -- before the positional args + and append --no-pretty. We must keep any dmypy-specific named flags + (like --export-types, --log-file FILE) before the -- separator. + """ + # Match: "dmypy run" or "dmypy start", then the rest of the args + m = re.match(r"(dmypy (?:run|start))\s*(.*)", input) + if not m: + return input + prefix = m.group(1) + rest = m.group(2) + + # Known dmypy run/start flags that take no value + no_value_flags = {"--export-types", "--verbose", "-v"} + # Known dmypy run/start flags that take a value + value_flags = {"--log-file", "--timeout", "--junit-xml", "--perf-stats-file"} + + parts = rest.split() + dmypy_flags: list[str] = [] + positional: list[str] = [] + i = 0 + while i < len(parts): + if parts[i] in no_value_flags: + dmypy_flags.append(parts[i]) + i += 1 + elif parts[i] in value_flags: + dmypy_flags.append(parts[i]) + if i + 1 < len(parts): + dmypy_flags.append(parts[i + 1]) + i += 2 + elif parts[i].startswith("-") and "=" in parts[i]: + # Handle --flag=value style for known flags + flag_name = parts[i].split("=")[0] + if flag_name in value_flags: + dmypy_flags.append(parts[i]) + else: + positional.append(parts[i]) + i += 1 + else: + positional.append(parts[i]) + i += 1 + + dmypy_part = " ".join(dmypy_flags) + positional_part = " ".join(positional) + result = prefix + if dmypy_part: + result += " " + dmypy_part + result += " -- " + if positional_part: + result += positional_part + " " + result += "--no-pretty" + return result + + def run_cmd(input: str) -> tuple[int, str]: if input[1:].startswith("mypy run --") and "--show-error-codes" not in input: input += " --hide-error-codes" if "--pretty" not in input: - if input.startswith("dmypy ") and " -- " in input: - # For dmypy commands, mypy flags come after --, so append at end + if input.startswith(("dmypy run ", "dmypy start")) and " -- " in input: + # For dmypy run/start, mypy flags come after --, so append at end input += " --no-pretty" elif input.startswith("mypy ") and " -- " in input: # For mypy commands, options come before --, so insert before -- input = input.replace(" -- ", " --no-pretty -- ", 1) elif input.startswith(("dmypy run ", "dmypy start")): - # dmypy commands without -- need the separator added - input += " -- --no-pretty" + # dmypy run/start without -- need the separator added + input = _add_no_pretty_to_dmypy(input) elif input.startswith("mypy "): input += " --no-pretty" if input.startswith("dmypy "):