3
3
from __future__ import annotations
4
4
5
5
from collections .abc import Sequence
6
- from typing import Callable , cast
6
+ from typing import Callable , TypeVar , cast
7
7
8
8
from mypy import message_registry , state , subtypes
9
9
from mypy .checker_shared import TypeCheckerSharedApi
18
18
from mypy .nodes import (
19
19
ARG_POS ,
20
20
ARG_STAR ,
21
+ ARG_STAR2 ,
21
22
EXCLUDED_ENUM_ATTRIBUTES ,
22
23
SYMBOL_FUNCBASE_TYPES ,
23
24
Context ,
@@ -359,10 +360,13 @@ def analyze_instance_member_access(
359
360
signature = method .type
360
361
signature = freshen_all_functions_type_vars (signature )
361
362
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 )
366
370
# TODO: should we skip these steps for static methods as well?
367
371
# Since generic static methods should not be allowed.
368
372
typ = map_instance_to_supertype (typ , method .info )
@@ -521,9 +525,11 @@ def analyze_member_var_access(
521
525
mx .chk .warn_deprecated (v , mx .context )
522
526
523
527
vv = v
528
+ is_trivial_self = False
524
529
if isinstance (vv , Decorator ):
525
530
# The associated Var node of a decorator contains the type.
526
531
v = vv .var
532
+ is_trivial_self = vv .func .is_trivial_self and not vv .decorators
527
533
if mx .is_super and not mx .suppress_errors :
528
534
validate_super_call (vv .func , mx )
529
535
@@ -555,7 +561,7 @@ def analyze_member_var_access(
555
561
if mx .is_lvalue and not mx .chk .get_final_context ():
556
562
check_final_member (name , info , mx .msg , mx .context )
557
563
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 )
559
565
elif isinstance (v , FuncDef ):
560
566
assert False , "Did not expect a function"
561
567
elif isinstance (v , MypyFile ):
@@ -850,14 +856,21 @@ def is_instance_var(var: Var) -> bool:
850
856
851
857
852
858
def 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 ,
854
866
) -> Type :
855
867
"""Analyze access to an attribute via a Var node.
856
868
857
869
This is conceptually part of analyze_member_access and the arguments are similar.
858
870
itype is the instance type in which attribute should be looked up
859
871
original_type is the type of E in the expression E.var
860
872
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().
861
874
"""
862
875
# Found a member variable.
863
876
original_itype = itype
@@ -904,7 +917,7 @@ def analyze_var(
904
917
for ct in call_type .items if isinstance (call_type , UnionType ) else [call_type ]:
905
918
p_ct = get_proper_type (ct )
906
919
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 )
908
921
else :
909
922
item = expand_without_binding (ct , var , itype , original_itype , mx )
910
923
bound_items .append (item )
@@ -938,13 +951,21 @@ def expand_without_binding(
938
951
939
952
940
953
def 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 ,
942
960
) -> Type :
943
961
functype = freshen_all_functions_type_vars (functype )
944
962
typ = get_proper_type (expand_self_type (var , functype , mx .original_type ))
945
963
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 )
948
969
expanded = expand_type_by_instance (typ , itype )
949
970
freeze_all_type_vars (expanded )
950
971
if not var .is_property :
@@ -1203,10 +1224,22 @@ def analyze_class_attribute_access(
1203
1224
isinstance (node .node , SYMBOL_FUNCBASE_TYPES ) and node .node .is_static
1204
1225
)
1205
1226
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 :
1207
1234
t = check_self_arg (t , mx .self_type , False , mx .context , name , mx .msg )
1208
1235
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 ,
1210
1243
)
1211
1244
# __set__ is not called on class objects.
1212
1245
if not mx .is_lvalue :
@@ -1255,7 +1288,7 @@ def analyze_class_attribute_access(
1255
1288
# Annotated and/or explicit class methods go through other code paths above, for
1256
1289
# unannotated implicit class methods we do this here.
1257
1290
if node .node .is_class :
1258
- typ = bind_self (typ , is_classmethod = True )
1291
+ typ = bind_self_fast (typ )
1259
1292
return apply_class_attr_hook (mx , hook , typ )
1260
1293
1261
1294
@@ -1342,6 +1375,7 @@ def add_class_tvars(
1342
1375
is_staticmethod : bool ,
1343
1376
original_type : Type ,
1344
1377
original_vars : Sequence [TypeVarLikeType ] | None = None ,
1378
+ is_trivial_self : bool = False ,
1345
1379
) -> Type :
1346
1380
"""Instantiate type variables during analyze_class_attribute_access,
1347
1381
e.g T and Q in the following:
@@ -1362,6 +1396,7 @@ class B(A[str]): pass
1362
1396
original_type: The value of the type B in the expression B.foo() or the corresponding
1363
1397
component in case of a union (this is used to bind the self-types)
1364
1398
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().
1365
1400
Returns:
1366
1401
Expanded method type with added type variables (when needed).
1367
1402
"""
@@ -1383,7 +1418,10 @@ class B(A[str]): pass
1383
1418
tvars = original_vars if original_vars is not None else []
1384
1419
t = freshen_all_functions_type_vars (t )
1385
1420
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 )
1387
1425
if is_classmethod or is_staticmethod :
1388
1426
assert isuper is not None
1389
1427
t = expand_type_by_instance (t , isuper )
@@ -1422,5 +1460,45 @@ def analyze_decorator_or_funcbase_access(
1422
1460
if isinstance (defn , Decorator ):
1423
1461
return analyze_var (name , defn .var , itype , mx )
1424
1462
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 )
1425
1471
typ = check_self_arg (typ , mx .self_type , defn .is_class , mx .context , name , mx .msg )
1426
1472
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