Skip to content

Commit 8d6141f

Browse files
committed
Start moving away from named_type callback architecture
1 parent a399e1c commit 8d6141f

File tree

15 files changed

+147
-146
lines changed

15 files changed

+147
-146
lines changed

mypy/build.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@
143143

144144
from mypy import errorcodes as codes
145145
from mypy.config_parser import get_config_module_names, parse_mypy_comments
146-
from mypy.fixer_state import fixer_state
147146
from mypy.fixup import NodeFixer
148147
from mypy.freetree import free_tree
149148
from mypy.fscache import FileSystemCache
@@ -159,6 +158,7 @@
159158
SearchPaths,
160159
compute_search_paths,
161160
)
161+
from mypy.modules_state import modules_state
162162
from mypy.nodes import Expression
163163
from mypy.options import Options
164164
from mypy.parse import load_from_raw, parse
@@ -816,7 +816,8 @@ def __init__(
816816
# Share same modules dictionary with the global fixer state.
817817
# We need to set allow_missing when doing a fine-grained cache
818818
# load because we need to gracefully handle missing modules.
819-
fixer_state.node_fixer = NodeFixer(self.modules, self.options.use_fine_grained_cache)
819+
modules_state.modules = self.modules
820+
modules_state.node_fixer = NodeFixer(self.modules, self.options.use_fine_grained_cache)
820821
self.import_map: dict[str, set[str]] = {}
821822
self.missing_modules: dict[str, int] = {}
822823
self.fg_deps_meta: dict[str, FgDepMeta] = {}
@@ -2816,9 +2817,9 @@ def load_tree(self, temporary: bool = False) -> None:
28162817
def fix_cross_refs(self) -> None:
28172818
assert self.tree is not None, "Internal error: method must be called on parsed file only"
28182819
# Do initial lightweight pass fixing TypeInfos and module cross-references.
2819-
assert fixer_state.node_fixer is not None
2820-
fixer_state.node_fixer.visit_symbol_table(self.tree.names)
2821-
type_fixer = fixer_state.node_fixer.type_fixer
2820+
assert modules_state.node_fixer is not None
2821+
modules_state.node_fixer.visit_symbol_table(self.tree.names)
2822+
type_fixer = modules_state.node_fixer.type_fixer
28222823
# Eagerly fix shared instances, before they are used by named_type() calls.
28232824
if instance_cache.str_type is not None:
28242825
instance_cache.str_type.accept(type_fixer)

mypy/checker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2702,7 +2702,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
27022702
self.check_final_deletable(typ)
27032703

27042704
if defn.decorators:
2705-
sig: Type = type_object_type(defn.info, self.named_type)
2705+
sig: Type = type_object_type(defn.info)
27062706
# Decorators are applied in reverse order.
27072707
for decorator in reversed(defn.decorators):
27082708
if isinstance(decorator, CallExpr) and isinstance(

mypy/checkexpr.py

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ def analyze_static_reference(
427427
# We special case NoneType, because its stub definition is not related to None.
428428
return TypeType(NoneType())
429429
else:
430-
return type_object_type(node, self.named_type)
430+
return type_object_type(node)
431431
elif isinstance(node, TypeAlias):
432432
# Something that refers to a type alias appears in runtime context.
433433
# Note that we suppress bogus errors for alias redefinitions,
@@ -1908,7 +1908,7 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type:
19081908
if isinstance(item, AnyType):
19091909
return AnyType(TypeOfAny.from_another_any, source_any=item)
19101910
if isinstance(item, Instance):
1911-
res = type_object_type(item.type, self.named_type)
1911+
res = type_object_type(item.type)
19121912
if isinstance(res, CallableType):
19131913
res = res.copy_modified(from_type_type=True)
19141914
expanded = expand_type_by_instance(res, item)
@@ -4076,6 +4076,44 @@ def check_method_call(
40764076
object_type=base_type,
40774077
)
40784078

4079+
def lookup_operator(self, op_name: str, base_type: Type, context: Context) -> Type | None:
4080+
"""Looks up the given operator and returns the corresponding type,
4081+
if it exists."""
4082+
4083+
# This check is an important performance optimization.
4084+
if not has_operator(base_type, op_name):
4085+
return None
4086+
4087+
with self.msg.filter_errors() as w:
4088+
member = analyze_member_access(
4089+
name=op_name,
4090+
typ=base_type,
4091+
is_lvalue=False,
4092+
is_super=False,
4093+
is_operator=True,
4094+
original_type=base_type,
4095+
context=context,
4096+
chk=self.chk,
4097+
in_literal_context=self.is_literal_context(),
4098+
)
4099+
return None if w.has_new_errors() else member
4100+
4101+
def lookup_definer(self, typ: Instance, attr_name: str) -> str | None:
4102+
"""Returns the name of the class that contains the actual definition of attr_name.
4103+
4104+
So if class A defines foo and class B subclasses A, running
4105+
`get_class_defined_in(B, "foo")` would return the full name of A.
4106+
4107+
However, if B were to override and redefine foo, that method call would
4108+
return the full name of B instead.
4109+
4110+
If the attr name is not present in the given class or its MRO, returns None.
4111+
"""
4112+
for cls in typ.type.mro:
4113+
if cls.names.get(attr_name):
4114+
return cls.fullname
4115+
return None
4116+
40794117
def check_op_reversible(
40804118
self,
40814119
op_name: str,
@@ -4085,48 +4123,10 @@ def check_op_reversible(
40854123
right_expr: Expression,
40864124
context: Context,
40874125
) -> tuple[Type, Type]:
4088-
def lookup_operator(op_name: str, base_type: Type) -> Type | None:
4089-
"""Looks up the given operator and returns the corresponding type,
4090-
if it exists."""
4091-
4092-
# This check is an important performance optimization.
4093-
if not has_operator(base_type, op_name, self.named_type):
4094-
return None
4095-
4096-
with self.msg.filter_errors() as w:
4097-
member = analyze_member_access(
4098-
name=op_name,
4099-
typ=base_type,
4100-
is_lvalue=False,
4101-
is_super=False,
4102-
is_operator=True,
4103-
original_type=base_type,
4104-
context=context,
4105-
chk=self.chk,
4106-
in_literal_context=self.is_literal_context(),
4107-
)
4108-
return None if w.has_new_errors() else member
4109-
4110-
def lookup_definer(typ: Instance, attr_name: str) -> str | None:
4111-
"""Returns the name of the class that contains the actual definition of attr_name.
4112-
4113-
So if class A defines foo and class B subclasses A, running
4114-
'get_class_defined_in(B, "foo")` would return the full name of A.
4115-
4116-
However, if B were to override and redefine foo, that method call would
4117-
return the full name of B instead.
4118-
4119-
If the attr name is not present in the given class or its MRO, returns None.
4120-
"""
4121-
for cls in typ.type.mro:
4122-
if cls.names.get(attr_name):
4123-
return cls.fullname
4124-
return None
4125-
41264126
left_type = get_proper_type(left_type)
41274127
right_type = get_proper_type(right_type)
41284128

4129-
# If either the LHS or the RHS are Any, we can't really concluding anything
4129+
# If either the LHS or the RHS are Any, we can't really conclude anything
41304130
# about the operation since the Any type may or may not define an
41314131
# __op__ or __rop__ method. So, we punt and return Any instead.
41324132

@@ -4142,8 +4142,8 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
41424142

41434143
rev_op_name = operators.reverse_op_methods[op_name]
41444144

4145-
left_op = lookup_operator(op_name, left_type)
4146-
right_op = lookup_operator(rev_op_name, right_type)
4145+
left_op = self.lookup_operator(op_name, left_type, context)
4146+
right_op = self.lookup_operator(rev_op_name, right_type, context)
41474147

41484148
# STEP 2a:
41494149
# We figure out in which order Python will call the operator methods. As it
@@ -4168,7 +4168,8 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
41684168
# B: right's __rop__ method is different from left's __op__ method
41694169
not (isinstance(left_type, Instance) and isinstance(right_type, Instance))
41704170
or (
4171-
lookup_definer(left_type, op_name) != lookup_definer(right_type, rev_op_name)
4171+
self.lookup_definer(left_type, op_name)
4172+
!= self.lookup_definer(right_type, rev_op_name)
41724173
and (
41734174
left_type.type.alt_promote is None
41744175
or left_type.type.alt_promote.type is not right_type.type
@@ -4931,10 +4932,10 @@ def visit_type_application(self, tapp: TypeApplication) -> Type:
49314932
)
49324933
item = get_proper_type(item)
49334934
if isinstance(item, Instance):
4934-
tp = type_object_type(item.type, self.named_type)
4935+
tp = type_object_type(item.type)
49354936
return self.apply_type_arguments_to_callable(tp, item.args, tapp)
49364937
elif isinstance(item, TupleType) and item.partial_fallback.type.is_named_tuple:
4937-
tp = type_object_type(item.partial_fallback.type, self.named_type)
4938+
tp = type_object_type(item.partial_fallback.type)
49384939
return self.apply_type_arguments_to_callable(tp, item.partial_fallback.args, tapp)
49394940
elif isinstance(item, TypedDictType):
49404941
return self.typeddict_callable_from_context(item)
@@ -5003,7 +5004,7 @@ class LongName(Generic[T]): ...
50035004
if isinstance(item, Instance):
50045005
# Normally we get a callable type (or overloaded) with .is_type_obj() true
50055006
# representing the class's constructor
5006-
tp = type_object_type(item.type, self.named_type)
5007+
tp = type_object_type(item.type)
50075008
if alias.no_args:
50085009
return tp
50095010
return self.apply_type_arguments_to_callable(tp, item.args, ctx)
@@ -5013,7 +5014,7 @@ class LongName(Generic[T]): ...
50135014
# Tuple[str, int]() fails at runtime, only named tuples and subclasses work.
50145015
tuple_fallback(item).type.fullname != "builtins.tuple"
50155016
):
5016-
return type_object_type(tuple_fallback(item).type, self.named_type)
5017+
return type_object_type(tuple_fallback(item).type)
50175018
elif isinstance(item, TypedDictType):
50185019
return self.typeddict_callable_from_context(item)
50195020
elif isinstance(item, NoneType):

mypy/checkmember.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
expand_type_by_instance,
1414
freshen_all_functions_type_vars,
1515
)
16+
from mypy.lookup import lookup_stdlib_typeinfo
1617
from mypy.maptype import map_instance_to_supertype
1718
from mypy.meet import is_overlapping_types
1819
from mypy.messages import MessageBuilder
20+
from mypy.modules_state import modules_state
1921
from mypy.nodes import (
2022
ARG_POS,
2123
ARG_STAR,
@@ -74,6 +76,7 @@
7476
UninhabitedType,
7577
UnionType,
7678
get_proper_type,
79+
instance_cache,
7780
)
7881

7982

@@ -1525,7 +1528,7 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F:
15251528
)
15261529

15271530

1528-
def has_operator(typ: Type, op_method: str, named_type: Callable[[str], Instance]) -> bool:
1531+
def has_operator(typ: Type, op_method: str) -> bool:
15291532
"""Does type have operator with the given name?
15301533
15311534
Note: this follows the rules for operator access, in particular:
@@ -1544,7 +1547,7 @@ def has_operator(typ: Type, op_method: str, named_type: Callable[[str], Instance
15441547
if isinstance(typ, AnyType):
15451548
return True
15461549
if isinstance(typ, UnionType):
1547-
return all(has_operator(x, op_method, named_type) for x in typ.relevant_items())
1550+
return all(has_operator(x, op_method) for x in typ.relevant_items())
15481551
if isinstance(typ, FunctionLike) and typ.is_type_obj():
15491552
return typ.fallback.type.has_readable_member(op_method)
15501553
if isinstance(typ, TypeType):
@@ -1555,27 +1558,33 @@ def has_operator(typ: Type, op_method: str, named_type: Callable[[str], Instance
15551558
if isinstance(item, TypeVarType):
15561559
item = item.values_or_bound()
15571560
if isinstance(item, UnionType):
1558-
return all(meta_has_operator(x, op_method, named_type) for x in item.relevant_items())
1559-
return meta_has_operator(item, op_method, named_type)
1560-
return instance_fallback(typ, named_type).type.has_readable_member(op_method)
1561+
return all(meta_has_operator(x, op_method) for x in item.relevant_items())
1562+
return meta_has_operator(item, op_method)
1563+
return instance_fallback(typ).type.has_readable_member(op_method)
15611564

15621565

1563-
def instance_fallback(typ: ProperType, named_type: Callable[[str], Instance]) -> Instance:
1566+
def instance_fallback(typ: ProperType) -> Instance:
15641567
if isinstance(typ, Instance):
15651568
return typ
15661569
if isinstance(typ, TupleType):
15671570
return tuple_fallback(typ)
15681571
if isinstance(typ, (LiteralType, TypedDictType)):
15691572
return typ.fallback
1570-
return named_type("builtins.object")
1573+
if instance_cache.object_type is None:
1574+
object_typeinfo = lookup_stdlib_typeinfo("builtins.object", modules_state.modules)
1575+
instance_cache.object_type = Instance(object_typeinfo, [])
1576+
return instance_cache.object_type
15711577

15721578

1573-
def meta_has_operator(item: Type, op_method: str, named_type: Callable[[str], Instance]) -> bool:
1579+
def meta_has_operator(item: Type, op_method: str) -> bool:
15741580
item = get_proper_type(item)
15751581
if isinstance(item, AnyType):
15761582
return True
1577-
item = instance_fallback(item, named_type)
1578-
meta = item.type.metaclass_type or named_type("builtins.type")
1583+
item = instance_fallback(item)
1584+
meta = item.type.metaclass_type
1585+
if meta is None:
1586+
type_type = lookup_stdlib_typeinfo("builtins.type", modules_state.modules)
1587+
meta = Instance(type_type, [])
15791588
return meta.type.has_readable_member(op_method)
15801589

15811590

mypy/lookup.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,15 @@ def lookup_fully_qualified(
6666
assert node, f"Cannot find {name}"
6767
return None
6868
names = node.names
69+
70+
71+
def lookup_stdlib_typeinfo(fullname: str, modules: dict[str, MypyFile]) -> TypeInfo:
72+
"""Find TypeInfo for a standard library type.
73+
74+
This fast path assumes that the type exists at module top-level, use this
75+
function only for common types like `builtins.object` or `typing.Iterable`.
76+
"""
77+
module, name = fullname.rsplit(".", maxsplit=1)
78+
sym = modules[module].names[name]
79+
assert isinstance(sym.node, TypeInfo)
80+
return sym.node

mypy/messages.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
IS_VAR,
6969
find_member,
7070
get_member_flags,
71+
get_protocol_member,
7172
is_same_type,
7273
is_subtype,
7374
)
@@ -3110,7 +3111,7 @@ def get_conflict_protocol_types(
31103111
continue
31113112
supertype = find_member(member, right, left)
31123113
assert supertype is not None
3113-
subtype = mypy.typeops.get_protocol_member(left, member, class_obj)
3114+
subtype = get_protocol_member(left, member, class_obj)
31143115
if not subtype:
31153116
continue
31163117
is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True, options=options)
@@ -3126,7 +3127,7 @@ def get_conflict_protocol_types(
31263127
different_setter = True
31273128
supertype = set_supertype
31283129
if IS_EXPLICIT_SETTER in get_member_flags(member, left):
3129-
set_subtype = mypy.typeops.get_protocol_member(left, member, class_obj, is_lvalue=True)
3130+
set_subtype = get_protocol_member(left, member, class_obj, is_lvalue=True)
31303131
if set_subtype and not is_same_type(set_subtype, subtype):
31313132
different_setter = True
31323133
subtype = set_subtype
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
if TYPE_CHECKING:
66
from mypy.fixup import NodeFixer
7+
from mypy.nodes import MypyFile
78

89
# This is global mutable state. Don't add anything here unless there's a very
910
# good reason. This exists as a separate file to avoid method-level import in
@@ -13,6 +14,7 @@
1314
class FixerState:
1415
def __init__(self) -> None:
1516
self.node_fixer: NodeFixer | None = None
17+
self.modules: dict[str, MypyFile] = {}
1618

1719

18-
fixer_state: Final = FixerState()
20+
modules_state: Final = FixerState()

mypy/nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
write_str_opt_list,
6969
write_tag,
7070
)
71-
from mypy.fixer_state import fixer_state
71+
from mypy.modules_state import modules_state
7272
from mypy.options import Options
7373
from mypy.util import is_sunder, is_typeshed_file, short_type
7474
from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor
@@ -4819,7 +4819,7 @@ def type(self) -> mypy.types.Type | None:
48194819
@property
48204820
def node(self) -> SymbolNode | None:
48214821
if self.unfixed:
4822-
node_fixer = fixer_state.node_fixer
4822+
node_fixer = modules_state.node_fixer
48234823
assert node_fixer is not None
48244824
if self.cross_ref is not None:
48254825
node_fixer.resolve_cross_ref(self)

mypy/plugins/attrs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,15 +734,15 @@ def _parse_converter(
734734
):
735735
converter_type = converter_expr.node.type
736736
elif isinstance(converter_expr.node, TypeInfo):
737-
converter_type = type_object_type(converter_expr.node, ctx.api.named_type)
737+
converter_type = type_object_type(converter_expr.node)
738738
elif (
739739
isinstance(converter_expr, IndexExpr)
740740
and isinstance(converter_expr.analyzed, TypeApplication)
741741
and isinstance(converter_expr.base, RefExpr)
742742
and isinstance(converter_expr.base.node, TypeInfo)
743743
):
744744
# The converter is a generic type.
745-
converter_type = type_object_type(converter_expr.base.node, ctx.api.named_type)
745+
converter_type = type_object_type(converter_expr.base.node)
746746
if isinstance(converter_type, CallableType):
747747
converter_type = apply_generic_arguments(
748748
converter_type,

0 commit comments

Comments
 (0)