Add fast path for checking self types
diff --git a/mypy/checker.py b/mypy/checker.py
index e1b65a9..99dd912 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -1195,13 +1195,14 @@
# 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
@@ -1211,30 +1212,31 @@
):
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
diff --git a/mypy/subtypes.py b/mypy/subtypes.py
index 6385538..c4495a5 100644
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -258,6 +258,25 @@
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 (
+ isinstance(a, Instance)
+ and isinstance(b, 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):
+ if (
+ a.id == b.id
+ and not a.values
+ and not b.values
+ and is_same_type(a.upper_bound, b.upper_bound)
+ and a.variance == b.variance
+ ):
+ 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