Skip to content

Commit 77eb25e

Browse files
Merge branch 'master' into import
2 parents cfc2ce2 + 325f776 commit 77eb25e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+919
-222
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22

33
## Next Release
44

5+
### Remove Support for targeting Python 3.8
6+
7+
Mypy now requires `--python-version 3.9` or greater. Support for only Python 3.8 is
8+
fully removed now. Given an unsupported version, mypy will default to the oldest
9+
supported one, currently 3.9.
10+
11+
This change is necessary because typeshed stopped supporting Python 3.8 after it
12+
reached its End of Life in October 2024.
13+
14+
Contributed by Marc Mueller
15+
(PR [19157](https://github.com/python/mypy/pull/19157), PR [19162](https://github.com/python/mypy/pull/19162)).
16+
17+
### Initial Support for Python 3.14
18+
19+
Mypy is now tested on 3.14 and mypyc works with 3.14.0b3 and later.
20+
Mypyc compiled wheels of mypy itself will be available for new versions after 3.14.0rc1 is released.
21+
22+
Note that not all new features might be supported just yet.
23+
24+
Contributed by Marc Mueller (PR [19164](https://github.com/python/mypy/pull/19164))
25+
26+
### Deprecated Flag: \--force-uppercase-builtins
27+
28+
Mypy only supports Python 3.9+. The \--force-uppercase-builtins flag is now deprecated and a no-op. It will be removed in a future version.
29+
30+
Contributed by Marc Mueller (PR [19176](https://github.com/python/mypy/pull/19176))
31+
532
## Mypy 1.16
633

734
We’ve just uploaded mypy 1.16 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)).

docs/source/command_line.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -937,11 +937,6 @@ in error messages.
937937
useful or they may be overly noisy. If ``N`` is negative, there is
938938
no limit. The default limit is -1.
939939

940-
.. option:: --force-uppercase-builtins
941-
942-
Always use ``List`` instead of ``list`` in error messages,
943-
even on Python 3.9+.
944-
945940
.. option:: --force-union-syntax
946941

947942
Always use ``Union[]`` and ``Optional[]`` for union types

docs/source/config_file.rst

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -922,14 +922,6 @@ These options may only be set in the global section (``[mypy]``).
922922

923923
Show absolute paths to files.
924924

925-
.. confval:: force_uppercase_builtins
926-
927-
:type: boolean
928-
:default: False
929-
930-
Always use ``List`` instead of ``list`` in error messages,
931-
even on Python 3.9+.
932-
933925
.. confval:: force_union_syntax
934926

935927
:type: boolean

mypy/checker.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from mypy.constraints import SUPERTYPE_OF
2626
from mypy.erasetype import erase_type, erase_typevars, remove_instance_last_known_values
2727
from mypy.errorcodes import TYPE_VAR, UNUSED_AWAITABLE, UNUSED_COROUTINE, ErrorCode
28-
from mypy.errors import Errors, ErrorWatcher, report_internal_error
28+
from mypy.errors import Errors, ErrorWatcher, LoopErrorWatcher, report_internal_error
2929
from mypy.expandtype import expand_type
3030
from mypy.literals import Key, extract_var_from_literal_hash, literal, literal_hash
3131
from mypy.maptype import map_instance_to_supertype
@@ -599,19 +599,27 @@ def accept_loop(
599599
# on without bound otherwise)
600600
widened_old = len(self.widened_vars)
601601

602-
# Disable error types that we cannot safely identify in intermediate iteration steps:
603-
warn_unreachable = self.options.warn_unreachable
604-
warn_redundant = codes.REDUNDANT_EXPR in self.options.enabled_error_codes
605-
self.options.warn_unreachable = False
606-
self.options.enabled_error_codes.discard(codes.REDUNDANT_EXPR)
607-
602+
# one set of `unreachable`, `redundant-expr`, and `redundant-casts` errors
603+
# per iteration step:
604+
uselessness_errors = []
605+
# one set of unreachable line numbers per iteration step:
606+
unreachable_lines = []
607+
# one set of revealed types per line where `reveal_type` is used (each
608+
# created set can grow during the iteration):
609+
revealed_types = defaultdict(set)
608610
iter = 1
609611
while True:
610612
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
611613
if on_enter_body is not None:
612614
on_enter_body()
613615

614-
self.accept(body)
616+
with LoopErrorWatcher(self.msg.errors) as watcher:
617+
self.accept(body)
618+
uselessness_errors.append(watcher.uselessness_errors)
619+
unreachable_lines.append(watcher.unreachable_lines)
620+
for key, values in watcher.revealed_types.items():
621+
revealed_types[key].update(values)
622+
615623
partials_new = sum(len(pts.map) for pts in self.partial_types)
616624
widened_new = len(self.widened_vars)
617625
# Perform multiple iterations if something changed that might affect
@@ -632,16 +640,29 @@ def accept_loop(
632640
if iter == 20:
633641
raise RuntimeError("Too many iterations when checking a loop")
634642

635-
# If necessary, reset the modified options and make up for the postponed error checks:
636-
self.options.warn_unreachable = warn_unreachable
637-
if warn_redundant:
638-
self.options.enabled_error_codes.add(codes.REDUNDANT_EXPR)
639-
if warn_unreachable or warn_redundant:
640-
with self.binder.frame_context(can_skip=True, break_frame=2, continue_frame=1):
641-
if on_enter_body is not None:
642-
on_enter_body()
643-
644-
self.accept(body)
643+
# Report only those `unreachable`, `redundant-expr`, and `redundant-casts`
644+
# errors that could not be ruled out in any iteration step:
645+
persistent_uselessness_errors = set()
646+
for candidate in set(itertools.chain(*uselessness_errors)):
647+
if all(
648+
(candidate in errors) or (candidate[2] in lines)
649+
for errors, lines in zip(uselessness_errors, unreachable_lines)
650+
):
651+
persistent_uselessness_errors.add(candidate)
652+
for error_info in persistent_uselessness_errors:
653+
context = Context(line=error_info[2], column=error_info[3])
654+
context.end_line = error_info[4]
655+
context.end_column = error_info[5]
656+
self.msg.fail(error_info[1], context, code=error_info[0])
657+
658+
# Report all types revealed in at least one iteration step:
659+
for note_info, types in revealed_types.items():
660+
sorted_ = sorted(types, key=lambda typ: typ.lower())
661+
revealed = sorted_[0] if len(types) == 1 else f"Union[{', '.join(sorted_)}]"
662+
context = Context(line=note_info[1], column=note_info[2])
663+
context.end_line = note_info[3]
664+
context.end_column = note_info[4]
665+
self.note(f'Revealed type is "{revealed}"', context)
645666

646667
# If exit_condition is set, assume it must be False on exit from the loop:
647668
if exit_condition:
@@ -2264,7 +2285,7 @@ def check_method_override_for_base_with_name(
22642285
original_type,
22652286
defn.name,
22662287
name,
2267-
base.name,
2288+
base.name if base.module_name == self.tree.fullname else base.fullname,
22682289
original_class_or_static,
22692290
override_class_or_static,
22702291
context,
@@ -2449,7 +2470,7 @@ def erase_override(t: Type) -> Type:
24492470
if not is_subtype(original_arg_type, erase_override(override_arg_type)):
24502471
context: Context = node
24512472
if isinstance(node, FuncDef) and not node.is_property:
2452-
arg_node = node.arguments[i + len(override.bound_args)]
2473+
arg_node = node.arguments[i + override.bound()]
24532474
if arg_node.line != -1:
24542475
context = arg_node
24552476
self.msg.argument_incompatible_with_supertype(
@@ -2664,7 +2685,7 @@ def check_typevar_defaults(self, tvars: Sequence[TypeVarLikeType]) -> None:
26642685
continue
26652686
if not is_subtype(tv.default, tv.upper_bound):
26662687
self.fail("TypeVar default must be a subtype of the bound type", tv)
2667-
if tv.values and not any(tv.default == value for value in tv.values):
2688+
if tv.values and not any(is_same_type(tv.default, value) for value in tv.values):
26682689
self.fail("TypeVar default must be one of the constraint types", tv)
26692690

26702691
def check_enum(self, defn: ClassDef) -> None:
@@ -7697,9 +7718,13 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
76977718
types: list[TypeRange] = []
76987719
for typ in all_types:
76997720
if isinstance(typ, FunctionLike) and typ.is_type_obj():
7700-
# Type variables may be present -- erase them, which is the best
7701-
# we can do (outside disallowing them here).
7702-
erased_type = erase_typevars(typ.items[0].ret_type)
7721+
# If a type is generic, `isinstance` can only narrow its variables to Any.
7722+
any_parameterized = fill_typevars_with_any(typ.type_object())
7723+
# Tuples may have unattended type variables among their items
7724+
if isinstance(any_parameterized, TupleType):
7725+
erased_type = erase_typevars(any_parameterized)
7726+
else:
7727+
erased_type = any_parameterized
77037728
types.append(TypeRange(erased_type, is_upper_bound=False))
77047729
elif isinstance(typ, TypeType):
77057730
# Type[A] means "any type that is a subtype of A" rather than "precisely type A"

mypy/checkexpr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4975,7 +4975,7 @@ def apply_type_arguments_to_callable(
49754975
tp.fallback,
49764976
name="tuple",
49774977
definition=tp.definition,
4978-
bound_args=tp.bound_args,
4978+
is_bound=tp.is_bound,
49794979
)
49804980
self.msg.incompatible_type_application(
49814981
min_arg_count, len(type_vars), len(args), ctx
@@ -6171,7 +6171,7 @@ def visit_type_var_expr(self, e: TypeVarExpr) -> Type:
61716171
):
61726172
if not is_subtype(p_default, e.upper_bound):
61736173
self.chk.fail("TypeVar default must be a subtype of the bound type", e)
6174-
if e.values and not any(p_default == value for value in e.values):
6174+
if e.values and not any(is_same_type(p_default, value) for value in e.values):
61756175
self.chk.fail("TypeVar default must be one of the constraint types", e)
61766176
return AnyType(TypeOfAny.special_form)
61776177

mypy/checkmember.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ def analyze_var(
921921
bound_items = []
922922
for ct in call_type.items if isinstance(call_type, UnionType) else [call_type]:
923923
p_ct = get_proper_type(ct)
924-
if isinstance(p_ct, FunctionLike) and not p_ct.is_type_obj():
924+
if isinstance(p_ct, FunctionLike) and (not p_ct.bound() or var.is_property):
925925
item = expand_and_bind_callable(p_ct, var, itype, name, mx, is_trivial_self)
926926
else:
927927
item = expand_without_binding(ct, var, itype, original_itype, mx)
@@ -1498,6 +1498,6 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F:
14981498
arg_types=func.arg_types[1:],
14991499
arg_kinds=func.arg_kinds[1:],
15001500
arg_names=func.arg_names[1:],
1501-
bound_args=[original_type],
1501+
is_bound=True,
15021502
)
15031503
return cast(F, res)

mypy/config_parser.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
_INI_PARSER_CALLABLE: _TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES]
2929

3030

31+
class VersionTypeError(argparse.ArgumentTypeError):
32+
"""Provide a fallback value if the Python version is unsupported."""
33+
34+
def __init__(self, *args: Any, fallback: tuple[int, int]) -> None:
35+
self.fallback = fallback
36+
super().__init__(*args)
37+
38+
3139
def parse_version(v: str | float) -> tuple[int, int]:
3240
m = re.match(r"\A(\d)\.(\d+)\Z", str(v))
3341
if not m:
@@ -44,7 +52,7 @@ def parse_version(v: str | float) -> tuple[int, int]:
4452
if isinstance(v, float):
4553
msg += ". You may need to put quotes around your Python version"
4654

47-
raise argparse.ArgumentTypeError(msg)
55+
raise VersionTypeError(msg, fallback=defaults.PYTHON3_VERSION_MIN)
4856
else:
4957
raise argparse.ArgumentTypeError(
5058
f"Python major version '{major}' out of range (must be 3)"
@@ -548,6 +556,9 @@ def parse_section(
548556
continue
549557
try:
550558
v = ct(section.get(key))
559+
except VersionTypeError as err_version:
560+
print(f"{prefix}{key}: {err_version}", file=stderr)
561+
v = err_version.fallback
551562
except argparse.ArgumentTypeError as err:
552563
print(f"{prefix}{key}: {err}", file=stderr)
553564
continue

mypy/errors.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections import defaultdict
77
from collections.abc import Iterable
88
from typing import Callable, Final, NoReturn, Optional, TextIO, TypeVar
9-
from typing_extensions import Literal, TypeAlias as _TypeAlias
9+
from typing_extensions import Literal, Self, TypeAlias as _TypeAlias
1010

1111
from mypy import errorcodes as codes
1212
from mypy.error_formatter import ErrorFormatter
@@ -179,7 +179,7 @@ def __init__(
179179
self._filter_deprecated = filter_deprecated
180180
self._filtered: list[ErrorInfo] | None = [] if save_filtered_errors else None
181181

182-
def __enter__(self) -> ErrorWatcher:
182+
def __enter__(self) -> Self:
183183
self.errors._watchers.append(self)
184184
return self
185185

@@ -220,6 +220,60 @@ def filtered_errors(self) -> list[ErrorInfo]:
220220
return self._filtered
221221

222222

223+
class LoopErrorWatcher(ErrorWatcher):
224+
"""Error watcher that filters and separately collects `unreachable` errors,
225+
`redundant-expr` and `redundant-casts` errors, and revealed types when analysing
226+
loops iteratively to help avoid making too-hasty reports."""
227+
228+
# Meaning of the tuple items: ErrorCode, message, line, column, end_line, end_column:
229+
uselessness_errors: set[tuple[ErrorCode, str, int, int, int, int]]
230+
231+
# Meaning of the tuple items: function_or_member, line, column, end_line, end_column:
232+
revealed_types: dict[tuple[str | None, int, int, int, int], set[str]]
233+
234+
# Not only the lines where the error report occurs but really all unreachable lines:
235+
unreachable_lines: set[int]
236+
237+
def __init__(
238+
self,
239+
errors: Errors,
240+
*,
241+
filter_errors: bool | Callable[[str, ErrorInfo], bool] = False,
242+
save_filtered_errors: bool = False,
243+
filter_deprecated: bool = False,
244+
) -> None:
245+
super().__init__(
246+
errors,
247+
filter_errors=filter_errors,
248+
save_filtered_errors=save_filtered_errors,
249+
filter_deprecated=filter_deprecated,
250+
)
251+
self.uselessness_errors = set()
252+
self.unreachable_lines = set()
253+
self.revealed_types = defaultdict(set)
254+
255+
def on_error(self, file: str, info: ErrorInfo) -> bool:
256+
257+
if info.code in (codes.UNREACHABLE, codes.REDUNDANT_EXPR, codes.REDUNDANT_CAST):
258+
self.uselessness_errors.add(
259+
(info.code, info.message, info.line, info.column, info.end_line, info.end_column)
260+
)
261+
if info.code == codes.UNREACHABLE:
262+
self.unreachable_lines.update(range(info.line, info.end_line + 1))
263+
return True
264+
265+
if info.code == codes.MISC and info.message.startswith("Revealed type is "):
266+
key = info.function_or_member, info.line, info.column, info.end_line, info.end_column
267+
types = info.message.split('"')[1]
268+
if types.startswith("Union["):
269+
self.revealed_types[key].update(types[6:-1].split(", "))
270+
else:
271+
self.revealed_types[key].add(types)
272+
return True
273+
274+
return super().on_error(file, info)
275+
276+
223277
class Errors:
224278
"""Container for compile errors.
225279

mypy/fixup.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,6 @@ def visit_callable_type(self, ct: CallableType) -> None:
271271
ct.ret_type.accept(self)
272272
for v in ct.variables:
273273
v.accept(self)
274-
for arg in ct.bound_args:
275-
if arg:
276-
arg.accept(self)
277274
if ct.type_guard is not None:
278275
ct.type_guard.accept(self)
279276
if ct.type_is is not None:

mypy/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ def add_invertible_flag(
801801
help="Disable strict Optional checks (inverse: --strict-optional)",
802802
)
803803

804+
# This flag is deprecated, Mypy only supports Python 3.9+
804805
add_invertible_flag(
805806
"--force-uppercase-builtins", default=False, help=argparse.SUPPRESS, group=none_group
806807
)
@@ -1494,6 +1495,9 @@ def set_strict_flags() -> None:
14941495
if options.strict_concatenate and not strict_option_set:
14951496
print("Warning: --strict-concatenate is deprecated; use --extra-checks instead")
14961497

1498+
if options.force_uppercase_builtins:
1499+
print("Warning: --force-uppercase-builtins is deprecated; mypy only supports Python 3.9+")
1500+
14971501
# Set target.
14981502
if special_opts.modules + special_opts.packages:
14991503
options.build_type = BuildType.MODULE

0 commit comments

Comments
 (0)