Skip to content

Commit 0d1ab28

Browse files
Merge branch 'master' into patch-2
2 parents 3dd344b + 325f776 commit 0d1ab28

Some content is hidden

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

60 files changed

+1385
-257
lines changed

.github/workflows/mypy_primer.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ jobs:
6767
--debug \
6868
--additional-flags="--debug-serialize" \
6969
--output concise \
70-
--show-speed-regression \
7170
| tee diff_${{ matrix.shard-index }}.txt
7271
) || [ $? -eq 1 ]
7372
- if: ${{ matrix.shard-index == 0 }}

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: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,7 @@ of the above sections.
845845
x = 'a string'
846846
x.trim() # error: "str" has no attribute "trim" [attr-defined]
847847
848+
848849
.. _configuring-error-messages:
849850

850851
Configuring error messages
@@ -936,11 +937,6 @@ in error messages.
936937
useful or they may be overly noisy. If ``N`` is negative, there is
937938
no limit. The default limit is -1.
938939

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

946942
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

docs/source/error_code_list2.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,3 +612,44 @@ Example:
612612
# mypy: disallow-any-explicit
613613
from typing import Any
614614
x: Any = 1 # Error: Explicit "Any" type annotation [explicit-any]
615+
616+
617+
.. _code-exhaustive-match:
618+
619+
Check that match statements match exhaustively [match-exhaustive]
620+
-----------------------------------------------------------------------
621+
622+
If enabled with :option:`--enable-error-code exhaustive-match <mypy --enable-error-code>`,
623+
mypy generates an error if a match statement does not match all possible cases/types.
624+
625+
626+
Example:
627+
628+
.. code-block:: python
629+
630+
import enum
631+
632+
633+
class Color(enum.Enum):
634+
RED = 1
635+
BLUE = 2
636+
637+
val: Color = Color.RED
638+
639+
# OK without --enable-error-code exhaustive-match
640+
match val:
641+
case Color.RED:
642+
print("red")
643+
644+
# With --enable-error-code exhaustive-match
645+
# Error: Match statement has unhandled case for values of type "Literal[Color.BLUE]"
646+
match val:
647+
case Color.RED:
648+
print("red")
649+
650+
# OK with or without --enable-error-code exhaustive-match, since all cases are handled
651+
match val:
652+
case Color.RED:
653+
print("red")
654+
case _:
655+
print("other")

docs/source/literal_types.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,10 @@ If we forget to handle one of the cases, mypy will generate an error:
468468
assert_never(direction) # E: Argument 1 to "assert_never" has incompatible type "Direction"; expected "NoReturn"
469469
470470
Exhaustiveness checking is also supported for match statements (Python 3.10 and later).
471+
For match statements specifically, inexhaustive matches can be caught
472+
without needing to use ``assert_never`` by using
473+
:option:`--enable-error-code exhaustive-match <mypy --enable-error-code>`.
474+
471475

472476
Extra Enum checks
473477
*****************

mypy/checker.py

Lines changed: 58 additions & 25 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:
@@ -3427,7 +3448,9 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) ->
34273448
# store the rvalue type on the variable.
34283449
actual_lvalue_type = None
34293450
if lvalue_node.is_inferred and not lvalue_node.explicit_self_type:
3430-
rvalue_type = self.expr_checker.accept(rvalue, lvalue_node.type)
3451+
# Don't use partial types as context, similar to regular code path.
3452+
ctx = lvalue_node.type if not isinstance(lvalue_node.type, PartialType) else None
3453+
rvalue_type = self.expr_checker.accept(rvalue, ctx)
34313454
actual_lvalue_type = lvalue_node.type
34323455
lvalue_node.type = rvalue_type
34333456
lvalue_type, _ = self.node_type_from_base(lvalue_node.name, lvalue_node.info, lvalue)
@@ -5453,6 +5476,7 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
54535476
inferred_types = self.infer_variable_types_from_type_maps(type_maps)
54545477

54555478
# The second pass narrows down the types and type checks bodies.
5479+
unmatched_types: TypeMap = None
54565480
for p, g, b in zip(s.patterns, s.guards, s.bodies):
54575481
current_subject_type = self.expr_checker.narrow_type_from_binder(
54585482
named_subject, subject_type
@@ -5509,6 +5533,11 @@ def visit_match_stmt(self, s: MatchStmt) -> None:
55095533
else:
55105534
self.accept(b)
55115535
self.push_type_map(else_map, from_assignment=False)
5536+
unmatched_types = else_map
5537+
5538+
if unmatched_types is not None:
5539+
for typ in list(unmatched_types.values()):
5540+
self.msg.match_statement_inexhaustive_match(typ, s)
55125541

55135542
# This is needed due to a quirk in frame_context. Without it types will stay narrowed
55145543
# after the match.
@@ -7689,9 +7718,13 @@ def get_isinstance_type(self, expr: Expression) -> list[TypeRange] | None:
76897718
types: list[TypeRange] = []
76907719
for typ in all_types:
76917720
if isinstance(typ, FunctionLike) and typ.is_type_obj():
7692-
# Type variables may be present -- erase them, which is the best
7693-
# we can do (outside disallowing them here).
7694-
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
76957728
types.append(TypeRange(erased_type, is_upper_bound=False))
76967729
elif isinstance(typ, TypeType):
76977730
# 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: 15 additions & 10 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)
@@ -981,6 +981,10 @@ def expand_and_bind_callable(
981981
assert isinstance(expanded, CallableType)
982982
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
983983
# TODO: use check_call() to infer better type, same as for __set__().
984+
if not expanded.arg_types:
985+
# This can happen when accessing invalid property from its own body,
986+
# error will be reported elsewhere.
987+
return AnyType(TypeOfAny.from_error)
984988
return expanded.arg_types[0]
985989
else:
986990
return expanded.ret_type
@@ -1480,19 +1484,20 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F:
14801484
items = [bind_self_fast(c, original_type) for c in method.items]
14811485
return cast(F, Overloaded(items))
14821486
assert isinstance(method, CallableType)
1483-
if not method.arg_types:
1487+
func: CallableType = method
1488+
if not func.arg_types:
14841489
# Invalid method, return something.
1485-
return cast(F, method)
1486-
if method.arg_kinds[0] in (ARG_STAR, ARG_STAR2):
1490+
return method
1491+
if func.arg_kinds[0] in (ARG_STAR, ARG_STAR2):
14871492
# See typeops.py for details.
1488-
return cast(F, method)
1493+
return method
14891494
original_type = get_proper_type(original_type)
14901495
if isinstance(original_type, CallableType) and original_type.is_type_obj():
14911496
original_type = TypeType.make_normalized(original_type.ret_type)
1492-
res = method.copy_modified(
1493-
arg_types=method.arg_types[1:],
1494-
arg_kinds=method.arg_kinds[1:],
1495-
arg_names=method.arg_names[1:],
1496-
bound_args=[original_type],
1497+
res = func.copy_modified(
1498+
arg_types=func.arg_types[1:],
1499+
arg_kinds=func.arg_kinds[1:],
1500+
arg_names=func.arg_names[1:],
1501+
is_bound=True,
14971502
)
14981503
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

0 commit comments

Comments
 (0)