diff --git a/CHANGELOG.md b/CHANGELOG.md index 953cb414b126..43d2712b54cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ Support for this will be dropped in the first half of 2026! Contributed by Marc Mueller (PR [20156](https://github.com/python/mypy/pull/20156)). +### Removed Flag: `--force-union-syntax` + +Mypy only supports Python 3.10+. Removed the `--force-union-syntax` flag as it's no longer necessary. + +Contributed by Marc Mueller (PR [20405](https://github.com/python/mypy/pull/20405)) + ## Mypy 1.19 We’ve just uploaded mypy 1.19.0 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index 5efec6855593..310bb78a293b 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -958,12 +958,6 @@ in error messages. useful or they may be overly noisy. If ``N`` is negative, there is no limit. The default limit is -1. -.. option:: --force-union-syntax - - Always use ``Union[]`` and ``Optional[]`` for union types - in error messages (instead of the ``|`` operator), - even on Python 3.10+. - .. _incremental: diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 7abd1f02db68..77f952471007 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -930,15 +930,6 @@ These options may only be set in the global section (``[mypy]``). Show absolute paths to files. -.. confval:: force_union_syntax - - :type: boolean - :default: False - - Always use ``Union[]`` and ``Optional[]`` for union types - in error messages (instead of the ``|`` operator), - even on Python 3.10+. - Incremental mode **************** diff --git a/mypy/main.py b/mypy/main.py index 3d44499a13d3..fdfb7376bb3b 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -820,14 +820,6 @@ def add_invertible_flag( "--force-uppercase-builtins", default=False, help=argparse.SUPPRESS, group=none_group ) - add_invertible_flag( - "--force-union-syntax", default=False, help=argparse.SUPPRESS, group=none_group - ) - # For internal use only! Will be removed once Mypy drops support for Python 3.9. - add_invertible_flag( - "--overwrite-union-syntax", default=False, help=argparse.SUPPRESS, group=none_group - ) - lint_group = parser.add_argument_group( title="Configuring warnings", description="Detect code that is sound but redundant or problematic.", diff --git a/mypy/messages.py b/mypy/messages.py index 28a4f8d614ca..471b1f65e642 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1813,10 +1813,7 @@ def need_annotation_for_var( type_dec = "" if not node.type.type: # partial None - if options.use_or_syntax(): - recommended_type = f"{type_dec} | None" - else: - recommended_type = f"Optional[{type_dec}]" + recommended_type = f"{type_dec} | None" elif node.type.type.fullname in reverse_builtin_aliases: # partial types other than partial None name = node.type.type.fullname.partition(".")[2] @@ -2713,17 +2710,9 @@ def format_literal_value(typ: LiteralType) -> str: ) if len(union_items) == 1 and isinstance(get_proper_type(union_items[0]), NoneType): - return ( - f"{literal_str} | None" - if options.use_or_syntax() - else f"Optional[{literal_str}]" - ) + return f"{literal_str} | None" elif union_items: - return ( - f"{literal_str} | {format_union(union_items)}" - if options.use_or_syntax() - else f"Union[{', '.join(format_union_items(union_items))}, {literal_str}]" - ) + return f"{literal_str} | {format_union(union_items)}" else: return literal_str else: @@ -2734,17 +2723,9 @@ def format_literal_value(typ: LiteralType) -> str: ) if print_as_optional: rest = [t for t in typ.items if not isinstance(get_proper_type(t), NoneType)] - return ( - f"{format(rest[0])} | None" - if options.use_or_syntax() - else f"Optional[{format(rest[0])}]" - ) + return f"{format(rest[0])} | None" else: - s = ( - format_union(typ.items) - if options.use_or_syntax() - else f"Union[{', '.join(format_union_items(typ.items))}]" - ) + s = format_union(typ.items) return s elif isinstance(typ, NoneType): return "None" diff --git a/mypy/options.py b/mypy/options.py index a9e31218ae42..7ddda905d8fb 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -412,9 +412,6 @@ def __init__(self) -> None: self.disable_memoryview_promotion = False # Deprecated, Mypy only supports Python 3.9+ self.force_uppercase_builtins = False - self.force_union_syntax = False - # Mypy internal use only! Set during test run. - self.overwrite_union_syntax = False # Sets custom output format self.output: str | None = None @@ -433,11 +430,6 @@ def use_lowercase_names(self) -> bool: ) return True - def use_or_syntax(self) -> bool: - if self.python_version >= (3, 10): - return not self.force_union_syntax - return self.overwrite_union_syntax - def use_star_unpack(self) -> bool: return self.python_version >= (3, 11) diff --git a/mypy/suggestions.py b/mypy/suggestions.py index a583486799b7..39a220e34091 100644 --- a/mypy/suggestions.py +++ b/mypy/suggestions.py @@ -891,7 +891,7 @@ def visit_typeddict_type(self, t: TypedDictType) -> str: def visit_union_type(self, t: UnionType) -> str: if len(t.items) == 2 and is_overlapping_none(t): s = remove_optional(t).accept(self) - return f"{s} | None" if self.options.use_or_syntax() else f"Optional[{s}]" + return f"{s} | None" else: return super().visit_union_type(t) diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py index 658a87016e16..fbd3b8f2aac3 100644 --- a/mypy/test/helpers.py +++ b/mypy/test/helpers.py @@ -357,7 +357,6 @@ def parse_options( options = Options() options.error_summary = False options.hide_error_codes = True - options.overwrite_union_syntax = True # 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/testcheck.py b/mypy/test/testcheck.py index 155d7912bb9e..5e533e3c245d 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -136,7 +136,6 @@ def run_case_once( options = parse_options(original_program_text, testcase, incremental_step) options.use_builtins_fixtures = True options.show_traceback = True - options.overwrite_union_syntax = True if options.num_workers: options.fixed_format_cache = True diff --git a/mypy/test/testcmdline.py b/mypy/test/testcmdline.py index dc72917de788..450f5abc14c3 100644 --- a/mypy/test/testcmdline.py +++ b/mypy/test/testcmdline.py @@ -60,7 +60,6 @@ def test_python_cmdline(testcase: DataDrivenTestCase, step: int) -> None: args = parse_args(testcase.input[0]) custom_cwd = parse_cwd(testcase.input[1]) if len(testcase.input) > 1 else None args.append("--show-traceback") - args.append("--overwrite-union-syntax") if "--error-summary" not in args: args.append("--no-error-summary") if "--show-error-codes" not in args: diff --git a/mypy/test/testfinegrained.py b/mypy/test/testfinegrained.py index 7899c5b611a1..b098c1fb0ad2 100644 --- a/mypy/test/testfinegrained.py +++ b/mypy/test/testfinegrained.py @@ -152,7 +152,6 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo options.use_fine_grained_cache = self.use_cache and not build_cache options.cache_fine_grained = self.use_cache options.local_partial_types = True - options.overwrite_union_syntax = True options.export_types = "inspect" in testcase.file # Treat empty bodies safely for these test cases. options.allow_empty_bodies = not testcase.name.endswith("_no_empty") diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index cd9edd0a4790..c2c75f60be29 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -102,7 +102,6 @@ def build(self, source: str, testcase: DataDrivenTestCase) -> BuildResult | None options.export_types = True options.show_traceback = True options.allow_empty_bodies = True - options.overwrite_union_syntax = True main_path = os.path.join(test_temp_dir, "main") self.str_conv.options = options diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py index 324a38ee361d..d9d94a5edc38 100644 --- a/mypy/test/testparse.py +++ b/mypy/test/testparse.py @@ -38,7 +38,6 @@ def test_parser(testcase: DataDrivenTestCase) -> None: The argument contains the description of the test case. """ options = Options() - options.overwrite_union_syntax = True options.hide_error_codes = True if testcase.file.endswith("python310.test"): diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index bf2d99e15fe6..6d22aca07da7 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -52,7 +52,6 @@ def test_python_evaluation(testcase: DataDrivenTestCase, cache_dir: str) -> None "--no-error-summary", "--hide-error-codes", "--allow-empty-bodies", - "--overwrite-union-syntax", "--test-env", # Speeds up some checks ] interpreter = python3_path diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py index 58924f1b8389..11cd36c83fd7 100644 --- a/mypy/test/testsemanal.py +++ b/mypy/test/testsemanal.py @@ -38,7 +38,6 @@ def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Opti options.semantic_analysis_only = True options.show_traceback = True options.python_version = PYTHON3_VERSION - options.overwrite_union_syntax = True return options diff --git a/mypy/test/testtransform.py b/mypy/test/testtransform.py index caac13244d98..48a3eeed2115 100644 --- a/mypy/test/testtransform.py +++ b/mypy/test/testtransform.py @@ -38,7 +38,6 @@ def test_transform(testcase: DataDrivenTestCase) -> None: options.use_builtins_fixtures = True options.semantic_analysis_only = True options.show_traceback = True - options.overwrite_union_syntax = True result = build.build( sources=[BuildSource("main", None, src)], options=options, alt_lib_path=test_temp_dir ) diff --git a/mypy/test/testtypegen.py b/mypy/test/testtypegen.py index 38ac75df5f99..42d831beeecc 100644 --- a/mypy/test/testtypegen.py +++ b/mypy/test/testtypegen.py @@ -35,7 +35,6 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: options.export_types = True options.preserve_asts = True options.allow_empty_bodies = True - options.overwrite_union_syntax = True result = build.build( sources=[BuildSource("main", None, src)], options=options, diff --git a/mypy/types.py b/mypy/types.py index c1bc0fac52ff..c7516dd9f1c4 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3967,9 +3967,7 @@ def visit_literal_type(self, t: LiteralType, /) -> str: return f"Literal[{t.value_repr()}]" def visit_union_type(self, t: UnionType, /) -> str: - use_or_syntax = self.options.use_or_syntax() - s = self.list_str(t.items, use_or_syntax=use_or_syntax) - return s if use_or_syntax else f"Union[{s}]" + return self.list_str(t.items, use_or_syntax=True) def visit_partial_type(self, t: PartialType, /) -> str: if t.type is None: diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index e0e853c3dbf9..97db482e4cca 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3550,7 +3550,7 @@ if x: [builtins fixtures/dict.pyi] [case testSuggestPep604AnnotationForPartialNone] -# flags: --local-partial-types --python-version 3.10 --no-force-union-syntax +# flags: --local-partial-types --python-version 3.10 x = None # E: Need type annotation for "x" (hint: "x: | None = ...") [case testTupleContextFromIterable] diff --git a/test-data/unit/check-union-error-syntax.test b/test-data/unit/check-union-error-syntax.test index e938598aaefe..aea13ef59957 100644 --- a/test-data/unit/check-union-error-syntax.test +++ b/test-data/unit/check-union-error-syntax.test @@ -1,74 +1,37 @@ -[case testUnionErrorSyntax] -# flags: --python-version 3.10 --no-force-union-syntax -from typing import Union -x : Union[bool, str] -x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "bool | str") - [case testOrErrorSyntax] -# flags: --python-version 3.10 --force-union-syntax +# flags: --python-version 3.10 from typing import Union x : Union[bool, str] -x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Union[bool, str]") +x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "bool | str") [case testOrNoneErrorSyntax] -# flags: --python-version 3.10 --no-force-union-syntax +# flags: --python-version 3.10 from typing import Union x : Union[bool, None] x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "bool | None") -[case testOptionalErrorSyntax] -# flags: --python-version 3.10 --force-union-syntax -from typing import Union -x : Union[bool, None] -x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "Optional[bool]") - [case testNoneAsFinalItem] -# flags: --python-version 3.10 --no-force-union-syntax +# flags: --python-version 3.10 from typing import Union x : Union[bool, None, str] x = 3 # E: Incompatible types in assignment (expression has type "int", variable has type "bool | str | None") [case testLiteralOrErrorSyntax] -# flags: --python-version 3.10 --no-force-union-syntax +# flags: --python-version 3.10 from typing import Literal, Union x : Union[Literal[1], Literal[2], str] x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Literal[1, 2] | str") [builtins fixtures/tuple.pyi] -[case testLiteralUnionErrorSyntax] -# flags: --python-version 3.10 --force-union-syntax -from typing import Literal, Union -x : Union[Literal[1], Literal[2], str] -x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Union[str, Literal[1, 2]]") -[builtins fixtures/tuple.pyi] - [case testLiteralOrNoneErrorSyntax] -# flags: --python-version 3.10 --no-force-union-syntax +# flags: --python-version 3.10 from typing import Literal, Union x : Union[Literal[1], None] x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Literal[1] | None") [builtins fixtures/tuple.pyi] -[case testLiteralOptionalErrorSyntax] -# flags: --python-version 3.10 --force-union-syntax -from typing import Literal, Union -x : Union[Literal[1], None] -x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Optional[Literal[1]]") -[builtins fixtures/tuple.pyi] - -[case testUnionSyntaxRecombined] -# flags: --python-version 3.10 --force-union-syntax --allow-redefinition-new --local-partial-types -# The following revealed type is recombined because the finally body is visited twice. -try: - x = 1 - x = "" - x = {1: ""} -finally: - reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str, builtins.dict[builtins.int, builtins.str]]" -[builtins fixtures/isinstancelist.pyi] - [case testOrSyntaxRecombined] -# flags: --python-version 3.10 --no-force-union-syntax --allow-redefinition-new --local-partial-types +# flags: --python-version 3.10 --allow-redefinition-new --local-partial-types # The following revealed type is recombined because the finally body is visited twice. try: x = 1 diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test index c02f78be1834..2750c307ee73 100644 --- a/test-data/unit/daemon.test +++ b/test-data/unit/daemon.test @@ -456,7 +456,7 @@ $ dmypy inspect foo.py:3:10:3:17 -vv $ dmypy inspect foo.py:9:9:9:11 "int" $ dmypy inspect foo.py:11:1:11:3 -"Callable[[Optional[int]], None]" +"Callable[[int | None], None]" $ dmypy inspect foo.py:11:1:13:1 "None" $ dmypy inspect foo.py:1:2:3:4