33from __future__ import annotations
44
55from collections .abc import Sequence
6- from typing import Callable , cast
6+ from typing import Callable , TypeVar , cast
77
88from mypy import message_registry , state , subtypes
99from mypy .checker_shared import TypeCheckerSharedApi
1818from mypy .nodes import (
1919 ARG_POS ,
2020 ARG_STAR ,
21+ ARG_STAR2 ,
2122 EXCLUDED_ENUM_ATTRIBUTES ,
2223 SYMBOL_FUNCBASE_TYPES ,
2324 Context ,
@@ -359,10 +360,13 @@ def analyze_instance_member_access(
359360 signature = method .type
360361 signature = freshen_all_functions_type_vars (signature )
361362 if not method .is_static :
362- signature = check_self_arg (
363- signature , mx .self_type , method .is_class , mx .context , name , mx .msg
364- )
365- signature = bind_self (signature , mx .self_type , is_classmethod = method .is_class )
363+ if isinstance (method , (FuncDef , OverloadedFuncDef )) and method .is_trivial_self :
364+ signature = bind_self_fast (signature , mx .self_type )
365+ else :
366+ signature = check_self_arg (
367+ signature , mx .self_type , method .is_class , mx .context , name , mx .msg
368+ )
369+ signature = bind_self (signature , mx .self_type , is_classmethod = method .is_class )
366370 # TODO: should we skip these steps for static methods as well?
367371 # Since generic static methods should not be allowed.
368372 typ = map_instance_to_supertype (typ , method .info )
@@ -521,9 +525,11 @@ def analyze_member_var_access(
521525 mx .chk .warn_deprecated (v , mx .context )
522526
523527 vv = v
528+ is_trivial_self = False
524529 if isinstance (vv , Decorator ):
525530 # The associated Var node of a decorator contains the type.
526531 v = vv .var
532+ is_trivial_self = vv .func .is_trivial_self and not vv .decorators
527533 if mx .is_super and not mx .suppress_errors :
528534 validate_super_call (vv .func , mx )
529535
@@ -555,7 +561,7 @@ def analyze_member_var_access(
555561 if mx .is_lvalue and not mx .chk .get_final_context ():
556562 check_final_member (name , info , mx .msg , mx .context )
557563
558- return analyze_var (name , v , itype , mx , implicit = implicit )
564+ return analyze_var (name , v , itype , mx , implicit = implicit , is_trivial_self = is_trivial_self )
559565 elif isinstance (v , FuncDef ):
560566 assert False , "Did not expect a function"
561567 elif isinstance (v , MypyFile ):
@@ -850,14 +856,21 @@ def is_instance_var(var: Var) -> bool:
850856
851857
852858def analyze_var (
853- name : str , var : Var , itype : Instance , mx : MemberContext , * , implicit : bool = False
859+ name : str ,
860+ var : Var ,
861+ itype : Instance ,
862+ mx : MemberContext ,
863+ * ,
864+ implicit : bool = False ,
865+ is_trivial_self : bool = False ,
854866) -> Type :
855867 """Analyze access to an attribute via a Var node.
856868
857869 This is conceptually part of analyze_member_access and the arguments are similar.
858870 itype is the instance type in which attribute should be looked up
859871 original_type is the type of E in the expression E.var
860872 if implicit is True, the original Var was created as an assignment to self
873+ if is_trivial_self is True, we can use fast path for bind_self().
861874 """
862875 # Found a member variable.
863876 original_itype = itype
@@ -904,7 +917,7 @@ def analyze_var(
904917 for ct in call_type .items if isinstance (call_type , UnionType ) else [call_type ]:
905918 p_ct = get_proper_type (ct )
906919 if isinstance (p_ct , FunctionLike ) and not p_ct .is_type_obj ():
907- item = expand_and_bind_callable (p_ct , var , itype , name , mx )
920+ item = expand_and_bind_callable (p_ct , var , itype , name , mx , is_trivial_self )
908921 else :
909922 item = expand_without_binding (ct , var , itype , original_itype , mx )
910923 bound_items .append (item )
@@ -938,13 +951,21 @@ def expand_without_binding(
938951
939952
940953def expand_and_bind_callable (
941- functype : FunctionLike , var : Var , itype : Instance , name : str , mx : MemberContext
954+ functype : FunctionLike ,
955+ var : Var ,
956+ itype : Instance ,
957+ name : str ,
958+ mx : MemberContext ,
959+ is_trivial_self : bool ,
942960) -> Type :
943961 functype = freshen_all_functions_type_vars (functype )
944962 typ = get_proper_type (expand_self_type (var , functype , mx .original_type ))
945963 assert isinstance (typ , FunctionLike )
946- typ = check_self_arg (typ , mx .self_type , var .is_classmethod , mx .context , name , mx .msg )
947- typ = bind_self (typ , mx .self_type , var .is_classmethod )
964+ if is_trivial_self :
965+ typ = bind_self_fast (typ , mx .self_type )
966+ else :
967+ typ = check_self_arg (typ , mx .self_type , var .is_classmethod , mx .context , name , mx .msg )
968+ typ = bind_self (typ , mx .self_type , var .is_classmethod )
948969 expanded = expand_type_by_instance (typ , itype )
949970 freeze_all_type_vars (expanded )
950971 if not var .is_property :
@@ -1203,10 +1224,22 @@ def analyze_class_attribute_access(
12031224 isinstance (node .node , SYMBOL_FUNCBASE_TYPES ) and node .node .is_static
12041225 )
12051226 t = get_proper_type (t )
1206- if isinstance (t , FunctionLike ) and is_classmethod :
1227+ is_trivial_self = False
1228+ if isinstance (node .node , Decorator ):
1229+ # Use fast path if there are trivial decorators like @classmethod or @property
1230+ is_trivial_self = node .node .func .is_trivial_self and not node .node .decorators
1231+ elif isinstance (node .node , (FuncDef , OverloadedFuncDef )):
1232+ is_trivial_self = node .node .is_trivial_self
1233+ if isinstance (t , FunctionLike ) and is_classmethod and not is_trivial_self :
12071234 t = check_self_arg (t , mx .self_type , False , mx .context , name , mx .msg )
12081235 result = add_class_tvars (
1209- t , isuper , is_classmethod , is_staticmethod , mx .self_type , original_vars = original_vars
1236+ t ,
1237+ isuper ,
1238+ is_classmethod ,
1239+ is_staticmethod ,
1240+ mx .self_type ,
1241+ original_vars = original_vars ,
1242+ is_trivial_self = is_trivial_self ,
12101243 )
12111244 # __set__ is not called on class objects.
12121245 if not mx .is_lvalue :
@@ -1255,7 +1288,7 @@ def analyze_class_attribute_access(
12551288 # Annotated and/or explicit class methods go through other code paths above, for
12561289 # unannotated implicit class methods we do this here.
12571290 if node .node .is_class :
1258- typ = bind_self (typ , is_classmethod = True )
1291+ typ = bind_self_fast (typ )
12591292 return apply_class_attr_hook (mx , hook , typ )
12601293
12611294
@@ -1342,6 +1375,7 @@ def add_class_tvars(
13421375 is_staticmethod : bool ,
13431376 original_type : Type ,
13441377 original_vars : Sequence [TypeVarLikeType ] | None = None ,
1378+ is_trivial_self : bool = False ,
13451379) -> Type :
13461380 """Instantiate type variables during analyze_class_attribute_access,
13471381 e.g T and Q in the following:
@@ -1362,6 +1396,7 @@ class B(A[str]): pass
13621396 original_type: The value of the type B in the expression B.foo() or the corresponding
13631397 component in case of a union (this is used to bind the self-types)
13641398 original_vars: Type variables of the class callable on which the method was accessed
1399+ is_trivial_self: if True, we can use fast path for bind_self().
13651400 Returns:
13661401 Expanded method type with added type variables (when needed).
13671402 """
@@ -1383,7 +1418,10 @@ class B(A[str]): pass
13831418 tvars = original_vars if original_vars is not None else []
13841419 t = freshen_all_functions_type_vars (t )
13851420 if is_classmethod :
1386- t = bind_self (t , original_type , is_classmethod = True )
1421+ if is_trivial_self :
1422+ t = bind_self_fast (t , original_type )
1423+ else :
1424+ t = bind_self (t , original_type , is_classmethod = True )
13871425 if is_classmethod or is_staticmethod :
13881426 assert isuper is not None
13891427 t = expand_type_by_instance (t , isuper )
@@ -1422,5 +1460,45 @@ def analyze_decorator_or_funcbase_access(
14221460 if isinstance (defn , Decorator ):
14231461 return analyze_var (name , defn .var , itype , mx )
14241462 typ = function_type (defn , mx .chk .named_type ("builtins.function" ))
1463+ is_trivial_self = False
1464+ if isinstance (defn , Decorator ):
1465+ # Use fast path if there are trivial decorators like @classmethod or @property
1466+ is_trivial_self = defn .func .is_trivial_self and not defn .decorators
1467+ elif isinstance (defn , (FuncDef , OverloadedFuncDef )):
1468+ is_trivial_self = defn .is_trivial_self
1469+ if is_trivial_self :
1470+ return bind_self_fast (typ , mx .self_type )
14251471 typ = check_self_arg (typ , mx .self_type , defn .is_class , mx .context , name , mx .msg )
14261472 return bind_self (typ , original_type = mx .self_type , is_classmethod = defn .is_class )
1473+
1474+
1475+ F = TypeVar ("F" , bound = FunctionLike )
1476+
1477+
1478+ def bind_self_fast (method : F , original_type : Type | None = None ) -> F :
1479+ """Return a copy of `method`, with the type of its first parameter (usually
1480+ self or cls) bound to original_type.
1481+
1482+ This is a faster version of mypy.typeops.bind_self() that can be used for methods
1483+ with trivial self/cls annotations.
1484+ """
1485+ if isinstance (method , Overloaded ):
1486+ items = [bind_self_fast (c , original_type ) for c in method .items ]
1487+ return cast (F , Overloaded (items ))
1488+ assert isinstance (method , CallableType )
1489+ if not method .arg_types :
1490+ # Invalid method, return something.
1491+ return cast (F , method )
1492+ if method .arg_kinds [0 ] in (ARG_STAR , ARG_STAR2 ):
1493+ # See typeops.py for details.
1494+ return cast (F , method )
1495+ original_type = get_proper_type (original_type )
1496+ if isinstance (original_type , CallableType ) and original_type .is_type_obj ():
1497+ original_type = TypeType .make_normalized (original_type .ret_type )
1498+ res = method .copy_modified (
1499+ arg_types = method .arg_types [1 :],
1500+ arg_kinds = method .arg_kinds [1 :],
1501+ arg_names = method .arg_names [1 :],
1502+ bound_args = [original_type ],
1503+ )
1504+ return cast (F , res )
0 commit comments