Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)).
Expand Down
6 changes: 0 additions & 6 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
9 changes: 0 additions & 9 deletions docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
****************

Expand Down
8 changes: 0 additions & 8 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
29 changes: 5 additions & 24 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -1813,10 +1813,7 @@ def need_annotation_for_var(
type_dec = "<type>"
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]
Expand Down Expand Up @@ -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:
Expand All @@ -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"
Expand Down
8 changes: 0 additions & 8 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion mypy/suggestions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
1 change: 0 additions & 1 deletion mypy/test/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testcmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testfinegrained.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testmerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testpythoneval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testsemanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
1 change: 0 additions & 1 deletion mypy/test/testtransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
1 change: 0 additions & 1 deletion mypy/test/testtypegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 1 addition & 3 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -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: <type> | None = ...")

[case testTupleContextFromIterable]
Expand Down
51 changes: 7 additions & 44 deletions test-data/unit/check-union-error-syntax.test
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/daemon.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down