RepoContext & PackageContext for Label constructors

RepoContext = RepositoryName + RepositoryMapping
PackageContext = PackageIdentifier + RepositoryMapping
               = RepositoryName + PathFragment + RepositoryMapping
               = RepoContext + PathFragment

Making these one object stresses the fact that they often need to be used together for correctness.

Work towards https://github.com/bazelbuild/bazel/issues/14852

PiperOrigin-RevId: 458437385
Change-Id: I68c0f61ebfcfbb2f5ff929d985bdfa0ac45a912a
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
index 9f17494..ca39596 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
@@ -960,8 +960,7 @@
     BazelModuleContext moduleContext =
         BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread));
     try {
-      return Label.parseWithRepoContext(
-          labelString, moduleContext.label().getRepository(), moduleContext.repoMapping());
+      return Label.parseWithRepoContext(labelString, moduleContext.packageContext());
     } catch (LabelSyntaxException e) {
       throw Starlark.errorf("Illegal absolute label syntax: %s", e.getMessage());
     }
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/BazelModuleContext.java b/src/main/java/com/google/devtools/build/lib/cmdline/BazelModuleContext.java
index 6fd32f5..95e4b8f 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/BazelModuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/BazelModuleContext.java
@@ -80,4 +80,8 @@
     return new AutoValue_BazelModuleContext(
         label, repoMapping, filename, loads, bzlTransitiveDigest);
   }
+
+  public final Label.PackageContext packageContext() {
+    return Label.PackageContext.of(label().getPackageIdentifier(), repoMapping());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
index ebd9799..6ecb937 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
@@ -15,6 +15,7 @@
 
 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.ImmutableMap;
@@ -80,6 +81,37 @@
 
   private static final Interner<Label> LABEL_INTERNER = BlazeInterners.newWeakInterner();
 
+  /** 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
@@ -96,26 +128,28 @@
 
   /** Computes the repo name for the label, within the context of a current repo. */
   private static RepositoryName computeRepoNameWithRepoContext(
-      Parts parts, RepositoryName currentRepo, RepositoryMapping repoMapping) {
+      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 : currentRepo;
+      return ABSOLUTE_PACKAGE_NAMES.contains(parts.pkg)
+          ? RepositoryName.MAIN
+          : repoContext.currentRepo();
     }
-    return repoMapping.get(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
-   * repoMapping}, otherwise the label will be assumed to be in {@code currentRepo}.
+   * repoContext.repoMapping()}, otherwise the label will be assumed to be in {@code
+   * repoContext.currentRepo()}.
    */
-  public static Label parseWithRepoContext(
-      String raw, RepositoryName currentRepo, RepositoryMapping repoMapping)
+  public static Label parseWithRepoContext(String raw, RepoContext repoContext)
       throws LabelSyntaxException {
     Parts parts = Parts.parse(raw);
     parts.checkPkgIsAbsolute();
-    RepositoryName repoName = computeRepoNameWithRepoContext(parts, currentRepo, repoMapping);
+    RepositoryName repoName = computeRepoNameWithRepoContext(parts, repoContext);
     return createUnvalidated(
         PackageIdentifier.create(repoName, PathFragment.create(parts.pkg)), parts.target);
   }
@@ -124,23 +158,19 @@
    * 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
-   * repoMapping}, otherwise the label will be assumed to be in the repo of {@code
-   * packageIdentifier}.
+   * packageContext.repoMapping()}, otherwise the label will be assumed to be in the repo of {@code
+   * packageContext.currentRepo()}.
    */
-  public static Label parseWithPackageContext(
-      String raw, PackageIdentifier packageIdentifier, RepositoryMapping repoMapping)
+  public static Label parseWithPackageContext(String raw, PackageContext packageContext)
       throws LabelSyntaxException {
     Parts parts = Parts.parse(raw);
     // pkg is either absolute or empty
     if (!parts.pkg.isEmpty()) {
       parts.checkPkgIsAbsolute();
     }
-    RepositoryName repoName =
-        computeRepoNameWithRepoContext(parts, packageIdentifier.getRepository(), repoMapping);
+    RepositoryName repoName = computeRepoNameWithRepoContext(parts, packageContext);
     PathFragment pkgFragment =
-        parts.pkgIsAbsolute
-            ? PathFragment.create(parts.pkg)
-            : packageIdentifier.getPackageFragment();
+        parts.pkgIsAbsolute ? PathFragment.create(parts.pkg) : packageContext.packageFragment();
     return createUnvalidated(PackageIdentifier.create(repoName, pkgFragment), parts.target);
   }
 
@@ -168,7 +198,7 @@
   public static Label parseAbsolute(String absName, RepositoryMapping repositoryMapping)
       throws LabelSyntaxException {
     Preconditions.checkNotNull(repositoryMapping);
-    return parseWithRepoContext(absName, RepositoryName.MAIN, repositoryMapping);
+    return parseWithRepoContext(absName, RepoContext.of(RepositoryName.MAIN, repositoryMapping));
   }
 
   // TODO(b/200024947): Remove this.
@@ -480,7 +510,8 @@
     if (relName.isEmpty()) {
       throw new LabelSyntaxException("empty package-relative label");
     }
-    return parseWithPackageContext(relName, packageIdentifier, repositoryMapping);
+    return parseWithPackageContext(
+        relName, PackageContext.of(packageIdentifier, repositoryMapping));
   }
 
   // TODO(b/200024947): Remove this.
diff --git a/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java b/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java
index cf3b7d7..1fef168 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/LabelConverter.java
@@ -38,23 +38,24 @@
   public static LabelConverter forBzlEvaluatingThread(StarlarkThread thread) {
     BazelModuleContext moduleContext =
         BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread));
-    return new LabelConverter(
-        moduleContext.label().getPackageIdentifier(), moduleContext.repoMapping());
+    return new LabelConverter(moduleContext.packageContext());
   }
 
-  private final PackageIdentifier base;
-  private final RepositoryMapping repositoryMapping;
+  private final Label.PackageContext packageContext;
   private final Map<String, Label> labelCache = new HashMap<>();
 
+  public LabelConverter(Label.PackageContext packageContext) {
+    this.packageContext = packageContext;
+  }
+
   /** Creates a label converter using the given base package and repo mapping. */
   public LabelConverter(PackageIdentifier base, RepositoryMapping repositoryMapping) {
-    this.base = base;
-    this.repositoryMapping = repositoryMapping;
+    this(Label.PackageContext.of(base, repositoryMapping));
   }
 
   /** Returns the base package identifier that relative labels will be resolved against. */
   PackageIdentifier getBasePackage() {
-    return base;
+    return packageContext.packageIdentifier();
   }
 
   /** Returns the Label corresponding to the input, using the current conversion context. */
@@ -65,7 +66,7 @@
     // label-strings across all their attribute values.
     Label converted = labelCache.get(input);
     if (converted == null) {
-      converted = Label.parseWithPackageContext(input, base, repositoryMapping);
+      converted = Label.parseWithPackageContext(input, packageContext);
       labelCache.put(input, converted);
     }
     return converted;
@@ -73,6 +74,6 @@
 
   @Override
   public String toString() {
-    return base.toString();
+    return getBasePackage().toString();
   }
 }