Skip to content

Commit 3019fd9

Browse files
Merge branch 'master' into index-range
2 parents 0e89fff + a8d2f13 commit 3019fd9

File tree

9 files changed

+73
-16
lines changed

9 files changed

+73
-16
lines changed

docs/source/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,9 @@
278278
intersphinx_mapping = {
279279
"python": ("https://docs.python.org/3", None),
280280
"attrs": ("https://www.attrs.org/en/stable/", None),
281-
"cython": ("https://docs.cython.org/en/latest", None),
281+
"cython": ("https://cython.readthedocs.io/en/stable", None),
282282
"monkeytype": ("https://monkeytype.readthedocs.io/en/latest", None),
283-
"setuptools": ("https://setuptools.readthedocs.io/en/latest", None),
283+
"setuptools": ("https://setuptools.pypa.io/en/latest", None),
284284
}
285285

286286

mypy/checker.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def __init__(
397397
self.is_stub = tree.is_stub
398398
self.is_typeshed_stub = tree.is_typeshed_file(options)
399399
self.inferred_attribute_types = None
400+
self.allow_constructor_cache = True
400401

401402
# If True, process function definitions. If False, don't. This is used
402403
# for processing module top levels in fine-grained incremental mode.
@@ -500,12 +501,16 @@ def check_first_pass(self) -> None:
500501
)
501502

502503
def check_second_pass(
503-
self, todo: Sequence[DeferredNode | FineGrainedDeferredNode] | None = None
504+
self,
505+
todo: Sequence[DeferredNode | FineGrainedDeferredNode] | None = None,
506+
*,
507+
allow_constructor_cache: bool = True,
504508
) -> bool:
505509
"""Run second or following pass of type checking.
506510
507511
This goes through deferred nodes, returning True if there were any.
508512
"""
513+
self.allow_constructor_cache = allow_constructor_cache
509514
self.recurse_into_functions = True
510515
with state.strict_optional_set(self.options.strict_optional), checker_state.set(self):
511516
if not todo and not self.deferred_nodes:
@@ -5577,6 +5582,8 @@ def infer_variable_types_from_type_maps(
55775582
previous_type, _, _ = self.check_lvalue(expr)
55785583
if previous_type is not None:
55795584
already_exists = True
5585+
if isinstance(expr.node, Var) and expr.node.is_final:
5586+
self.msg.cant_assign_to_final(expr.name, False, expr)
55805587
if self.check_subtype(
55815588
typ,
55825589
previous_type,

mypy/checker_shared.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class TypeCheckerSharedApi(CheckerPluginInterface):
137137
module_refs: set[str]
138138
scope: CheckerScope
139139
checking_missing_await: bool
140+
allow_constructor_cache: bool
140141

141142
@property
142143
@abstractmethod

mypy/checkexpr.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3469,7 +3469,7 @@ def visit_op_expr(self, e: OpExpr) -> Type:
34693469
# It's actually a type expression X | Y.
34703470
return self.accept(e.analyzed)
34713471
if e.op == "and" or e.op == "or":
3472-
return self.check_boolean_op(e, e)
3472+
return self.check_boolean_op(e)
34733473
if e.op == "*" and isinstance(e.left, ListExpr):
34743474
# Expressions of form [...] * e get special type inference.
34753475
return self.check_list_multiply(e)
@@ -4256,20 +4256,18 @@ def check_op(
42564256
context=context,
42574257
)
42584258

4259-
def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
4259+
def check_boolean_op(self, e: OpExpr) -> Type:
42604260
"""Type check a boolean operation ('and' or 'or')."""
42614261

42624262
# A boolean operation can evaluate to either of the operands.
42634263

4264-
# We use the current type context to guide the type inference of of
4264+
# We use the current type context to guide the type inference of
42654265
# the left operand. We also use the left operand type to guide the type
42664266
# inference of the right operand so that expressions such as
42674267
# '[1] or []' are inferred correctly.
42684268
ctx = self.type_context[-1]
42694269
left_type = self.accept(e.left, ctx)
4270-
expanded_left_type = try_expanding_sum_type_to_union(
4271-
self.accept(e.left, ctx), "builtins.bool"
4272-
)
4270+
expanded_left_type = try_expanding_sum_type_to_union(left_type, "builtins.bool")
42734271

42744272
assert e.op in ("and", "or") # Checked by visit_op_expr
42754273

mypy/nodes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3026,6 +3026,7 @@ class is generic then it will be a type constructor of higher kind.
30263026
"dataclass_transform_spec",
30273027
"is_type_check_only",
30283028
"deprecated",
3029+
"type_object_type",
30293030
)
30303031

30313032
_fullname: str # Fully qualified name
@@ -3182,6 +3183,10 @@ class is generic then it will be a type constructor of higher kind.
31823183
# The type's deprecation message (in case it is deprecated)
31833184
deprecated: str | None
31843185

3186+
# Cached value of class constructor type, i.e. the type of class object when it
3187+
# appears in runtime context.
3188+
type_object_type: mypy.types.FunctionLike | None
3189+
31853190
FLAGS: Final = [
31863191
"is_abstract",
31873192
"is_enum",
@@ -3240,6 +3245,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
32403245
self.dataclass_transform_spec = None
32413246
self.is_type_check_only = False
32423247
self.deprecated = None
3248+
self.type_object_type = None
32433249

32443250
def add_type_vars(self) -> None:
32453251
self.has_type_var_tuple_type = False

mypy/semanal_infer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def infer_decorator_signature_if_simple(
3131
"""
3232
if dec.var.is_property:
3333
# Decorators are expected to have a callable type (it's a little odd).
34+
# TODO: this may result in wrong type if @property is applied to decorated method.
3435
if dec.func.type is None:
3536
dec.var.type = CallableType(
3637
[AnyType(TypeOfAny.special_form)],
@@ -47,6 +48,8 @@ def infer_decorator_signature_if_simple(
4748
for expr in dec.decorators:
4849
preserve_type = False
4950
if isinstance(expr, RefExpr) and isinstance(expr.node, FuncDef):
51+
if expr.fullname == "typing.no_type_check":
52+
return
5053
if expr.node.type and is_identity_signature(expr.node.type):
5154
preserve_type = True
5255
if not preserve_type:

mypy/server/update.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,10 +1025,12 @@ def key(node: FineGrainedDeferredNode) -> int:
10251025
# We seem to need additional passes in fine-grained incremental mode.
10261026
checker.pass_num = 0
10271027
checker.last_pass = 3
1028-
more = checker.check_second_pass(nodes)
1028+
# It is tricky to reliably invalidate constructor cache in fine-grained increments.
1029+
# See PR 19514 description for details.
1030+
more = checker.check_second_pass(nodes, allow_constructor_cache=False)
10291031
while more:
10301032
more = False
1031-
if graph[module_id].type_checker().check_second_pass():
1033+
if graph[module_id].type_checker().check_second_pass(allow_constructor_cache=False):
10321034
more = True
10331035

10341036
if manager.options.export_types:

mypy/typeops.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from collections.abc import Iterable, Sequence
1212
from typing import Any, Callable, TypeVar, cast
1313

14+
from mypy.checker_state import checker_state
1415
from mypy.copytype import copy_type
1516
from mypy.expandtype import expand_type, expand_type_by_instance
1617
from mypy.maptype import map_instance_to_supertype
@@ -145,6 +146,15 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
145146
where ... are argument types for the __init__/__new__ method (without the self
146147
argument). Also, the fallback type will be 'type' instead of 'function'.
147148
"""
149+
allow_cache = (
150+
checker_state.type_checker is not None
151+
and checker_state.type_checker.allow_constructor_cache
152+
)
153+
154+
if info.type_object_type is not None:
155+
if allow_cache:
156+
return info.type_object_type
157+
info.type_object_type = None
148158

149159
# We take the type from whichever of __init__ and __new__ is first
150160
# in the MRO, preferring __init__ if there is a tie.
@@ -167,7 +177,15 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
167177
init_index = info.mro.index(init_method.node.info)
168178
new_index = info.mro.index(new_method.node.info)
169179

170-
fallback = info.metaclass_type or named_type("builtins.type")
180+
if info.metaclass_type is not None:
181+
fallback = info.metaclass_type
182+
elif checker_state.type_checker:
183+
# Prefer direct call when it is available. It is faster, and,
184+
# unfortunately, some callers provide bogus callback.
185+
fallback = checker_state.type_checker.named_type("builtins.type")
186+
else:
187+
fallback = named_type("builtins.type")
188+
171189
if init_index < new_index:
172190
method: FuncBase | Decorator = init_method.node
173191
is_new = False
@@ -189,7 +207,10 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
189207
is_bound=True,
190208
fallback=named_type("builtins.function"),
191209
)
192-
return class_callable(sig, info, fallback, None, is_new=False)
210+
result: FunctionLike = class_callable(sig, info, fallback, None, is_new=False)
211+
if allow_cache:
212+
info.type_object_type = result
213+
return result
193214

194215
# Otherwise prefer __init__ in a tie. It isn't clear that this
195216
# is the right thing, but __new__ caused problems with
@@ -199,12 +220,19 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
199220
# Construct callable type based on signature of __init__. Adjust
200221
# return type and insert type arguments.
201222
if isinstance(method, FuncBase):
223+
if isinstance(method, OverloadedFuncDef) and not method.type:
224+
# Do not cache if the type is not ready. Same logic for decorators is
225+
# achieved in early return above because is_valid_constructor() is False.
226+
allow_cache = False
202227
t = function_type(method, fallback)
203228
else:
204229
assert isinstance(method.type, ProperType)
205230
assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this
206231
t = method.type
207-
return type_object_type_from_function(t, info, method.info, fallback, is_new)
232+
result = type_object_type_from_function(t, info, method.info, fallback, is_new)
233+
if allow_cache:
234+
info.type_object_type = result
235+
return result
208236

209237

210238
def is_valid_constructor(n: SymbolNode | None) -> bool:
@@ -865,8 +893,8 @@ def function_type(func: FuncBase, fallback: Instance) -> FunctionLike:
865893
if isinstance(func, FuncItem):
866894
return callable_type(func, fallback)
867895
else:
868-
# Broken overloads can have self.type set to None.
869-
# TODO: should we instead always set the type in semantic analyzer?
896+
# Either a broken overload, or decorated overload type is not ready.
897+
# TODO: make sure the caller defers if possible.
870898
assert isinstance(func, OverloadedFuncDef)
871899
any_type = AnyType(TypeOfAny.from_error)
872900
dummy = CallableType(
@@ -1254,6 +1282,8 @@ def get_protocol_member(
12541282
if member == "__call__" and class_obj:
12551283
# Special case: class objects always have __call__ that is just the constructor.
12561284

1285+
# TODO: this is wrong, it creates callables that are not recognized as type objects.
1286+
# Long-term, we should probably get rid of this callback argument altogether.
12571287
def named_type(fullname: str) -> Instance:
12581288
return Instance(left.type.mro[-1], [])
12591289

test-data/unit/check-python310.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,3 +2839,13 @@ match value_type:
28392839
case _:
28402840
assert_never(value_type)
28412841
[builtins fixtures/tuple.pyi]
2842+
2843+
[case testAssignmentToFinalInMatchCaseNotAllowed]
2844+
from typing import Final
2845+
2846+
FOO: Final[int] = 10
2847+
2848+
val: int = 8
2849+
match val:
2850+
case FOO: # E: Cannot assign to final name "FOO"
2851+
pass

0 commit comments

Comments
 (0)