1414
1515package com .google .devtools .build .lib .packages ;
1616
17+ import com .google .common .annotations .VisibleForTesting ;
1718import com .google .common .base .Preconditions ;
1819import com .google .common .collect .ImmutableList ;
1920import com .google .devtools .build .lib .cmdline .Label ;
2021import com .google .devtools .build .lib .events .EventHandler ;
2122import com .google .devtools .build .lib .util .Fingerprint ;
2223import java .util .Collection ;
24+ import java .util .Map ;
2325import java .util .Objects ;
2426import javax .annotation .Nullable ;
27+ import net .starlark .java .eval .Dict ;
2528import net .starlark .java .eval .EvalException ;
2629import net .starlark .java .eval .Printer ;
2730import net .starlark .java .eval .Starlark ;
3942 * providers can have any set of fields on them, whereas instances of schemaful providers may have
4043 * only the fields that are named in the schema.
4144 *
45+ * <p>{@code StarlarkProvider} may have a custom initializer callback, which might perform
46+ * preprocessing or validation of field values. This callback (if defined) is automatically invoked
47+ * when the provider is called. To create instances of the provider without calling the initializer
48+ * callback, use the callable returned by {@code StarlarkProvider#createRawConstructor}.
49+ *
4250 * <p>Exporting a {@code StarlarkProvider} creates a key that is used to uniquely identify it.
4351 * Usually a provider is exported by calling {@link #export}, but a test may wish to just create a
4452 * pre-exported provider directly. Exported providers use only their key for {@link #equals} and
@@ -53,6 +61,11 @@ public final class StarlarkProvider implements StarlarkCallable, StarlarkExporta
5361 // as it lets us verify table ⊆ schema in O(n) time without temporaries.
5462 @ Nullable private final ImmutableList <String > schema ;
5563
64+ // Optional custom initializer callback. If present, it is invoked with the same positional and
65+ // keyword arguments as were passed to the provider constructor. The return value must be a
66+ // Starlark dict mapping field names (string keys) to their values.
67+ @ Nullable private final StarlarkCallable init ;
68+
5669 /** Null iff this provider has not yet been exported. Mutated by {@link export}. */
5770 @ Nullable private Key key ;
5871
@@ -78,6 +91,8 @@ public static final class Builder {
7891
7992 @ Nullable private ImmutableList <String > schema ;
8093
94+ @ Nullable private StarlarkCallable init ;
95+
8196 @ Nullable private Key key ;
8297
8398 private Builder (Location location ) {
@@ -93,6 +108,24 @@ public Builder setSchema(Collection<String> schema) {
93108 return this ;
94109 }
95110
111+ /**
112+ * Sets the custom initializer callback for instances of the provider built by this builder.
113+ *
114+ * <p>The initializer callback will be automatically invoked when the provider is called. To
115+ * bypass the custom initializer callback, use the callable returned by {@link
116+ * StarlarkProvider#createRawConstructor}.
117+ *
118+ * @param init A callback that accepts the arguments passed to the provider constructor, and
119+ * which returns a dict mapping field names to their values. The resulting provider instance
120+ * is created as though the dict were passed as **kwargs to the raw constructor. In
121+ * particular, for a schemaful provider, the dict may not contain keys not listed in the
122+ * schema.
123+ */
124+ public Builder setInit (StarlarkCallable init ) {
125+ this .init = init ;
126+ return this ;
127+ }
128+
96129 /** Sets the provider built by this builder to be exported with the given key. */
97130 public Builder setExported (Key key ) {
98131 this .key = key ;
@@ -101,32 +134,102 @@ public Builder setExported(Key key) {
101134
102135 /** Builds a StarlarkProvider. */
103136 public StarlarkProvider build () {
104- return new StarlarkProvider (location , schema , key );
137+ return new StarlarkProvider (location , schema , init , key );
105138 }
106139 }
107140
108141 /**
109142 * Constructs the provider.
110143 *
111- * <p>If {@code key} is null, the provider is unexported. If {@code schema} is null, the provider
112- * is schemaless.
144+ * <p>If {@code schema} is null, the provider is schemaless. If {@code init} is null, no custom
145+ * initializer callback will be used (i.e., calling the provider is the same as simply calling the
146+ * raw constructor). If {@code key} is null, the provider is unexported.
113147 */
114148 private StarlarkProvider (
115- Location location , @ Nullable ImmutableList <String > schema , @ Nullable Key key ) {
149+ Location location ,
150+ @ Nullable ImmutableList <String > schema ,
151+ @ Nullable StarlarkCallable init ,
152+ @ Nullable Key key ) {
116153 this .location = location ;
117154 this .schema = schema ;
155+ this .init = init ;
118156 this .key = key ;
119157 }
120158
159+ private static Object [] toNamedArgs (Object value , String descriptionForError )
160+ throws EvalException {
161+ Dict <String , Object > kwargs = Dict .cast (value , String .class , Object .class , descriptionForError );
162+ Object [] named = new Object [2 * kwargs .size ()];
163+ int i = 0 ;
164+ for (Map .Entry <String , Object > e : kwargs .entrySet ()) {
165+ named [i ++] = e .getKey ();
166+ named [i ++] = e .getValue ();
167+ }
168+ return named ;
169+ }
170+
121171 @ Override
122172 public Object fastcall (StarlarkThread thread , Object [] positional , Object [] named )
173+ throws InterruptedException , EvalException {
174+ if (init == null ) {
175+ return fastcallRawConstructor (thread , positional , named );
176+ }
177+
178+ Object initResult = Starlark .fastcall (thread , init , positional , named );
179+ return StarlarkInfo .createFromNamedArgs (
180+ this ,
181+ toNamedArgs (initResult , "return value of provider init()" ),
182+ schema ,
183+ thread .getCallerLocation ());
184+ }
185+
186+ private Object fastcallRawConstructor (StarlarkThread thread , Object [] positional , Object [] named )
123187 throws EvalException {
124188 if (positional .length > 0 ) {
125189 throw Starlark .errorf ("%s: unexpected positional arguments" , getName ());
126190 }
127191 return StarlarkInfo .createFromNamedArgs (this , named , schema , thread .getCallerLocation ());
128192 }
129193
194+ private static final class RawConstructor implements StarlarkCallable {
195+ private final StarlarkProvider provider ;
196+
197+ private RawConstructor (StarlarkProvider provider ) {
198+ this .provider = provider ;
199+ }
200+
201+ @ Override
202+ public Object fastcall (StarlarkThread thread , Object [] positional , Object [] named )
203+ throws EvalException {
204+ return provider .fastcallRawConstructor (thread , positional , named );
205+ }
206+
207+ @ Override
208+ public String getName () {
209+ StringBuilder name = new StringBuilder ("<raw constructor" );
210+ if (provider .isExported ()) {
211+ name .append (" for " ).append (provider .getName ());
212+ }
213+ name .append (">" );
214+ return name .toString ();
215+ }
216+
217+ @ Override
218+ public Location getLocation () {
219+ return provider .location ;
220+ }
221+ }
222+
223+ public StarlarkCallable createRawConstructor () {
224+ return new RawConstructor (this );
225+ }
226+
227+ @ Nullable
228+ @ VisibleForTesting
229+ public StarlarkCallable getInit () {
230+ return init ;
231+ }
232+
130233 @ Override
131234 public Location getLocation () {
132235 return location ;
0 commit comments