Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@

from mypy import errorcodes as codes
from mypy.config_parser import get_config_module_names, parse_mypy_comments
from mypy.fixer_state import fixer_state
from mypy.fixup import NodeFixer
from mypy.freetree import free_tree
from mypy.fscache import FileSystemCache
Expand All @@ -159,6 +158,7 @@
SearchPaths,
compute_search_paths,
)
from mypy.modules_state import modules_state
from mypy.nodes import Expression
from mypy.options import Options
from mypy.parse import load_from_raw, parse
Expand Down Expand Up @@ -816,7 +816,8 @@ def __init__(
# Share same modules dictionary with the global fixer state.
# We need to set allow_missing when doing a fine-grained cache
# load because we need to gracefully handle missing modules.
fixer_state.node_fixer = NodeFixer(self.modules, self.options.use_fine_grained_cache)
modules_state.modules = self.modules
modules_state.node_fixer = NodeFixer(self.modules, self.options.use_fine_grained_cache)
self.import_map: dict[str, set[str]] = {}
self.missing_modules: dict[str, int] = {}
self.fg_deps_meta: dict[str, FgDepMeta] = {}
Expand Down Expand Up @@ -2816,9 +2817,9 @@ def load_tree(self, temporary: bool = False) -> None:
def fix_cross_refs(self) -> None:
assert self.tree is not None, "Internal error: method must be called on parsed file only"
# Do initial lightweight pass fixing TypeInfos and module cross-references.
assert fixer_state.node_fixer is not None
fixer_state.node_fixer.visit_symbol_table(self.tree.names)
type_fixer = fixer_state.node_fixer.type_fixer
assert modules_state.node_fixer is not None
modules_state.node_fixer.visit_symbol_table(self.tree.names)
type_fixer = modules_state.node_fixer.type_fixer
# Eagerly fix shared instances, before they are used by named_type() calls.
if instance_cache.str_type is not None:
instance_cache.str_type.accept(type_fixer)
Expand Down
2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2702,7 +2702,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
self.check_final_deletable(typ)

if defn.decorators:
sig: Type = type_object_type(defn.info, self.named_type)
sig: Type = type_object_type(defn.info)
# Decorators are applied in reverse order.
for decorator in reversed(defn.decorators):
if isinstance(decorator, CallExpr) and isinstance(
Expand Down
97 changes: 49 additions & 48 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ def analyze_static_reference(
# We special case NoneType, because its stub definition is not related to None.
return TypeType(NoneType())
else:
return type_object_type(node, self.named_type)
return type_object_type(node)
elif isinstance(node, TypeAlias):
# Something that refers to a type alias appears in runtime context.
# Note that we suppress bogus errors for alias redefinitions,
Expand Down Expand Up @@ -1908,7 +1908,7 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type:
if isinstance(item, AnyType):
return AnyType(TypeOfAny.from_another_any, source_any=item)
if isinstance(item, Instance):
res = type_object_type(item.type, self.named_type)
res = type_object_type(item.type)
if isinstance(res, CallableType):
res = res.copy_modified(from_type_type=True)
expanded = expand_type_by_instance(res, item)
Expand Down Expand Up @@ -4076,6 +4076,44 @@ def check_method_call(
object_type=base_type,
)

def lookup_operator(self, op_name: str, base_type: Type, context: Context) -> Type | None:
"""Looks up the given operator and returns the corresponding type,
if it exists."""

# This check is an important performance optimization.
if not has_operator(base_type, op_name):
return None

with self.msg.filter_errors() as w:
member = analyze_member_access(
name=op_name,
typ=base_type,
is_lvalue=False,
is_super=False,
is_operator=True,
original_type=base_type,
context=context,
chk=self.chk,
in_literal_context=self.is_literal_context(),
)
return None if w.has_new_errors() else member

def lookup_definer(self, typ: Instance, attr_name: str) -> str | None:
"""Returns the name of the class that contains the actual definition of attr_name.

So if class A defines foo and class B subclasses A, running
`get_class_defined_in(B, "foo")` would return the full name of A.

However, if B were to override and redefine foo, that method call would
return the full name of B instead.

If the attr name is not present in the given class or its MRO, returns None.
"""
for cls in typ.type.mro:
if cls.names.get(attr_name):
return cls.fullname
return None

def check_op_reversible(
self,
op_name: str,
Expand All @@ -4085,48 +4123,10 @@ def check_op_reversible(
right_expr: Expression,
context: Context,
) -> tuple[Type, Type]:
def lookup_operator(op_name: str, base_type: Type) -> Type | None:
"""Looks up the given operator and returns the corresponding type,
if it exists."""

# This check is an important performance optimization.
if not has_operator(base_type, op_name, self.named_type):
return None

with self.msg.filter_errors() as w:
member = analyze_member_access(
name=op_name,
typ=base_type,
is_lvalue=False,
is_super=False,
is_operator=True,
original_type=base_type,
context=context,
chk=self.chk,
in_literal_context=self.is_literal_context(),
)
return None if w.has_new_errors() else member

def lookup_definer(typ: Instance, attr_name: str) -> str | None:
"""Returns the name of the class that contains the actual definition of attr_name.

So if class A defines foo and class B subclasses A, running
'get_class_defined_in(B, "foo")` would return the full name of A.

However, if B were to override and redefine foo, that method call would
return the full name of B instead.

If the attr name is not present in the given class or its MRO, returns None.
"""
for cls in typ.type.mro:
if cls.names.get(attr_name):
return cls.fullname
return None

left_type = get_proper_type(left_type)
right_type = get_proper_type(right_type)

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

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

rev_op_name = operators.reverse_op_methods[op_name]

left_op = lookup_operator(op_name, left_type)
right_op = lookup_operator(rev_op_name, right_type)
left_op = self.lookup_operator(op_name, left_type, context)
right_op = self.lookup_operator(rev_op_name, right_type, context)

# STEP 2a:
# We figure out in which order Python will call the operator methods. As it
Expand All @@ -4168,7 +4168,8 @@ def lookup_definer(typ: Instance, attr_name: str) -> str | None:
# B: right's __rop__ method is different from left's __op__ method
not (isinstance(left_type, Instance) and isinstance(right_type, Instance))
or (
lookup_definer(left_type, op_name) != lookup_definer(right_type, rev_op_name)
self.lookup_definer(left_type, op_name)
!= self.lookup_definer(right_type, rev_op_name)
and (
left_type.type.alt_promote is None
or left_type.type.alt_promote.type is not right_type.type
Expand Down Expand Up @@ -4931,10 +4932,10 @@ def visit_type_application(self, tapp: TypeApplication) -> Type:
)
item = get_proper_type(item)
if isinstance(item, Instance):
tp = type_object_type(item.type, self.named_type)
tp = type_object_type(item.type)
return self.apply_type_arguments_to_callable(tp, item.args, tapp)
elif isinstance(item, TupleType) and item.partial_fallback.type.is_named_tuple:
tp = type_object_type(item.partial_fallback.type, self.named_type)
tp = type_object_type(item.partial_fallback.type)
return self.apply_type_arguments_to_callable(tp, item.partial_fallback.args, tapp)
elif isinstance(item, TypedDictType):
return self.typeddict_callable_from_context(item)
Expand Down Expand Up @@ -5003,7 +5004,7 @@ class LongName(Generic[T]): ...
if isinstance(item, Instance):
# Normally we get a callable type (or overloaded) with .is_type_obj() true
# representing the class's constructor
tp = type_object_type(item.type, self.named_type)
tp = type_object_type(item.type)
if alias.no_args:
return tp
return self.apply_type_arguments_to_callable(tp, item.args, ctx)
Expand All @@ -5013,7 +5014,7 @@ class LongName(Generic[T]): ...
# Tuple[str, int]() fails at runtime, only named tuples and subclasses work.
tuple_fallback(item).type.fullname != "builtins.tuple"
):
return type_object_type(tuple_fallback(item).type, self.named_type)
return type_object_type(tuple_fallback(item).type)
elif isinstance(item, TypedDictType):
return self.typeddict_callable_from_context(item)
elif isinstance(item, NoneType):
Expand Down
29 changes: 19 additions & 10 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
expand_type_by_instance,
freshen_all_functions_type_vars,
)
from mypy.lookup import lookup_stdlib_typeinfo
from mypy.maptype import map_instance_to_supertype
from mypy.meet import is_overlapping_types
from mypy.messages import MessageBuilder
from mypy.modules_state import modules_state
from mypy.nodes import (
ARG_POS,
ARG_STAR,
Expand Down Expand Up @@ -74,6 +76,7 @@
UninhabitedType,
UnionType,
get_proper_type,
instance_cache,
)


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


def has_operator(typ: Type, op_method: str, named_type: Callable[[str], Instance]) -> bool:
def has_operator(typ: Type, op_method: str) -> bool:
"""Does type have operator with the given name?

Note: this follows the rules for operator access, in particular:
Expand All @@ -1544,7 +1547,7 @@ def has_operator(typ: Type, op_method: str, named_type: Callable[[str], Instance
if isinstance(typ, AnyType):
return True
if isinstance(typ, UnionType):
return all(has_operator(x, op_method, named_type) for x in typ.relevant_items())
return all(has_operator(x, op_method) for x in typ.relevant_items())
if isinstance(typ, FunctionLike) and typ.is_type_obj():
return typ.fallback.type.has_readable_member(op_method)
if isinstance(typ, TypeType):
Expand All @@ -1555,27 +1558,33 @@ def has_operator(typ: Type, op_method: str, named_type: Callable[[str], Instance
if isinstance(item, TypeVarType):
item = item.values_or_bound()
if isinstance(item, UnionType):
return all(meta_has_operator(x, op_method, named_type) for x in item.relevant_items())
return meta_has_operator(item, op_method, named_type)
return instance_fallback(typ, named_type).type.has_readable_member(op_method)
return all(meta_has_operator(x, op_method) for x in item.relevant_items())
return meta_has_operator(item, op_method)
return instance_fallback(typ).type.has_readable_member(op_method)


def instance_fallback(typ: ProperType, named_type: Callable[[str], Instance]) -> Instance:
def instance_fallback(typ: ProperType) -> Instance:
if isinstance(typ, Instance):
return typ
if isinstance(typ, TupleType):
return tuple_fallback(typ)
if isinstance(typ, (LiteralType, TypedDictType)):
return typ.fallback
return named_type("builtins.object")
if instance_cache.object_type is None:
object_typeinfo = lookup_stdlib_typeinfo("builtins.object", modules_state.modules)
instance_cache.object_type = Instance(object_typeinfo, [])
return instance_cache.object_type


def meta_has_operator(item: Type, op_method: str, named_type: Callable[[str], Instance]) -> bool:
def meta_has_operator(item: Type, op_method: str) -> bool:
item = get_proper_type(item)
if isinstance(item, AnyType):
return True
item = instance_fallback(item, named_type)
meta = item.type.metaclass_type or named_type("builtins.type")
item = instance_fallback(item)
meta = item.type.metaclass_type
if meta is None:
type_type = lookup_stdlib_typeinfo("builtins.type", modules_state.modules)
meta = Instance(type_type, [])
return meta.type.has_readable_member(op_method)


Expand Down
12 changes: 12 additions & 0 deletions mypy/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,15 @@ def lookup_fully_qualified(
assert node, f"Cannot find {name}"
return None
names = node.names


def lookup_stdlib_typeinfo(fullname: str, modules: dict[str, MypyFile]) -> TypeInfo:
"""Find TypeInfo for a standard library type.

This fast path assumes that the type exists at module top-level, use this
function only for common types like `builtins.object` or `typing.Iterable`.
"""
module, name = fullname.rsplit(".", maxsplit=1)
sym = modules[module].names[name]
assert isinstance(sym.node, TypeInfo)
return sym.node
5 changes: 3 additions & 2 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
IS_VAR,
find_member,
get_member_flags,
get_protocol_member,
is_same_type,
is_subtype,
)
Expand Down Expand Up @@ -3110,7 +3111,7 @@ def get_conflict_protocol_types(
continue
supertype = find_member(member, right, left)
assert supertype is not None
subtype = mypy.typeops.get_protocol_member(left, member, class_obj)
subtype = get_protocol_member(left, member, class_obj)
if not subtype:
continue
is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True, options=options)
Expand All @@ -3126,7 +3127,7 @@ def get_conflict_protocol_types(
different_setter = True
supertype = set_supertype
if IS_EXPLICIT_SETTER in get_member_flags(member, left):
set_subtype = mypy.typeops.get_protocol_member(left, member, class_obj, is_lvalue=True)
set_subtype = get_protocol_member(left, member, class_obj, is_lvalue=True)
if set_subtype and not is_same_type(set_subtype, subtype):
different_setter = True
subtype = set_subtype
Expand Down
4 changes: 3 additions & 1 deletion mypy/fixer_state.py → mypy/modules_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

if TYPE_CHECKING:
from mypy.fixup import NodeFixer
from mypy.nodes import MypyFile

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


fixer_state: Final = FixerState()
modules_state: Final = FixerState()
4 changes: 2 additions & 2 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
write_str_opt_list,
write_tag,
)
from mypy.fixer_state import fixer_state
from mypy.modules_state import modules_state
from mypy.options import Options
from mypy.util import is_sunder, is_typeshed_file, short_type
from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor
Expand Down Expand Up @@ -4819,7 +4819,7 @@ def type(self) -> mypy.types.Type | None:
@property
def node(self) -> SymbolNode | None:
if self.unfixed:
node_fixer = fixer_state.node_fixer
node_fixer = modules_state.node_fixer
assert node_fixer is not None
if self.cross_ref is not None:
node_fixer.resolve_cross_ref(self)
Expand Down
4 changes: 2 additions & 2 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,15 +734,15 @@ def _parse_converter(
):
converter_type = converter_expr.node.type
elif isinstance(converter_expr.node, TypeInfo):
converter_type = type_object_type(converter_expr.node, ctx.api.named_type)
converter_type = type_object_type(converter_expr.node)
elif (
isinstance(converter_expr, IndexExpr)
and isinstance(converter_expr.analyzed, TypeApplication)
and isinstance(converter_expr.base, RefExpr)
and isinstance(converter_expr.base.node, TypeInfo)
):
# The converter is a generic type.
converter_type = type_object_type(converter_expr.base.node, ctx.api.named_type)
converter_type = type_object_type(converter_expr.base.node)
if isinstance(converter_type, CallableType):
converter_type = apply_generic_arguments(
converter_type,
Expand Down
Loading
Loading