11"""Overlay for functools."""
22
3+ from __future__ import annotations
4+
5+ from collections .abc import Mapping , Sequence
6+ from typing import TYPE_CHECKING
7+
8+ from pytype .abstract import abstract
9+ from pytype .abstract import function
10+ from pytype .abstract import mixin
311from pytype .overlays import overlay
412from pytype .overlays import special_builtins
13+ from pytype .typegraph import cfg
14+
15+ if TYPE_CHECKING :
16+ from pytype import context # pylint: disable=g-import-not-at-top
17+
518
619_MODULE_NAME = "functools"
720
@@ -14,6 +27,80 @@ def __init__(self, ctx):
1427 "cached_property" : overlay .add_name (
1528 "cached_property" , special_builtins .Property .make_alias
1629 ),
30+ "partial" : Partial ,
1731 }
1832 ast = ctx .loader .import_name (_MODULE_NAME )
1933 super ().__init__ (ctx , _MODULE_NAME , member_map , ast )
34+
35+
36+ class Partial (abstract .PyTDClass , mixin .HasSlots ):
37+ """Implementation of functools.partial."""
38+
39+ def __init__ (self , ctx : "context.Context" , module : str ):
40+ pytd_cls = ctx .loader .lookup_pytd (module , "partial" )
41+ super ().__init__ ("partial" , pytd_cls , ctx )
42+ mixin .HasSlots .init_mixin (self )
43+
44+ self ._pytd_new = self .pytd_cls .Lookup ("__new__" )
45+
46+ def new_slot (
47+ self , node , cls , * args , ** kwargs
48+ ) -> tuple [cfg .CFGNode , cfg .Variable ]:
49+ # Make sure the call is well typed before binding the partial
50+ new = self .ctx .convert .convert_pytd_function (self ._pytd_new )
51+ _ , specialized_obj = function .call_function (
52+ self .ctx ,
53+ node ,
54+ new .to_variable (node ),
55+ function .Args (posargs = (cls , * args ), namedargs = kwargs ),
56+ fallback_to_unsolvable = False ,
57+ )
58+ [specialized_obj ] = specialized_obj .data
59+ type_arg = specialized_obj .get_formal_type_parameter ("_T" )
60+ [cls ] = cls .data
61+ cls = abstract .ParameterizedClass (cls , {"_T" : type_arg }, self .ctx )
62+ obj = bind_partial (node , cls , args , kwargs , self .ctx )
63+ return node , obj .to_variable (node )
64+
65+ def get_own_new (self , node , value ) -> tuple [cfg .CFGNode , cfg .Variable ]:
66+ new = abstract .NativeFunction ("__new__" , self .new_slot , self .ctx )
67+ return node , new .to_variable (node )
68+
69+
70+ def bind_partial (node , cls , args , kwargs , ctx ) -> BoundPartial :
71+ del node # Unused.
72+ obj = BoundPartial (ctx , cls )
73+ obj .underlying = args [0 ]
74+ obj .args = args [1 :]
75+ obj .kwargs = kwargs
76+ return obj
77+
78+
79+ class BoundPartial (abstract .Instance , mixin .HasSlots ):
80+ """An instance of functools.partial."""
81+
82+ underlying : cfg .Variable
83+ args : Sequence [cfg .Variable ]
84+ kwargs : Mapping [str , cfg .Variable ]
85+
86+ def __init__ (self , ctx , cls , container = None ):
87+ super ().__init__ (cls , ctx , container )
88+ mixin .HasSlots .init_mixin (self )
89+ self .set_native_slot ("__call__" , self .call_slot )
90+
91+ @property
92+ def func (self ) -> cfg .Variable :
93+ # The ``func`` attribute marks this class as a wrapper for
94+ # ``maybe_unwrap_decorated_function``.
95+ return self .underlying
96+
97+ def call_slot (self , node : cfg .CFGNode , * args , ** kwargs ):
98+ return function .call_function (
99+ self .ctx ,
100+ node ,
101+ self .underlying ,
102+ function .Args (
103+ posargs = (* self .args , * args ), namedargs = {** self .kwargs , ** kwargs }
104+ ),
105+ fallback_to_unsolvable = False ,
106+ )
0 commit comments