From 01019a55ebc21e42ce0f628ac02bfdb7dfa93bd0 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 22 Oct 2023 11:07:45 -0700 Subject: [PATCH] Add fast path for checking self types 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. --- mypy/checker.py | 58 +++++++++++++++++++++++++----------------------- mypy/subtypes.py | 12 ++++++++++ 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e68dc4178962..500e3b3c8a97 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -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 @@ -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 diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 383e6eddd317..c6ccb27084ac 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -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