Skip to content

Commit

Permalink
Add fast path for checking self types (#16352)
Browse files Browse the repository at this point in the history
The check was pretty expensive, though usually it's not doing anything
non-trivial. Added a fast path for cases where we use the implicit self
type, which covers the vast majority of cases.

This makes self-check about 4% faster.
  • Loading branch information
JukkaL authored Oct 28, 2023
1 parent c4ab46e commit c76132f
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 28 deletions.
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

0 comments on commit c76132f

Please sign in to comment.