// 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 com.google.devtools.build.lib.analysis.skylark;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.config.HostTransition;
import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
import com.google.devtools.build.lib.packages.Attribute.ImmutableAttributeFactory;
import com.google.devtools.build.lib.packages.Attribute.SkylarkComputedDefaultTemplate;
import com.google.devtools.build.lib.packages.Attribute.SplitTransitionProvider;
import com.google.devtools.build.lib.packages.AttributeValueSource;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.Provider;
import com.google.devtools.build.lib.packages.SkylarkAspect;
import com.google.devtools.build.lib.packages.SkylarkProviderIdentifier;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.ParamType;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.BuiltinFunction;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.syntax.SkylarkUtils;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.syntax.Type.ConversionException;
import com.google.devtools.build.lib.syntax.Type.LabelClass;
import com.google.devtools.build.lib.syntax.UserDefinedFunction;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.FileTypeSet;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * A helper class to provide Attr module in Skylark.
 *
 * <p>It exposes functions (for example, 'attr.string', 'attr.label_list', etc.) to Skylark users.
 * The functions are executed through reflection. As everywhere in Skylark, arguments are
 * type-checked with the signature and cannot be null.
 */
@SkylarkModule(
  name = "attr",
  namespace = true,
  category = SkylarkModuleCategory.BUILTIN,
  doc =
      "Module for creating new attributes. "
          + "They are only for use with <a href=\"globals.html#rule\">rule</a> or "
          + "<a href=\"globals.html#aspect\">aspect</a>. "
          + "<a href=\"https://github.com/bazelbuild/examples/tree/master/rules/"
          + "attributes/printer.bzl\">See example of use</a>."
)
public final class SkylarkAttr implements SkylarkValue {

  // Arguments

  private static final String ALLOW_FILES_ARG = "allow_files";
  private static final String ALLOW_FILES_DOC =
      "Whether File targets are allowed. Can be True, False (default), or a list of file "
      + "extensions that are allowed (for example, <code>[\".cc\", \".cpp\"]</code>).";

  private static final String ALLOW_RULES_ARG = "allow_rules";
  private static final String ALLOW_RULES_DOC =
      "Which rule targets (name of the classes) are allowed. This is deprecated (kept only for "
          + "compatibility), use providers instead.";

  private static final String ASPECTS_ARG = "aspects";
  private static final String ASPECTS_ARG_DOC =
      "Aspects that should be applied to the dependency or dependencies specified by this "
          + "attribute.";

  private static final String CONFIGURATION_ARG = "cfg";
  private static final String CONFIGURATION_DOC =
      "<a href=\"../rules.$DOC_EXT#configurations\">Configuration</a> of the attribute. It can be "
          + "either <code>\"data\"</code>, <code>\"host\"</code>, or <code>\"target\"</code>.";

  private static final String DEFAULT_ARG = "default";
  // A trailing space is required because it's often prepended to other sentences
  private static final String DEFAULT_DOC = "The default value of the attribute. ";

  private static final String DOC_ARG = "doc";
  private static final String DOC_DOC =
      "A description of the attribute that can be extracted by documentation generating tools.";

  private static final String EXECUTABLE_ARG = "executable";
  private static final String EXECUTABLE_DOC =
      "True if the label has to be executable. This means the label must refer to an "
          + "executable file, or to a rule that outputs an executable file. Access the label "
          + "with <code>ctx.executable.&lt;attribute_name&gt;</code>.";

  private static final String FLAGS_ARG = "flags";
  private static final String FLAGS_DOC = "Deprecated, will be removed.";

  private static final String MANDATORY_ARG = "mandatory";
  private static final String MANDATORY_DOC = "True if the value must be explicitly specified.";

  private static final String NON_EMPTY_ARG = "non_empty";
  private static final String NON_EMPTY_DOC =
      "True if the attribute must not be empty. Deprecated: Use allow_empty instead.";

  private static final String ALLOW_EMPTY_ARG = "allow_empty";
  private static final String ALLOW_EMPTY_DOC = "True if the attribute can be empty.";

  private static final String PROVIDERS_ARG = "providers";
  private static final String PROVIDERS_DOC =
      "Mandatory providers list. It should be either a list of providers, or a "
          + "list of lists of providers. Every dependency should provide ALL providers "
          + "from at least ONE of these lists. A single list of providers will be "
          + "automatically converted to a list containing one list of providers.";

  private static final String SINGLE_FILE_ARG = "single_file";
  private static final String ALLOW_SINGLE_FILE_ARG = "allow_single_file";

  private static final String VALUES_ARG = "values";
  private static final String VALUES_DOC =
      "The list of allowed values for the attribute. An error is raised if any other "
          + "value is given.";

  private static boolean containsNonNoneKey(SkylarkDict<String, Object> arguments, String key) {
    return arguments.containsKey(key) && arguments.get(key) != Runtime.NONE;
  }

  private static void setAllowedFileTypes(
      String attr, Object fileTypesObj, FuncallExpression ast, Attribute.Builder<?> builder)
      throws EvalException {
    if (fileTypesObj == Boolean.TRUE) {
      builder.allowedFileTypes(FileTypeSet.ANY_FILE);
    } else if (fileTypesObj == Boolean.FALSE) {
      builder.allowedFileTypes(FileTypeSet.NO_FILE);
    } else if (fileTypesObj instanceof SkylarkFileType) {
      // TODO(laurentlb): deprecated, to be removed
      builder.allowedFileTypes(((SkylarkFileType) fileTypesObj).getFileTypeSet());
    } else if (fileTypesObj instanceof SkylarkList) {
      List<String> arg =
          SkylarkList.castSkylarkListOrNoneToList(
              fileTypesObj, String.class, "allow_files argument");
      builder.allowedFileTypes(FileType.of(arg));
    } else {
      throw new EvalException(
          ast.getLocation(), attr + " should be a boolean or a string list");
    }
  }

  private static ImmutableAttributeFactory createAttributeFactory(
      Type<?> type, SkylarkDict<String, Object> arguments, FuncallExpression ast, Environment env)
      throws EvalException {
    // We use an empty name now so that we can set it later.
    // This trick makes sense only in the context of Skylark (builtin rules should not use it).
    return createAttributeFactory(type, arguments, ast, env, "");
  }

  private static ImmutableAttributeFactory createAttributeFactory(
      Type<?> type,
      SkylarkDict<String, Object> arguments,
      FuncallExpression ast,
      Environment env,
      String name)
      throws EvalException {
    return createAttribute(type, arguments, ast, env, name).buildPartial();
  }

  private static Attribute.Builder<?> createAttribute(
      Type<?> type,
      SkylarkDict<String, Object> arguments,
      FuncallExpression ast,
      Environment env,
      String name)
      throws EvalException {
    Attribute.Builder<?> builder = Attribute.attr(name, type);

    Object defaultValue = arguments.get(DEFAULT_ARG);
    if (!EvalUtils.isNullOrNone(defaultValue)) {
      if (defaultValue instanceof UserDefinedFunction) {
        // Computed attribute. Non label type attributes already caused a type check error.
        SkylarkCallbackFunction callback =
            new SkylarkCallbackFunction(
                (UserDefinedFunction) defaultValue, ast, env.getSemantics());
        // SkylarkComputedDefaultTemplate needs to know the names of all attributes that it depends
        // on. However, this method does not know anything about other attributes.
        // We solve this problem by asking the SkylarkCallbackFunction for the parameter names used
        // in the function definition, which must be the names of attributes used by the callback.
        builder.value(
            new SkylarkComputedDefaultTemplate(
                type, callback.getParameterNames(), callback, ast.getLocation()));
      } else if (defaultValue instanceof SkylarkLateBoundDefault) {
        builder.value((SkylarkLateBoundDefault) defaultValue);
      } else {
        builder.defaultValue(defaultValue, env.getGlobals().getTransitiveLabel(), DEFAULT_ARG);
      }
    }

    for (String flag : SkylarkList.castSkylarkListOrNoneToList(
        arguments.get(FLAGS_ARG), String.class, FLAGS_ARG)) {
      builder.setPropertyFlag(flag);
    }

    if (containsNonNoneKey(arguments, MANDATORY_ARG) && (Boolean) arguments.get(MANDATORY_ARG)) {
      builder.setPropertyFlag("MANDATORY");
    }

    // TODO(laurentlb): Deprecated, remove in August 2016 (use allow_empty instead).
    if (containsNonNoneKey(arguments, NON_EMPTY_ARG) && (Boolean) arguments.get(NON_EMPTY_ARG)) {
      builder.setPropertyFlag("NON_EMPTY");
    }

    if (containsNonNoneKey(arguments, ALLOW_EMPTY_ARG)
        && !(Boolean) arguments.get(ALLOW_EMPTY_ARG)) {
      builder.setPropertyFlag("NON_EMPTY");
    }

    if (containsNonNoneKey(arguments, EXECUTABLE_ARG) && (Boolean) arguments.get(EXECUTABLE_ARG)) {
      builder.setPropertyFlag("EXECUTABLE");
      if (!containsNonNoneKey(arguments, CONFIGURATION_ARG)) {
        throw new EvalException(
            ast.getLocation(),
            "cfg parameter is mandatory when executable=True is provided. Please see "
            + "https://www.bazel.build/versions/master/docs/skylark/rules.html#configurations "
            + "for more details.");
      }
    }

    // TODO(laurentlb): Deprecated, remove in August 2016 (use allow_single_file).
    if (containsNonNoneKey(arguments, SINGLE_FILE_ARG)
        && (Boolean) arguments.get(SINGLE_FILE_ARG)) {
      if (containsNonNoneKey(arguments, ALLOW_SINGLE_FILE_ARG)) {
        throw new EvalException(
            ast.getLocation(),
            "Cannot specify both single_file (deprecated) and allow_single_file");
      }
      builder.setPropertyFlag("SINGLE_ARTIFACT");
    }

    if (containsNonNoneKey(arguments, ALLOW_FILES_ARG)
        && containsNonNoneKey(arguments, ALLOW_SINGLE_FILE_ARG)) {
      throw new EvalException(
          ast.getLocation(), "Cannot specify both allow_files and allow_single_file");
    }

    if (containsNonNoneKey(arguments, ALLOW_FILES_ARG)) {
      Object fileTypesObj = arguments.get(ALLOW_FILES_ARG);
      setAllowedFileTypes(ALLOW_FILES_ARG, fileTypesObj, ast, builder);
    } else if (containsNonNoneKey(arguments, ALLOW_SINGLE_FILE_ARG)) {
      Object fileTypesObj = arguments.get(ALLOW_SINGLE_FILE_ARG);
      setAllowedFileTypes(ALLOW_SINGLE_FILE_ARG, fileTypesObj, ast, builder);
      builder.setPropertyFlag("SINGLE_ARTIFACT");
    } else if (type.getLabelClass() == LabelClass.DEPENDENCY) {
      builder.allowedFileTypes(FileTypeSet.NO_FILE);
    }

    Object ruleClassesObj = arguments.get(ALLOW_RULES_ARG);
    if (ruleClassesObj != null && ruleClassesObj != Runtime.NONE) {
      builder.allowedRuleClasses(
          SkylarkList.castSkylarkListOrNoneToList(
              ruleClassesObj, String.class, "allowed rule classes for attribute definition"));
    }

    List<Object> values = SkylarkList.castSkylarkListOrNoneToList(
        arguments.get(VALUES_ARG), Object.class, VALUES_ARG);
    if (!Iterables.isEmpty(values)) {
      builder.allowedValues(new AllowedValueSet(values));
    }

    if (containsNonNoneKey(arguments, PROVIDERS_ARG)) {
      Object obj = arguments.get(PROVIDERS_ARG);
      SkylarkType.checkType(obj, SkylarkList.class, PROVIDERS_ARG);
      ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> providersList = buildProviderPredicate(
          (SkylarkList<?>) obj, PROVIDERS_ARG, ast.getLocation());

      // If there is at least one empty set, there is no restriction.
      if (providersList.stream().noneMatch(ImmutableSet::isEmpty)) {
        builder.mandatoryProvidersList(providersList);
      }
    }

    if (containsNonNoneKey(arguments, CONFIGURATION_ARG)) {
      Object trans = arguments.get(CONFIGURATION_ARG);
      if (trans.equals("data")) {
        builder.cfg((PatchTransition) SkylarkUtils.getLipoDataTransition(env));
      } else if (trans.equals("host")) {
        builder.cfg(HostTransition.INSTANCE);
      } else if (trans instanceof SplitTransition) {
        builder.cfg((SplitTransition) trans);
      } else if (trans instanceof SplitTransitionProvider) {
        builder.cfg((SplitTransitionProvider) trans);
      } else if (!trans.equals("target")) {
        throw new EvalException(ast.getLocation(),
            "cfg must be either 'data', 'host', or 'target'.");
      }
    }

    if (containsNonNoneKey(arguments, ASPECTS_ARG)) {
      Object obj = arguments.get(ASPECTS_ARG);
      SkylarkType.checkType(obj, SkylarkList.class, ASPECTS_ARG);

      List<SkylarkAspect> aspects =
          ((SkylarkList<?>) obj).getContents(SkylarkAspect.class, "aspects");
      for (SkylarkAspect aspect : aspects) {
        aspect.attachToAttribute(builder, ast.getLocation());
      }
    }

    return builder;
  }

  /**
   * Builds a list of sets of accepted providers from Skylark list {@code obj}.
   * The list can either be a list of providers (in that case the result is a list with one
   * set) or a list of lists of providers (then the result is the list of sets).
   * @param argumentName used in error messages.
   * @param location location for error messages.
   */
  static ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> buildProviderPredicate(
      SkylarkList<?> obj, String argumentName, Location location) throws EvalException {
    if (obj.isEmpty()) {
      return ImmutableList.of();
    }
    boolean isListOfProviders = true;
    for (Object o : obj) {
      if (!isProvider(o)) {
        isListOfProviders = false;
        break;
      }
    }
    if (isListOfProviders) {
      return ImmutableList.of(getSkylarkProviderIdentifiers(obj, location));
    } else {
      return getProvidersList(obj, argumentName, location);
    }
  }

  /**
   * Returns true if {@code o} is a Skylark provider (either a declared provider or
   * a legacy provider name.
   */
  static boolean isProvider(Object o) {
    return o instanceof String || o instanceof Provider;
  }

  /**
   * Converts Skylark identifiers of providers (either a string or a provider value)
   * to their internal representations.
   */
  static ImmutableSet<SkylarkProviderIdentifier> getSkylarkProviderIdentifiers(
      SkylarkList<?> list, Location location) throws EvalException {
    ImmutableList.Builder<SkylarkProviderIdentifier> result = ImmutableList.builder();

    for (Object obj : list) {
      if (obj instanceof String) {
        result.add(SkylarkProviderIdentifier.forLegacy((String) obj));
      } else if (obj instanceof Provider) {
        Provider constructor = (Provider) obj;
        if (!constructor.isExported()) {
          throw new EvalException(location,
              "Providers should be top-level values in extension files that define them.");
        }
        result.add(SkylarkProviderIdentifier.forKey(constructor.getKey()));
      }
    }
    return ImmutableSet.copyOf(result.build());
  }

  private static ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> getProvidersList(
      SkylarkList<?> skylarkList, String argumentName, Location location) throws EvalException {
    ImmutableList.Builder<ImmutableSet<SkylarkProviderIdentifier>> providersList =
        ImmutableList.builder();
    String errorMsg = "Illegal argument: element in '%s' is of unexpected type. "
        + "Either all elements should be providers, "
        + "or all elements should be lists of providers, but got %s.";

    for (Object o : skylarkList) {
      if (!(o instanceof SkylarkList)) {
        throw new EvalException(location, String.format(errorMsg, PROVIDERS_ARG,
            "an element of type " + EvalUtils.getDataTypeName(o, true)));
      }
      for (Object value : (SkylarkList) o) {
        if (!isProvider(value)) {
          throw new EvalException(location, String.format(errorMsg, argumentName,
              "list with an element of type "
                  + EvalUtils.getDataTypeNameFromClass(value.getClass())));
        }
      }
      providersList.add(getSkylarkProviderIdentifiers((SkylarkList<?>) o, location));
    }
    return providersList.build();
  }

  private static Descriptor createAttrDescriptor(
      String name,
      SkylarkDict<String, Object> kwargs,
      Type<?> type,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    try {
      return new Descriptor(name, createAttributeFactory(type, kwargs, ast, env));
    } catch (ConversionException e) {
      throw new EvalException(ast.getLocation(), e.getMessage());
    }
  }

  private static final Map<Type<?>, String> whyNotConfigurable =
      ImmutableMap.<Type<?>, String>builder()
          .put(BuildType.LICENSE,
              "loading phase license checking logic assumes non-configurable values")
          .put(BuildType.OUTPUT, "output paths are part of the static graph structure")
          .build();

  /**
   * If the given attribute type is non-configurable, returns the reason why. Otherwise, returns
   * {@code null}.
   */
  @Nullable
  public static String maybeGetNonConfigurableReason(Type<?> type) {
    return whyNotConfigurable.get(type);
  }

  private static Descriptor createNonconfigurableAttrDescriptor(
      String name,
      SkylarkDict<String, Object> kwargs,
      Type<?> type,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    String whyNotConfigurableReason =
        Preconditions.checkNotNull(maybeGetNonConfigurableReason(type), type);
    try {
      // We use an empty name now so that we can set it later.
      // This trick makes sense only in the context of Skylark (builtin rules should not use it).
      return new Descriptor(
          name,
          createAttribute(type, kwargs, ast, env, "")
              .nonconfigurable(whyNotConfigurableReason)
              .buildPartial());
    } catch (ConversionException e) {
      throw new EvalException(ast.getLocation(), e.getMessage());
    }
  }

  @Override
  public void repr(SkylarkPrinter printer) {
    printer.append("<attr>");
  }

  @SkylarkCallable(
    name = "int",
    doc = "Creates an attribute of type int.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = Integer.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        defaultValue = "0",
        doc = DEFAULT_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        doc = MANDATORY_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = VALUES_ARG,
        type = SkylarkList.class,
        generic1 = Integer.class,
        defaultValue = "[]",
        doc = VALUES_DOC,
        named = true,
        positional = false
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor intAttribute(
      Integer defaultInt,
      String doc,
      Boolean mandatory,
      SkylarkList<?> values,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    // TODO(bazel-team): Replace literal strings with constants.
    env.checkLoadingOrWorkspacePhase("attr.int", ast.getLocation());
    return createAttrDescriptor(
        "int",
        EvalUtils.<String, Object>optionMap(
            env, DEFAULT_ARG, defaultInt, MANDATORY_ARG, mandatory, VALUES_ARG, values),
        Type.INTEGER,
        ast,
        env);
  }

  @SkylarkSignature(
    name = "configuration_field",
    returnType = SkylarkLateBoundDefault.class,
    // TODO(cparsons): Provide a link to documentation for available SkylarkConfigurationFields.
    doc = "References a late-bound default value for an attribute of type "
      + "<a href=\"attr.html#label\">label</a>. A value is 'late-bound' if it requires "
      + "the configuration to be built before determining the value. Any attribute using this "
      + "as a value must <a href=\"../rules.html#private-attributes\">be private</a>.",
    parameters = {
        @Param(
            name = "fragment",
            type = String.class,
            doc = "The name of a configuration fragment which contains the late-bound value."
        ),
        @Param(
            name = "name",
            type = String.class,
            doc = "The name of the value to obtain from the configuration fragment."),
    },
    useLocation = true,
    useEnvironment = true
  )
  private static final BuiltinFunction configurationField =
      new BuiltinFunction("configuration_field") {
        public SkylarkLateBoundDefault<?> invoke(
            String fragment, String name, Location loc, Environment env)
            throws EvalException {
          Class<?> fragmentClass = SkylarkUtils.getFragmentMap(env).get(fragment);

          if (fragmentClass == null) {
            throw new EvalException(
                loc,
                String.format("invalid configuration fragment name '%s'", fragment));
          }
          try {
            return SkylarkLateBoundDefault.forConfigurationField(
                fragmentClass, name, SkylarkUtils.getToolsRepository(env));
          } catch (SkylarkLateBoundDefault.InvalidConfigurationFieldException exception) {
            throw new EvalException(loc, exception);
          }
        }
      };

  @SkylarkCallable(
    name = "string",
    doc = "Creates an attribute of type <a href=\"string.html\">string</a>.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = String.class),
        },
        defaultValue = "''",
        doc = DEFAULT_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        doc = MANDATORY_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = VALUES_ARG,
        type = SkylarkList.class,
        generic1 = String.class,
        defaultValue = "[]",
        doc = VALUES_DOC,
        named = true,
        positional = false
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor stringAttribute(
      String defaultString,
      String doc,
      Boolean mandatory,
      SkylarkList<?> values,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.string", ast.getLocation());
    return createAttrDescriptor(
        "string",
        EvalUtils.<String, Object>optionMap(
            env, DEFAULT_ARG, defaultString, MANDATORY_ARG, mandatory, VALUES_ARG, values),
        Type.STRING,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "label",
    doc =
        "Creates an attribute of type <a href=\"Target.html\">Target</a> which is the target "
            + "referred to by the label. "
            + "It is the only way to specify a dependency to another target. "
            + "If you need a dependency that the user cannot overwrite, "
            + "<a href=\"../rules.$DOC_EXT#private-attributes\">make the attribute private</a>.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = Label.class),
          @ParamType(type = String.class),
          @ParamType(type = SkylarkLateBoundDefault.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        callbackEnabled = true,
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc =
            DEFAULT_DOC
                + "Use a string or the <a href=\"globals.html#Label\"><code>Label</code></a> "
                + "function to specify a default value, for example, "
                + "<code>attr.label(default = \"//a:b\")</code>."
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = EXECUTABLE_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = EXECUTABLE_DOC
      ),
      @Param(
        name = ALLOW_FILES_ARG,
        defaultValue = "None",
        named = true,
        positional = false,
        noneable = true,
        doc = ALLOW_FILES_DOC
      ),
      @Param(
        name = ALLOW_SINGLE_FILE_ARG,
        defaultValue = "None",
        named = true,
        positional = false,
        noneable = true,
        doc =
            "This is similar to <code>allow_files</code>, with the restriction that the label must "
                + "correspond to a single <a href=\"File.html\">File</a>. "
                + "Access it through <code>ctx.file.&lt;attribute_name&gt;</code>."
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      ),
      @Param(
        name = PROVIDERS_ARG,
        type = SkylarkList.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = PROVIDERS_DOC
      ),
      @Param(
        name = ALLOW_RULES_ARG,
        type = SkylarkList.class,
        generic1 = String.class,
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc = ALLOW_RULES_DOC
      ),
      @Param(
        name = SINGLE_FILE_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc =
            "Deprecated: Use <code>allow_single_file</code> instead. "
                + "If True, the label must correspond to a single <a href=\"File.html\">File</a>. "
                + "Access it through <code>ctx.file.&lt;attribute_name&gt;</code>."
      ),
      @Param(
        name = CONFIGURATION_ARG,
        type = Object.class,
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc = CONFIGURATION_DOC + " This parameter is required if <code>executable</code> is True."
      ),
      @Param(
        name = ASPECTS_ARG,
        type = SkylarkList.class,
        generic1 = SkylarkAspect.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = ASPECTS_ARG_DOC
      ),
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor labelAttribute(
      Object defaultO,
      String doc,
      Boolean executable,
      Object allowFiles,
      Object allowSingleFile,
      Boolean mandatory,
      SkylarkList<?> providers,
      Object allowRules,
      Boolean singleFile,
      Object cfg,
      SkylarkList<?> aspects,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.label", ast.getLocation());
    try {
      ImmutableAttributeFactory attribute =
          createAttributeFactory(
              BuildType.LABEL,
              EvalUtils.<String, Object>optionMap(
                  env,
                  DEFAULT_ARG,
                  defaultO,
                  EXECUTABLE_ARG,
                  executable,
                  ALLOW_FILES_ARG,
                  allowFiles,
                  ALLOW_SINGLE_FILE_ARG,
                  allowSingleFile,
                  MANDATORY_ARG,
                  mandatory,
                  PROVIDERS_ARG,
                  providers,
                  ALLOW_RULES_ARG,
                  allowRules,
                  SINGLE_FILE_ARG,
                  singleFile,
                  CONFIGURATION_ARG,
                  cfg,
                  ASPECTS_ARG,
                  aspects),
              ast,
              env,
              "label");
      return new Descriptor("label", attribute);
    } catch (EvalException e) {
      throw new EvalException(ast.getLocation(), e.getMessage(), e);
    }
  }

  @SkylarkCallable(
    name = "string_list",
    doc =
        "Creates an attribute which is a <a href=\"list.html\">list</a> of "
            + "<a href=\"string.html\">strings</a>.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = SkylarkList.class, generic1 = String.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        defaultValue = "[]",
        doc = DEFAULT_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        doc = MANDATORY_DOC,
        named = true
      ),
      @Param(
        name = NON_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        doc = NON_EMPTY_DOC,
        named = true
      ),
      @Param(
        name = ALLOW_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "True",
        doc = ALLOW_EMPTY_DOC,
        named = true
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor stringListAttribute(
      SkylarkList<?> defaultList,
      String doc,
      Boolean mandatory,
      Boolean nonEmpty,
      Boolean allowEmpty,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.string_list", ast.getLocation());
    return createAttrDescriptor(
        "string_list",
        EvalUtils.<String, Object>optionMap(
            env,
            DEFAULT_ARG,
            defaultList,
            MANDATORY_ARG,
            mandatory,
            NON_EMPTY_ARG,
            nonEmpty,
            ALLOW_EMPTY_ARG,
            allowEmpty),
        Type.STRING_LIST,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "int_list",
    doc = "Creates an attribute which is a <a href=\"list.html\">list</a> of ints.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = SkylarkList.class, generic1 = Integer.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        defaultValue = "[]",
        doc = DEFAULT_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        doc = MANDATORY_DOC,
        named = true
      ),
      @Param(
        name = NON_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        doc = NON_EMPTY_DOC,
        named = true
      ),
      @Param(
        name = ALLOW_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "True",
        doc = ALLOW_EMPTY_DOC,
        named = true
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor intListAttribute(
      SkylarkList<?> defaultList,
      String doc,
      Boolean mandatory,
      Boolean nonEmpty,
      Boolean allowEmpty,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.int_list", ast.getLocation());
    return createAttrDescriptor(
        "int_list",
        EvalUtils.<String, Object>optionMap(
            env,
            DEFAULT_ARG,
            defaultList,
            MANDATORY_ARG,
            mandatory,
            NON_EMPTY_ARG,
            nonEmpty,
            ALLOW_EMPTY_ARG,
            allowEmpty),
        Type.INTEGER_LIST,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "label_list",
    doc =
        "Creates an attribute which is a <a href=\"list.html\">list</a> of type "
            + "<a href=\"Target.html\">Target</a> which are specified by the labels in the list. "
            + "See <a href=\"attr.html#label\">label</a> for more information.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = SkylarkList.class, generic1 = Label.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        callbackEnabled = true,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc =
            DEFAULT_DOC
                + "Use strings or the <a href=\"globals.html#Label\"><code>Label</code></a> "
                + "function to specify default values, for example, "
                + "<code>attr.label_list(default = [\"//a:b\", \"//a:c\"])</code>."
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = ALLOW_FILES_ARG, // bool or FileType filter
        defaultValue = "None",
        named = true,
        positional = false,
        noneable = true,
        doc = ALLOW_FILES_DOC
      ),
      @Param(
        name = ALLOW_RULES_ARG,
        type = SkylarkList.class,
        generic1 = String.class,
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc = ALLOW_RULES_DOC
      ),
      @Param(
        name = PROVIDERS_ARG,
        type = SkylarkList.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = PROVIDERS_DOC
      ),
      @Param(
        name = FLAGS_ARG,
        type = SkylarkList.class,
        generic1 = String.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = FLAGS_DOC
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      ),
      @Param(
        name = NON_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = NON_EMPTY_DOC
      ),
      @Param(
        name = ALLOW_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "True",
        doc = ALLOW_EMPTY_DOC,
        named = true
      ),
      @Param(
        name = CONFIGURATION_ARG,
        type = Object.class,
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc = CONFIGURATION_DOC
      ),
      @Param(
        name = ASPECTS_ARG,
        type = SkylarkList.class,
        generic1 = SkylarkAspect.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = ASPECTS_ARG_DOC
      ),
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor labelListAttribute(
      Object defaultList,
      String doc,
      Object allowFiles,
      Object allowRules,
      SkylarkList<?> providers,
      SkylarkList<?> flags,
      Boolean mandatory,
      Boolean nonEmpty,
      Boolean allowEmpty,
      Object cfg,
      SkylarkList<?> aspects,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.label_list", ast.getLocation());
    SkylarkDict<String, Object> kwargs =
        EvalUtils.<String, Object>optionMap(
            env,
            DEFAULT_ARG,
            defaultList,
            ALLOW_FILES_ARG,
            allowFiles,
            ALLOW_RULES_ARG,
            allowRules,
            PROVIDERS_ARG,
            providers,
            FLAGS_ARG,
            flags,
            MANDATORY_ARG,
            mandatory,
            NON_EMPTY_ARG,
            nonEmpty,
            ALLOW_EMPTY_ARG,
            allowEmpty,
            CONFIGURATION_ARG,
            cfg,
            ASPECTS_ARG,
            aspects);
    try {
      ImmutableAttributeFactory attribute =
          createAttributeFactory(BuildType.LABEL_LIST, kwargs, ast, env, "label_list");
      return new Descriptor("label_list", attribute);
    } catch (EvalException e) {
      throw new EvalException(ast.getLocation(), e.getMessage(), e);
    }
  }

  @SkylarkCallable(
    name = "label_keyed_string_dict",
    doc =
        "Creates an attribute which is a <a href=\"dict.html\">dict</a>. Its keys are type "
            + "<a href=\"Target.html\">Target</a> and are specified by the label keys of the "
            + "input dict. Its values are <a href=\"string.html\">strings</a>. See "
            + "<a href=\"attr.html#label\">label</a> for more information.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = SkylarkDict.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        callbackEnabled = true,
        defaultValue = "{}",
        named = true,
        positional = false,
        doc =
            DEFAULT_DOC
                + "Use strings or the <a href=\"globals.html#Label\"><code>Label</code></a> "
                + "function to specify default values, for example, "
                + "<code>attr.label_keyed_string_dict(default = "
                + "{\"//a:b\": \"value\", \"//a:c\": \"string\"})</code>."
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = ALLOW_FILES_ARG, // bool or FileType filter
        defaultValue = "None",
        named = true,
        positional = false,
        noneable = true,
        doc = ALLOW_FILES_DOC
      ),
      @Param(
        name = ALLOW_RULES_ARG,
        type = SkylarkList.class,
        generic1 = String.class,
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc = ALLOW_RULES_DOC
      ),
      @Param(
        name = PROVIDERS_ARG,
        type = SkylarkList.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = PROVIDERS_DOC
      ),
      @Param(
        name = FLAGS_ARG,
        type = SkylarkList.class,
        generic1 = String.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = FLAGS_DOC
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      ),
      @Param(
        name = NON_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = NON_EMPTY_DOC
      ),
      @Param(
        name = ALLOW_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "True",
        doc = ALLOW_EMPTY_DOC,
        named = true
      ),
      @Param(
        name = CONFIGURATION_ARG,
        type = Object.class,
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc = CONFIGURATION_DOC
      ),
      @Param(
        name = ASPECTS_ARG,
        type = SkylarkList.class,
        generic1 = SkylarkAspect.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = ASPECTS_ARG_DOC
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor labelKeyedStringDictAttribute(
      Object defaultList,
      String doc,
      Object allowFiles,
      Object allowRules,
      SkylarkList<?> providers,
      SkylarkList<?> flags,
      Boolean mandatory,
      Boolean nonEmpty,
      Boolean allowEmpty,
      Object cfg,
      SkylarkList<?> aspects,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.label_keyed_string_dict", ast.getLocation());
    SkylarkDict<String, Object> kwargs =
        EvalUtils.<String, Object>optionMap(
            env,
            DEFAULT_ARG,
            defaultList,
            ALLOW_FILES_ARG,
            allowFiles,
            ALLOW_RULES_ARG,
            allowRules,
            PROVIDERS_ARG,
            providers,
            FLAGS_ARG,
            flags,
            MANDATORY_ARG,
            mandatory,
            NON_EMPTY_ARG,
            nonEmpty,
            ALLOW_EMPTY_ARG,
            allowEmpty,
            CONFIGURATION_ARG,
            cfg,
            ASPECTS_ARG,
            aspects);
    try {
      ImmutableAttributeFactory attribute =
          createAttributeFactory(
              BuildType.LABEL_KEYED_STRING_DICT, kwargs, ast, env, "label_keyed_string_dict");
      return new Descriptor("label_keyed_string_dict", attribute);
    } catch (EvalException e) {
      throw new EvalException(ast.getLocation(), e.getMessage(), e);
    }
  }

  @SkylarkCallable(
    name = "bool",
    doc = "Creates an attribute of type bool.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = Boolean.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        defaultValue = "False",
        named = true,
        positional = false,
        doc = DEFAULT_DOC
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor boolAttribute(
      Boolean defaultO, String doc, Boolean mandatory, FuncallExpression ast, Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.bool", ast.getLocation());
    return createAttrDescriptor(
        "bool",
        EvalUtils.<String, Object>optionMap(env, DEFAULT_ARG, defaultO, MANDATORY_ARG, mandatory),
        Type.BOOLEAN,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "output",
    doc =
        "Creates an attribute of type output. "
            + "The user provides a file name (string) and the rule must create an action that "
            + "generates the file.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = Label.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        noneable = true,
        defaultValue = "None",
        named = true,
        positional = false,
        doc = DEFAULT_DOC
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor outputAttribute(
      Object defaultO, String doc, Boolean mandatory, FuncallExpression ast, Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.output", ast.getLocation());
    return createNonconfigurableAttrDescriptor(
        "output",
        EvalUtils.<String, Object>optionMap(env, DEFAULT_ARG, defaultO, MANDATORY_ARG, mandatory),
        BuildType.OUTPUT,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "output_list",
    doc =
        "Creates an attribute which is a <a href=\"list.html\">list</a> of outputs. "
            + "See <a href=\"attr.html#output\">output</a> for more information.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = SkylarkList.class, generic1 = Label.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        generic1 = Label.class,
        defaultValue = "[]",
        named = true,
        positional = false,
        doc = DEFAULT_DOC
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      ),
      @Param(
        name = NON_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = NON_EMPTY_DOC
      ),
      @Param(
        name = ALLOW_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "True",
        doc = ALLOW_EMPTY_DOC,
        named = true
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor outputListAttribute(
      SkylarkList defaultList,
      String doc,
      Boolean mandatory,
      Boolean nonEmpty,
      Boolean allowEmpty,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.output_list", ast.getLocation());
    return createAttrDescriptor(
        "output_list",
        EvalUtils.<String, Object>optionMap(
            env,
            DEFAULT_ARG,
            defaultList,
            MANDATORY_ARG,
            mandatory,
            NON_EMPTY_ARG,
            nonEmpty,
            ALLOW_EMPTY_ARG,
            allowEmpty),
        BuildType.OUTPUT_LIST,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "string_dict",
    doc =
        "Creates an attribute of type <a href=\"dict.html\">dict</a>, mapping from "
            + "<a href=\"string.html\">string</a> to <a href=\"string.html\">string</a>.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = SkylarkDict.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        named = true,
        positional = false,
        defaultValue = "{}",
        doc = DEFAULT_DOC
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        named = true,
        positional = false,
        defaultValue = "False",
        doc = MANDATORY_DOC
      ),
      @Param(
        name = NON_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = NON_EMPTY_DOC
      ),
      @Param(
        name = ALLOW_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "True",
        doc = ALLOW_EMPTY_DOC,
        named = true
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor stringDictAttribute(
      SkylarkDict<?, ?> defaultO,
      String doc,
      Boolean mandatory,
      Boolean nonEmpty,
      Boolean allowEmpty,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.string_dict", ast.getLocation());
    return createAttrDescriptor(
        "string_dict",
        EvalUtils.<String, Object>optionMap(
            env,
            DEFAULT_ARG,
            defaultO,
            MANDATORY_ARG,
            mandatory,
            NON_EMPTY_ARG,
            nonEmpty,
            ALLOW_EMPTY_ARG,
            allowEmpty),
        Type.STRING_DICT,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "string_list_dict",
    doc =
        "Creates an attribute of type <a href=\"dict.html\">dict</a>, mapping from "
            + "<a href=\"string.html\">string</a> to <a href=\"list.html\">list</a> of "
            + "<a href=\"string.html\">string</a>.",
    parameters = {
      @Param(
        name = DEFAULT_ARG,
        allowedTypes = {
          @ParamType(type = SkylarkDict.class),
          @ParamType(type = UserDefinedFunction.class)
        },
        defaultValue = "{}",
        named = true,
        positional = false,
        doc = DEFAULT_DOC
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      ),
      @Param(
        name = NON_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = NON_EMPTY_DOC
      ),
      @Param(
        name = ALLOW_EMPTY_ARG,
        type = Boolean.class,
        defaultValue = "True",
        doc = ALLOW_EMPTY_DOC,
        named = true
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor stringListDictAttribute(
      SkylarkDict<?, ?> defaultO,
      String doc,
      Boolean mandatory,
      Boolean nonEmpty,
      Boolean allowEmpty,
      FuncallExpression ast,
      Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.string_list_dict", ast.getLocation());
    return createAttrDescriptor(
        "string_list_dict",
        EvalUtils.<String, Object>optionMap(
            env,
            DEFAULT_ARG,
            defaultO,
            MANDATORY_ARG,
            mandatory,
            NON_EMPTY_ARG,
            nonEmpty,
            ALLOW_EMPTY_ARG,
            allowEmpty),
        Type.STRING_LIST_DICT,
        ast,
        env);
  }

  @SkylarkCallable(
    name = "license",
    doc = "Creates an attribute of type license.",
    // TODO(bazel-team): Implement proper license support for Skylark.
    parameters = {
      // TODO(bazel-team): ensure this is the correct default value
      @Param(
        name = DEFAULT_ARG,
        defaultValue = "None",
        noneable = true,
        named = true,
        positional = false,
        doc = DEFAULT_DOC
      ),
      @Param(
        name = DOC_ARG,
        type = String.class,
        defaultValue = "''",
        doc = DOC_DOC,
        named = true,
        positional = false
      ),
      @Param(
        name = MANDATORY_ARG,
        type = Boolean.class,
        defaultValue = "False",
        named = true,
        positional = false,
        doc = MANDATORY_DOC
      )
    },
    useAst = true,
    useEnvironment = true
  )
  public Descriptor licenseAttribute(
      Object defaultO, String doc, Boolean mandatory, FuncallExpression ast, Environment env)
      throws EvalException {
    env.checkLoadingOrWorkspacePhase("attr.license", ast.getLocation());
    return createNonconfigurableAttrDescriptor(
        "license",
        EvalUtils.<String, Object>optionMap(env, DEFAULT_ARG, defaultO, MANDATORY_ARG, mandatory),
        BuildType.LICENSE,
        ast,
        env);
  }

  /** A descriptor of an attribute defined in Skylark. */
  @SkylarkModule(
    name = "Attribute",
    category = SkylarkModuleCategory.NONE,
    doc =
        "Representation of a definition of an attribute. Use the <a href=\"attr.html\">attr</a> "
            + "module to create an Attribute. They are only for use with a "
            + "<a href=\"globals.html#rule\">rule</a> or an "
            + "<a href=\"globals.html#aspect\">aspect</a>."
  )
  @AutoCodec
  public static final class Descriptor implements SkylarkValue {
    private final ImmutableAttributeFactory attributeFactory;
    private final String name;

    @AutoCodec.VisibleForSerialization
    Descriptor(String name, ImmutableAttributeFactory attributeFactory) {
      this.attributeFactory = Preconditions.checkNotNull(attributeFactory);
      this.name = name;
    }

    public boolean hasDefault() {
      return attributeFactory.isValueSet();
    }

    public AttributeValueSource getValueSource() {
      return attributeFactory.getValueSource();
    }

    public Attribute build(String name) {
      return attributeFactory.build(name);
    }

    @Override
    public void repr(SkylarkPrinter printer) {
      printer.append("<attr." + name + ">");
    }
  }

  static {
    SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkAttr.class);
  }
}
