Skip to content

Commit 53777e4

Browse files
authored
Merge branch 'master' into assignment-expr-type
2 parents e532ccd + b1ac028 commit 53777e4

Some content is hidden

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

51 files changed

+1534
-932
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
# the oldest and newest supported Python versions
3434
- name: Test suite with py39-ubuntu, mypyc-compiled
3535
python: '3.9'
36-
os: ubuntu-22.04-arm
36+
os: ubuntu-24.04-arm
3737
toxenv: py
3838
tox_extra_args: "-n 4"
3939
test_mypyc: true
@@ -44,31 +44,31 @@ jobs:
4444
tox_extra_args: "-n 4"
4545
- name: Test suite with py310-ubuntu
4646
python: '3.10'
47-
os: ubuntu-22.04-arm
47+
os: ubuntu-24.04-arm
4848
toxenv: py
4949
tox_extra_args: "-n 4"
5050
- name: Test suite with py311-ubuntu, mypyc-compiled
5151
python: '3.11'
52-
os: ubuntu-22.04-arm
52+
os: ubuntu-24.04-arm
5353
toxenv: py
5454
tox_extra_args: "-n 4"
5555
test_mypyc: true
5656
- name: Test suite with py312-ubuntu, mypyc-compiled
5757
python: '3.12'
58-
os: ubuntu-22.04-arm
58+
os: ubuntu-24.04-arm
5959
toxenv: py
6060
tox_extra_args: "-n 4"
6161
test_mypyc: true
6262
- name: Test suite with py313-ubuntu, mypyc-compiled
6363
python: '3.13'
64-
os: ubuntu-22.04-arm
64+
os: ubuntu-24.04-arm
6565
toxenv: py
6666
tox_extra_args: "-n 4"
6767
test_mypyc: true
6868

6969
# - name: Test suite with py314-dev-ubuntu
7070
# python: '3.14-dev'
71-
# os: ubuntu-22.04-arm
71+
# os: ubuntu-24.04-arm
7272
# toxenv: py
7373
# tox_extra_args: "-n 4"
7474
# allow_failure: true

mypy/checker.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -216,22 +216,22 @@
216216

217217
T = TypeVar("T")
218218

219-
DEFAULT_LAST_PASS: Final = 1 # Pass numbers start at 0
219+
DEFAULT_LAST_PASS: Final = 2 # Pass numbers start at 0
220220

221221
# Maximum length of fixed tuple types inferred when narrowing from variadic tuples.
222222
MAX_PRECISE_TUPLE_SIZE: Final = 8
223223

224-
DeferredNodeType: _TypeAlias = Union[FuncDef, LambdaExpr, OverloadedFuncDef, Decorator]
224+
DeferredNodeType: _TypeAlias = Union[FuncDef, OverloadedFuncDef, Decorator]
225225
FineGrainedDeferredNodeType: _TypeAlias = Union[FuncDef, MypyFile, OverloadedFuncDef]
226226

227227

228228
# A node which is postponed to be processed during the next pass.
229229
# In normal mode one can defer functions and methods (also decorated and/or overloaded)
230-
# and lambda expressions. Nested functions can't be deferred -- only top-level functions
230+
# but not lambda expressions. Nested functions can't be deferred -- only top-level functions
231231
# and methods of classes not defined within a function can be deferred.
232232
class DeferredNode(NamedTuple):
233233
node: DeferredNodeType
234-
# And its TypeInfo (for semantic analysis self type handling
234+
# And its TypeInfo (for semantic analysis self type handling)
235235
active_typeinfo: TypeInfo | None
236236

237237

@@ -528,10 +528,7 @@ def check_partial(self, node: DeferredNodeType | FineGrainedDeferredNodeType) ->
528528
else:
529529
self.recurse_into_functions = True
530530
with self.binder.top_frame_context():
531-
if isinstance(node, LambdaExpr):
532-
self.expr_checker.accept(node)
533-
else:
534-
self.accept(node)
531+
self.accept(node)
535532

536533
def check_top_level(self, node: MypyFile) -> None:
537534
"""Check only the top-level of a module, skipping function definitions."""
@@ -558,13 +555,13 @@ def defer_node(self, node: DeferredNodeType, enclosing_class: TypeInfo | None) -
558555
self.deferred_nodes.append(DeferredNode(node, enclosing_class))
559556

560557
def handle_cannot_determine_type(self, name: str, context: Context) -> None:
561-
node = self.scope.top_non_lambda_function()
558+
node = self.scope.top_level_function()
562559
if self.pass_num < self.last_pass and isinstance(node, FuncDef):
563560
# Don't report an error yet. Just defer. Note that we don't defer
564561
# lambdas because they are coupled to the surrounding function
565562
# through the binder and the inferred type of the lambda, so it
566563
# would get messy.
567-
enclosing_class = self.scope.enclosing_class()
564+
enclosing_class = self.scope.enclosing_class(node)
568565
self.defer_node(node, enclosing_class)
569566
# Set a marker so that we won't infer additional types in this
570567
# function. Any inferred types could be bogus, because there's at
@@ -1111,6 +1108,7 @@ def check_func_item(
11111108
"""
11121109
self.dynamic_funcs.append(defn.is_dynamic() and not type_override)
11131110

1111+
enclosing_node_deferred = self.current_node_deferred
11141112
with self.enter_partial_types(is_function=True):
11151113
typ = self.function_type(defn)
11161114
if type_override:
@@ -1122,7 +1120,7 @@ def check_func_item(
11221120
raise RuntimeError("Not supported")
11231121

11241122
self.dynamic_funcs.pop()
1125-
self.current_node_deferred = False
1123+
self.current_node_deferred = enclosing_node_deferred
11261124

11271125
if name == "__exit__":
11281126
self.check__exit__return_type(defn)
@@ -2156,7 +2154,14 @@ def check_method_override_for_base_with_name(
21562154
if self.pass_num < self.last_pass:
21572155
# If there are passes left, defer this node until next pass,
21582156
# otherwise try reconstructing the method type from available information.
2159-
self.defer_node(defn, defn.info)
2157+
# For consistency, defer an enclosing top-level function (if any).
2158+
top_level = self.scope.top_level_function()
2159+
if isinstance(top_level, FuncDef):
2160+
self.defer_node(top_level, self.scope.enclosing_class(top_level))
2161+
else:
2162+
# Specify enclosing class explicitly, as we check type override before
2163+
# entering e.g. decorators or overloads.
2164+
self.defer_node(defn, defn.info)
21602165
return True
21612166
elif isinstance(original_node, (FuncDef, OverloadedFuncDef)):
21622167
original_type = self.function_type(original_node)
@@ -4767,7 +4772,7 @@ def visit_return_stmt(self, s: ReturnStmt) -> None:
47674772
self.binder.unreachable()
47684773

47694774
def check_return_stmt(self, s: ReturnStmt) -> None:
4770-
defn = self.scope.top_function()
4775+
defn = self.scope.current_function()
47714776
if defn is not None:
47724777
if defn.is_generator:
47734778
return_type = self.get_generator_return_type(
@@ -4779,7 +4784,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
47794784
return_type = self.return_types[-1]
47804785
return_type = get_proper_type(return_type)
47814786

4782-
is_lambda = isinstance(self.scope.top_function(), LambdaExpr)
4787+
is_lambda = isinstance(defn, LambdaExpr)
47834788
if isinstance(return_type, UninhabitedType):
47844789
# Avoid extra error messages for failed inference in lambdas
47854790
if not is_lambda and not return_type.ambiguous:
@@ -5337,6 +5342,7 @@ def check_for_untyped_decorator(
53375342
self.options.disallow_untyped_decorators
53385343
and is_typed_callable(func.type)
53395344
and is_untyped_decorator(dec_type)
5345+
and not self.current_node_deferred
53405346
):
53415347
self.msg.typed_function_untyped_decorator(func.name, dec_expr)
53425348

@@ -8554,14 +8560,15 @@ class CheckerScope:
85548560
def __init__(self, module: MypyFile) -> None:
85558561
self.stack = [module]
85568562

8557-
def top_function(self) -> FuncItem | None:
8563+
def current_function(self) -> FuncItem | None:
85588564
for e in reversed(self.stack):
85598565
if isinstance(e, FuncItem):
85608566
return e
85618567
return None
85628568

8563-
def top_non_lambda_function(self) -> FuncItem | None:
8564-
for e in reversed(self.stack):
8569+
def top_level_function(self) -> FuncItem | None:
8570+
"""Return top-level non-lambda function."""
8571+
for e in self.stack:
85658572
if isinstance(e, FuncItem) and not isinstance(e, LambdaExpr):
85668573
return e
85678574
return None
@@ -8571,11 +8578,11 @@ def active_class(self) -> TypeInfo | None:
85718578
return self.stack[-1]
85728579
return None
85738580

8574-
def enclosing_class(self) -> TypeInfo | None:
8581+
def enclosing_class(self, func: FuncItem | None = None) -> TypeInfo | None:
85758582
"""Is there a class *directly* enclosing this function?"""
8576-
top = self.top_function()
8577-
assert top, "This method must be called from inside a function"
8578-
index = self.stack.index(top)
8583+
func = func or self.current_function()
8584+
assert func, "This method must be called from inside a function"
8585+
index = self.stack.index(func)
85798586
assert index, "CheckerScope stack must always start with a module"
85808587
enclosing = self.stack[index - 1]
85818588
if isinstance(enclosing, TypeInfo):
@@ -8589,7 +8596,7 @@ def active_self_type(self) -> Instance | TupleType | None:
85898596
In particular, inside a function nested in method this returns None.
85908597
"""
85918598
info = self.active_class()
8592-
if not info and self.top_function():
8599+
if not info and self.current_function():
85938600
info = self.enclosing_class()
85948601
if info:
85958602
return fill_typevars(info)

mypy/checkexpr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5523,7 +5523,7 @@ def visit_super_expr(self, e: SuperExpr) -> Type:
55235523
if type_info in mro:
55245524
index = mro.index(type_info)
55255525
else:
5526-
method = self.chk.scope.top_function()
5526+
method = self.chk.scope.current_function()
55275527
# Mypy explicitly allows supertype upper bounds (and no upper bound at all)
55285528
# for annotating self-types. However, if such an annotation is used for
55295529
# checking super() we will still get an error. So to be consistent, we also
@@ -5598,7 +5598,7 @@ def _super_arg_types(self, e: SuperExpr) -> Type | tuple[Type, Type]:
55985598
type_type: ProperType = TypeType(current_type)
55995599

56005600
# Use the type of the self argument, in case it was annotated
5601-
method = self.chk.scope.top_function()
5601+
method = self.chk.scope.current_function()
56025602
assert method is not None
56035603
if method.arguments:
56045604
instance_type: Type = method.arguments[0].variable.type or current_type

mypy/semanal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3708,9 +3708,9 @@ def store_final_status(self, s: AssignmentStmt) -> None:
37083708
cur_node = self.type.names.get(lval.name, None)
37093709
if cur_node and isinstance(cur_node.node, Var) and cur_node.node.is_final:
37103710
assert self.function_stack
3711-
top_function = self.function_stack[-1]
3711+
current_function = self.function_stack[-1]
37123712
if (
3713-
top_function.name == "__init__"
3713+
current_function.name == "__init__"
37143714
and cur_node.node.final_unset_in_class
37153715
and not cur_node.node.final_set_in_init
37163716
and not (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs)

mypy/semanal_main.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626

2727
from __future__ import annotations
2828

29+
from collections.abc import Iterator
2930
from contextlib import nullcontext
31+
from itertools import groupby
3032
from typing import TYPE_CHECKING, Callable, Final, Optional, Union
3133
from typing_extensions import TypeAlias as _TypeAlias
3234

@@ -232,26 +234,66 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None:
232234
final_iteration = not any_progress
233235

234236

237+
def order_by_subclassing(targets: list[FullTargetInfo]) -> Iterator[FullTargetInfo]:
238+
"""Make sure that superclass methods are always processed before subclass methods.
239+
240+
This algorithm is not very optimal, but it is simple and should work well for lists
241+
that are already almost correctly ordered.
242+
"""
243+
244+
# First, group the targets by their TypeInfo (since targets are sorted by line,
245+
# we know that each TypeInfo will appear as group key only once).
246+
grouped = [(k, list(g)) for k, g in groupby(targets, key=lambda x: x[3])]
247+
remaining_infos = {info for info, _ in grouped if info is not None}
248+
249+
next_group = 0
250+
while grouped:
251+
if next_group >= len(grouped):
252+
# This should never happen, if there is an MRO cycle, it should be reported
253+
# and fixed during top-level processing.
254+
raise ValueError("Cannot order method targets by MRO")
255+
next_info, group = grouped[next_group]
256+
if next_info is None:
257+
# Trivial case, not methods but functions, process them straight away.
258+
yield from group
259+
grouped.pop(next_group)
260+
continue
261+
if any(parent in remaining_infos for parent in next_info.mro[1:]):
262+
# We cannot process this method group yet, try a next one.
263+
next_group += 1
264+
continue
265+
yield from group
266+
grouped.pop(next_group)
267+
remaining_infos.discard(next_info)
268+
# Each time after processing a method group we should retry from start,
269+
# since there may be some groups that are not blocked on parents anymore.
270+
next_group = 0
271+
272+
235273
def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None:
236274
# Process functions.
275+
all_targets = []
237276
for module in scc:
238277
tree = graph[module].tree
239278
assert tree is not None
240-
analyzer = graph[module].manager.semantic_analyzer
241279
# In principle, functions can be processed in arbitrary order,
242280
# but _methods_ must be processed in the order they are defined,
243281
# because some features (most notably partial types) depend on
244282
# order of definitions on self.
245283
#
246284
# There can be multiple generated methods per line. Use target
247-
# name as the second sort key to get a repeatable sort order on
248-
# Python 3.5, which doesn't preserve dictionary order.
285+
# name as the second sort key to get a repeatable sort order.
249286
targets = sorted(get_all_leaf_targets(tree), key=lambda x: (x[1].line, x[0]))
250-
for target, node, active_type in targets:
251-
assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator))
252-
process_top_level_function(
253-
analyzer, graph[module], module, target, node, active_type, patches
254-
)
287+
all_targets.extend(
288+
[(module, target, node, active_type) for target, node, active_type in targets]
289+
)
290+
291+
for module, target, node, active_type in order_by_subclassing(all_targets):
292+
analyzer = graph[module].manager.semantic_analyzer
293+
assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator))
294+
process_top_level_function(
295+
analyzer, graph[module], module, target, node, active_type, patches
296+
)
255297

256298

257299
def process_top_level_function(
@@ -308,6 +350,11 @@ def process_top_level_function(
308350
str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo]
309351
]
310352

353+
# Same as above but includes module as first item.
354+
FullTargetInfo: _TypeAlias = tuple[
355+
str, str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo]
356+
]
357+
311358

312359
def get_all_leaf_targets(file: MypyFile) -> list[TargetInfo]:
313360
"""Return all leaf targets in a symbol table (module-level and methods)."""

mypy/stubdoc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
Sig: _TypeAlias = tuple[str, str]
2222

2323

24-
_TYPE_RE: Final = re.compile(r"^[a-zA-Z_][\w\[\], .\"\']*(\.[a-zA-Z_][\w\[\], ]*)*$")
24+
_TYPE_RE: Final = re.compile(r"^[a-zA-Z_][\w\[\], .\"\'|]*(\.[a-zA-Z_][\w\[\], ]*)*$")
2525
_ARG_NAME_RE: Final = re.compile(r"\**[A-Za-z_][A-Za-z0-9_]*$")
2626

2727

mypy/stubtest.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -574,9 +574,23 @@ def verify_typeinfo(
574574

575575
# If it came from the metaclass, consider the runtime_attr to be MISSING
576576
# for a more accurate message
577-
if runtime_attr is not MISSING and type(runtime) is not runtime:
578-
if getattr(runtime_attr, "__objclass__", None) is type(runtime):
579-
runtime_attr = MISSING
577+
if (
578+
runtime_attr is not MISSING
579+
and type(runtime) is not runtime
580+
and getattr(runtime_attr, "__objclass__", None) is type(runtime)
581+
):
582+
runtime_attr = MISSING
583+
584+
# __setattr__ and __delattr__ on object are a special case,
585+
# so if we only have these methods inherited from there, pretend that
586+
# we don't have them. See python/typeshed#7385.
587+
if (
588+
entry in ("__setattr__", "__delattr__")
589+
and runtime_attr is not MISSING
590+
and runtime is not object
591+
and getattr(runtime_attr, "__objclass__", None) is object
592+
):
593+
runtime_attr = MISSING
580594

581595
# Do not error for an object missing from the stub
582596
# If the runtime object is a types.WrapperDescriptorType object
@@ -1092,9 +1106,11 @@ def verify_funcitem(
10921106

10931107

10941108
@verify.register(Missing)
1095-
def verify_none(
1109+
def verify_missing(
10961110
stub: Missing, runtime: MaybeMissing[Any], object_path: list[str]
10971111
) -> Iterator[Error]:
1112+
if runtime is MISSING:
1113+
return
10981114
yield Error(object_path, "is not present in stub", stub, runtime)
10991115

11001116

mypy/test/teststubgen.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,9 @@ def test_is_valid_type(self) -> None:
14051405
assert is_valid_type("Literal[True]")
14061406
assert is_valid_type("Literal[Color.RED]")
14071407
assert is_valid_type("Literal[None]")
1408+
assert is_valid_type("str | int")
1409+
assert is_valid_type("dict[str, int] | int")
1410+
assert is_valid_type("tuple[str, ...]")
14081411
assert is_valid_type(
14091412
'Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]'
14101413
)

0 commit comments

Comments
 (0)