xref: /aosp_15_r20/external/bazel-skylib/lib/partial.bzl (revision bcb5dc7965af6ee42bf2f21341a2ec00233a8c8a)
1*bcb5dc79SHONG Yifan# Copyright 2018 The Bazel Authors. All rights reserved.
2*bcb5dc79SHONG Yifan#
3*bcb5dc79SHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License");
4*bcb5dc79SHONG Yifan# you may not use this file except in compliance with the License.
5*bcb5dc79SHONG Yifan# You may obtain a copy of the License at
6*bcb5dc79SHONG Yifan#
7*bcb5dc79SHONG Yifan#    http://www.apache.org/licenses/LICENSE-2.0
8*bcb5dc79SHONG Yifan#
9*bcb5dc79SHONG Yifan# Unless required by applicable law or agreed to in writing, software
10*bcb5dc79SHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS,
11*bcb5dc79SHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*bcb5dc79SHONG Yifan# See the License for the specific language governing permissions and
13*bcb5dc79SHONG Yifan# limitations under the License.
14*bcb5dc79SHONG Yifan
15*bcb5dc79SHONG Yifan"""Starlark module for working with partial function objects.
16*bcb5dc79SHONG Yifan
17*bcb5dc79SHONG YifanPartial function objects allow some parameters are bound before the call.
18*bcb5dc79SHONG Yifan
19*bcb5dc79SHONG YifanSimilar to https://docs.python.org/3/library/functools.html#functools.partial.
20*bcb5dc79SHONG Yifan"""
21*bcb5dc79SHONG Yifan
22*bcb5dc79SHONG Yifan# create instance singletons to avoid unnecessary allocations
23*bcb5dc79SHONG Yifan_a_dict_type = type({})
24*bcb5dc79SHONG Yifan_a_tuple_type = type(())
25*bcb5dc79SHONG Yifan_a_struct_type = type(struct())
26*bcb5dc79SHONG Yifan
27*bcb5dc79SHONG Yifandef _call(partial, *args, **kwargs):
28*bcb5dc79SHONG Yifan    """Calls a partial created using `make`.
29*bcb5dc79SHONG Yifan
30*bcb5dc79SHONG Yifan    Args:
31*bcb5dc79SHONG Yifan      partial: The partial to be called.
32*bcb5dc79SHONG Yifan      *args: Additional positional arguments to be appended to the ones given to
33*bcb5dc79SHONG Yifan             make.
34*bcb5dc79SHONG Yifan      **kwargs: Additional keyword arguments to augment and override the ones
35*bcb5dc79SHONG Yifan                given to make.
36*bcb5dc79SHONG Yifan
37*bcb5dc79SHONG Yifan    Returns:
38*bcb5dc79SHONG Yifan      Whatever the function in the partial returns.
39*bcb5dc79SHONG Yifan    """
40*bcb5dc79SHONG Yifan    function_args = partial.args + args
41*bcb5dc79SHONG Yifan    function_kwargs = dict(partial.kwargs)
42*bcb5dc79SHONG Yifan    function_kwargs.update(kwargs)
43*bcb5dc79SHONG Yifan    return partial.function(*function_args, **function_kwargs)
44*bcb5dc79SHONG Yifan
45*bcb5dc79SHONG Yifandef _make(func, *args, **kwargs):
46*bcb5dc79SHONG Yifan    """Creates a partial that can be called using `call`.
47*bcb5dc79SHONG Yifan
48*bcb5dc79SHONG Yifan    A partial can have args assigned to it at the make site, and can have args
49*bcb5dc79SHONG Yifan    passed to it at the call sites.
50*bcb5dc79SHONG Yifan
51*bcb5dc79SHONG Yifan    A partial 'function' can be defined with positional args and kwargs:
52*bcb5dc79SHONG Yifan
53*bcb5dc79SHONG Yifan      # function with no args
54*bcb5dc79SHONG Yifan      ```
55*bcb5dc79SHONG Yifan      def function1():
56*bcb5dc79SHONG Yifan        ...
57*bcb5dc79SHONG Yifan      ```
58*bcb5dc79SHONG Yifan
59*bcb5dc79SHONG Yifan      # function with 2 args
60*bcb5dc79SHONG Yifan      ```
61*bcb5dc79SHONG Yifan      def function2(arg1, arg2):
62*bcb5dc79SHONG Yifan        ...
63*bcb5dc79SHONG Yifan      ```
64*bcb5dc79SHONG Yifan
65*bcb5dc79SHONG Yifan      # function with 2 args and keyword args
66*bcb5dc79SHONG Yifan      ```
67*bcb5dc79SHONG Yifan      def function3(arg1, arg2, x, y):
68*bcb5dc79SHONG Yifan        ...
69*bcb5dc79SHONG Yifan      ```
70*bcb5dc79SHONG Yifan
71*bcb5dc79SHONG Yifan    The positional args passed to the function are the args passed into make
72*bcb5dc79SHONG Yifan    followed by any additional positional args given to call. The below example
73*bcb5dc79SHONG Yifan    illustrates a function with two positional arguments where one is supplied by
74*bcb5dc79SHONG Yifan    make and the other by call:
75*bcb5dc79SHONG Yifan
76*bcb5dc79SHONG Yifan      # function demonstrating 1 arg at make site, and 1 arg at call site
77*bcb5dc79SHONG Yifan      ```
78*bcb5dc79SHONG Yifan      def _foo(make_arg1, func_arg1):
79*bcb5dc79SHONG Yifan        print(make_arg1 + " " + func_arg1 + "!")
80*bcb5dc79SHONG Yifan      ```
81*bcb5dc79SHONG Yifan
82*bcb5dc79SHONG Yifan    For example:
83*bcb5dc79SHONG Yifan
84*bcb5dc79SHONG Yifan      ```
85*bcb5dc79SHONG Yifan      hi_func = partial.make(_foo, "Hello")
86*bcb5dc79SHONG Yifan      bye_func = partial.make(_foo, "Goodbye")
87*bcb5dc79SHONG Yifan      partial.call(hi_func, "Jennifer")
88*bcb5dc79SHONG Yifan      partial.call(hi_func, "Dave")
89*bcb5dc79SHONG Yifan      partial.call(bye_func, "Jennifer")
90*bcb5dc79SHONG Yifan      partial.call(bye_func, "Dave")
91*bcb5dc79SHONG Yifan      ```
92*bcb5dc79SHONG Yifan
93*bcb5dc79SHONG Yifan    prints:
94*bcb5dc79SHONG Yifan
95*bcb5dc79SHONG Yifan      ```
96*bcb5dc79SHONG Yifan      "Hello, Jennifer!"
97*bcb5dc79SHONG Yifan      "Hello, Dave!"
98*bcb5dc79SHONG Yifan      "Goodbye, Jennifer!"
99*bcb5dc79SHONG Yifan      "Goodbye, Dave!"
100*bcb5dc79SHONG Yifan      ```
101*bcb5dc79SHONG Yifan
102*bcb5dc79SHONG Yifan    The keyword args given to the function are the kwargs passed into make
103*bcb5dc79SHONG Yifan    unioned with the keyword args given to call. In case of a conflict, the
104*bcb5dc79SHONG Yifan    keyword args given to call take precedence. This allows you to set a default
105*bcb5dc79SHONG Yifan    value for keyword arguments and override it at the call site.
106*bcb5dc79SHONG Yifan
107*bcb5dc79SHONG Yifan    Example with a make site arg, a call site arg, a make site kwarg and a
108*bcb5dc79SHONG Yifan    call site kwarg:
109*bcb5dc79SHONG Yifan
110*bcb5dc79SHONG Yifan      ```
111*bcb5dc79SHONG Yifan      def _foo(make_arg1, call_arg1, make_location, call_location):
112*bcb5dc79SHONG Yifan        print(make_arg1 + " is from " + make_location + " and " +
113*bcb5dc79SHONG Yifan              call_arg1 + " is from " + call_location + "!")
114*bcb5dc79SHONG Yifan
115*bcb5dc79SHONG Yifan      func = partial.make(_foo, "Ben", make_location="Hollywood")
116*bcb5dc79SHONG Yifan      partial.call(func, "Jennifer", call_location="Denver")
117*bcb5dc79SHONG Yifan      ```
118*bcb5dc79SHONG Yifan
119*bcb5dc79SHONG Yifan    Prints "Ben is from Hollywood and Jennifer is from Denver!".
120*bcb5dc79SHONG Yifan
121*bcb5dc79SHONG Yifan      ```
122*bcb5dc79SHONG Yifan      partial.call(func, "Jennifer", make_location="LA", call_location="Denver")
123*bcb5dc79SHONG Yifan      ```
124*bcb5dc79SHONG Yifan
125*bcb5dc79SHONG Yifan    Prints "Ben is from LA and Jennifer is from Denver!".
126*bcb5dc79SHONG Yifan
127*bcb5dc79SHONG Yifan    Note that keyword args may not overlap with positional args, regardless of
128*bcb5dc79SHONG Yifan    whether they are given during the make or call step. For instance, you can't
129*bcb5dc79SHONG Yifan    do:
130*bcb5dc79SHONG Yifan
131*bcb5dc79SHONG Yifan    ```
132*bcb5dc79SHONG Yifan    def foo(x):
133*bcb5dc79SHONG Yifan      pass
134*bcb5dc79SHONG Yifan
135*bcb5dc79SHONG Yifan    func = partial.make(foo, 1)
136*bcb5dc79SHONG Yifan    partial.call(func, x=2)
137*bcb5dc79SHONG Yifan    ```
138*bcb5dc79SHONG Yifan
139*bcb5dc79SHONG Yifan    Args:
140*bcb5dc79SHONG Yifan      func: The function to be called.
141*bcb5dc79SHONG Yifan      *args: Positional arguments to be passed to function.
142*bcb5dc79SHONG Yifan      **kwargs: Keyword arguments to be passed to function. Note that these can
143*bcb5dc79SHONG Yifan                be overridden at the call sites.
144*bcb5dc79SHONG Yifan
145*bcb5dc79SHONG Yifan    Returns:
146*bcb5dc79SHONG Yifan      A new `partial` that can be called using `call`
147*bcb5dc79SHONG Yifan    """
148*bcb5dc79SHONG Yifan    return struct(function = func, args = args, kwargs = kwargs)
149*bcb5dc79SHONG Yifan
150*bcb5dc79SHONG Yifandef _is_instance(v):
151*bcb5dc79SHONG Yifan    """Returns True if v is a partial created using `make`.
152*bcb5dc79SHONG Yifan
153*bcb5dc79SHONG Yifan    Args:
154*bcb5dc79SHONG Yifan      v: The value to check.
155*bcb5dc79SHONG Yifan
156*bcb5dc79SHONG Yifan    Returns:
157*bcb5dc79SHONG Yifan      True if v was created by `make`, False otherwise.
158*bcb5dc79SHONG Yifan    """
159*bcb5dc79SHONG Yifan
160*bcb5dc79SHONG Yifan    # Note that in bazel 3.7.0 and earlier, type(v.function) is the same
161*bcb5dc79SHONG Yifan    # as the type of a function even if v.function is a rule. But we
162*bcb5dc79SHONG Yifan    # cannot rely on this in later bazels due to breaking change
163*bcb5dc79SHONG Yifan    # https://github.com/bazelbuild/bazel/commit/e379ece1908aafc852f9227175dd3283312b4b82
164*bcb5dc79SHONG Yifan    #
165*bcb5dc79SHONG Yifan    # Since this check is heuristic anyway, we simply check for the
166*bcb5dc79SHONG Yifan    # presence of a "function" attribute without checking its type.
167*bcb5dc79SHONG Yifan    return type(v) == _a_struct_type and \
168*bcb5dc79SHONG Yifan           hasattr(v, "function") and \
169*bcb5dc79SHONG Yifan           hasattr(v, "args") and type(v.args) == _a_tuple_type and \
170*bcb5dc79SHONG Yifan           hasattr(v, "kwargs") and type(v.kwargs) == _a_dict_type
171*bcb5dc79SHONG Yifan
172*bcb5dc79SHONG Yifanpartial = struct(
173*bcb5dc79SHONG Yifan    make = _make,
174*bcb5dc79SHONG Yifan    call = _call,
175*bcb5dc79SHONG Yifan    is_instance = _is_instance,
176*bcb5dc79SHONG Yifan)
177