An undocumented parameter must be {@link #named} and may not be followed by positional
+ * parameters or {@code **kwargs}.
+ */
+ boolean documented() default true;
+
+ /**
+ * Default value for the parameter, written as a Starlark expression (e.g. "False", "True", "[]",
+ * "None").
+ *
+ *
If this is empty (the default), the parameter is treated as mandatory. (Thus an exception
+ * will be thrown if left unspecified by the caller).
+ *
+ *
If the function implementation needs to distinguish the case where the caller does not
+ * supply a value for this parameter, you can set the default to the magic string "unbound", which
+ * maps to the sentinal object {@link net.starlark.java.eval.Starlark#UNBOUND} (which can't appear
+ * in normal Starlark code).
+ */
+ String defaultValue() default "";
+
+ /**
+ * List of allowed types for the parameter.
+ *
+ *
The array may be omitted, in which case the parameter accepts any value whose class is
+ * assignable to the class of the parameter variable.
+ *
+ *
If a function should accept None, NoneType should be in this list.
+ */
+ ParamType[] allowedTypes() default {};
+
+ /**
+ * If true, the parameter may be specified as a named parameter. For example for an integer named
+ * parameter {@code foo} of a method {@code bar}, then the method call will look like {@code
+ * bar(foo=1)}.
+ *
+ *
If false, then {@link #positional} must be true (otherwise there is no way to reference the
+ * parameter via an argument).
+ *
+ *
If this parameter represents the 'extra positionals' (args) or 'extra keywords' (kwargs)
+ * element of a method, this field has no effect.
+ */
+ boolean named() default false;
+
+ /**
+ * If true, the parameter may be specified as a positional parameter. For example for an integer
+ * positional parameter {@code foo} of a method {@code bar}, then the method call will look like
+ * {@code bar(1)}. If {@link #named()} is {@code false}, then this will be the only way to call
+ * {@code bar}.
+ *
+ *
If false, then {@link #named} must be true (otherwise there is no way to reference the
+ * parameter via an argument)
+ *
+ *
Positional arguments should come first.
+ *
+ *
If this parameter represents the 'extra positionals' (args) or 'extra keywords' (kwargs)
+ * element of a method, this field has no effect.
+ */
+ boolean positional() default true;
+
+ /**
+ * If non-empty, the annotated parameter will only be present if the given semantic flag is true.
+ * (If the parameter is disabled, it may not be specified by a user, and the Java method will
+ * always be invoked with the parameter set to its default value.)
+ *
+ *
Note that at most one of {@link #enableOnlyWithFlag} and {@link #disableWithFlag} can be
+ * non-empty.
+ */
+ String enableOnlyWithFlag() default "";
+
+ /**
+ * If non-empty, the annotated parameter will only be present if the given semantic flag is false.
+ * (If the parameter is disabled, it may not be specified by a user, and the Java method will
+ * always be invoked with the parameter set to its default value.)
+ *
+ *
Note that at most one of {@link #enableOnlyWithFlag} and {@link #disableWithFlag} can be
+ * non-empty.
+ */
+ String disableWithFlag() default "";
+
+ /**
+ * Value for the parameter when the parameter is "disabled" based on semantic flags. (When the
+ * parameter is disabled, it may not be set from Starlark, but an argument of the given value is
+ * passed to the annotated Java method when invoked.) (See {@link #enableOnlyWithFlag()} and
+ * {@link #disableWithFlag()} for toggling a parameter with semantic flags.
+ *
+ *
The parameter value is written as a Starlark expression (for example: "False", "True", "[]",
+ * "None").
+ *
+ *
This should be set (non-empty) if and only if the parameter may be disabled with a semantic
+ * flag.
+ *
+ *
Note that this is very similar to {@link #defaultValue}; it may be considered "the default
+ * value if no parameter is specified". It is important that this is distinct, however, in cases
+ * where it is desired to have a normally-mandatory parameter toggled by flag. Such a parameter
+ * should have no {@link #defaultValue} set, but should have a sensible {@link
+ * #valueWhenDisabled()} set. ("unbound" may be used in cases where no value would be valid. See
+ * {@link #defaultValue}.)
+ */
+ String valueWhenDisabled() default "";
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/annot/ParamType.java b/third_party/bazel/main/java/net/starlark/java/annot/ParamType.java
new file mode 100644
index 000000000..f4fc5dff0
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/annot/ParamType.java
@@ -0,0 +1,41 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package net.starlark.java.annot;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** An annotation for parameter types for Starlark built-in functions. */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ParamType {
+ /**
+ * The Java class of the type, e.g. {@link String}.class or {@link
+ * net.starlark.java.eval.Sequence}.class.
+ */
+ Class> type();
+
+ /**
+ * When {@link #type()} is a generic type (e.g., {@link net.starlark.java.eval.Sequence}), specify
+ * the type parameter (e.g. {@link String}.class} along with {@link
+ * net.starlark.java.eval.Sequence} for {@link #type()} to specify a list of strings).
+ *
+ *
This is only used for documentation generation. The actual generic type is not checked at
+ * runtime, so the Java method signature should use a generic type of Object and cast
+ * appropriately.
+ */
+ // TODO(#13365): make this a data structure so we can represent a {@link
+ // net.starlark.java.eval.Sequence} of types {@code A} or {@code B} intermixed, a {@link
+ // net.starlark.java.eval.Dict} mapping from {@code A} to {@code B}, etc.
+ Class> generic1() default Object.class;
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/annot/README.md b/third_party/bazel/main/java/net/starlark/java/annot/README.md
new file mode 100644
index 000000000..a03140b83
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/annot/README.md
@@ -0,0 +1,4 @@
+# Starlark interface framework
+
+The classes in this package define annotations and interfaces used to enable
+Starlark access to data types and methods implemented in Java.
diff --git a/third_party/bazel/main/java/net/starlark/java/annot/StarlarkAnnotations.java b/third_party/bazel/main/java/net/starlark/java/annot/StarlarkAnnotations.java
new file mode 100644
index 000000000..6fbf56ba4
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/annot/StarlarkAnnotations.java
@@ -0,0 +1,200 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.starlark.java.annot;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import javax.annotation.Nullable;
+
+/** Utility functions for Starlark annotations. */
+public final class StarlarkAnnotations {
+
+ /**
+ * Returns the more specific class of two classes. Class x is more specific than class y if x is
+ * assignable to y. For example, of String.class and Object.class, String.class is more specific.
+ *
+ *
If either class is null, returns the other class.
+ *
+ *
If the classes are identical, returns the class.
+ *
+ * @throws IllegalArgumentException if neither class is assignable to the other
+ */
+ private static Class> moreSpecific(Class> x, Class> y) {
+ if (x == null) {
+ return y;
+ } else if (y == null) {
+ return x;
+ } else if (x.isAssignableFrom(y)) {
+ return y;
+ } else if (y.isAssignableFrom(x)) {
+ return x;
+ } else {
+ // If this exception occurs, it indicates the following error scenario:
+ //
+ // Suppose class A is a subclass of both B and C, where B and C are annotated with
+ // @StarlarkBuiltin annotations (and are thus considered "Starlark types"). If B is not a
+ // subclass of C (nor vice versa), then it's impossible to resolve whether A is of type
+ // B or if A is of type C. It's both! The way to resolve this is usually to have A be its own
+ // type (annotated with @StarlarkBuiltin), and thus have the explicit type of A be
+ // semantically "B and C".
+ throw new IllegalArgumentException(
+ String.format("Expected one of %s and %s to be a subclass of the other", x, y));
+ }
+ }
+
+ /**
+ * Searches a class or interface's class hierarchy for the given class annotation.
+ *
+ *
If the given class annotation appears multiple times within the class hierachy, this chooses
+ * the annotation on the most-specified class in the hierarchy.
+ *
+ * @return the best-fit class that declares the annotation, or null if no class in the hierarchy
+ * declares it
+ * @throws IllegalArgumentException if the most-specified class in the hierarchy having the
+ * annotation is not unique
+ */
+ @Nullable
+ private static Class> findAnnotatedAncestor(
+ Class> classObj, Class extends Annotation> annotation) {
+ if (classObj.isAnnotationPresent(annotation)) {
+ return classObj;
+ }
+ Class> bestCandidate = null;
+ Class> superclass = classObj.getSuperclass();
+ if (superclass != null) {
+ Class> result = findAnnotatedAncestor(superclass, annotation);
+ bestCandidate = moreSpecific(result, bestCandidate);
+ }
+ for (Class> interfaceObj : classObj.getInterfaces()) {
+ Class> result = findAnnotatedAncestor(interfaceObj, annotation);
+ bestCandidate = moreSpecific(result, bestCandidate);
+ }
+ return bestCandidate;
+ }
+
+ /**
+ * Returns the {@link StarlarkBuiltin} annotation for the given class, if it exists, and
+ * null otherwise. The first annotation found will be returned, starting with {@code classObj}
+ * and following its base classes and interfaces recursively.
+ */
+ @Nullable
+ public static StarlarkBuiltin getStarlarkBuiltin(Class> classObj) {
+ Class> cls = findAnnotatedAncestor(classObj, StarlarkBuiltin.class);
+ return cls == null ? null : cls.getAnnotation(StarlarkBuiltin.class);
+ }
+
+ /**
+ * Searches {@code classObj}'s class hierarchy and returns the first superclass or interface that
+ * is annotated with {@link StarlarkBuiltin} (including possibly {@code classObj} itself), or null
+ * if none is found.
+ */
+ @Nullable
+ public static Class> getParentWithStarlarkBuiltin(Class> classObj) {
+ return findAnnotatedAncestor(classObj, StarlarkBuiltin.class);
+ }
+
+ /**
+ * Returns the {@link StarlarkMethod} annotation for the given method, if it exists, and null
+ * otherwise.
+ *
+ *
Note that the annotation may be defined on a supermethod, rather than directly on the given
+ * method.
+ *
+ *
{@code classObj} is the class on which the given method is defined.
+ */
+ @Nullable
+ public static StarlarkMethod getStarlarkMethod(Class> classObj, Method method) {
+ StarlarkMethod callable = getAnnotationOnClassMatchingSignature(classObj, method);
+ if (callable != null) {
+ return callable;
+ }
+ if (classObj.getSuperclass() != null) {
+ StarlarkMethod annotation = getStarlarkMethod(classObj.getSuperclass(), method);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+ for (Class> interfaceObj : classObj.getInterfaces()) {
+ StarlarkMethod annotation = getStarlarkMethod(interfaceObj, method);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convenience version of {@code getAnnotationsFromParentClass(Class, Method)} that uses the
+ * declaring class of the method.
+ */
+ @Nullable
+ public static StarlarkMethod getStarlarkMethod(Method method) {
+ return getStarlarkMethod(method.getDeclaringClass(), method);
+ }
+
+ /**
+ * Returns the {@code StarlarkMethod} annotation corresponding to the given method of the given
+ * class, or null if there is no such annotation.
+ *
+ *
This method checks assignability instead of exact matches for purposes of generics. If Clazz
+ * has parameters BarT (extends BarInterface) and BazT (extends BazInterface), then foo(BarT,
+ * BazT) should match if the given method signature is foo(BarImpl, BazImpl). The signatures are
+ * in inexact match, but an "assignable" match.
+ */
+ @Nullable
+ private static StarlarkMethod getAnnotationOnClassMatchingSignature(
+ Class> classObj, Method signatureToMatch) {
+ // TODO(b/79877079): This method validates several invariants of @StarlarkMethod. These
+ // invariants should be verified in annotation processor or in test, and left out of this
+ // method.
+ Method[] methods = classObj.getDeclaredMethods();
+ Class>[] paramsToMatch = signatureToMatch.getParameterTypes();
+
+ StarlarkMethod callable = null;
+
+ for (Method method : methods) {
+ if (signatureToMatch.getName().equals(method.getName())
+ && method.isAnnotationPresent(StarlarkMethod.class)) {
+ Class>[] paramTypes = method.getParameterTypes();
+
+ if (paramTypes.length == paramsToMatch.length) {
+ for (int i = 0; i < paramTypes.length; i++) {
+ // This verifies assignability of the method signature to ensure this is not a
+ // coincidental overload. We verify assignability instead of matching exact parameter
+ // classes in order to match generic methods.
+ if (!paramTypes[i].isAssignableFrom(paramsToMatch[i])) {
+ throw new IllegalStateException(
+ String.format(
+ "Class %s has an incompatible overload of annotated method %s declared by %s",
+ classObj, signatureToMatch.getName(), signatureToMatch.getDeclaringClass()));
+ }
+ }
+ }
+ if (callable == null) {
+ callable = method.getAnnotation(StarlarkMethod.class);
+ } else {
+ throw new IllegalStateException(
+ String.format(
+ "Class %s has multiple overloaded methods named '%s' annotated "
+ + "with @StarlarkMethod",
+ classObj, signatureToMatch.getName()));
+ }
+ }
+ }
+ return callable;
+ }
+
+ private StarlarkAnnotations() {}
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/annot/StarlarkBuiltin.java b/third_party/bazel/main/java/net/starlark/java/annot/StarlarkBuiltin.java
new file mode 100644
index 000000000..cbdc76a34
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/annot/StarlarkBuiltin.java
@@ -0,0 +1,80 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package net.starlark.java.annot;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used on classes and interfaces that represent Starlark data types.
+ *
+ *
Conceptually, every {@link StarlarkBuiltin} annotation corresponds to a user-distinguishable
+ * Starlark type. The annotation holds metadata associated with that type, in particular its name
+ * and documentation. The annotation also implicitly demarcates the Starlark API of the type. It
+ * does not matter whether the annotation is used on a class or an interface.
+ *
+ *
Annotations are "inherited" and "overridden", in the sense that a child class or interface
+ * takes on the Starlark type of its ancestor by default, unless it has a direct annotation of its
+ * own. If there are multiple ancestors that have an annotation, then to avoid ambiguity we require
+ * that one of them is a subtype of the rest; that is the one whose annotation gets inherited. This
+ * ensures that every class implements at most one Starlark type, and not an ad hoc hybrid of
+ * multiple types. (In mathematical terms, the most-derived annotation for class or interface C is
+ * the minimum element in the partial order of all annotations defined on C and its ancestors, where
+ * the order relationship is X < Y if X annotates a subtype of what Y annotates.) The lookup logic
+ * for retrieving a class's {@link StarlarkBuiltin} is implemented by {@link
+ * StarlarkAnnotations#getStarlarkBuiltin}.
+ *
+ *
Inheriting an annotation is useful when the class is an implementation detail, such as a
+ * concrete implementation of an abstract interface. Overriding an annotation is useful when the
+ * class should have its own distinct user-visible API or documentation. For example, {@link
+ * Sequence} is an abstract type implemented by both {@link StarlarkList} and {@link
+ * Sequence.Tuple}, all three of which are annotated. Annotating the list and tuple types allows
+ * them to define different methods, while annotating {@link Sequence} allows them to be identified
+ * as a single type for the purpose of type checking, documentation, and error messages.
+ *
+ *
All {@link StarlarkBuiltin}-annotated types must implement {@link StarlarkValue}. Nearly all
+ * non-abstract implementations of {@link StarlarkValue} have or inherit a {@link StarlarkBuiltin}
+ * annotation. (It is possible, though quite unusual, to declare an implementation of {@code
+ * StarlarkValue} without using the annotation mechanism defined in this package. {@code
+ * StarlarkFunction} is one example.)
+ */
+// TODO(adonovan): rename to StarlarkType now that that name is available again.
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface StarlarkBuiltin {
+
+ /**
+ * The name of this data type, as returned by the Starlark expression {@code type(x)}.
+ *
+ *
Applications should ensure that data type names are unique. This is especially important for
+ * a type that implements Comparable, as its {@code compareTo} method may be passed any value of
+ * the same Starlark type, not necessarily one of the same Java class.
+ */
+ String name();
+
+ /** Module documentation in HTML. May be empty only if {@code !documented()}. */
+ String doc() default "";
+
+ /** Whether the module should appear in the documentation. */
+ boolean documented() default true;
+
+ /**
+ * The category of the documentation to which this data type belongs. Applications may use this
+ * field as they wish for their documentation tools. The core data types of the Starlark
+ * interpreter all have category "core".
+ */
+ String category() default "";
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/annot/StarlarkMethod.java b/third_party/bazel/main/java/net/starlark/java/annot/StarlarkMethod.java
new file mode 100644
index 000000000..bedc13575
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/annot/StarlarkMethod.java
@@ -0,0 +1,206 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package net.starlark.java.annot;
+
+import com.google.errorprone.annotations.Keep;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a Java method that can be called from Starlark.
+ *
+ *
A method annotated with {@code @StarlarkMethod} may not have overloads or hide any static or
+ * default methods. Overriding is allowed, but the {@code @StarlarkMethod} annotation itself must
+ * not be repeated on the override. This ensures that given a method, we can always determine its
+ * corresponding {@code @StarlarkMethod} annotation, if it has one, by scanning all methods of the
+ * same name in its class hierarchy, without worrying about complications like overloading or
+ * generics. The lookup functionality is implemented by {@link
+ * StarlarkAnnotations#getStarlarkMethod}.
+ *
+ *
Methods having this annotation must satisfy the following requirements, which are enforced at
+ * compile time by {@link StarlarkMethodProcessor}:
+ *
+ *
+ *
The method must be public and non-static, and its class must implement StarlarkValue.
+ *
The method must declare the following parameters, in order:
+ *
+ *
one for each {@code Param} marked {@link Param#positional}. These parameters may be
+ * specified positionally. Among these, required parameters must precede optional ones.
+ * A suffix of the optional positional parameters may additionally be marked {@link
+ * Param#named}, meaning they may be specified by position or by name.
+ *
one for each {@code Param} marked {@link Param#named} but not {@link
+ * Param#positional}. These parameters must be specified by name. Again, required
+ * named-only parameters must precede optional ones.
+ *
one for the {@code Tuple} of extra positional arguments ({@code *args}), if {@code
+ * extraPositionals};
+ *
a {@code Dict} of extra keyword arguments ({@code **kwargs}), if
+ * {@code extraKeywords};
+ *
a {@code StarlarkThread}, if {@code useStarlarkThread};
+ *
a {@code StarlarkSemantics}, if {@code useStarlarkSemantics}.
+ *
+ * The last three parameters are implicitly supplied by the interpreter when the method is
+ * called from Starlark.
+ *
If {@code structField}, there must be no {@code @Param} annotations or parameters, and the
+ * only permitted special parameter is {@code StarlarkSemantics}. Rationale: unlike a method,
+ * which is actively called within in the context of a Starlark thread (which encapsulates a
+ * call stack of locations), a field is a passive thing, part of a data structure, that may be
+ * accessed by a Java caller without a Starlark thread.
+ *
Each {@code Param} annotation, if explicitly typed, may use either {@code type} or {@code
+ * allowedTypes}, but not both.
+ *
Each {@code Param} annotation must be positional or named, or both.
+ *
Noneable parameter variables must be declared with type Object, as the actual value may be
+ * either {@code None} or some other value, which do not share a superclass other than Object
+ * (or StarlarkValue, which is typically no more descriptive than Object).
+ *
Parameter variables whose class is generic must be declared using wildcard types. For
+ * example, {@code Sequence>} is allowed but {@code Sequence} is forbidden. This is
+ * because the call-time dynamic checks verify the class but cannot verify the type
+ * parameters. Such parameters may require additional validation within the method
+ * implementation.
+ *
The class of the declared result type, if final, must be accepted by {@link
+ * Starlark#fromJava}. Rationale: this check helps reject clearly invalid parameter types.
+ *
The {@code doc} string must be non-empty, or {@code documented} must be false. Rationale:
+ * Leaving a function undocumented requires an explicit decision.
+ *
Each class may have up to one method annotated with {@code selfCall}, which must not be
+ * marked {@code structField=true}.
+ *
+ *
+ *
When an annotated method is called from Starlark, it is a dynamic error if it returns null,
+ * unless the method is marked as {@link #allowReturnNones}, in which case {@link Starlark#fromJava}
+ * converts the Java null value to {@link Starlark#NONE}. This feature prevents a method whose
+ * declared (and documented) result type is T from unexpectedly returning a value of type NoneType.
+ *
+ *
The annotated method may throw any checked or unchecked exceptions. When it is invoked,
+ * unchecked exceptions, {@code EvalException}s, and {@code InterruptedException}s are passed
+ * through; all other (checked) exceptions are wrapped in an {@code EvalException} and thrown.
+ */
+// TODO(adonovan): rename to StarlarkAttribute and factor Starlark{Method,Field} as subinterfaces.
+@Keep
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface StarlarkMethod {
+
+ /** Name of the method, as exposed to Starlark. */
+ String name();
+
+ /**
+ * The documentation text in Starlark. It can contain HTML tags for special formatting.
+ *
+ *
It is allowed to be empty only if {@link #documented()} is false.
+ */
+ String doc() default "";
+
+ /**
+ * If true, the function will appear in the Starlark documentation. Set this to false if the
+ * function is experimental or an overloading and doesn't need to be documented.
+ */
+ boolean documented() default true;
+
+ /**
+ * If true, this method will be considered as a field of the enclosing Java object. E.g., if set
+ * to true on a method {@code foo}, then the callsites of this method will look like {@code
+ * bar.foo} instead of {@code bar.foo()}. The annotated method must be parameterless and {@link
+ * #parameters()} should be empty.
+ */
+ boolean structField() default false;
+
+ /**
+ * List of parameters this function accepts.
+ */
+ Param[] parameters() default {};
+
+ /**
+ * Defines a catch-all list for additional unspecified positional parameters.
+ *
+ *
If this is left as default, it is an error for the caller to pass more positional arguments
+ * than are explicitly allowed by the method signature. If this is defined, all additional
+ * positional arguments are passed as elements of a {@link Tuple} to the method.
+ *
+ *
See Python's *args (http://thepythonguru.com/python-args-and-kwargs/).
+ *
+ *
If defined, the annotated method must declare a corresponding parameter to which a {@code
+ * Tuple} may be assigned. See the interface-level javadoc for details.
+ */
+ // TODO(adonovan): consider using a simpler type than Param here. All that's needed at run-time
+ // is a boolean. The doc tools want a name and doc string, but the rest is irrelevant and
+ // distracting.
+ // Ditto extraKeywords.
+ Param extraPositionals() default @Param(name = "");
+
+ /**
+ * Defines a catch-all dictionary for additional unspecified named parameters.
+ *
+ *
If this is left as default, it is an error for the caller to pass any named arguments not
+ * explicitly declared by the method signature. If this is defined, all additional named arguments
+ * are passed as elements of a {@code Dict} to the method.
+ *
+ *
See Python's **kwargs (http://thepythonguru.com/python-args-and-kwargs/).
+ *
+ *
If defined, the annotated method must declare a corresponding parameter to which a {@code
+ * Dict} may be assigned. See the interface-level javadoc for details.
+ */
+ Param extraKeywords() default @Param(name = "");
+
+ /**
+ * If true, indicates that the class containing the annotated method has the ability to be called
+ * from Starlark (as if it were a function) and that the annotated method should be invoked when
+ * this occurs.
+ *
+ *
A class may only have one method with selfCall set to true.
+ *
+ *
A method with selfCall=true must not be a structField, and must have name specified (used
+ * for descriptive errors if, for example, there are missing arguments).
+ */
+ boolean selfCall() default false;
+
+ /**
+ * Permits the Java method to return null, which {@link Starlark#fromJava} then converts to {@link
+ * Starlark#NONE}. If false, a null result causes the Starlark call to fail.
+ */
+ boolean allowReturnNones() default false;
+
+ /**
+ * If true, the StarlarkThread will be passed as an argument of the annotated function. (Thus, the
+ * annotated method signature must contain StarlarkThread as a parameter. See the interface-level
+ * javadoc for details.)
+ *
+ *
This is incompatible with structField=true. If structField is true, this must be false.
+ */
+ boolean useStarlarkThread() default false;
+
+ /**
+ * If true, the Starlark semantics will be passed to the annotated Java method. (Thus, the
+ * annotated method signature must contain StarlarkSemantics as a parameter. See the
+ * interface-level javadoc for details.)
+ *
+ *
This option is allowed only for fields ({@code structField=true}). For methods, the {@code
+ * StarlarkThread} parameter provides access to the semantics, and more.
+ */
+ boolean useStarlarkSemantics() default false;
+
+ /**
+ * If non-empty, the annotated method will only be callable if the given semantic flag is true.
+ * Note that at most one of {@link #enableOnlyWithFlag} and {@link #disableWithFlag} can be
+ * non-empty.
+ */
+ String enableOnlyWithFlag() default "";
+
+ /**
+ * If non-empty, the annotated method will only be callable if the given semantic flag is false.
+ * Note that at most one of {@link #enableOnlyWithFlag} and {@link #disableWithFlag} can be
+ * non-empty.
+ */
+ String disableWithFlag() default "";
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/annot/processor/BUILD b/third_party/bazel/main/java/net/starlark/java/annot/processor/BUILD
new file mode 100644
index 000000000..bef38a1ba
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/annot/processor/BUILD
@@ -0,0 +1,32 @@
+load("@rules_java//java:defs.bzl", "java_library", "java_plugin")
+
+package(
+ default_applicable_licenses = ["//third_party/bazel:license"],
+ default_visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = ["//src:__subpackages__"],
+)
+
+# A processor for Starlark annotations.
+java_plugin(
+ name = "processor",
+ processor_class = "net.starlark.java.annot.processor.StarlarkMethodProcessor",
+ visibility = ["//third_party/bazel/main/java/net/starlark/java:starlark"],
+ deps = [":processor_lib"],
+)
+
+java_library(
+ name = "processor_lib",
+ srcs = ["StarlarkMethodProcessor.java"],
+ visibility = ["//third_party/bazel/main/java/net/starlark/java:starlark"],
+ deps = [
+ "//third_party/bazel/main/java/net/starlark/java/annot:annot_sans_processor",
+ "//third_party/bazel/third_party:error_prone_annotations",
+ "//third_party/bazel/third_party:guava",
+ "//third_party/bazel/third_party:jsr305",
+ ],
+)
diff --git a/third_party/bazel/main/java/net/starlark/java/annot/processor/StarlarkMethodProcessor.java b/third_party/bazel/main/java/net/starlark/java/annot/processor/StarlarkMethodProcessor.java
new file mode 100644
index 000000000..c2406f056
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/annot/processor/StarlarkMethodProcessor.java
@@ -0,0 +1,527 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.starlark.java.annot.processor;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.errorprone.annotations.FormatMethod;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import net.starlark.java.annot.Param;
+import net.starlark.java.annot.ParamType;
+import net.starlark.java.annot.StarlarkBuiltin;
+import net.starlark.java.annot.StarlarkMethod;
+
+/**
+ * Annotation processor for {@link StarlarkMethod}. See that class for requirements.
+ *
+ *
These properties can be relied upon at runtime without additional checks.
+ */
+@SupportedAnnotationTypes({
+ "net.starlark.java.annot.StarlarkMethod",
+ "net.starlark.java.annot.StarlarkBuiltin"
+})
+public class StarlarkMethodProcessor extends AbstractProcessor {
+
+ private Types types;
+ private Elements elements;
+ private Messager messager;
+
+ // A set containing a TypeElement for each class with a StarlarkMethod.selfCall annotation.
+ private Set classesWithSelfcall;
+ // A multimap where keys are class element, and values are the callable method names identified in
+ // that class (where "method name" is StarlarkMethod.name).
+ private SetMultimap processedClassMethods;
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported();
+ }
+
+ @Override
+ public synchronized void init(ProcessingEnvironment env) {
+ super.init(env);
+ this.types = env.getTypeUtils();
+ this.elements = env.getElementUtils();
+ this.messager = env.getMessager();
+ this.classesWithSelfcall = new HashSet<>();
+ this.processedClassMethods = LinkedHashMultimap.create();
+ }
+
+ private TypeMirror getType(String canonicalName) {
+ return elements.getTypeElement(canonicalName).asType();
+ }
+
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ TypeMirror stringType = getType("java.lang.String");
+ TypeMirror integerType = getType("java.lang.Integer");
+ TypeMirror booleanType = getType("java.lang.Boolean");
+ TypeMirror listType = getType("java.util.List");
+ TypeMirror mapType = getType("java.util.Map");
+ TypeMirror starlarkValueType = getType("net.starlark.java.eval.StarlarkValue");
+
+ // Ensure StarlarkBuiltin-annotated classes implement StarlarkValue.
+ for (Element cls : roundEnv.getElementsAnnotatedWith(StarlarkBuiltin.class)) {
+ if (!types.isAssignable(cls.asType(), starlarkValueType)) {
+ errorf(
+ cls,
+ "class %s has StarlarkBuiltin annotation but does not implement StarlarkValue",
+ cls.getSimpleName());
+ }
+ }
+
+ for (Element element : roundEnv.getElementsAnnotatedWith(StarlarkMethod.class)) {
+ // Only methods are annotated with StarlarkMethod.
+ // This is ensured by the @Target(ElementType.METHOD) annotation.
+ ExecutableElement method = (ExecutableElement) element;
+ if (!method.getModifiers().contains(Modifier.PUBLIC)) {
+ errorf(method, "StarlarkMethod-annotated methods must be public.");
+ }
+ if (method.getModifiers().contains(Modifier.STATIC)) {
+ errorf(method, "StarlarkMethod-annotated methods cannot be static.");
+ }
+
+ // Check the annotation itself.
+ StarlarkMethod annot = method.getAnnotation(StarlarkMethod.class);
+ if (annot.name().isEmpty()) {
+ errorf(method, "StarlarkMethod.name must be non-empty.");
+ }
+ Element cls = method.getEnclosingElement();
+ if (!processedClassMethods.put(cls, annot.name())) {
+ errorf(method, "Containing class defines more than one method named '%s'.", annot.name());
+ }
+ if (annot.documented() && annot.doc().isEmpty()) {
+ errorf(method, "The 'doc' string must be non-empty if 'documented' is true.");
+ }
+ if (annot.structField()) {
+ checkStructFieldAnnotation(method, annot);
+ } else if (annot.useStarlarkSemantics()) {
+ errorf(
+ method,
+ "a StarlarkMethod-annotated method with structField=false may not also specify"
+ + " useStarlarkSemantics. (Instead, set useStarlarkThread and call"
+ + " getSemantics().)");
+ }
+ if (annot.selfCall() && !classesWithSelfcall.add(cls)) {
+ errorf(method, "Containing class has more than one selfCall method defined.");
+ }
+
+ boolean hasFlag = false;
+ if (!annot.enableOnlyWithFlag().isEmpty()) {
+ if (!hasPlusMinusPrefix(annot.enableOnlyWithFlag())) {
+ errorf(method, "enableOnlyWithFlag name must have a + or - prefix");
+ }
+ hasFlag = true;
+ }
+ if (!annot.disableWithFlag().isEmpty()) {
+ if (!hasPlusMinusPrefix(annot.disableWithFlag())) {
+ errorf(method, "disableWithFlag name must have a + or - prefix");
+ }
+ if (hasFlag) {
+ errorf(
+ method,
+ "Only one of StarlarkMethod.enableOnlyWithFlag and StarlarkMethod.disableWithFlag"
+ + " may be specified.");
+ }
+ hasFlag = true;
+ }
+
+ if (annot.allowReturnNones() != (method.getAnnotation(Nullable.class) != null)) {
+ errorf(method, "Method must be annotated with @Nullable iff allowReturnNones is set.");
+ }
+
+ checkParameters(method, annot);
+
+ // Verify that result type, if final, might satisfy Starlark.fromJava.
+ // (If the type is non-final we can't prove that all subclasses are invalid.)
+ TypeMirror ret = method.getReturnType();
+ if (ret.getKind() == TypeKind.DECLARED) {
+ DeclaredType obj = (DeclaredType) ret;
+ if (obj.asElement().getModifiers().contains(Modifier.FINAL)
+ && !types.isSameType(ret, stringType)
+ && !types.isSameType(ret, integerType)
+ && !types.isSameType(ret, booleanType)
+ && !types.isAssignable(obj, starlarkValueType)
+ && !types.isAssignable(obj, listType)
+ && !types.isAssignable(obj, mapType)) {
+ errorf(
+ method,
+ "StarlarkMethod-annotated method %s returns %s, which has no legal Starlark values"
+ + " (see Starlark.fromJava)",
+ method.getSimpleName(),
+ ret);
+ }
+ }
+ }
+
+ // Returning false allows downstream processors to work on the same annotations
+ return false;
+ }
+
+ // TODO(adonovan): obviate these checks by separating field/method interfaces.
+ private void checkStructFieldAnnotation(ExecutableElement method, StarlarkMethod annot) {
+ // useStructField is incompatible with special thread-related parameters,
+ // because unlike a method, which is actively called within a thread,
+ // a field is a passive part of a data structure that may be accessed
+ // from Java threads that don't have anything to do with Starlark threads.
+ // However, the StarlarkSemantics is available even to fields,
+ // because it is a required parameter for all attribute-selection
+ // operations x.f.
+ //
+ // Not having a thread forces implementations to assume Mutability=null,
+ // which is not quite right. Perhaps one day we can abolish Mutability
+ // in favor of a tracing approach as in go.starlark.net.
+ if (annot.useStarlarkThread()) {
+ errorf(
+ method,
+ "a StarlarkMethod-annotated method with structField=true may not also specify"
+ + " useStarlarkThread");
+ }
+ if (!annot.extraPositionals().name().isEmpty()) {
+ errorf(
+ method,
+ "a StarlarkMethod-annotated method with structField=true may not also specify"
+ + " extraPositionals");
+ }
+ if (!annot.extraKeywords().name().isEmpty()) {
+ errorf(
+ method,
+ "a StarlarkMethod-annotated method with structField=true may not also specify"
+ + " extraKeywords");
+ }
+ if (annot.selfCall()) {
+ errorf(
+ method,
+ "a StarlarkMethod-annotated method with structField=true may not also specify"
+ + " selfCall=true");
+ }
+ int nparams = annot.parameters().length;
+ if (nparams > 0) {
+ errorf(
+ method,
+ "method %s is annotated structField=true but also has %d Param annotations",
+ method.getSimpleName(),
+ nparams);
+ }
+ }
+
+ private void checkParameters(ExecutableElement method, StarlarkMethod annot) {
+ List extends VariableElement> params = method.getParameters();
+
+ boolean allowPositionalNext = true;
+ boolean allowPositionalOnlyNext = true;
+ boolean allowNonDefaultPositionalNext = true;
+ boolean hasUndocumentedMethods = false;
+
+ // Check @Param annotations match parameters.
+ Param[] paramAnnots = annot.parameters();
+ for (int i = 0; i < paramAnnots.length; i++) {
+ Param paramAnnot = paramAnnots[i];
+ if (i >= params.size()) {
+ errorf(
+ method,
+ "method %s has %d Param annotations but only %d parameters",
+ method.getSimpleName(),
+ paramAnnots.length,
+ params.size());
+ return;
+ }
+ VariableElement param = params.get(i);
+
+ checkParameter(param, paramAnnot);
+
+ // Check parameter ordering.
+ if (paramAnnot.positional()) {
+ if (!allowPositionalNext) {
+ errorf(
+ param,
+ "Positional parameter '%s' is specified after one or more non-positional parameters",
+ paramAnnot.name());
+ }
+ if (!paramAnnot.named() && !allowPositionalOnlyNext) {
+ errorf(
+ param,
+ "Positional-only parameter '%s' is specified after one or more named or undocumented"
+ + " parameters",
+ paramAnnot.name());
+ }
+ if (paramAnnot.defaultValue().isEmpty()) { // There is no default value.
+ if (!allowNonDefaultPositionalNext) {
+ errorf(
+ param,
+ "Positional parameter '%s' has no default value but is specified after one "
+ + "or more positional parameters with default values",
+ paramAnnot.name());
+ }
+ } else { // There is a default value.
+ // No positional parameters without a default value can come after this parameter.
+ allowNonDefaultPositionalNext = false;
+ }
+ } else { // Not positional.
+ // No positional parameters can come after this parameter.
+ allowPositionalNext = false;
+
+ if (!paramAnnot.named()) {
+ errorf(param, "Parameter '%s' must be either positional or named", paramAnnot.name());
+ }
+ }
+ if (!paramAnnot.documented()) {
+ hasUndocumentedMethods = true;
+ }
+ if (paramAnnot.named() || !paramAnnot.documented()) {
+ // No positional-only parameters can come after this parameter.
+ allowPositionalOnlyNext = false;
+ }
+ }
+
+ if (hasUndocumentedMethods && !annot.extraKeywords().name().isEmpty()) {
+ errorf(
+ method,
+ "Method '%s' has undocumented parameters but also allows extra keyword parameters",
+ annot.name());
+ }
+ checkSpecialParams(method, annot);
+ }
+
+ // Checks consistency of a single parameter with its Param annotation.
+ private void checkParameter(Element param, Param paramAnnot) {
+ TypeMirror paramType = param.asType(); // type of the Java method parameter
+
+ // Give helpful hint for parameter of type Integer.
+ TypeMirror integerType = getType("java.lang.Integer");
+ if (types.isSameType(paramType, integerType)) {
+ errorf(
+ param,
+ "use StarlarkInt, not Integer for parameter '%s' (and see Starlark.toInt)",
+ paramAnnot.name());
+ }
+
+ // Reject an entry of Param.allowedTypes if not assignable to the parameter variable.
+ for (ParamType paramTypeAnnot : paramAnnot.allowedTypes()) {
+ TypeMirror t = getParamTypeType(paramTypeAnnot);
+ if (!types.isAssignable(t, types.erasure(paramType))) {
+ errorf(
+ param,
+ "annotated allowedTypes entry %s of parameter '%s' is not assignable to variable of "
+ + "type %s",
+ t,
+ paramAnnot.name(),
+ paramType);
+ }
+ }
+
+ // Reject generic types C other than C>,
+ // since reflective calls check only the toplevel class.
+ if (paramType instanceof DeclaredType declaredType) {
+ for (TypeMirror typeArg : declaredType.getTypeArguments()) {
+ if (!(typeArg instanceof WildcardType)) {
+ errorf(
+ param,
+ "parameter '%s' has generic type %s, but only wildcard type parameters are"
+ + " allowed. Type inference in a Starlark-exposed method is unsafe. See"
+ + " StarlarkMethod class documentation for details.",
+ param.getSimpleName(),
+ paramType);
+ }
+ }
+ }
+
+ // Check sense of flag-controlled parameters.
+ boolean hasFlag = false;
+ if (!paramAnnot.enableOnlyWithFlag().isEmpty()) {
+ if (!hasPlusMinusPrefix(paramAnnot.enableOnlyWithFlag())) {
+ errorf(param, "enableOnlyWithFlag name must have a + or - prefix");
+ }
+ hasFlag = true;
+ }
+ if (!paramAnnot.disableWithFlag().isEmpty()) {
+ if (!hasPlusMinusPrefix(paramAnnot.disableWithFlag())) {
+ errorf(param, "disableWithFlag name must have a + or - prefix");
+ }
+ if (hasFlag) {
+ errorf(
+ param,
+ "Parameter '%s' has enableOnlyWithFlag and disableWithFlag set. At most one may be set",
+ paramAnnot.name());
+ }
+ hasFlag = true;
+ }
+ if (hasFlag == paramAnnot.valueWhenDisabled().isEmpty()) {
+ errorf(
+ param,
+ hasFlag
+ ? "Parameter '%s' may be disabled by semantic flag, thus valueWhenDisabled must be"
+ + " set"
+ : "Parameter '%s' has valueWhenDisabled set, but is always enabled",
+ paramAnnot.name());
+ }
+
+ // Ensure positional arguments are documented.
+ if (!paramAnnot.documented() && paramAnnot.positional()) {
+ errorf(
+ param, "Parameter '%s' must be documented because it is positional.", paramAnnot.name());
+ }
+ }
+
+ private static boolean hasPlusMinusPrefix(String s) {
+ return s.charAt(0) == '-' || s.charAt(0) == '+';
+ }
+
+ // Returns the logical type of ParamType.type.
+ private static TypeMirror getParamTypeType(ParamType paramType) {
+ // See explanation of this hack at Element.getAnnotation
+ // and at https://stackoverflow.com/a/10167558.
+ try {
+ paramType.type();
+ throw new IllegalStateException("unreachable");
+ } catch (MirroredTypeException ex) {
+ return ex.getTypeMirror();
+ }
+ }
+
+ private void checkSpecialParams(ExecutableElement method, StarlarkMethod annot) {
+ if (!annot.extraPositionals().enableOnlyWithFlag().isEmpty()
+ || !annot.extraPositionals().disableWithFlag().isEmpty()) {
+ errorf(method, "The extraPositionals parameter may not be toggled by semantic flag");
+ }
+ if (!annot.extraKeywords().enableOnlyWithFlag().isEmpty()
+ || !annot.extraKeywords().disableWithFlag().isEmpty()) {
+ errorf(method, "The extraKeywords parameter may not be toggled by semantic flag");
+ }
+
+ List extends VariableElement> params = method.getParameters();
+ int index = annot.parameters().length;
+
+ // insufficient parameters?
+ int special = numExpectedSpecialParams(annot);
+ if (index + special > params.size()) {
+ errorf(
+ method,
+ "method %s is annotated with %d Params plus %d special parameters, but has only %d"
+ + " parameter variables",
+ method.getSimpleName(),
+ index,
+ special,
+ params.size());
+ return; // not safe to proceed
+ }
+
+ if (!annot.extraPositionals().name().isEmpty()) {
+ VariableElement param = params.get(index++);
+ // Allow any supertype of Tuple.
+ TypeMirror tupleType =
+ types.getDeclaredType(elements.getTypeElement("net.starlark.java.eval.Tuple"));
+ if (!types.isAssignable(tupleType, param.asType())) {
+ errorf(
+ param,
+ "extraPositionals special parameter '%s' has type %s, to which a Tuple cannot be"
+ + " assigned",
+ param.getSimpleName(),
+ param.asType());
+ }
+ }
+
+ if (!annot.extraKeywords().name().isEmpty()) {
+ VariableElement param = params.get(index++);
+ // Allow any supertype of Dict.
+ TypeMirror dictOfStringObjectType =
+ types.getDeclaredType(
+ elements.getTypeElement("net.starlark.java.eval.Dict"),
+ getType("java.lang.String"),
+ getType("java.lang.Object"));
+ if (!types.isAssignable(dictOfStringObjectType, param.asType())) {
+ errorf(
+ param,
+ "extraKeywords special parameter '%s' has type %s, to which Dict"
+ + " cannot be assigned",
+ param.getSimpleName(),
+ param.asType());
+ }
+ }
+
+ if (annot.useStarlarkThread()) {
+ VariableElement param = params.get(index++);
+ TypeMirror threadType = getType("net.starlark.java.eval.StarlarkThread");
+ if (!types.isSameType(threadType, param.asType())) {
+ errorf(
+ param,
+ "for useStarlarkThread special parameter '%s', got type %s, want StarlarkThread",
+ param.getSimpleName(),
+ param.asType());
+ }
+ }
+
+ if (annot.useStarlarkSemantics()) {
+ VariableElement param = params.get(index++);
+ TypeMirror semanticsType = getType("net.starlark.java.eval.StarlarkSemantics");
+ if (!types.isSameType(semanticsType, param.asType())) {
+ errorf(
+ param,
+ "for useStarlarkSemantics special parameter '%s', got type %s, want StarlarkSemantics",
+ param.getSimpleName(),
+ param.asType());
+ }
+ }
+
+ // surplus parameters?
+ if (index < params.size()) {
+ errorf(
+ params.get(index), // first surplus parameter
+ "method %s is annotated with %d Params plus %d special parameters, yet has %d parameter"
+ + " variables",
+ method.getSimpleName(),
+ annot.parameters().length,
+ special,
+ params.size());
+ }
+ }
+
+ private static int numExpectedSpecialParams(StarlarkMethod annot) {
+ int n = 0;
+ n += annot.extraPositionals().name().isEmpty() ? 0 : 1;
+ n += annot.extraKeywords().name().isEmpty() ? 0 : 1;
+ n += annot.useStarlarkThread() ? 1 : 0;
+ n += annot.useStarlarkSemantics() ? 1 : 0;
+ return n;
+ }
+
+ // Reports a (formatted) error and fails the compilation.
+ @FormatMethod
+ private void errorf(Element e, String format, Object... args) {
+ messager.printMessage(Diagnostic.Kind.ERROR, String.format(format, args), e);
+ }
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/eval/BUILD b/third_party/bazel/main/java/net/starlark/java/eval/BUILD
new file mode 100644
index 000000000..5cc552274
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/eval/BUILD
@@ -0,0 +1,116 @@
+# Description: Bazel's Starlark interpreter
+
+load("@rules_java//java:defs.bzl", "java_library")
+
+licenses(["notice"]) # Apache 2.0
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+ visibility = ["//third_party/bazel/main/java/net/starlark/java:bazel"],
+)
+
+# The Starlark evaluator
+java_library(
+ name = "eval",
+ srcs = [
+ "BuiltinFunction.java",
+ "CallUtils.java",
+ "CpuProfiler.java",
+ "Debug.java",
+ "Dict.java",
+ "Eval.java",
+ "EvalException.java",
+ "EvalUtils.java",
+ "FlagGuardedValue.java",
+ "FormatParser.java",
+ "GuardedValue.java",
+ "HasBinary.java",
+ "ImmutableSingletonStarlarkList.java",
+ "ImmutableStarlarkList.java",
+ "JNI.java",
+ "LazyImmutableStarlarkList.java",
+ "MethodDescriptor.java",
+ "MethodLibrary.java",
+ "Module.java",
+ "Mutability.java",
+ "MutableStarlarkList.java",
+ "NoneType.java",
+ "ParamDescriptor.java",
+ "Printer.java",
+ "RangeList.java",
+ "RegularImmutableStarlarkList.java",
+ "RegularTuple.java",
+ "Sequence.java",
+ "SingletonTuple.java",
+ "Starlark.java",
+ "StarlarkCallable.java",
+ "StarlarkFloat.java",
+ "StarlarkFunction.java",
+ "StarlarkIndexable.java",
+ "StarlarkInt.java",
+ "StarlarkIterable.java",
+ "StarlarkList.java",
+ "StarlarkMembershipTestable.java",
+ "StarlarkSemantics.java",
+ "StarlarkSet.java",
+ "StarlarkThread.java",
+ "StarlarkValue.java",
+ "StringModule.java",
+ "Structure.java",
+ "SymbolGenerator.java",
+ "Tuple.java",
+ ],
+ visibility = ["//third_party/bazel/main/java/net/starlark/java:clients"],
+ deps = [
+ # Do not add Bazel or Google dependencies here!
+ "//third_party/bazel/main/java/net/starlark/java/annot",
+ "//third_party/bazel/main/java/net/starlark/java/spelling",
+ "//third_party/bazel/main/java/net/starlark/java/syntax",
+ "//third_party/bazel/third_party:error_prone_annotations",
+ "//third_party/bazel/third_party:auto_value",
+ "//third_party/bazel/third_party:flogger",
+ "//third_party/bazel/third_party:guava",
+ "//third_party/bazel/third_party:jsr305",
+ ],
+)
+
+# Dynamic library for Starlark CPU profiler.
+# The application or test must arrange for the library to be added
+# to the java.library.path directory (e.g. by 'java -Djava.library.path=').
+# What a mess.
+filegroup(
+ name = "cpu_profiler",
+ srcs = select({
+ "@platforms//os:macos": [":libcpu_profiler.dylib"],
+ "@platforms//os:windows": [":cpu_profiler.dll"],
+ "//conditions:default": [":libcpu_profiler.so"], # POSIX
+ }),
+ visibility = ["//third_party/bazel/main/java/net/starlark/java:clients"],
+)
+
+genrule(
+ name = "cpu_profiler_darwin",
+ srcs = ["libcpu_profiler.so"],
+ outs = ["libcpu_profiler.dylib"],
+ cmd = "cp $< $@",
+)
+
+genrule(
+ name = "cpu_profiler_windows",
+ srcs = ["libcpu_profiler.so"],
+ outs = ["cpu_profiler.dll"],
+ cmd = "cp $< $@",
+)
+
+# The C++ portion of the Starlark CPU profiler.
+cc_binary(
+ name = "libcpu_profiler.so",
+ srcs = select({
+ "@platforms//os:macos": ["cpu_profiler_posix.cc"],
+ "@platforms//os:linux": ["cpu_profiler_posix.cc"],
+ "//conditions:default": ["cpu_profiler_unimpl.cc"],
+ }),
+ linkshared = 1,
+ deps = ["@bazel_tools//tools/jdk:jni"],
+)
diff --git a/third_party/bazel/main/java/net/starlark/java/eval/BuiltinFunction.java b/third_party/bazel/main/java/net/starlark/java/eval/BuiltinFunction.java
new file mode 100644
index 000000000..c7c4b5eff
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/eval/BuiltinFunction.java
@@ -0,0 +1,395 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package net.starlark.java.eval;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import javax.annotation.Nullable;
+import net.starlark.java.annot.StarlarkBuiltin;
+import net.starlark.java.annot.StarlarkMethod;
+import net.starlark.java.spelling.SpellChecker;
+
+/**
+ * A BuiltinFunction is a callable Starlark value that reflectively invokes a {@link
+ * StarlarkMethod}-annotated method of a Java object. The Java object may or may not itself be a
+ * Starlark value. BuiltinFunctions are not produced for Java methods for which {@link
+ * StarlarkMethod#structField} is true.
+ */
+// TODO(adonovan): support annotated static methods.
+@StarlarkBuiltin(
+ name = "builtin_function_or_method", // (following Python)
+ category = "core",
+ doc = "The type of a built-in function, defined by Java code.")
+public final class BuiltinFunction implements StarlarkCallable {
+
+ private final Object obj;
+ private final String methodName;
+ @Nullable private final MethodDescriptor desc;
+
+ /**
+ * Constructs a BuiltinFunction for a StarlarkMethod-annotated method of the given name (as seen
+ * by Starlark, not Java).
+ */
+ BuiltinFunction(Object obj, String methodName) {
+ this.obj = obj;
+ this.methodName = methodName;
+ this.desc = null; // computed later
+ }
+
+ /**
+ * Constructs a BuiltinFunction for a StarlarkMethod-annotated method (not field) of the given
+ * name (as seen by Starlark, not Java).
+ *
+ *
This constructor should be used only for ephemeral BuiltinFunction values created
+ * transiently during a call such as {@code x.f()}, when the caller has already looked up the
+ * MethodDescriptor using the same semantics as the thread that will be used in the call. Use the
+ * other (slower) constructor if there is any possibility that the semantics of the {@code x.f}
+ * operation differ from those of the thread used in the call.
+ */
+ BuiltinFunction(Object obj, String methodName, MethodDescriptor desc) {
+ Preconditions.checkArgument(!desc.isStructField());
+ this.obj = obj;
+ this.methodName = methodName;
+ this.desc = desc;
+ }
+
+ @Override
+ public Object fastcall(StarlarkThread thread, Object[] positional, Object[] named)
+ throws EvalException, InterruptedException {
+ MethodDescriptor desc = getMethodDescriptor(thread.getSemantics());
+ Object[] vector = getArgumentVector(thread, desc, positional, named);
+ return desc.call(
+ obj instanceof String ? StringModule.INSTANCE : obj, vector, thread.mutability());
+ }
+
+ private MethodDescriptor getMethodDescriptor(StarlarkSemantics semantics) {
+ MethodDescriptor desc = this.desc;
+ if (desc == null) {
+ desc = CallUtils.getAnnotatedMethods(semantics, obj.getClass()).get(methodName);
+ Preconditions.checkArgument(
+ !desc.isStructField(),
+ "BuiltinFunction constructed for MethodDescriptor(structField=True)");
+ }
+ return desc;
+ }
+
+ /**
+ * Returns the StarlarkMethod annotation of this Starlark-callable Java method.
+ */
+ public StarlarkMethod getAnnotation() {
+ return getMethodDescriptor(StarlarkSemantics.DEFAULT).getAnnotation();
+ }
+
+ @Override
+ public String getName() {
+ return methodName;
+ }
+
+ @Override
+ public void repr(Printer printer) {
+ if (obj instanceof StarlarkValue || obj instanceof String) {
+ printer
+ .append("");
+ } else {
+ printer.append("");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return methodName;
+ }
+
+ /**
+ * Converts the arguments of a Starlark call into the argument vector for a reflective call to a
+ * StarlarkMethod-annotated Java method.
+ *
+ * @param thread the Starlark thread for the call
+ * @param loc the location of the call expression, or BUILTIN for calls from Java
+ * @param desc descriptor for the StarlarkMethod-annotated method
+ * @param positional an array of positional arguments; as an optimization, in simple cases, this
+ * array may be reused as the method's return value
+ * @param named a list of named arguments, as alternating Strings/Objects. May contain dups.
+ * @return the array of arguments which may be passed to {@link MethodDescriptor#call}. It is
+ * unsafe to mutate the returned array.
+ * @throws EvalException if the given set of arguments are invalid for the given method. For
+ * example, if any arguments are of unexpected type, or not all mandatory parameters are
+ * specified by the user
+ */
+ private Object[] getArgumentVector(
+ StarlarkThread thread,
+ MethodDescriptor desc, // intentionally shadows this.desc
+ Object[] positional,
+ Object[] named)
+ throws EvalException {
+
+ // Overview of steps:
+ // - allocate vector of actual arguments of correct size.
+ // - process positional arguments, accumulating surplus ones into *args.
+ // - process named arguments, accumulating surplus ones into **kwargs.
+ // - set default values for missing optionals, and report missing mandatory parameters.
+ // - set special parameters.
+ // The static checks ensure that positional parameters appear before named,
+ // and mandatory positionals appear before optional.
+ // No additional memory allocation occurs in the common (success) case.
+ // Flag-disabled parameters are skipped during argument matching, as if they do not exist. They
+ // are instead assigned their flag-disabled values.
+
+ ParamDescriptor[] parameters = desc.getParameters();
+
+ // Fast case: we only accept positionals and can reuse `positional` as the Java args vector.
+ // Note that StringModule methods, which are treated specially below, will never match this case
+ // since their `self` parameter is restricted to String and thus
+ // isPositionalsReusableAsCallArgsVectorIfArgumentCountValid() would be false.
+ if (desc.isPositionalsReusableAsJavaArgsVectorIfArgumentCountValid()
+ && positional.length == parameters.length
+ && named.length == 0) {
+ return positional;
+ }
+
+ // Allocate argument vector.
+ int n = parameters.length;
+ if (desc.acceptsExtraArgs()) {
+ n++;
+ }
+ if (desc.acceptsExtraKwargs()) {
+ n++;
+ }
+ if (desc.isUseStarlarkThread()) {
+ n++;
+ }
+ Object[] vector = new Object[n];
+
+ // positional arguments
+ int paramIndex = 0;
+ int argIndex = 0;
+ if (obj instanceof String) {
+ // String methods get the string as an extra argument
+ // because their true receiver is StringModule.INSTANCE.
+ vector[paramIndex++] = obj;
+ }
+ for (; argIndex < positional.length && paramIndex < parameters.length; paramIndex++) {
+ ParamDescriptor param = parameters[paramIndex];
+ if (!param.isPositional()) {
+ break;
+ }
+
+ // disabled?
+ if (param.disabledByFlag() != null) {
+ // Skip disabled parameter as if not present at all.
+ // The default value will be filled in below.
+ continue;
+ }
+
+ Object value = positional[argIndex++];
+ checkParamValue(param, value);
+ vector[paramIndex] = value;
+ }
+
+ // *args
+ Tuple varargs = null;
+ if (desc.acceptsExtraArgs()) {
+ varargs = Tuple.wrap(Arrays.copyOfRange(positional, argIndex, positional.length));
+ } else if (argIndex < positional.length) {
+ if (argIndex == 0) {
+ throw Starlark.errorf("%s() got unexpected positional argument", methodName);
+ } else {
+ throw Starlark.errorf(
+ "%s() accepts no more than %d positional argument%s but got %d",
+ methodName, argIndex, plural(argIndex), positional.length);
+ }
+ }
+
+ // named arguments
+ LinkedHashMap kwargs =
+ desc.acceptsExtraKwargs() ? Maps.newLinkedHashMapWithExpectedSize(1) : null;
+ for (int i = 0; i < named.length; i += 2) {
+ String name = (String) named[i]; // safe
+ Object value = named[i + 1];
+
+ // look up parameter
+ int index = desc.getParameterIndex(name);
+ // unknown parameter?
+ if (index < 0) {
+ // spill to **kwargs
+ if (kwargs == null) {
+ List allNames =
+ Arrays.stream(parameters)
+ .map(ParamDescriptor::getName)
+ .collect(ImmutableList.toImmutableList());
+ throw Starlark.errorf(
+ "%s() got unexpected keyword argument '%s'%s",
+ methodName, name, SpellChecker.didYouMean(name, allNames));
+ }
+
+ // duplicate named argument?
+ if (kwargs.put(name, value) != null) {
+ throw Starlark.errorf(
+ "%s() got multiple values for keyword argument '%s'", methodName, name);
+ }
+ continue;
+ }
+ ParamDescriptor param = parameters[index];
+
+ // positional-only param?
+ if (!param.isNamed()) {
+ // spill to **kwargs
+ if (kwargs == null) {
+ throw Starlark.errorf(
+ "%s() got named argument for positional-only parameter '%s'", methodName, name);
+ }
+
+ // duplicate named argument?
+ if (kwargs.put(name, value) != null) {
+ throw Starlark.errorf(
+ "%s() got multiple values for keyword argument '%s'", methodName, name);
+ }
+ continue;
+ }
+
+ // disabled?
+ String flag = param.disabledByFlag();
+ if (flag != null) {
+ // spill to **kwargs
+ if (kwargs == null) {
+ throw Starlark.errorf(
+ "in call to %s(), parameter '%s' is %s",
+ methodName, param.getName(), disabled(flag, thread.getSemantics()));
+ }
+
+ // duplicate named argument?
+ if (kwargs.put(name, value) != null) {
+ throw Starlark.errorf(
+ "%s() got multiple values for keyword argument '%s'", methodName, name);
+ }
+ continue;
+ }
+
+ checkParamValue(param, value);
+
+ // duplicate?
+ if (vector[index] != null) {
+ throw Starlark.errorf("%s() got multiple values for argument '%s'", methodName, name);
+ }
+
+ vector[index] = value;
+ }
+
+ // Set default values for missing parameters,
+ // and report any that are still missing.
+ List missingPositional = null;
+ List missingNamed = null;
+ for (int i = 0; i < parameters.length; i++) {
+ if (vector[i] == null) {
+ ParamDescriptor param = parameters[i];
+ vector[i] = param.getDefaultValue();
+ if (vector[i] == null) {
+ if (param.isPositional()) {
+ if (missingPositional == null) {
+ missingPositional = new ArrayList<>();
+ }
+ missingPositional.add(param.getName());
+ } else {
+ if (missingNamed == null) {
+ missingNamed = new ArrayList<>();
+ }
+ missingNamed.add(param.getName());
+ }
+ }
+ }
+ }
+ if (missingPositional != null) {
+ throw Starlark.errorf(
+ "%s() missing %d required positional argument%s: %s",
+ methodName,
+ missingPositional.size(),
+ plural(missingPositional.size()),
+ Joiner.on(", ").join(missingPositional));
+ }
+ if (missingNamed != null) {
+ throw Starlark.errorf(
+ "%s() missing %d required named argument%s: %s",
+ methodName,
+ missingNamed.size(),
+ plural(missingNamed.size()),
+ Joiner.on(", ").join(missingNamed));
+ }
+
+ // special parameters
+ int i = parameters.length;
+ if (desc.acceptsExtraArgs()) {
+ vector[i++] = varargs;
+ }
+ if (desc.acceptsExtraKwargs()) {
+ vector[i++] = Dict.wrap(thread.mutability(), kwargs);
+ }
+ if (desc.isUseStarlarkThread()) {
+ vector[i++] = thread;
+ }
+
+ return vector;
+ }
+
+ private static String plural(int n) {
+ return n == 1 ? "" : "s";
+ }
+
+ private void checkParamValue(ParamDescriptor param, Object value) throws EvalException {
+ List> allowedClasses = param.getAllowedClasses();
+ if (allowedClasses == null) {
+ return;
+ }
+
+ // Value must belong to one of the specified classes.
+ boolean ok = false;
+ for (Class> cls : allowedClasses) {
+ if (cls.isInstance(value)) {
+ ok = true;
+ break;
+ }
+ }
+ if (!ok) {
+ throw Starlark.errorf(
+ "in call to %s(), parameter '%s' got value of type '%s', want '%s'",
+ methodName, param.getName(), Starlark.type(value), param.getTypeErrorMessage());
+ }
+ }
+
+ // Returns a phrase meaning "disabled" appropriate to the specified flag.
+ private static String disabled(String flag, StarlarkSemantics semantics) {
+ // If the flag is True, it must be a deprecation flag. Otherwise it's an experimental flag.
+ // TODO(adonovan): is that assumption sound?
+ if (semantics.getBool(flag)) {
+ return String.format(
+ "deprecated and will be removed soon. It may be temporarily re-enabled by setting"
+ + " --%s=false",
+ flag.substring(1)); // remove [+-] prefix
+ } else {
+ return String.format(
+ "experimental and thus unavailable with the current flags. It may be enabled by setting"
+ + " --%s",
+ flag.substring(1)); // remove [+-] prefix
+ }
+ }
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/eval/CallUtils.java b/third_party/bazel/main/java/net/starlark/java/eval/CallUtils.java
new file mode 100644
index 000000000..1a8805f31
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/eval/CallUtils.java
@@ -0,0 +1,234 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.starlark.java.eval;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.Nullable;
+import net.starlark.java.annot.StarlarkAnnotations;
+import net.starlark.java.annot.StarlarkMethod;
+
+/** Helper functions for {@link StarlarkMethod}-annotated fields and methods. */
+final class CallUtils {
+
+ private CallUtils() {} // uninstantiable
+
+ /**
+ * Returns the {@link StarlarkClassDescriptor} for the given {@link StarlarkSemantics} and {@link
+ * Class}.
+ *
+ *
This method is a hotspot! It's called on every function call and field access. A single
+ * `bazel build` invocation can make tens or even hundreds of millions of calls to this method.
+ */
+ private static StarlarkClassDescriptor getStarlarkClassDescriptor(
+ StarlarkSemantics semantics, Class> clazz) {
+ if (clazz == String.class) {
+ clazz = StringModule.class;
+ }
+
+ // We use two layers of caches, with the first layer being keyed by StarlarkSemantics and the
+ // second layer being keyed by Class. This optimizes for the common case of very few different
+ // StarlarkSemantics instances (typically, one) being in play. In contrast, if we used a single
+ // cache data structure then we'd need to use a dedicated tuple object for the keys of that data
+ // structure, and the GC churn and method call overhead become meaningful at scale.
+ //
+ // We implement each cache ourselves using CHM#get and CHM#putIfAbsent. We don't use
+ // CHM#computeIfAbsent since it is not reentrant: If #getStarlarkClassDescriptor is called
+ // before Starlark.UNIVERSE is initialized then the computation will re-enter the cache and have
+ // a cycle; see b/161479826 for history.
+ // TODO(bazel-team): Maybe the above cycle concern doesn't exist now that CallUtils is private.
+ ConcurrentHashMap, StarlarkClassDescriptor> starlarkClassDescriptorCache =
+ starlarkClassDescriptorCachesBySemantics.get(
+ semantics.getStarlarkClassDescriptorCacheKey());
+ if (starlarkClassDescriptorCache == null) {
+ starlarkClassDescriptorCache =
+ new ConcurrentHashMap<>(
+ // In May 2023, typical Bazel usage results in ~150 entries in this cache. Therefore
+ // we presize the CHM accordingly to reduce the chance two entries use the same hash
+ // bucket (in May 2023 this strategy was completely effective!). We used to use the
+ // default capacity, and then the CHM would get dynamically resized to have 256
+ // buckets, many of which had at least 2 entries which is suboptimal for such a hot
+ // data structure.
+ // TODO(bazel-team): Better would be to precompute the entire lookup table on server
+ // startup (best would be to do this at compile time via an annotation processor),
+ // rather than rely on it getting built-up dynamically as Starlark code gets
+ // evaluated over the lifetime of the server. This way there are no concurrency
+ // concerns, so we can use a more efficient data structure that doesn't need to
+ // handle concurrent writes.
+ /* initialCapacity= */ 1000);
+ ConcurrentHashMap, StarlarkClassDescriptor> prev =
+ starlarkClassDescriptorCachesBySemantics.putIfAbsent(
+ semantics, starlarkClassDescriptorCache);
+ if (prev != null) {
+ starlarkClassDescriptorCache = prev; // first thread wins
+ }
+ }
+
+ StarlarkClassDescriptor starlarkClassDescriptor = starlarkClassDescriptorCache.get(clazz);
+ if (starlarkClassDescriptor == null) {
+ starlarkClassDescriptor = buildStarlarkClassDescriptor(semantics, clazz);
+ StarlarkClassDescriptor prev =
+ starlarkClassDescriptorCache.putIfAbsent(clazz, starlarkClassDescriptor);
+ if (prev != null) {
+ starlarkClassDescriptor = prev; // first thread wins
+ }
+ }
+ return starlarkClassDescriptor;
+ }
+
+ /**
+ * Information derived from a {@link Class} (that has methods annotated with {@link
+ * StarlarkMethod}) based on a {@link StarlarkSemantics}.
+ */
+ private static class StarlarkClassDescriptor {
+ @Nullable MethodDescriptor selfCall;
+
+ /**
+ * All {@link StarlarkMethod}-annotated Java methods, sans ones where {@code selfCall() ==
+ * true}, sorted by Java method name.
+ */
+ ImmutableMap methods;
+ /**
+ * Submap of {@link #methods} for which {@code structField() == true}, sorted by Java method
+ * name.
+ */
+ ImmutableMap fields;
+ }
+
+ /**
+ * Two-layer cache of {@link #buildStarlarkClassDescriptor}, managed by {@link
+ * #getStarlarkClassDescriptor}.
+ */
+ private static final ConcurrentHashMap<
+ StarlarkSemantics, ConcurrentHashMap, StarlarkClassDescriptor>>
+ starlarkClassDescriptorCachesBySemantics = new ConcurrentHashMap<>();
+
+ private static StarlarkClassDescriptor buildStarlarkClassDescriptor(
+ StarlarkSemantics semantics, Class> clazz) {
+ MethodDescriptor selfCall = null;
+ ImmutableMap.Builder methods = ImmutableMap.builder();
+ Map fields = new HashMap<>();
+
+ // Sort methods by Java name, for determinism.
+ Method[] classMethods = clazz.getMethods();
+ Arrays.sort(classMethods, Comparator.comparing(Method::getName));
+ for (Method method : classMethods) {
+ // Synthetic methods lead to false multiple matches
+ if (method.isSynthetic()) {
+ continue;
+ }
+
+ // annotated?
+ StarlarkMethod callable = StarlarkAnnotations.getStarlarkMethod(method);
+ if (callable == null) {
+ continue;
+ }
+
+ // enabled by semantics?
+ if (!semantics.isFeatureEnabledBasedOnTogglingFlags(
+ callable.enableOnlyWithFlag(), callable.disableWithFlag())) {
+ continue;
+ }
+
+ MethodDescriptor descriptor = MethodDescriptor.of(method, callable, semantics);
+
+ // self-call method?
+ if (callable.selfCall()) {
+ if (selfCall != null) {
+ throw new IllegalArgumentException(
+ String.format("Class %s has two selfCall methods defined", clazz.getName()));
+ }
+ selfCall = descriptor;
+ continue;
+ }
+
+ // regular method
+ methods.put(callable.name(), descriptor);
+
+ // field method?
+ if (descriptor.isStructField() && fields.put(callable.name(), descriptor) != null) {
+ // TODO(b/72113542): Validate with annotation processor instead of at runtime.
+ throw new IllegalArgumentException(
+ String.format(
+ "Class %s declares two structField methods named %s",
+ clazz.getName(), callable.name()));
+ }
+ }
+
+ StarlarkClassDescriptor starlarkClassDescriptor = new StarlarkClassDescriptor();
+ starlarkClassDescriptor.selfCall = selfCall;
+ starlarkClassDescriptor.methods = methods.buildOrThrow();
+ starlarkClassDescriptor.fields = ImmutableMap.copyOf(fields);
+ return starlarkClassDescriptor;
+ }
+
+ /**
+ * Returns the set of all StarlarkMethod-annotated Java methods (excluding the self-call method)
+ * of the specified class.
+ */
+ static ImmutableMap getAnnotatedMethods(
+ StarlarkSemantics semantics, Class> objClass) {
+ return getStarlarkClassDescriptor(semantics, objClass).methods;
+ }
+
+ /**
+ * Returns the value of the Starlark field of {@code x}, implemented by a Java method with a
+ * {@code StarlarkMethod(structField=true)} annotation.
+ */
+ static Object getAnnotatedField(StarlarkSemantics semantics, Object x, String fieldName)
+ throws EvalException, InterruptedException {
+ MethodDescriptor desc =
+ getStarlarkClassDescriptor(semantics, x.getClass()).fields.get(fieldName);
+ if (desc == null) {
+ throw Starlark.errorf("value of type %s has no .%s field", Starlark.type(x), fieldName);
+ }
+ return desc.callField(x, semantics, /*mu=*/ null);
+ }
+
+ /** Returns the names of the Starlark fields of {@code x} under the specified semantics. */
+ static ImmutableSet getAnnotatedFieldNames(StarlarkSemantics semantics, Object x) {
+ return getStarlarkClassDescriptor(semantics, x.getClass()).fields.keySet();
+ }
+
+ /**
+ * Returns a {@link MethodDescriptor} object representing a function which calls the selfCall java
+ * method of the given object (the {@link StarlarkMethod} method with {@link
+ * StarlarkMethod#selfCall()} set to true). Returns null if no such method exists.
+ */
+ @Nullable
+ static MethodDescriptor getSelfCallMethodDescriptor(
+ StarlarkSemantics semantics, Class> objClass) {
+ return getStarlarkClassDescriptor(semantics, objClass).selfCall;
+ }
+
+ /**
+ * Returns a {@code selfCall=true} method for the given class under the given Starlark semantics,
+ * or null if no such method exists.
+ */
+ @Nullable
+ static Method getSelfCallMethod(StarlarkSemantics semantics, Class> objClass) {
+ MethodDescriptor descriptor = getStarlarkClassDescriptor(semantics, objClass).selfCall;
+ if (descriptor == null) {
+ return null;
+ }
+ return descriptor.getMethod();
+ }
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/eval/CpuProfiler.java b/third_party/bazel/main/java/net/starlark/java/eval/CpuProfiler.java
new file mode 100644
index 000000000..acdf80098
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/eval/CpuProfiler.java
@@ -0,0 +1,471 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.starlark.java.eval;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.GoogleLogger;
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.zip.GZIPOutputStream;
+import javax.annotation.Nullable;
+import net.starlark.java.syntax.Location;
+
+// Overview
+//
+// A CPU profiler measures CPU cycles consumed by each thread.
+// It does not account for time a thread is blocked in I/O
+// (e.g. within a call to glob), or runnable but not actually
+// running, as happens when there are more runnable threads than cores.
+//
+// CPU profiling requires operating system support.
+// On POSIX systems, the setitimer system call causes
+// the kernel to signal an application periodically.
+// With the ITIMER_PROF option, setitimer delivers a
+// SIGPROF signal to a running thread each time its CPU usage
+// exceeds the specific quantum. A profiler builds a histogram
+// of these these signals, grouped by the current program
+// counter location, or more usefully by the complete stack of
+// program counter locations.
+//
+// This profiler calls a C++ function to install a SIGPROF handler.
+// Like all handlers for asynchronous signals (that is, signals not
+// caused by the execution of program instructions), it is extremely
+// constrained in what it may do. It cannot acquire locks, allocate
+// memory, or interact with the JVM in any way. Our signal handler
+// simply sends a message into a global pipe; the message records
+// the operating system's identifier (tid) for the signalled thread.
+//
+// Reading from the other end of the pipe is a Java thread, the router.
+// Its job is to map each OS tid to a StarlarkThread, if the
+// thread is currently executing Starlark code, and increment
+// a volatile counter in that StarlarkThread. If the thread is
+// not executing Starlark code, the router discards the event.
+// When a Starlark thread enters or leaves a function during profiling,
+// it updates the StarlarkThread-to-OS-thread mapping consulted by the
+// router.
+//
+// If the router does not drain the pipe in a timely manner (on the
+// order of 10s; see signal handler), the signal handler prints a
+// warning and discards the event.
+//
+// The router may induce a delay between the kernel signal and the
+// thread's stack sampling, during which Starlark execution may have
+// moved on to another function. Assuming uniform delay, this is
+// equivalent to shifting the phase but not the frequency of CPU ticks.
+// Nonetheless it may bias the profile because, for example,
+// it would cause a Starlark 'sleep' function to accrue a nonzero
+// number of CPU ticks that properly belong to the preceding computation.
+//
+// When a Starlark thread leaves any function, it reads and clears
+// its counter of CPU ticks. If the counter was nonzero, the thread
+// writes a copy of its stack to the profiler log in pprof form,
+// which is a gzip-compressed stream of protocol messages.
+//
+// The profiler is inherently global to the process,
+// and records the effects of all Starlark threads.
+// It may be started and stopped concurrent with Starlark execution,
+// allowing profiling of a portion of a long-running computation.
+
+/** A CPU profiler for Starlark (POSIX only for now). */
+final class CpuProfiler {
+
+ static {
+ JNI.load();
+ }
+
+ private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
+
+ private final PprofWriter pprof;
+
+ private CpuProfiler(OutputStream out, Duration period) {
+ this.pprof = new PprofWriter(out, period);
+ }
+
+ // The active profiler, if any.
+ @Nullable private static volatile CpuProfiler instance;
+
+ /** Returns the active profiler, or null if inactive. */
+ @Nullable
+ static CpuProfiler get() {
+ return instance;
+ }
+
+ // Maps OS thread ID to StarlarkThread.
+ // The StarlarkThread is needed only for its cpuTicks field.
+ private static final Map threads = new ConcurrentHashMap<>();
+
+ /**
+ * Associates the specified StarlarkThread with the current OS thread. Returns the StarlarkThread
+ * previously associated with it, if any.
+ */
+ @Nullable
+ static StarlarkThread setStarlarkThread(StarlarkThread thread) {
+ if (thread == null) {
+ return threads.remove(gettid());
+ } else {
+ return threads.put(gettid(), thread);
+ }
+ }
+
+ /** Start the profiler. */
+ static boolean start(OutputStream out, Duration period) {
+ if (!supported()) {
+ logger.atWarning().log("--starlark_cpu_profile is unsupported on this platform");
+ return false;
+ }
+ if (instance != null) {
+ throw new IllegalStateException("profiler started twice without intervening stop");
+ }
+
+ startRouter();
+ if (!startTimer(period.toNanos() / 1000L)) {
+ throw new IllegalStateException("profile signal handler already in use");
+ }
+
+ instance = new CpuProfiler(out, period);
+ return true;
+ }
+
+ /** Stop the profiler and wait for the log to be written. */
+ static void stop() throws IOException {
+ if (instance == null) {
+ throw new IllegalStateException("stop without start");
+ }
+
+ CpuProfiler profiler = instance;
+ instance = null;
+
+ stopTimer();
+
+ // Finish writing the file and fail if there were any I/O errors.
+ profiler.pprof.writeEnd();
+ }
+
+ /** Records a profile event. */
+ void addEvent(int ticks, ImmutableList stack) {
+ pprof.writeEvent(ticks, stack);
+ }
+
+ // ---- signal router ----
+
+ private static FileInputStream pipe;
+
+ // Starts the routing thread if not already started (idempotent).
+ // On return, it is safe to install the signal handler.
+ private static synchronized void startRouter() {
+ if (pipe == null) {
+ pipe = new FileInputStream(createPipe());
+ Thread router = new Thread(CpuProfiler::router, "SIGPROF router");
+ router.setDaemon(true);
+ router.start();
+ }
+ }
+
+ // The Router thread routes SIGPROF events (from the pipe)
+ // to the relevant StarlarkThread. Once started, it runs forever.
+ //
+ // TODO(adonovan): opt: a more efficient implementation of routing would be
+ // to use, instead of a pipe from the signal handler to the routing thread,
+ // a mapping, maintained in C++, from OS thread ID to cpuTicks pointer.
+ // The {add,remove}Thread operations would update this mapping,
+ // and the signal handler would read it. The mapping would have to
+ // be a lock-free hash table so that it can be safely read in an
+ // async signal handler. The pointer would point to the sole element
+ // of direct memory buffer belonging to the StarlarkThread, allocated
+ // by JNI NewDirectByteBuffer.
+ // In this way, the signal handler could update the StarlarkThread directly,
+ // saving 100 write+read calls per second per core.
+ //
+ private static void router() {
+ byte[] buf = new byte[4];
+ while (true) {
+ try {
+ int n = pipe.read(buf);
+ if (n < 0) {
+ throw new IllegalStateException("pipe closed");
+ }
+ if (n != 4) {
+ throw new IllegalStateException("short read");
+ }
+ } catch (IOException ex) {
+ throw new IllegalStateException("unexpected I/O error", ex);
+ }
+
+ int tid = int32be(buf);
+
+ // Record a CPU tick against tid.
+ //
+ // It's not safe to grab the thread's stack here because the thread
+ // may be changing it, so we increment the thread's counter.
+ // When the thread later observes the counter is non-zero,
+ // it gives us the stack by calling addEvent.
+ StarlarkThread thread = threads.get(tid);
+ if (thread != null) {
+ thread.cpuTicks.getAndIncrement();
+ }
+ }
+ }
+
+ // Decodes a signed 32-bit big-endian integer from b[0:4].
+ private static int int32be(byte[] b) {
+ return b[0] << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff);
+ }
+
+ // --- native code (see cpu_profiler) ---
+
+ // Reports whether the profiler is supported on this platform.
+ private static native boolean supported();
+
+ // Returns the read end of a pipe from which profile events may be read.
+ // Each event is an operating system thread ID encoded as uint32be.
+ private static native FileDescriptor createPipe();
+
+ // Starts the operating system's interval timer.
+ // The period must be a positive number of microseconds.
+ // Returns false if SIGPROF is already in use.
+ private static native boolean startTimer(long periodMicros);
+
+ // Stops the operating system's interval timer.
+ private static native void stopTimer();
+
+ // Returns the operating system's identifier for the calling thread.
+ private static native int gettid();
+
+ // Encoder for pprof format profiles.
+ // See https://github.com/google/pprof/tree/master/proto
+ // We encode the protocol messages by hand to avoid
+ // adding a dependency on the protocol compiler.
+ private static final class PprofWriter {
+
+ private final Duration period;
+ private final long startNano;
+ private GZIPOutputStream gz;
+ private IOException error; // the first write error, if any; reported during stop()
+
+ PprofWriter(OutputStream out, Duration period) {
+ this.period = period;
+ this.startNano = System.nanoTime();
+
+ try {
+ this.gz = new GZIPOutputStream(out);
+ getStringID(""); // entry 0 is always ""
+
+ // dimension and unit
+ ByteArrayOutputStream unit = new ByteArrayOutputStream();
+ writeLong(unit, VALUETYPE_TYPE, getStringID("CPU"));
+ writeLong(unit, VALUETYPE_UNIT, getStringID("microseconds"));
+
+ // informational fields of Profile
+ writeByteArray(gz, PROFILE_SAMPLE_TYPE, unit.toByteArray());
+ // magnitude of sampling period:
+ writeLong(gz, PROFILE_PERIOD, period.toNanos() / 1000L);
+ // dimension and unit of period:
+ writeByteArray(gz, PROFILE_PERIOD_TYPE, unit.toByteArray());
+ // start (real) time of profile:
+ writeLong(gz, PROFILE_TIME_NANOS, System.currentTimeMillis() * 1000000L);
+ } catch (IOException ex) {
+ this.error = ex;
+ }
+ }
+
+ synchronized void writeEvent(int ticks, ImmutableList stack) {
+ if (this.error == null) {
+ try {
+ ByteArrayOutputStream sample = new ByteArrayOutputStream();
+ writeLong(sample, SAMPLE_VALUE, ticks * period.toNanos() / 1000L);
+ for (Debug.Frame fr : stack.reverse()) {
+ writeLong(sample, SAMPLE_LOCATION_ID, getLocationID(fr));
+ }
+ writeByteArray(gz, PROFILE_SAMPLE, sample.toByteArray());
+ } catch (IOException ex) {
+ this.error = ex;
+ }
+ }
+ }
+
+ synchronized void writeEnd() throws IOException {
+ long endNano = System.nanoTime();
+ try {
+ writeLong(gz, PROFILE_DURATION_NANOS, endNano - startNano);
+ if (this.error != null) {
+ throw this.error; // retained from an earlier error
+ }
+ } finally {
+ gz.close();
+ }
+ }
+
+ // Protocol encoding helpers; see https://developers.google.com/protocol-buffers/docs/encoding.
+ // (Copied to avoid a dependency on the corresponding methods of protobuf.CodedOutputStream.)
+
+ private static void writeLong(OutputStream out, int fieldNumber, long x) throws IOException {
+ writeVarint(out, (fieldNumber << 3) | 0); // wire type 0 = varint
+ writeVarint(out, x);
+ }
+
+ private static void writeString(OutputStream out, int fieldNumber, String x)
+ throws IOException {
+ writeByteArray(out, fieldNumber, x.getBytes(UTF_8));
+ }
+
+ private static void writeByteArray(OutputStream out, int fieldNumber, byte[] x)
+ throws IOException {
+ writeVarint(out, (fieldNumber << 3) | 2); // wire type 2 = length-delimited
+ writeVarint(out, x.length);
+ out.write(x);
+ }
+
+ private static void writeVarint(OutputStream out, long value) throws IOException {
+ for (; (value & ~0x7f) != 0; value >>>= 7) {
+ out.write((byte) ((value & 0x7f) | 0x80));
+ }
+ out.write((byte) value);
+ }
+
+ // Field numbers from pprof protocol.
+ // See https://github.com/google/pprof/blob/master/proto/profile.proto
+ private static final int PROFILE_SAMPLE_TYPE = 1; // repeated ValueType
+ private static final int PROFILE_SAMPLE = 2; // repeated Sample
+ private static final int PROFILE_MAPPING = 3; // repeated Mapping
+ private static final int PROFILE_LOCATION = 4; // repeated Location
+ private static final int PROFILE_FUNCTION = 5; // repeated Function
+ private static final int PROFILE_STRING_TABLE = 6; // repeated string
+ private static final int PROFILE_TIME_NANOS = 9; // int64
+ private static final int PROFILE_DURATION_NANOS = 10; // int64
+ private static final int PROFILE_PERIOD_TYPE = 11; // ValueType
+ private static final int PROFILE_PERIOD = 12; // int64
+ private static final int VALUETYPE_TYPE = 1; // int64
+ private static final int VALUETYPE_UNIT = 2; // int64
+ private static final int SAMPLE_LOCATION_ID = 1; // repeated uint64
+ private static final int SAMPLE_VALUE = 2; // repeated int64
+ private static final int SAMPLE_LABEL = 3; // repeated Label
+ private static final int LABEL_KEY = 1; // int64
+ private static final int LABEL_STR = 2; // int64
+ private static final int LABEL_NUM = 3; // int64
+ private static final int LABEL_NUM_UNIT = 4; // int64
+ private static final int LOCATION_ID = 1; // uint64
+ private static final int LOCATION_MAPPING_ID = 2; // uint64
+ private static final int LOCATION_ADDRESS = 3; // uint64
+ private static final int LOCATION_LINE = 4; // repeated Line
+ private static final int LINE_FUNCTION_ID = 1; // uint64
+ private static final int LINE_LINE = 2; // int64
+ private static final int FUNCTION_ID = 1; // uint64
+ private static final int FUNCTION_NAME = 2; // int64
+ private static final int FUNCTION_SYSTEM_NAME = 3; // int64
+ private static final int FUNCTION_FILENAME = 4; // int64
+ private static final int FUNCTION_START_LINE = 5; // int64
+
+ // Every string, function, and PC location is emitted once
+ // and thereafter referred to by its identifier, a Long.
+ private final Map stringIDs = new HashMap<>();
+ private final Map functionIDs = new HashMap<>(); // key is "address" of function
+ private final Map locationIDs = new HashMap<>(); // key is "address" of PC location
+
+ // Returns the ID of the specified string,
+ // emitting a pprof string record the first time it is encountered.
+ private long getStringID(String s) throws IOException {
+ Long i = stringIDs.putIfAbsent(s, Long.valueOf(stringIDs.size()));
+ if (i == null) {
+ writeString(gz, PROFILE_STRING_TABLE, s);
+ return stringIDs.size() - 1L;
+ }
+ return i;
+ }
+
+ // Returns the ID of a StarlarkCallable for use in Line.FunctionId,
+ // emitting a pprof Function record the first time fn is encountered.
+ // The ID is the same as the function's logical address,
+ // which is supplied by the caller to avoid the need to recompute it.
+ private long getFunctionID(StarlarkCallable fn, long addr) throws IOException {
+ Long id = functionIDs.get(addr);
+ if (id == null) {
+ id = addr;
+
+ Location loc = fn.getLocation();
+ String filename = loc.file(); // TODO(adonovan): make WORKSPACE-relative
+ String name = fn.getName();
+ if (name.equals(StarlarkThread.TOP_LEVEL)) {
+ name = filename;
+ }
+
+ long nameID = getStringID(name);
+
+ ByteArrayOutputStream fun = new ByteArrayOutputStream();
+ writeLong(fun, FUNCTION_ID, id);
+ writeLong(fun, FUNCTION_NAME, nameID);
+ writeLong(fun, FUNCTION_SYSTEM_NAME, nameID);
+ writeLong(fun, FUNCTION_FILENAME, getStringID(filename));
+ writeLong(fun, FUNCTION_START_LINE, (long) loc.line());
+ writeByteArray(gz, PROFILE_FUNCTION, fun.toByteArray());
+
+ functionIDs.put(addr, id);
+ }
+ return id;
+ }
+
+ // Returns the ID of the location denoted by fr,
+ // emitting a pprof Location record the first time it is encountered.
+ // For Starlark frames, this is the Frame pc.
+ private long getLocationID(Debug.Frame fr) throws IOException {
+ StarlarkCallable fn = fr.getFunction();
+ // fnAddr identifies a function as a whole.
+ int fnAddr = System.identityHashCode(fn); // very imperfect
+
+ // pcAddr identifies the current program point.
+ //
+ // For now, this is the same as fnAddr, because
+ // we don't track the syntax node currently being
+ // evaluated. Statement-level profile information
+ // in the leaf function (displayed by 'pprof list ')
+ // is thus unreliable for now.
+ long pcAddr = fnAddr;
+ if (fn instanceof StarlarkFunction) {
+ // TODO(adonovan): when we use a byte code representation
+ // of function bodies, mix the program counter fr.pc into fnAddr.
+ // TODO(adonovan): even cleaner: treat each function's byte
+ // code segment as its own Profile.Mapping, indexed by pc.
+ //
+ // pcAddr = (pcAddr << 16) ^ fr.pc;
+ }
+
+ Long id = locationIDs.get(pcAddr);
+ if (id == null) {
+ id = pcAddr;
+
+ ByteArrayOutputStream line = new ByteArrayOutputStream();
+ writeLong(line, LINE_FUNCTION_ID, getFunctionID(fn, fnAddr));
+ writeLong(line, LINE_LINE, (long) fr.getLocation().line());
+
+ ByteArrayOutputStream loc = new ByteArrayOutputStream();
+ writeLong(loc, LOCATION_ID, id);
+ writeLong(loc, LOCATION_ADDRESS, pcAddr);
+ writeByteArray(loc, LOCATION_LINE, line.toByteArray());
+ writeByteArray(gz, PROFILE_LOCATION, loc.toByteArray());
+
+ locationIDs.put(pcAddr, id);
+ }
+ return id;
+ }
+ }
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/eval/Debug.java b/third_party/bazel/main/java/net/starlark/java/eval/Debug.java
new file mode 100644
index 000000000..e820b1469
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/eval/Debug.java
@@ -0,0 +1,176 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.starlark.java.eval;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
+import javax.annotation.Nullable;
+import net.starlark.java.syntax.Location;
+
+/** Debugger API. */
+// TODO(adonovan): move Debugger to Debug.Debugger.
+public final class Debug {
+
+ /**
+ * A simple interface for the Starlark interpreter to notify a debugger of events during
+ * execution.
+ */
+ public interface Debugger {
+ /** Notify the debugger that execution is at the point immediately before {@code loc}. */
+ void before(StarlarkThread thread, Location loc);
+
+ /** Notify the debugger that it will no longer receive events from the interpreter. */
+ void close();
+ }
+
+ /** A Starlark value that can expose additional information to a debugger. */
+ public interface ValueWithDebugAttributes extends StarlarkValue {
+ /**
+ * Returns a list of DebugAttribute of this value. For example, it can be the internal fields of
+ * a value that are not accessible from Starlark, or the values inside a collection.
+ */
+ ImmutableList getDebugAttributes();
+ }
+
+ /** A name/value pair used in the return value of getDebugAttributes. */
+ public static final class DebugAttribute {
+ public final String name;
+ public final Object value; // a legal Starlark value
+
+ public DebugAttribute(String name, Object value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ /** See stepControl */
+ public interface ReadyToPause extends Predicate {}
+
+ /**
+ * Describes the stepping behavior that should occur when execution of a thread is continued.
+ * (Debugger API)
+ */
+ public enum Stepping {
+ /** Continue execution without stepping. */
+ NONE,
+ /**
+ * If the thread is paused on a statement that contains a function call, step into that
+ * function. Otherwise, this is the same as OVER.
+ */
+ INTO,
+ /**
+ * Step over the current statement and any functions that it may call, stopping at the next
+ * statement in the same frame. If no more statements are available in the current frame, same
+ * as OUT.
+ */
+ OVER,
+ /**
+ * Continue execution until the current frame has been exited and then pause. If we are
+ * currently in the outer-most frame, same as NONE.
+ */
+ OUT,
+ }
+
+ private Debug() {} // uninstantiable
+
+ static final AtomicReference debugger = new AtomicReference<>();
+
+ /**
+ * Installs a global hook that causes subsequently executed Starlark threads to notify the
+ * debugger of important events. Closes any previously set debugger. Call {@code
+ * setDebugger(null)} to disable debugging.
+ */
+ public static void setDebugger(Debugger dbg) {
+ Debugger prev = debugger.getAndSet(dbg);
+ if (prev != null) {
+ prev.close();
+ }
+ }
+
+ /**
+ * Returns a copy of the current stack of call frames, outermost call first.
+ *
+ *
This function is intended for use only when execution of {@code thread} is stopped, for
+ * example at a breakpoint. The resulting DebugFrames should not be retained after execution of
+ * the thread has resumed. Most clients should instead use {@link StarlarkThread#getCallStack}.
+ */
+ public static ImmutableList getCallStack(StarlarkThread thread) {
+ return thread.getDebugCallStack();
+ }
+
+ /**
+ * Given a requested stepping behavior, returns a predicate over the context that tells the
+ * debugger when to pause. (Debugger API)
+ *
+ *
The predicate will return true if we are at the next statement where execution should pause,
+ * and it will return false if we are not yet at that statement. No guarantee is made about the
+ * predicate's return value after we have reached the desired statement.
+ *
+ *
A null return value indicates that no further pausing should occur.
+ */
+ @Nullable
+ public static Debug.ReadyToPause stepControl(StarlarkThread th, Debug.Stepping stepping) {
+ final int depth = th.getCallStackSize();
+ switch (stepping) {
+ case NONE:
+ return null;
+ case INTO:
+ // pause at the very next statement
+ return thread -> true;
+ case OVER:
+ return thread -> thread.getCallStackSize() <= depth;
+ case OUT:
+ // if we're at the outermost frame, same as NONE
+ return depth == 0 ? null : thread -> thread.getCallStackSize() < depth;
+ }
+ throw new IllegalArgumentException("Unsupported stepping type: " + stepping);
+ }
+
+ /** Debugger interface to the interpreter's internal call frame representation. */
+ public interface Frame {
+
+ /** Returns function called in this frame. */
+ StarlarkCallable getFunction();
+
+ /** Returns the location of the current program counter. */
+ Location getLocation();
+
+ /** Returns the local environment of this frame. */
+ ImmutableMap getLocals();
+ }
+
+ /**
+ * Interface by which debugging tools are notified of a thread entering or leaving its top-level
+ * frame.
+ */
+ public interface ThreadHook {
+ void onPushFirst(StarlarkThread thread);
+
+ void onPopLast(StarlarkThread thread);
+ }
+
+ static ThreadHook threadHook = null;
+
+ /**
+ * Installs a global hook that is notified each time a thread pushes or pops its top-level frame.
+ * This interface is provided to support special tools; ordinary clients should have no need for
+ * it.
+ */
+ public static void setThreadHook(ThreadHook hook) {
+ threadHook = hook;
+ }
+}
diff --git a/third_party/bazel/main/java/net/starlark/java/eval/Dict.java b/third_party/bazel/main/java/net/starlark/java/eval/Dict.java
new file mode 100644
index 000000000..376fb140c
--- /dev/null
+++ b/third_party/bazel/main/java/net/starlark/java/eval/Dict.java
@@ -0,0 +1,818 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.starlark.java.eval;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import javax.annotation.Nullable;
+import net.starlark.java.annot.Param;
+import net.starlark.java.annot.StarlarkBuiltin;
+import net.starlark.java.annot.StarlarkMethod;
+
+/**
+ * A Dict is a Starlark dictionary (dict), a mapping from keys to values.
+ *
+ *
Dicts are iterable in both Java and Starlark; the iterator yields successive keys.
+ *
+ *
Starlark operations on dicts, including element update {@code dict[k]=v} and the {@code
+ * update} and {@code setdefault} methods, may insert arbitrary Starlark values as dict keys/values,
+ * regardless of the type argument used to reference the dict from Java code. Therefore, as long as
+ * a dict is mutable, Java code should refer to it only through a type such as {@code Dict