Skip to content

Commit c76132f

Browse files
authored
Add fast path for checking self types (#16352)
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.
1 parent c4ab46e commit c76132f

File tree

2 files changed

+42
-28
lines changed

2 files changed

+42
-28
lines changed

mypy/checker.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,13 +1199,14 @@ def check_func_def(
11991199
# Push return type.
12001200
self.return_types.append(typ.ret_type)
12011201

1202+
with self.scope.push_function(defn):
1203+
# We temporary push the definition to get the self type as
1204+
# visible from *inside* of this function/method.
1205+
ref_type: Type | None = self.scope.active_self_type()
1206+
12021207
# Store argument types.
12031208
for i in range(len(typ.arg_types)):
12041209
arg_type = typ.arg_types[i]
1205-
with self.scope.push_function(defn):
1206-
# We temporary push the definition to get the self type as
1207-
# visible from *inside* of this function/method.
1208-
ref_type: Type | None = self.scope.active_self_type()
12091210
if (
12101211
isinstance(defn, FuncDef)
12111212
and ref_type is not None
@@ -1215,30 +1216,31 @@ def check_func_def(
12151216
):
12161217
if defn.is_class or defn.name == "__new__":
12171218
ref_type = mypy.types.TypeType.make_normalized(ref_type)
1218-
# This level of erasure matches the one in checkmember.check_self_arg(),
1219-
# better keep these two checks consistent.
1220-
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
1221-
if not is_subtype(ref_type, erased, ignore_type_params=True):
1222-
if (
1223-
isinstance(erased, Instance)
1224-
and erased.type.is_protocol
1225-
or isinstance(erased, TypeType)
1226-
and isinstance(erased.item, Instance)
1227-
and erased.item.type.is_protocol
1228-
):
1229-
# We allow the explicit self-type to be not a supertype of
1230-
# the current class if it is a protocol. For such cases
1231-
# the consistency check will be performed at call sites.
1232-
msg = None
1233-
elif typ.arg_names[i] in {"self", "cls"}:
1234-
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
1235-
erased.str_with_options(self.options),
1236-
ref_type.str_with_options(self.options),
1237-
)
1238-
else:
1239-
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
1240-
if msg:
1241-
self.fail(msg, defn)
1219+
if not is_same_type(arg_type, ref_type):
1220+
# This level of erasure matches the one in checkmember.check_self_arg(),
1221+
# better keep these two checks consistent.
1222+
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
1223+
if not is_subtype(ref_type, erased, ignore_type_params=True):
1224+
if (
1225+
isinstance(erased, Instance)
1226+
and erased.type.is_protocol
1227+
or isinstance(erased, TypeType)
1228+
and isinstance(erased.item, Instance)
1229+
and erased.item.type.is_protocol
1230+
):
1231+
# We allow the explicit self-type to be not a supertype of
1232+
# the current class if it is a protocol. For such cases
1233+
# the consistency check will be performed at call sites.
1234+
msg = None
1235+
elif typ.arg_names[i] in {"self", "cls"}:
1236+
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
1237+
erased.str_with_options(self.options),
1238+
ref_type.str_with_options(self.options),
1239+
)
1240+
else:
1241+
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
1242+
if msg:
1243+
self.fail(msg, defn)
12421244
elif isinstance(arg_type, TypeVarType):
12431245
# Refuse covariant parameter type variables
12441246
# TODO: check recursively for inner type variables

mypy/subtypes.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,18 @@ def is_same_type(
258258
This means types may have different representation (e.g. an alias, or
259259
a non-simplified union) but are semantically exchangeable in all contexts.
260260
"""
261+
# First, use fast path for some common types. This is performance-critical.
262+
if (
263+
type(a) is Instance
264+
and type(b) is Instance
265+
and a.type == b.type
266+
and len(a.args) == len(b.args)
267+
and a.last_known_value is b.last_known_value
268+
):
269+
return all(is_same_type(x, y) for x, y in zip(a.args, b.args))
270+
elif isinstance(a, TypeVarType) and isinstance(b, TypeVarType) and a.id == b.id:
271+
return True
272+
261273
# Note that using ignore_promotions=True (default) makes types like int and int64
262274
# considered not the same type (which is the case at runtime).
263275
# Also Union[bool, int] (if it wasn't simplified before) will be different

0 commit comments

Comments
 (0)