Skip to content

Move some functions from checkmember to typeops #18820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 20, 2025
Merged
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
2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
analyze_decorator_or_funcbase_access,
analyze_descriptor_access,
analyze_member_access,
type_object_type,
)
from mypy.checkpattern import PatternChecker
from mypy.constraints import SUPERTYPE_OF
Expand Down Expand Up @@ -168,6 +167,7 @@
try_getting_str_literals,
try_getting_str_literals_from_type,
tuple_fallback,
type_object_type,
)
from mypy.types import (
ANY_STRATEGY,
Expand Down
9 changes: 3 additions & 6 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@
import mypy.errorcodes as codes
from mypy import applytype, erasetype, join, message_registry, nodes, operators, types
from mypy.argmap import ArgTypeExpander, map_actuals_to_formals, map_formals_to_actuals
from mypy.checkmember import (
analyze_member_access,
freeze_all_type_vars,
type_object_type,
typeddict_callable,
)
from mypy.checkmember import analyze_member_access, typeddict_callable
from mypy.checkstrformat import StringFormatterChecker
from mypy.erasetype import erase_type, remove_instance_last_known_values, replace_meta_vars
from mypy.errors import ErrorWatcher, report_internal_error
Expand Down Expand Up @@ -138,6 +133,7 @@
erase_to_union_or_bound,
false_only,
fixup_partial_type,
freeze_all_type_vars,
function_type,
get_all_type_vars,
get_type_vars,
Expand All @@ -148,6 +144,7 @@
try_expanding_sum_type_to_union,
try_getting_str_literals,
tuple_fallback,
type_object_type,
)
from mypy.types import (
LITERAL_TYPE_NAMES,
Expand Down
102 changes: 2 additions & 100 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from mypy.nodes import (
ARG_POS,
ARG_STAR,
ARG_STAR2,
EXCLUDED_ENUM_ATTRIBUTES,
SYMBOL_FUNCBASE_TYPES,
ArgKind,
Expand All @@ -29,7 +28,6 @@
MypyFile,
NameExpr,
OverloadedFuncDef,
SymbolNode,
SymbolTable,
TempNode,
TypeAlias,
Expand All @@ -41,14 +39,14 @@
from mypy.plugin import AttributeContext
from mypy.typeops import (
bind_self,
class_callable,
erase_to_bound,
freeze_all_type_vars,
function_type,
get_type_vars,
make_simplified_union,
supported_self_type,
tuple_fallback,
type_object_type_from_function,
type_object_type,
)
from mypy.types import (
AnyType,
Expand All @@ -73,7 +71,6 @@
UnionType,
get_proper_type,
)
from mypy.typetraverser import TypeTraverserVisitor

if TYPE_CHECKING: # import for forward declaration only
import mypy.checker
Expand Down Expand Up @@ -881,17 +878,6 @@ def expand_self_type_if_needed(
return t


def freeze_all_type_vars(member_type: Type) -> None:
member_type.accept(FreezeTypeVarsVisitor())


class FreezeTypeVarsVisitor(TypeTraverserVisitor):
def visit_callable_type(self, t: CallableType) -> None:
for v in t.variables:
v.id.meta_level = 0
super().visit_callable_type(t)


def check_self_arg(
functype: FunctionLike,
dispatched_arg_type: Type,
Expand Down Expand Up @@ -1319,77 +1305,6 @@ def typeddict_callable(info: TypeInfo, named_type: Callable[[str], Instance]) ->
)


def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> ProperType:
"""Return the type of a type object.

For a generic type G with type variables T and S the type is generally of form

Callable[..., G[T, S]]

where ... are argument types for the __init__/__new__ method (without the self
argument). Also, the fallback type will be 'type' instead of 'function'.
"""

# We take the type from whichever of __init__ and __new__ is first
# in the MRO, preferring __init__ if there is a tie.
init_method = info.get("__init__")
new_method = info.get("__new__")
if not init_method or not is_valid_constructor(init_method.node):
# Must be an invalid class definition.
return AnyType(TypeOfAny.from_error)
# There *should* always be a __new__ method except the test stubs
# lack it, so just copy init_method in that situation
new_method = new_method or init_method
if not is_valid_constructor(new_method.node):
# Must be an invalid class definition.
return AnyType(TypeOfAny.from_error)

# The two is_valid_constructor() checks ensure this.
assert isinstance(new_method.node, (SYMBOL_FUNCBASE_TYPES, Decorator))
assert isinstance(init_method.node, (SYMBOL_FUNCBASE_TYPES, Decorator))

init_index = info.mro.index(init_method.node.info)
new_index = info.mro.index(new_method.node.info)

fallback = info.metaclass_type or named_type("builtins.type")
if init_index < new_index:
method: FuncBase | Decorator = init_method.node
is_new = False
elif init_index > new_index:
method = new_method.node
is_new = True
else:
if init_method.node.info.fullname == "builtins.object":
# Both are defined by object. But if we've got a bogus
# base class, we can't know for sure, so check for that.
if info.fallback_to_any:
# Construct a universal callable as the prototype.
any_type = AnyType(TypeOfAny.special_form)
sig = CallableType(
arg_types=[any_type, any_type],
arg_kinds=[ARG_STAR, ARG_STAR2],
arg_names=["_args", "_kwds"],
ret_type=any_type,
fallback=named_type("builtins.function"),
)
return class_callable(sig, info, fallback, None, is_new=False)

# Otherwise prefer __init__ in a tie. It isn't clear that this
# is the right thing, but __new__ caused problems with
# typeshed (#5647).
method = init_method.node
is_new = False
# Construct callable type based on signature of __init__. Adjust
# return type and insert type arguments.
if isinstance(method, FuncBase):
t = function_type(method, fallback)
else:
assert isinstance(method.type, ProperType)
assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this
t = method.type
return type_object_type_from_function(t, info, method.info, fallback, is_new)


def analyze_decorator_or_funcbase_access(
defn: Decorator | FuncBase, itype: Instance, name: str, mx: MemberContext
) -> Type:
Expand All @@ -1403,16 +1318,3 @@ def analyze_decorator_or_funcbase_access(
return bind_self(
function_type(defn, mx.chk.named_type("builtins.function")), original_type=mx.self_type
)


def is_valid_constructor(n: SymbolNode | None) -> bool:
"""Does this node represents a valid constructor method?

This includes normal functions, overloaded functions, and decorators
that return a callable type.
"""
if isinstance(n, SYMBOL_FUNCBASE_TYPES):
return True
if isinstance(n, Decorator):
return isinstance(get_proper_type(n.type), FunctionLike)
return False
11 changes: 6 additions & 5 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@
)
from mypy.server.trigger import make_wildcard_trigger
from mypy.state import state
from mypy.typeops import get_type_vars, make_simplified_union, map_type_from_supertype
from mypy.typeops import (
get_type_vars,
make_simplified_union,
map_type_from_supertype,
type_object_type,
)
from mypy.types import (
AnyType,
CallableType,
Expand Down Expand Up @@ -726,8 +731,6 @@ def _parse_converter(
):
converter_type = converter_expr.node.type
elif isinstance(converter_expr.node, TypeInfo):
from mypy.checkmember import type_object_type # To avoid import cycle.

converter_type = type_object_type(converter_expr.node, ctx.api.named_type)
elif (
isinstance(converter_expr, IndexExpr)
Expand All @@ -736,8 +739,6 @@ def _parse_converter(
and isinstance(converter_expr.base.node, TypeInfo)
):
# The converter is a generic type.
from mypy.checkmember import type_object_type # To avoid import cycle.

converter_type = type_object_type(converter_expr.base.node, ctx.api.named_type)
if isinstance(converter_type, CallableType):
converter_type = apply_generic_arguments(
Expand Down
100 changes: 98 additions & 2 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import itertools
from collections.abc import Iterable, Sequence
from typing import Any, TypeVar, cast
from typing import Any, Callable, TypeVar, cast

from mypy.copytype import copy_type
from mypy.expandtype import expand_type, expand_type_by_instance
Expand All @@ -27,6 +27,7 @@
FuncItem,
OverloadedFuncDef,
StrExpr,
SymbolNode,
TypeInfo,
Var,
)
Expand Down Expand Up @@ -63,6 +64,7 @@
get_proper_type,
get_proper_types,
)
from mypy.typetraverser import TypeTraverserVisitor
from mypy.typevars import fill_typevars


Expand Down Expand Up @@ -132,6 +134,90 @@ def get_self_type(func: CallableType, default_self: Instance | TupleType) -> Typ
return None


def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> ProperType:
"""Return the type of a type object.

For a generic type G with type variables T and S the type is generally of form

Callable[..., G[T, S]]

where ... are argument types for the __init__/__new__ method (without the self
argument). Also, the fallback type will be 'type' instead of 'function'.
"""

# We take the type from whichever of __init__ and __new__ is first
# in the MRO, preferring __init__ if there is a tie.
init_method = info.get("__init__")
new_method = info.get("__new__")
if not init_method or not is_valid_constructor(init_method.node):
# Must be an invalid class definition.
return AnyType(TypeOfAny.from_error)
# There *should* always be a __new__ method except the test stubs
# lack it, so just copy init_method in that situation
new_method = new_method or init_method
if not is_valid_constructor(new_method.node):
# Must be an invalid class definition.
return AnyType(TypeOfAny.from_error)

# The two is_valid_constructor() checks ensure this.
assert isinstance(new_method.node, (SYMBOL_FUNCBASE_TYPES, Decorator))
assert isinstance(init_method.node, (SYMBOL_FUNCBASE_TYPES, Decorator))

init_index = info.mro.index(init_method.node.info)
new_index = info.mro.index(new_method.node.info)

fallback = info.metaclass_type or named_type("builtins.type")
if init_index < new_index:
method: FuncBase | Decorator = init_method.node
is_new = False
elif init_index > new_index:
method = new_method.node
is_new = True
else:
if init_method.node.info.fullname == "builtins.object":
# Both are defined by object. But if we've got a bogus
# base class, we can't know for sure, so check for that.
if info.fallback_to_any:
# Construct a universal callable as the prototype.
any_type = AnyType(TypeOfAny.special_form)
sig = CallableType(
arg_types=[any_type, any_type],
arg_kinds=[ARG_STAR, ARG_STAR2],
arg_names=["_args", "_kwds"],
ret_type=any_type,
fallback=named_type("builtins.function"),
)
return class_callable(sig, info, fallback, None, is_new=False)

# Otherwise prefer __init__ in a tie. It isn't clear that this
# is the right thing, but __new__ caused problems with
# typeshed (#5647).
method = init_method.node
is_new = False
# Construct callable type based on signature of __init__. Adjust
# return type and insert type arguments.
if isinstance(method, FuncBase):
t = function_type(method, fallback)
else:
assert isinstance(method.type, ProperType)
assert isinstance(method.type, FunctionLike) # is_valid_constructor() ensures this
t = method.type
return type_object_type_from_function(t, info, method.info, fallback, is_new)


def is_valid_constructor(n: SymbolNode | None) -> bool:
"""Does this node represents a valid constructor method?

This includes normal functions, overloaded functions, and decorators
that return a callable type.
"""
if isinstance(n, SYMBOL_FUNCBASE_TYPES):
return True
if isinstance(n, Decorator):
return isinstance(get_proper_type(n.type), FunctionLike)
return False


def type_object_type_from_function(
signature: FunctionLike, info: TypeInfo, def_info: TypeInfo, fallback: Instance, is_new: bool
) -> FunctionLike:
Expand Down Expand Up @@ -1070,6 +1156,17 @@ def visit_type_var_tuple(self, t: TypeVarTupleType) -> list[TypeVarLikeType]:
return [t] if self.include_all else []


def freeze_all_type_vars(member_type: Type) -> None:
member_type.accept(FreezeTypeVarsVisitor())


class FreezeTypeVarsVisitor(TypeTraverserVisitor):
def visit_callable_type(self, t: CallableType) -> None:
for v in t.variables:
v.id.meta_level = 0
super().visit_callable_type(t)


def custom_special_method(typ: Type, name: str, check_all: bool = False) -> bool:
"""Does this type have a custom special method such as __format__() or __eq__()?

Expand Down Expand Up @@ -1152,7 +1249,6 @@ def get_protocol_member(
) -> Type | None:
if member == "__call__" and class_obj:
# Special case: class objects always have __call__ that is just the constructor.
from mypy.checkmember import type_object_type

def named_type(fullname: str) -> Instance:
return Instance(left.type.mro[-1], [])
Expand Down