Skip to content

Commit 45ba182

Browse files
authored
Merge branch 'master' into feature/gh-19392-decorator-selftypes
2 parents aad9eda + 3a2b788 commit 45ba182

File tree

12 files changed

+116
-58
lines changed

12 files changed

+116
-58
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/build.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def default_flush_errors(
194194
result.errors = messages
195195
return result
196196
except CompileError as e:
197-
# CompileErrors raised from an errors object carry all of the
197+
# CompileErrors raised from an errors object carry all the
198198
# messages that have not been reported out by error streaming.
199199
# Patch it up to contain either none or all none of the messages,
200200
# depending on whether we are flushing errors.
@@ -802,11 +802,11 @@ def correct_rel_imp(imp: ImportFrom | ImportAll) -> str:
802802
res.append((pri, sub_id, imp.line))
803803
else:
804804
all_are_submodules = False
805-
# Add cur_id as a dependency, even if all of the
805+
# Add cur_id as a dependency, even if all the
806806
# imports are submodules. Processing import from will try
807807
# to look through cur_id, so we should depend on it.
808-
# As a workaround for for some bugs in cycle handling (#4498),
809-
# if all of the imports are submodules, do the import at a lower
808+
# As a workaround for some bugs in cycle handling (#4498),
809+
# if all the imports are submodules, do the import at a lower
810810
# priority.
811811
pri = import_priority(imp, PRI_HIGH if not all_are_submodules else PRI_LOW)
812812
res.append((pri, cur_id, imp.line))
@@ -929,7 +929,7 @@ def write_deps_cache(
929929
) -> None:
930930
"""Write cache files for fine-grained dependencies.
931931
932-
Serialize fine-grained dependencies map for fine grained mode.
932+
Serialize fine-grained dependencies map for fine-grained mode.
933933
934934
Dependencies on some module 'm' is stored in the dependency cache
935935
file m.deps.json. This entails some spooky action at a distance:
@@ -943,7 +943,7 @@ def write_deps_cache(
943943
fine-grained dependencies in a global cache file:
944944
* We take a snapshot of current sources to later check consistency
945945
between the fine-grained dependency cache and module cache metadata
946-
* We store the mtime of all of the dependency files to verify they
946+
* We store the mtime of all the dependency files to verify they
947947
haven't changed
948948
"""
949949
metastore = manager.metastore
@@ -1111,7 +1111,7 @@ def read_deps_cache(manager: BuildManager, graph: Graph) -> dict[str, FgDepMeta]
11111111
if deps_meta is None:
11121112
return None
11131113
meta_snapshot = deps_meta["snapshot"]
1114-
# Take a snapshot of the source hashes from all of the metas we found.
1114+
# Take a snapshot of the source hashes from all the metas we found.
11151115
# (Including the ones we rejected because they were out of date.)
11161116
# We use this to verify that they match up with the proto_deps.
11171117
current_meta_snapshot = {

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:
@@ -5602,6 +5607,8 @@ def infer_variable_types_from_type_maps(
56025607
previous_type, _, _ = self.check_lvalue(expr)
56035608
if previous_type is not None:
56045609
already_exists = True
5610+
if isinstance(expr.node, Var) and expr.node.is_final:
5611+
self.msg.cant_assign_to_final(expr.name, False, expr)
56055612
if self.check_subtype(
56065613
typ,
56075614
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
@@ -3468,7 +3468,7 @@ def visit_op_expr(self, e: OpExpr) -> Type:
34683468
# It's actually a type expression X | Y.
34693469
return self.accept(e.analyzed)
34703470
if e.op == "and" or e.op == "or":
3471-
return self.check_boolean_op(e, e)
3471+
return self.check_boolean_op(e)
34723472
if e.op == "*" and isinstance(e.left, ListExpr):
34733473
# Expressions of form [...] * e get special type inference.
34743474
return self.check_list_multiply(e)
@@ -4255,20 +4255,18 @@ def check_op(
42554255
context=context,
42564256
)
42574257

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

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

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

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

mypy/nodes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,6 +3022,7 @@ class is generic then it will be a type constructor of higher kind.
30223022
"dataclass_transform_spec",
30233023
"is_type_check_only",
30243024
"deprecated",
3025+
"type_object_type",
30253026
)
30263027

30273028
_fullname: str # Fully qualified name
@@ -3178,6 +3179,10 @@ class is generic then it will be a type constructor of higher kind.
31783179
# The type's deprecation message (in case it is deprecated)
31793180
deprecated: str | None
31803181

3182+
# Cached value of class constructor type, i.e. the type of class object when it
3183+
# appears in runtime context.
3184+
type_object_type: mypy.types.FunctionLike | None
3185+
31813186
FLAGS: Final = [
31823187
"is_abstract",
31833188
"is_enum",
@@ -3236,6 +3241,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
32363241
self.dataclass_transform_spec = None
32373242
self.is_type_check_only = False
32383243
self.deprecated = None
3244+
self.type_object_type = None
32393245

32403246
def add_type_vars(self) -> None:
32413247
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

mypyc/build.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,12 @@ def build_using_shared_lib(
270270
) -> list[Extension]:
271271
"""Produce the list of extension modules when a shared library is needed.
272272
273-
This creates one shared library extension module that all of the
274-
others import and then one shim extension module for each
275-
module in the build, that simply calls an initialization function
273+
This creates one shared library extension module that all the
274+
others import, and one shim extension module for each
275+
module in the build. Each shim simply calls an initialization function
276276
in the shared library.
277277
278-
The shared library (which lib_name is the name of) is a python
278+
The shared library (which lib_name is the name of) is a Python
279279
extension module that exports the real initialization functions in
280280
Capsules stored in module attributes.
281281
"""
@@ -511,7 +511,7 @@ def mypycify(
511511
separate: Should compiled modules be placed in separate extension modules.
512512
If False, all modules are placed in a single shared library.
513513
If True, every module is placed in its own library.
514-
Otherwise separate should be a list of
514+
Otherwise, separate should be a list of
515515
(file name list, optional shared library name) pairs specifying
516516
groups of files that should be placed in the same shared library
517517
(while all other modules will be placed in its own library).

0 commit comments

Comments
 (0)