Skip to content
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

Add fast path for checking self types #16352

Merged
merged 1 commit into from
Oct 28, 2023
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
58 changes: 30 additions & 28 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,13 +1199,14 @@ def check_func_def(
# Push return type.
self.return_types.append(typ.ret_type)

with self.scope.push_function(defn):
# We temporary push the definition to get the self type as
# visible from *inside* of this function/method.
ref_type: Type | None = self.scope.active_self_type()

# Store argument types.
for i in range(len(typ.arg_types)):
arg_type = typ.arg_types[i]
with self.scope.push_function(defn):
# We temporary push the definition to get the self type as
# visible from *inside* of this function/method.
ref_type: Type | None = self.scope.active_self_type()
if (
isinstance(defn, FuncDef)
and ref_type is not None
Expand All @@ -1215,30 +1216,31 @@ def check_func_def(
):
if defn.is_class or defn.name == "__new__":
ref_type = mypy.types.TypeType.make_normalized(ref_type)
# This level of erasure matches the one in checkmember.check_self_arg(),
# better keep these two checks consistent.
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
if not is_subtype(ref_type, erased, ignore_type_params=True):
if (
isinstance(erased, Instance)
and erased.type.is_protocol
or isinstance(erased, TypeType)
and isinstance(erased.item, Instance)
and erased.item.type.is_protocol
):
# We allow the explicit self-type to be not a supertype of
# the current class if it is a protocol. For such cases
# the consistency check will be performed at call sites.
msg = None
elif typ.arg_names[i] in {"self", "cls"}:
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
erased.str_with_options(self.options),
ref_type.str_with_options(self.options),
)
else:
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
if msg:
self.fail(msg, defn)
if not is_same_type(arg_type, ref_type):
# This level of erasure matches the one in checkmember.check_self_arg(),
# better keep these two checks consistent.
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
if not is_subtype(ref_type, erased, ignore_type_params=True):
if (
isinstance(erased, Instance)
and erased.type.is_protocol
or isinstance(erased, TypeType)
and isinstance(erased.item, Instance)
and erased.item.type.is_protocol
):
# We allow the explicit self-type to be not a supertype of
# the current class if it is a protocol. For such cases
# the consistency check will be performed at call sites.
msg = None
elif typ.arg_names[i] in {"self", "cls"}:
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
erased.str_with_options(self.options),
ref_type.str_with_options(self.options),
)
else:
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
if msg:
self.fail(msg, defn)
elif isinstance(arg_type, TypeVarType):
# Refuse covariant parameter type variables
# TODO: check recursively for inner type variables
Expand Down
12 changes: 12 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,18 @@ def is_same_type(
This means types may have different representation (e.g. an alias, or
a non-simplified union) but are semantically exchangeable in all contexts.
"""
# First, use fast path for some common types. This is performance-critical.
if (
type(a) is Instance
and type(b) is Instance
and a.type == b.type
and len(a.args) == len(b.args)
and a.last_known_value is b.last_known_value
):
return all(is_same_type(x, y) for x, y in zip(a.args, b.args))
elif isinstance(a, TypeVarType) and isinstance(b, TypeVarType) and a.id == b.id:
return True

# Note that using ignore_promotions=True (default) makes types like int and int64
# considered not the same type (which is the case at runtime).
# Also Union[bool, int] (if it wasn't simplified before) will be different
Expand Down