// 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.cmdline;

import static com.google.devtools.build.lib.cmdline.LabelParser.validateAndProcessTargetName;

import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Striped;
import com.google.devtools.build.docgen.annot.DocCategory;
import com.google.devtools.build.lib.actions.CommandLineItem;
import com.google.devtools.build.lib.cmdline.LabelParser.Parts;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.concurrent.PooledInterner;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import javax.annotation.Nullable;
import net.starlark.java.annot.Param;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.eval.StarlarkValue;

/**
 * A class to identify a BUILD target. All targets belong to exactly one package. The name of a
 * target is called its label. A typical label looks like this: //dir1/dir2:target_name where
 * 'dir1/dir2' identifies the package containing a BUILD file, and 'target_name' identifies the
 * target within the package.
 *
 * <p>Parsing is robust against bad input, for example, from the command line.
 */
@StarlarkBuiltin(name = "Label", category = DocCategory.BUILTIN, doc = "A BUILD target identifier.")
@AutoCodec
@Immutable
@ThreadSafe
public final class Label implements Comparable<Label>, StarlarkValue, SkyKey, CommandLineItem {
  /**
   * Package names that aren't made relative to the current repository because they mean special
   * things to Bazel.
   */
  private static final ImmutableSet<String> ABSOLUTE_PACKAGE_NAMES =
      ImmutableSet.of(
          // Used for select's `//conditions:default` label (not a target)
          "conditions",
          // Used for the public and private visibility labels (not targets)
          "visibility",
          // There is only one //external package
          LabelConstants.EXTERNAL_PACKAGE_NAME.getPathString());

  // Intern "__pkg__" and "__subpackages__" pseudo-targets, which appears in labels used for
  // visibility specifications. This saves a couple tenths of a percent of RAM off the loading
  // phase. Note that general interning of all values for `name` is *not* beneficial. See
  // Google-internal cl/386077913 and cl/185394812 for more context.
  private static final String PKG_VISIBILITY_NAME = "__pkg__";
  private static final String SUBPACKAGES_VISIBILITY_NAME = "__subpackages__";

  public static final SkyFunctionName TRANSITIVE_TRAVERSAL =
      SkyFunctionName.createHermetic("TRANSITIVE_TRAVERSAL");

  private static final LabelInterner interner = new LabelInterner();

  public static LabelInterner getLabelInterner() {
    return interner;
  }

  /** The context of a current repo, necessary to parse a repo-relative label ("//foo:bar"). */
  public interface RepoContext {
    static RepoContext of(RepositoryName currentRepo, RepositoryMapping repoMapping) {
      return new AutoValue_Label_RepoContextImpl(currentRepo, repoMapping);
    }

    RepositoryName currentRepo();

    RepositoryMapping repoMapping();
  }

  @AutoValue
  abstract static class RepoContextImpl implements RepoContext {}

  /** The context of a current package, necessary to parse a package-relative label (":foo"). */
  public interface PackageContext extends RepoContext {
    static PackageContext of(PackageIdentifier currentPackage, RepositoryMapping repoMapping) {
      return new AutoValue_Label_PackageContextImpl(
          currentPackage.getRepository(), repoMapping, currentPackage.getPackageFragment());
    }

    PathFragment packageFragment();

    default PackageIdentifier packageIdentifier() {
      return PackageIdentifier.create(currentRepo(), packageFragment());
    }
  }

  @AutoValue
  abstract static class PackageContextImpl implements PackageContext {}

  /**
   * Parses a raw label string that contains the canonical form of a label. It must be of the form
   * {@code [@repo]//foo/bar[:quux]}. If the {@code @repo} part is present, it must be a canonical
   * repo name, otherwise the label will be assumed to be in the main repo.
   */
  public static Label parseCanonical(String raw) throws LabelSyntaxException {
    Parts parts = Parts.parse(raw);
    parts.checkPkgDoesNotEndWithTripleDots();
    parts.checkPkgIsAbsolute();
    RepositoryName repoName =
        parts.repo() == null ? RepositoryName.MAIN : RepositoryName.createUnvalidated(parts.repo());
    return createUnvalidated(
        PackageIdentifier.create(repoName, PathFragment.create(parts.pkg())), parts.target());
  }

  /** Like {@link #parseCanonical}, but throws an unchecked exception instead. */
  public static Label parseCanonicalUnchecked(String raw) {
    try {
      return parseCanonical(raw);
    } catch (LabelSyntaxException e) {
      throw new IllegalArgumentException(e);
    }
  }

  /** Computes the repo name for the label, within the context of a current repo. */
  private static RepositoryName computeRepoNameWithRepoContext(
      Parts parts, RepoContext repoContext) {
    if (parts.repo() == null) {
      // Certain package names when used without a "@" part are always absolutely in the main repo,
      // disregarding the current repo and repo mappings.
      return ABSOLUTE_PACKAGE_NAMES.contains(parts.pkg())
          ? RepositoryName.MAIN
          : repoContext.currentRepo();
    }
    if (parts.repoIsCanonical()) {
      // This label uses the canonical label literal syntax starting with two @'s ("@@foo//bar").
      return RepositoryName.createUnvalidated(parts.repo());
    }
    return repoContext.repoMapping().get(parts.repo());
  }

  /**
   * Parses a raw label string within the context of a current repo. It must be of the form {@code
   * [@repo]//foo/bar[:quux]}. If the {@code @repo} part is present, it will undergo {@code
   * repoContext.repoMapping()}, otherwise the label will be assumed to be in {@code
   * repoContext.currentRepo()}.
   */
  public static Label parseWithRepoContext(String raw, RepoContext repoContext)
      throws LabelSyntaxException {
    Parts parts = Parts.parse(raw);
    parts.checkPkgDoesNotEndWithTripleDots();
    parts.checkPkgIsAbsolute();
    RepositoryName repoName = computeRepoNameWithRepoContext(parts, repoContext);
    return createUnvalidated(
        PackageIdentifier.create(repoName, PathFragment.create(parts.pkg())), parts.target());
  }

  /**
   * Parses a raw label string within the context of a current package. It can be of a
   * package-relative form ({@code :quux}). Otherwise, it must be of the form {@code
   * [@repo]//foo/bar[:quux]}. If the {@code @repo} part is present, it will undergo {@code
   * packageContext.repoMapping()}, otherwise the label will be assumed to be in the repo of {@code
   * packageContext.currentRepo()}.
   */
  public static Label parseWithPackageContext(String raw, PackageContext packageContext)
      throws LabelSyntaxException {
    Parts parts = Parts.parse(raw);
    parts.checkPkgDoesNotEndWithTripleDots();
    // pkg is either absolute or empty
    if (!parts.pkg().isEmpty()) {
      parts.checkPkgIsAbsolute();
    }
    RepositoryName repoName = computeRepoNameWithRepoContext(parts, packageContext);
    PathFragment pkgFragment =
        parts.pkgIsAbsolute() ? PathFragment.create(parts.pkg()) : packageContext.packageFragment();
    return createUnvalidated(PackageIdentifier.create(repoName, pkgFragment), parts.target());
  }

  /**
   * Factory for Labels from separate components.
   *
   * @param packageName The name of the package. The package name does <b>not</b> include {@code
   *     //}. Must be valid according to {@link LabelValidator#validatePackageName}.
   * @param targetName The name of the target within the package. Must be valid according to {@link
   *     LabelValidator#validateTargetName}.
   * @throws LabelSyntaxException if either of the arguments was invalid.
   */
  public static Label create(String packageName, String targetName) throws LabelSyntaxException {
    return createUnvalidated(
        PackageIdentifier.parse(packageName),
        validateAndProcessTargetName(packageName, targetName, /* pkgEndsWithTripleDots= */ false));
  }

  /**
   * Similar factory to above, but takes a package identifier to allow external repository labels to
   * be created.
   */
  public static Label create(PackageIdentifier packageId, String targetName)
      throws LabelSyntaxException {
    return createUnvalidated(
        packageId,
        validateAndProcessTargetName(
            packageId.getPackageFragment().getPathString(),
            targetName,
            /* pkgEndsWithTripleDots= */ false));
  }

  /**
   * Similar factory to above, but does not perform target name validation.
   *
   * <p>Only call this method if you know what you're doing; in particular, don't call it on
   * arbitrary {@code name} inputs
   */
  @AutoCodec.Instantiator
  public static Label createUnvalidated(PackageIdentifier packageIdentifier, String name) {
    String internedName = name;
    if (internedName.equals(PKG_VISIBILITY_NAME)) {
      internedName = PKG_VISIBILITY_NAME;
    } else if (internedName.equals(SUBPACKAGES_VISIBILITY_NAME)) {
      internedName = SUBPACKAGES_VISIBILITY_NAME;
    }
    return interner.intern(new Label(packageIdentifier, internedName));
  }

  /** The name and repository of the package. */
  private final PackageIdentifier packageIdentifier;

  /** The name of the target within the package. Canonical. */
  private final String name;

  private Label(PackageIdentifier packageIdentifier, String name) {
    Preconditions.checkNotNull(packageIdentifier);
    Preconditions.checkNotNull(name);

    this.packageIdentifier = packageIdentifier;
    this.name = name;
  }

  public PackageIdentifier getPackageIdentifier() {
    return packageIdentifier;
  }

  public RepositoryName getRepository() {
    return packageIdentifier.getRepository();
  }

  /**
   * Returns the name of the package in which this rule was declared (e.g. {@code
   * //file/base:fileutils_test} returns {@code file/base}).
   */
  @StarlarkMethod(
      name = "package",
      structField = true,
      doc =
          "The package part of this label. "
              + "For instance:<br>"
              + "<pre class=language-python>Label(\"//pkg/foo:abc\").package == \"pkg/foo\"</pre>")
  public String getPackageName() {
    return packageIdentifier.getPackageFragment().getPathString();
  }

  /**
   * Returns the execution root for the workspace, relative to the execroot (e.g., for label
   * {@code @repo//pkg:b}, it will returns {@code external/repo/pkg} and for label {@code //pkg:a},
   * it will returns an empty string.
   *
   * @deprecated The sole purpose of this method is to implement the workspace_root method. For
   *     other purposes, use {@link RepositoryName#getExecPath} instead.
   */
  @StarlarkMethod(
      name = "workspace_root",
      structField = true,
      doc =
          "Returns the execution root for the workspace of this label, relative to the execroot. "
              + "For instance:<br>"
              + "<pre class=language-python>Label(\"@repo//pkg/foo:abc\").workspace_root =="
              + " \"external/repo\"</pre>",
      useStarlarkSemantics = true)
  @Deprecated
  public String getWorkspaceRootForStarlarkOnly(StarlarkSemantics semantics) throws EvalException {
    checkRepoVisibilityForStarlark("workspace_root");
    return packageIdentifier
        .getRepository()
        .getExecPath(semantics.getBool(BuildLanguageOptions.EXPERIMENTAL_SIBLING_REPOSITORY_LAYOUT))
        .toString();
  }

  /**
   * Returns the path fragment of the package in which this rule was declared (e.g. {@code
   * //file/base:fileutils_test} returns {@code file/base}).
   *
   * <p>This is <b>not</b> suitable for inferring a path under which files related to a rule with
   * this label will be under the exec root, in particular, it won't work for rules in external
   * repositories.
   */
  public PathFragment getPackageFragment() {
    return packageIdentifier.getPackageFragment();
  }

  /**
   * Returns the label as a path fragment, using the package and the label name.
   *
   * <p>Make sure that the label refers to a file. Non-file labels do not necessarily have
   * PathFragment representations.
   *
   * <p>The package's repository is not included in the returned fragment. To account for it,
   * compose this with {@code #getRepository()#getExecPath}.
   */
  public PathFragment toPathFragment() {
    // PathFragments are normalized, so if we do this on a non-file target named '.'
    // then the package would be returned. Detect this and throw.
    // A target named '.' can never refer to a file.
    Preconditions.checkArgument(!name.equals("."));
    return packageIdentifier.getPackageFragment().getRelative(name);
  }

  /**
   * Returns the name by which this rule was declared (e.g. {@code //foo/bar:baz} returns {@code
   * baz}).
   */
  @StarlarkMethod(
      name = "name",
      structField = true,
      doc =
          "The name of this label within the package. "
              + "For instance:<br>"
              + "<pre class=language-python>Label(\"//pkg/foo:abc\").name == \"abc\"</pre>")
  public String getName() {
    return name;
  }

  /**
   * Renders this label in canonical form.
   *
   * <p>invariant: {@code parseCanonical(x.toString()).equals(x)}. Note that using {@link
   * #parseWithPackageContext} or {@link #parseWithRepoContext} on the returned string might not
   * yield the same label! For that, use {@link #getUnambiguousCanonicalForm()}.
   */
  @Override
  public String toString() {
    return getCanonicalForm();
  }

  /**
   * Renders this label in canonical form.
   *
   * <p>invariant: {@code parseCanonical(x.getCanonicalForm()).equals(x)}. Note that using {@link
   * #parseWithPackageContext} or {@link #parseWithRepoContext} on the returned string might not
   * yield the same label! For that, use {@link #getUnambiguousCanonicalForm()}.
   */
  public String getCanonicalForm() {
    return packageIdentifier.getCanonicalForm() + ":" + name;
  }

  /**
   * Returns an absolutely unambiguous canonical form for this label. Parsing this string in any
   * environment should yield the same label (as in {@code
   * Label.parse*(x.getUnambiguousCanonicalForm(), ...).equals(x)}).
   */
  public String getUnambiguousCanonicalForm() {
    return packageIdentifier.getUnambiguousCanonicalForm() + ":" + name;
  }

  /**
   * Returns a full label string that is suitable for display, i.e., it resolves to this label when
   * parsed in the context of the main repository and has a repository part that is as simple as
   * possible.
   *
   * @param mainRepositoryMapping the {@link RepositoryMapping} of the main repository
   * @return analogous to {@link PackageIdentifier#getDisplayForm(RepositoryMapping)}
   */
  public String getDisplayForm(RepositoryMapping mainRepositoryMapping) {
    return packageIdentifier.getDisplayForm(mainRepositoryMapping) + ":" + name;
  }

  /**
   * Returns a shorthand label string that is suitable for display, i.e. in addition to simplifying
   * the repository part, labels of the form {@code [@repo]//foo/bar:bar} are simplified to the
   * shorthand form {@code [@repo]//foo/bar}, and labels of the form {@code @repo//:repo} and
   * {@code @@repo//:repo} are simplified to {@code @repo}. The returned shorthand string resolves
   * back to this label only when parsed in the context of the main repository whose repository
   * mapping is provided.
   *
   * <p>Unlike {@link #getDisplayForm}, this method elides the name part of the label if possible.
   *
   * <p>Unlike {@link #toShorthandString}, this method respects {@link RepositoryMapping}.
   *
   * @param mainRepositoryMapping the {@link RepositoryMapping} of the main repository
   */
  public String getShorthandDisplayForm(RepositoryMapping mainRepositoryMapping) {
    if (getPackageFragment().getBaseName().equals(name)) {
      return packageIdentifier.getDisplayForm(mainRepositoryMapping);
    } else if (getPackageFragment().getBaseName().isEmpty()) {
      String repositoryDisplayForm =
          getPackageIdentifier().getRepository().getDisplayForm(mainRepositoryMapping);
      // Simplify @foo//:foo or @@foo//:foo to @foo; note that `name` cannot start with '@'
      if (repositoryDisplayForm.equals("@" + name) || repositoryDisplayForm.equals("@@" + name)) {
        return repositoryDisplayForm;
      }
    }
    return getDisplayForm(mainRepositoryMapping);
  }

  /** Return the name of the repository label refers to without the leading `at` symbol. */
  @StarlarkMethod(
      name = "workspace_name",
      structField = true,
      doc =
          "The repository part of this label. For instance, "
              + "<pre class=language-python>Label(\"@foo//bar:baz\").workspace_name"
              + " == \"foo\"</pre>")
  public String getWorkspaceName() throws EvalException {
    checkRepoVisibilityForStarlark("workspace_name");
    return packageIdentifier.getRepository().getName();
  }

  /**
   * Renders this label in shorthand form.
   *
   * <p>Labels with canonical form {@code //foo/bar:bar} have the shorthand form {@code //foo/bar}.
   * All other labels have identical shorthand and canonical forms.
   *
   * <p>Unlike {@link #getShorthandDisplayForm}, this method does not respect repository mapping.
   */
  public String toShorthandString() {
    if (!getPackageFragment().getBaseName().equals(name)) {
      return toString();
    }
    String repository;
    if (packageIdentifier.getRepository().isMain()) {
      repository = "";
    } else {
      repository = packageIdentifier.getRepository().getNameWithAt();
    }
    return repository + "//" + getPackageFragment();
  }

  /**
   * Returns a label in the same package as this label with the given target name.
   *
   * @throws LabelSyntaxException if {@code targetName} is not a valid target name
   */
  public Label getLocalTargetLabel(String targetName) throws LabelSyntaxException {
    return create(packageIdentifier, targetName);
  }

  /**
   * Resolves a relative or absolute label name.
   *
   * <p>For example: {@code :quux} relative to {@code //foo/bar:baz} is {@code //foo/bar:quux};
   * {@code //wiz:quux} relative to {@code //foo/bar:baz} is {@code //wiz:quux}.
   *
   * @param relName the relative label name; must be non-empty.
   * @param thread the Starlark thread.
   */
  @StarlarkMethod(
      name = "relative",
      doc =
          // TODO(#14503): Fix the documentation.
          "Resolves a label that is either absolute (starts with <code>//</code>) or relative to "
              + "the current package. If this label is in a remote repository, the argument will "
              + "be resolved relative to that repository. If the argument contains a repository "
              + "name, the current label is ignored and the argument is returned as-is, except "
              + "that the repository name is rewritten if it is in the current repository mapping. "
              + "Reserved labels will also be returned as-is.<br>"
              + "For example:<br>"
              + "<pre class=language-python>\n"
              + "Label(\"//foo/bar:baz\").relative(\":quux\") == Label(\"//foo/bar:quux\")\n"
              + "Label(\"//foo/bar:baz\").relative(\"//wiz:quux\") == Label(\"//wiz:quux\")\n"
              + "Label(\"@repo//foo/bar:baz\").relative(\"//wiz:quux\") == "
              + "Label(\"@repo//wiz:quux\")\n"
              + "Label(\"@repo//foo/bar:baz\").relative(\"//visibility:public\") == "
              + "Label(\"//visibility:public\")\n"
              + "Label(\"@repo//foo/bar:baz\").relative(\"@other//wiz:quux\") == "
              + "Label(\"@other//wiz:quux\")\n"
              + "</pre>"
              + "<p>If the repository mapping passed in is <code>{'@other' : '@remapped'}</code>, "
              + "then the following remapping will take place:<br>"
              + "<pre class=language-python>\n"
              + "Label(\"@repo//foo/bar:baz\").relative(\"@other//wiz:quux\") == "
              + "Label(\"@remapped//wiz:quux\")\n"
              + "</pre>",
      parameters = {
        @Param(name = "relName", doc = "The label that will be resolved relative to this one.")
      },
      useStarlarkThread = true)
  public Label getRelative(String relName, StarlarkThread thread) throws LabelSyntaxException {
    return parseWithPackageContext(
        relName,
        PackageContext.of(
            packageIdentifier, BazelModuleContext.ofInnermostBzlOrThrow(thread).repoMapping()));
  }

  @Override
  public SkyFunctionName functionName() {
    return TRANSITIVE_TRAVERSAL;
  }

  @Override
  public int hashCode() {
    return hashCode(name, packageIdentifier);
  }

  /**
   * Specialization of {@link Arrays#hashCode()} that does not require constructing a 2-element
   * array.
   */
  private static int hashCode(Object obj1, Object obj2) {
    int result = 31 + (obj1 == null ? 0 : obj1.hashCode());
    return 31 * result + (obj2 == null ? 0 : obj2.hashCode());
  }

  /** Two labels are equal iff both their name and their package name are equal. */
  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (!(other instanceof Label)) {
      return false;
    }
    Label otherLabel = (Label) other;
    // Package identifiers are (weakly) interned so we compare them first.
    return packageIdentifier.equals(otherLabel.packageIdentifier) && name.equals(otherLabel.name);
  }

  /**
   * Defines the order between labels.
   *
   * <p>Labels are ordered primarily by package name and secondarily by target name. Both components
   * are ordered lexicographically. Thus {@code //a:b/c} comes before {@code //a/b:a}, i.e. the
   * position of the colon is significant to the order.
   */
  @Override
  public int compareTo(Label other) {
    if (this == other) {
      return 0;
    }
    return ComparisonChain.start()
        .compare(packageIdentifier, other.packageIdentifier)
        .compare(name, other.name)
        .result();
  }

  /**
   * Returns a suitable string for the user-friendly representation of the Label. Works even if the
   * argument is null.
   */
  public static String print(@Nullable Label label) {
    return label == null ? "(unknown)" : label.toString();
  }

  /**
   * Returns a {@link PathFragment} corresponding to the directory in which {@code label} would
   * reside, if it were interpreted to be a path.
   */
  public static PathFragment getContainingDirectory(Label label) {
    PathFragment pkg = label.getPackageFragment();
    String name = label.name;
    if (name.equals(".")) {
      return pkg;
    }
    if (PathFragment.isNormalizedRelativePath(name) && !PathFragment.containsSeparator(name)) {
      // Optimize for the common case of a label like '//pkg:target'.
      return pkg;
    }
    return pkg.getRelative(name).getParentDirectory();
  }

  @Override
  public boolean isImmutable() {
    return true;
  }

  @Override
  public void repr(Printer printer) {
    // TODO(wyv): Consider using StarlarkSemantics here too for optional unambiguity.
    printer.append("Label(");
    printer.repr(getCanonicalForm());
    printer.append(")");
  }

  @Override
  public void str(Printer printer, StarlarkSemantics semantics) {
    if (getRepository().isMain()
        && !semantics.getBool(
            BuildLanguageOptions.INCOMPATIBLE_UNAMBIGUOUS_LABEL_STRINGIFICATION)) {
      // If this label is in the main repo and we're not using unambiguous label stringification,
      // the result should always be "//foo:bar".
      printer.append(getCanonicalForm());
      return;
    }

    if (semantics.getBool(BuildLanguageOptions.ENABLE_BZLMOD)) {
      // If Bzlmod is enabled, we use canonical label literal syntax here and prepend an extra '@'.
      // So the result looks like "@@//foo:bar" for the main repo and "@@foo~1.0//bar:quux" for
      // other repos.
      printer.append(getUnambiguousCanonicalForm());
      return;
    }
    // If Bzlmod is not enabled, we just use a single '@'.
    // So the result looks like "@//foo:bar" for the main repo and "@foo//bar:quux" for other repos.
    printer.append(
        String.format(
            "@%s//%s:%s",
            packageIdentifier.getRepository().getName(),
            packageIdentifier.getPackageFragment(),
            name));
  }

  @Override
  public String expandToCommandLine() {
    // TODO(wyv): Consider using StarlarkSemantics here too for optional unambiguity.
    return getCanonicalForm();
  }

  private void checkRepoVisibilityForStarlark(String method) throws EvalException {
    if (!getRepository().isVisible()) {
      throw Starlark.errorf("'%s' is not allowed on invalid Label %s", method, this);
    }
  }

  /** {@link PooledInterner} for {@link Label}s. */
  public static final class LabelInterner extends PooledInterner<Label> {
    @Nullable static Pool<Label> globalPool = null;

    private final Striped<ReadWriteLock> interningLocks =
        Striped.readWriteLock(BlazeInterners.concurrencyLevel());

    /**
     * Sets the {@link Pool} to be used for interning.
     *
     * <p>The pool is strongly retained until another pool is set. {@code null} can be passed to
     * clear the global pool.
     */
    @ThreadSafety.ThreadCompatible
    public static void setGlobalPool(Pool<Label> pool) {
      // No synchronization is needed. Setting global pool is guaranteed to happen sequentially
      // since only one build can happen at the same time.
      globalPool = pool;
    }

    /**
     * Returns the read lock for {@link LabelInterner} to guard looking up {@link Label} instance
     * from either the pool or weak interner.
     */
    public Lock getLockForLabelLookup(Label label) {
      return interningLocks.get(label.getPackageIdentifier()).readLock();
    }

    /**
     * Returns the write lock to guard transfer {@link Label} from weak interner to the in-memory
     * {@link com.google.devtools.build.lib.packages.Package} node when it is done evaluation in
     * {@code SkyframeProgressReceiver}.
     *
     * @param packageIdentifier The {@link PackageIdentifier} of the done package node.
     */
    public Lock getLockForLabelTransferToPool(PackageIdentifier packageIdentifier) {
      return interningLocks.get(packageIdentifier).writeLock();
    }

    @Override
    protected Pool<Label> getPool() {
      return globalPool;
    }

    public boolean enabled() {
      return globalPool != null;
    }
  }
}
