Introduce TreeFileArtifact, which represents files under TreeArtifacts.
Remove ArtifactFile, which is rendered obsolete by TreeFileArtifact.

--
MOS_MIGRATED_REVID=119789154
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContextFactory.java b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContextFactory.java
index e87d72e..fd5f2dd 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContextFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContextFactory.java
@@ -25,5 +25,5 @@
  */
 public interface ActionExecutionContextFactory {
   ActionExecutionContext getContext(ActionInputFileCache graphFileCache,
-      MetadataHandler metadataHandler, Map<Artifact, Collection<ArtifactFile>> expandedInputs);
+      MetadataHandler metadataHandler, Map<Artifact, Collection<Artifact>> expandedInputs);
 }
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java b/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java
index 4471c4c..a4a85d2 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java
@@ -21,6 +21,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
+import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
 import com.google.devtools.build.lib.util.Preconditions;
 import com.google.devtools.build.lib.vfs.PathFragment;
 
@@ -41,7 +42,7 @@
       final ActionGraph actionGraph) {
     return new ArtifactExpander() {
       @Override
-      public void expand(Artifact mm, Collection<? super ArtifactFile> output) {
+      public void expand(Artifact mm, Collection<? super Artifact> output) {
         // Skyframe is stricter in that it checks that "mm" is a input of the action, because
         // it cannot expand arbitrary middlemen without access to a global action graph.
         // We could check this constraint here too, but it seems unnecessary. This code is
@@ -129,60 +130,57 @@
   }
 
   /**
-   * Instantiates a concrete ArtifactFile with the given parent Artifact and path
+   * Instantiates a concrete TreeFileArtifact with the given parent Artifact and path
    * relative to that Artifact.
    */
-  public static ArtifactFile artifactFile(Artifact parent, PathFragment relativePath) {
+  public static TreeFileArtifact treeFileArtifact(
+      Artifact parent, PathFragment relativePath) {
     Preconditions.checkState(parent.isTreeArtifact(),
         "Given parent %s must be a TreeArtifact", parent);
-    return new TreeArtifactFile(parent, relativePath);
+    return new TreeFileArtifact(parent, relativePath);
+  }
+
+  public static TreeFileArtifact treeFileArtifact(
+      Artifact parent, PathFragment relativePath, ArtifactOwner artifactOwner) {
+    Preconditions.checkState(parent.isTreeArtifact(),
+        "Given parent %s must be a TreeArtifact", parent);
+    return new TreeFileArtifact(
+        parent,
+        relativePath,
+        artifactOwner);
   }
 
   /**
-   * Instantiates a concrete ArtifactFile with the given parent Artifact and path
+   * Instantiates a concrete TreeFileArtifact with the given parent Artifact and path
    * relative to that Artifact.
    */
-  public static ArtifactFile artifactFile(Artifact parent, String relativePath) {
-    return artifactFile(parent, new PathFragment(relativePath));
+  public static TreeFileArtifact treeFileArtifact(Artifact parent, String relativePath) {
+    return treeFileArtifact(parent, new PathFragment(relativePath));
   }
 
-  /** Returns an Iterable of ArtifactFiles with the given parent and parent relative paths. */
-  public static Iterable<ArtifactFile> asArtifactFiles(
+  /** Returns an Iterable of TreeFileArtifacts with the given parent and parent relative paths. */
+  public static Iterable<TreeFileArtifact> asTreeFileArtifacts(
       final Artifact parent, Iterable<? extends PathFragment> parentRelativePaths) {
     Preconditions.checkState(parent.isTreeArtifact(),
         "Given parent %s must be a TreeArtifact", parent);
     return Iterables.transform(parentRelativePaths,
-        new Function<PathFragment, ArtifactFile>() {
+        new Function<PathFragment, TreeFileArtifact>() {
           @Override
-          public ArtifactFile apply(PathFragment pathFragment) {
-            return artifactFile(parent, pathFragment);
+          public TreeFileArtifact apply(PathFragment pathFragment) {
+            return treeFileArtifact(parent, pathFragment);
           }
         });
   }
 
-  /** Returns an Collection of ArtifactFiles with the given parent and parent-relative paths. */
-  public static Collection<ArtifactFile> asArtifactFiles(
-      final Artifact parent, Collection<? extends PathFragment> parentRelativePaths) {
-    Preconditions.checkState(parent.isTreeArtifact(),
-        "Given parent %s must be a TreeArtifact", parent);
-    return Collections2.transform(parentRelativePaths,
-        new Function<PathFragment, ArtifactFile>() {
-          @Override
-          public ArtifactFile apply(PathFragment pathFragment) {
-            return artifactFile(parent, pathFragment);
-          }
-        });
-  }
-
-  /** Returns a Set of ArtifactFiles with the given parent and parent relative paths. */
-  public static Set<ArtifactFile> asArtifactFiles(
+  /** Returns a Set of TreeFileArtifacts with the given parent and parent-relative paths. */
+  public static Set<TreeFileArtifact> asTreeFileArtifacts(
       final Artifact parent, Set<? extends PathFragment> parentRelativePaths) {
     Preconditions.checkState(parent.isTreeArtifact(),
         "Given parent %s must be a TreeArtifact", parent);
 
-    ImmutableSet.Builder<ArtifactFile> builder = ImmutableSet.builder();
+    ImmutableSet.Builder<TreeFileArtifact> builder = ImmutableSet.builder();
     for (PathFragment path : parentRelativePaths) {
-      builder.add(artifactFile(parent, path));
+      builder.add(treeFileArtifact(parent, path));
     }
 
     return builder.build();
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
index 4e8989f..f28683b 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
@@ -70,7 +70,7 @@
  * In the usual case, an Artifact represents a single file. However, an Artifact may
  * also represent the following:
  * <ul>
- * <li>A TreeArtifact, which is a directory containing a tree of unknown {@link ArtifactFile}s.
+ * <li>A TreeArtifact, which is a directory containing a tree of unknown {@link Artifact}s.
  * In the future, Actions will be able to examine these files as inputs and declare them as outputs
  * at execution time, but this is not yet implemented. This is used for Actions where
  * the inputs and/or outputs might not be discoverable except during Action execution.
@@ -87,8 +87,8 @@
  * by new rule implementations.
  * </ul>
  * <p/>
- * This class implements {@link ArtifactFile}, and is modeled as an Artifact "containing" itself
- * as an ArtifactFile.
+ * This class implements {@link Artifact}, and is modeled as an Artifact "containing" itself
+ * as an Artifact.
  * <p/>
  * <p>This class is "theoretically" final; it should not be subclassed except by
  * {@link SpecialArtifact}.
@@ -98,7 +98,7 @@
     doc = "This type represents a file used by the build system. It can be "
         + "either a source file or a derived file produced by a rule.")
 public class Artifact
-    implements FileType.HasFilename, ArtifactFile, SkylarkValue , Comparable<Object> {
+    implements FileType.HasFilename, ActionInput, SkylarkValue, Comparable<Object> {
 
   /**
    * Compares artifact according to their exec paths. Sorts null values first.
@@ -136,7 +136,7 @@
      * <p>{@code artifact.isMiddlemanArtifact() || artifact.isTreeArtifact()} must be true.
      * Only aggregating middlemen and tree artifacts are expanded.
      */
-    void expand(Artifact artifact, Collection<? super ArtifactFile> output);
+    void expand(Artifact artifact, Collection<? super Artifact> output);
   }
 
   public static final ImmutableList<Artifact> NO_ARTIFACTS = ImmutableList.of();
@@ -239,20 +239,20 @@
         root.getExecPath().getRelative(rootRelativePath), ArtifactOwner.NULL_OWNER);
   }
 
-  @Override
   public final Path getPath() {
     return path;
   }
 
+  public boolean hasParent() {
+    return getParent() != null;
+  }
+
   /**
-   * Returns the Artifact containing this ArtifactFile. Since normal Artifacts correspond
-   * to only one ArtifactFile -- itself -- for normal Artifacts, this method returns {@code this}.
-   * For special artifacts, throws {@link UnsupportedOperationException}.
-   * See also {@link ArtifactFile#getParent()}.
+   * Returns the parent Artifact containing this Artifact. Artifacts without parents shall
+   * return null.
    */
-  @Override
-  public Artifact getParent() throws UnsupportedOperationException {
-    return this;
+  @Nullable public Artifact getParent() {
+    return null;
   }
 
   /**
@@ -307,7 +307,6 @@
    * package-path entries (for source Artifacts), or one of the bin, genfiles or includes dirs
    * (for derived Artifacts). It will always be an ancestor of getPath().
    */
-  @Override
   @SkylarkCallable(name = "root", structField = true,
       doc = "The root beneath which this file resides."
   )
@@ -315,18 +314,16 @@
     return root;
   }
 
-  @Override
   public final PathFragment getExecPath() {
     return execPath;
   }
 
   /**
-   * Returns the path of this ArtifactFile relative to this containing Artifact. Since
-   * ordinary Artifacts correspond to only one ArtifactFile -- itself -- for ordinary Artifacts,
+   * Returns the path of this Artifact relative to this containing Artifact. Since
+   * ordinary Artifacts correspond to only one Artifact -- itself -- for ordinary Artifacts,
    * this just returns the empty path. For special Artifacts, throws
-   * {@link UnsupportedOperationException}. See also {@link ArtifactFile#getParentRelativePath()}.
+   * {@link UnsupportedOperationException}. See also {@link Artifact#getParentRelativePath()}.
    */
-  @Override
   public PathFragment getParentRelativePath() {
     return PathFragment.EMPTY_FRAGMENT;
   }
@@ -350,7 +347,7 @@
   }
 
   /**
-   * Returns true iff this is a TreeArtifact representing a directory tree containing ArtifactFiles.
+   * Returns true iff this is a TreeArtifact representing a directory tree containing Artifacts.
    */
   public boolean isTreeArtifact() {
     return false;
@@ -418,13 +415,77 @@
     }
 
     @Override
+    public boolean hasParent() {
+      return false;
+    }
+
+    @Override
+    @Nullable
     public Artifact getParent() {
-      throw new UnsupportedOperationException();
+      return null;
+    }
+
+    @Override
+    @Nullable
+    public PathFragment getParentRelativePath() {
+      return null;
+    }
+  }
+
+  /**
+   * A special kind of artifact that represents a concrete file created at execution time under
+   * its associated TreeArtifact.
+   *
+   * <p> TreeFileArtifacts should be only created during execution time inside some special actions
+   * to support action inputs and outputs that are unpredictable at analysis time.
+   * TreeFileArtifacts should not be created directly by any rules at analysis time.
+   *
+   * <p>We subclass {@link Artifact} instead of storing the extra fields directly inside in order
+   * to save memory. The proportion of TreeFileArtifacts is very small, and by not having to keep
+   * around the extra fields for the rest we save some memory.
+   */
+  @Immutable
+  public static final class TreeFileArtifact extends Artifact {
+    private final Artifact parentTreeArtifact;
+    private final PathFragment parentRelativePath;
+
+    /**
+     * Constructs a TreeFileArtifact with the given parent-relative path under the given parent
+     * TreeArtifact. The {@link ArtifactOwner} of the TreeFileArtifact is the {@link ArtifactOwner}
+     * of the parent TreeArtifact.
+     */
+    TreeFileArtifact(Artifact parent, PathFragment parentRelativePath) {
+      this(parent, parentRelativePath, parent.getArtifactOwner());
+    }
+
+    /**
+     * Constructs a TreeFileArtifact with the given parent-relative path under the given parent
+     * TreeArtifact, owned by the given {@code artifactOwner}.
+     */
+    TreeFileArtifact(Artifact parent, PathFragment parentRelativePath,
+        ArtifactOwner artifactOwner) {
+      super(
+          parent.getPath().getRelative(parentRelativePath),
+          parent.getRoot(),
+          parent.getExecPath().getRelative(parentRelativePath),
+          artifactOwner);
+      Preconditions.checkState(
+          parent.isTreeArtifact(),
+          "The parent of TreeFileArtifact (parent-relative path: %s) is not a TreeArtifact: %s",
+          parentRelativePath,
+          parent);
+      this.parentTreeArtifact = parent;
+      this.parentRelativePath = parentRelativePath;
+    }
+
+    @Override
+    public Artifact getParent() {
+      return parentTreeArtifact;
     }
 
     @Override
     public PathFragment getParentRelativePath() {
-      throw new UnsupportedOperationException();
+      return parentRelativePath;
     }
   }
 
@@ -432,7 +493,6 @@
    * Returns the relative path to this artifact relative to its root.  (Useful
    * when deriving output filenames from input files, etc.)
    */
-  @Override
   public final PathFragment getRootRelativePath() {
     return rootRelativePath;
   }
@@ -478,7 +538,6 @@
     return getRootRelativePath().getPathString();
   }
 
-  @Override
   public final String prettyPrint() {
     // toDetailString would probably be more useful to users, but lots of tests rely on the
     // current values.
@@ -550,26 +609,26 @@
   /**
    * Formatter for execPath PathFragment output.
    */
-  private static final Function<ArtifactFile, PathFragment> EXEC_PATH_FORMATTER =
-      new Function<ArtifactFile, PathFragment>() {
+  private static final Function<Artifact, PathFragment> EXEC_PATH_FORMATTER =
+      new Function<Artifact, PathFragment>() {
         @Override
-        public PathFragment apply(ArtifactFile input) {
+        public PathFragment apply(Artifact input) {
           return input.getExecPath();
         }
       };
 
-  public static final Function<ArtifactFile, String> ROOT_RELATIVE_PATH_STRING =
-      new Function<ArtifactFile, String>() {
+  public static final Function<Artifact, String> ROOT_RELATIVE_PATH_STRING =
+      new Function<Artifact, String>() {
         @Override
-        public String apply(ArtifactFile artifact) {
+        public String apply(Artifact artifact) {
           return artifact.getRootRelativePath().getPathString();
         }
       };
 
-  public static final Function<ArtifactFile, String> ABSOLUTE_PATH_STRING =
-      new Function<ArtifactFile, String>() {
+  public static final Function<Artifact, String> ABSOLUTE_PATH_STRING =
+      new Function<Artifact, String>() {
         @Override
-        public String apply(ArtifactFile artifact) {
+        public String apply(Artifact artifact) {
           return artifact.getPath().getPathString();
         }
       };
@@ -654,8 +713,8 @@
    * {@link MiddlemanType#AGGREGATING_MIDDLEMAN} middleman actions expanded once.
    */
   public static void addExpandedArtifacts(Iterable<Artifact> artifacts,
-      Collection<? super ArtifactFile> output, ArtifactExpander artifactExpander) {
-    addExpandedArtifacts(artifacts, output, Functions.<ArtifactFile>identity(), artifactExpander);
+      Collection<? super Artifact> output, ArtifactExpander artifactExpander) {
+    addExpandedArtifacts(artifacts, output, Functions.<Artifact>identity(), artifactExpander);
   }
 
   /**
@@ -690,7 +749,7 @@
    */
   private static <E> void addExpandedArtifacts(Iterable<? extends Artifact> artifacts,
                                                Collection<? super E> output,
-                                               Function<? super ArtifactFile, E> outputFormatter,
+                                               Function<? super Artifact, E> outputFormatter,
                                                ArtifactExpander artifactExpander) {
     for (Artifact artifact : artifacts) {
       if (artifact.isMiddlemanArtifact() || artifact.isTreeArtifact()) {
@@ -703,12 +762,12 @@
 
   private static <E> void expandArtifact(Artifact middleman,
       Collection<? super E> output,
-      Function<? super ArtifactFile, E> outputFormatter,
+      Function<? super Artifact, E> outputFormatter,
       ArtifactExpander artifactExpander) {
     Preconditions.checkArgument(middleman.isMiddlemanArtifact() || middleman.isTreeArtifact());
-    List<ArtifactFile> artifacts = new ArrayList<>();
+    List<Artifact> artifacts = new ArrayList<>();
     artifactExpander.expand(middleman, artifacts);
-    for (ArtifactFile artifact : artifacts) {
+    for (Artifact artifact : artifacts) {
       output.add(outputFormatter.apply(artifact));
     }
   }
@@ -785,7 +844,7 @@
   /**
    * Converts artifacts into their exec paths. Returns an immutable list.
    */
-  public static List<PathFragment> asPathFragments(Iterable<? extends ArtifactFile> artifacts) {
+  public static List<PathFragment> asPathFragments(Iterable<? extends Artifact> artifacts) {
     return ImmutableList.copyOf(Iterables.transform(artifacts, EXEC_PATH_FORMATTER));
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFile.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFile.java
deleted file mode 100644
index be165fb..0000000
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFile.java
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2016 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.actions;
-
-import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
-
-/**
- * An ArtifactFile represents a file used by the build system during execution of Actions.
- * An ArtifactFile may be a source file or a derived (output) file.
- *
- * During the creation of {@link com.google.devtools.build.lib.analysis.ConfiguredTarget}s and
- * {@link com.google.devtools.build.lib.analysis.ConfiguredAspect}s, generally one
- * is only interested in {@link Artifact}s. Most Actions are only interested in Artifacts as well.
- * During action execution, however, some Artifacts (notably
- * TreeArtifacts) may correspond to more than one ArtifactFile. This is for the benefit
- * of some Actions which only know their inputs or outputs at execution time, via the
- * TreeArtifact mechanism.
- * <ul><li>
- *   Generally, most Artifacts represent a file on the filesystem, and correspond to exactly one
- *   ArtifactFile. For ease of use, Artifact itself implements ArtifactFile, and is modeled
- *   as an Artifact containing itself.
- * </li><li>
- *   Some Artifacts are so-called TreeArtifacts, for which the method
- *   {@link Artifact#isTreeArtifact()} returns true. These artifacts contain a (possibly empty)
- *   tree of ArtifactFiles, which are unknown until Action execution time.
- * </li><li>
- *   Some 'special artifacts' do not meaningfully represent a file or tree of files.
- * </li></ul>
- */
-public interface ArtifactFile extends ActionInput {
-  /**
-   * Returns the exec path of this ArtifactFile. The exec path is a relative path
-   * that is suitable for accessing this artifact relative to the execution
-   * directory for this build.
-   */
-  PathFragment getExecPath();
-
-  /**
-   * Returns the path of this ArtifactFile relative to its containing Artifact.
-   * See {@link #getParent()}.
-   * */
-  PathFragment getParentRelativePath();
-
-  /** Returns the location of this ArtifactFile on the filesystem. */
-  Path getPath();
-
-  /**
-   * Returns the relative path to this ArtifactFile relative to its root. Useful
-   * when deriving output filenames from input files, etc.
-   */
-  PathFragment getRootRelativePath();
-
-  /**
-   * Returns the root beneath which this ArtifactFile resides, if any. This may be one of the
-   * package-path entries (for files belonging to source {@link Artifact}s), or one of the bin
-   * genfiles or includes dirs (for files belonging to derived {@link Artifact}s). It will always be
-   * an ancestor of {@link #getPath()}.
-   */
-  Root getRoot();
-
-  /**
-   * Returns the Artifact containing this File. For Artifacts which are files, returns
-   * {@code this}. Otherwise, returns a Artifact whose path and root relative path
-   * are an ancestor of this ArtifactFile's path, and for which {@link Artifact#isTreeArtifact()}
-   * returns true.
-   * <p/>
-   * For ArtifactFiles which are special artifacts (including TreeArtifacts),
-   * this method throws UnsupportedOperationException, because the ArtifactFile abstraction
-   * fails for those cases.
-   */
-  Artifact getParent() throws UnsupportedOperationException;
-
-  /**
-   * Returns a pretty string representation of the path denoted by this ArtifactFile, suitable for
-   * use in user error messages. ArtifactFiles beneath a root will be printed relative to that root;
-   * other ArtifactFiles will be printed as an absolute path.
-   *
-   * <p>(The toString method is intended for developer messages since its more informative.)
-   */
-  String prettyPrint();
-
-  /**
-   * Two ArtifactFiles are equal if their parent Artifacts and parent relative paths
-   * are equal. If this ArtifactFile is itself an Artifact, see {@link Artifact#equals(Object)}.
-   * Note that within a build, two ArtifactFiles produced by the build system will be equal
-   * if and only if their full paths are equal.
-   */
-  @Override
-  boolean equals(Object o);
-
-  /**
-   * An ArtifactFile's hash code should be equal to
-   * {@code {@link #getParent().hashCode()} * 257 +
-   * {@link #getParentRelativePath().hashCode()}}. If this ArtifactFile is itself an Artifact,
-   * see {@link Artifact#hashCode()}.
-   * <p/>
-   * 257 is chosen because it is a Fermat prime, and has minimal 'bit overlap' with the Mersenne
-   * prime of 31 used in {@link Path#hashCode()} and {@link String#hashCode()}.
-   */
-  @Override
-  int hashCode();
-}
diff --git a/src/main/java/com/google/devtools/build/lib/actions/TreeArtifactFile.java b/src/main/java/com/google/devtools/build/lib/actions/TreeArtifactFile.java
deleted file mode 100644
index 8b4cde8..0000000
--- a/src/main/java/com/google/devtools/build/lib/actions/TreeArtifactFile.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2016 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.actions;
-
-import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
-
-/** An ArtifactFile implementation for descendants of TreeArtifacts. */
-final class TreeArtifactFile implements ArtifactFile {
-  private final Artifact parent;
-  private final PathFragment parentRelativePath;
-
-  TreeArtifactFile(Artifact parent, PathFragment parentRelativePath) {
-    Preconditions.checkArgument(parent.isTreeArtifact(), "%s must be a TreeArtifact", parent);
-    this.parent = parent;
-    this.parentRelativePath = parentRelativePath;
-  }
-
-  @Override
-  public PathFragment getExecPath() {
-    return parent.getExecPath().getRelative(parentRelativePath);
-  }
-
-  @Override
-  public PathFragment getParentRelativePath() {
-    return parentRelativePath;
-  }
-
-  @Override
-  public Path getPath() {
-    return parent.getPath().getRelative(parentRelativePath);
-  }
-
-  @Override
-  public PathFragment getRootRelativePath() {
-    return parent.getRootRelativePath().getRelative(parentRelativePath);
-  }
-
-  @Override
-  public Root getRoot() {
-    return parent.getRoot();
-  }
-
-  @Override
-  public Artifact getParent() {
-    return parent;
-  }
-
-  @Override
-  public String prettyPrint() {
-    return getRootRelativePath().toString();
-  }
-
-  @Override
-  public String getExecPathString() {
-    return getExecPath().toString();
-  }
-
-  @Override
-  public String toString() {
-    return "ArtifactFile:[" + parent.toDetailString() + "]" + parentRelativePath;
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (!(other instanceof ArtifactFile)) {
-      return false;
-    }
-
-    ArtifactFile that = (ArtifactFile) other;
-    return this.getParent().equals(that.getParent())
-        && this.getParentRelativePath().equals(that.getParentRelativePath());
-  }
-
-  @Override
-  public int hashCode() {
-    return getParent().hashCode() * 257 + getParentRelativePath().hashCode();
-  }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java
index e75ab9a..6fcb964 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java
@@ -15,7 +15,7 @@
 
 import com.google.devtools.build.lib.actions.ActionInput;
 import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.actions.ArtifactFile;
+import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
 import com.google.devtools.build.lib.actions.MiddlemanAction;
 import com.google.devtools.build.lib.vfs.FileStatus;
 
@@ -52,9 +52,8 @@
   /**
    * Registers the given output as contents of a TreeArtifact, without injecting its digest.
    * Prefer {@link #injectDigest} when the digest is available.
-   * @throws IllegalStateException if the given output does not have a TreeArtifact parent.
    */
-  void addExpandedTreeOutput(ArtifactFile output) throws IllegalStateException;
+  void addExpandedTreeOutput(TreeFileArtifact output);
 
   /**
    * Injects provided digest into the metadata handler, simultaneously caching lstat() data as well.
@@ -83,14 +82,14 @@
   boolean artifactOmitted(Artifact artifact);
 
   /**
-   * @return Whether the ArtifactFile's data was injected.
-   * @throws IOException if implementation tried to stat the ArtifactFile which threw an exception.
+   * @return Whether the artifact's data was injected.
+   * @throws IOException if implementation tried to stat the Artifact which threw an exception.
    *         Technically, this means that the artifact could not have been injected, but by throwing
    *         here we save the caller trying to stat this file on their own and throwing the same
    *         exception. Implementations are not guaranteed to throw in this case if they are able to
    *         determine that the artifact is not injected without statting it.
    */
-  boolean isInjected(ArtifactFile file) throws IOException;
+  boolean isInjected(Artifact file) throws IOException;
 
   /**
    * Discards all known output artifact metadata, presumably because outputs will be modified.