filter toolchain deps consistently with --noimplicit_deps

repro for how this doesn't work today:
blaze cquery "deps(//experimental/users/juliexxia:my-library, 1)" --noimplicit_deps

PiperOrigin-RevId: 293880272
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Util.java b/src/main/java/com/google/devtools/build/lib/analysis/Util.java
index 22e9058..f1b24a4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/Util.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/Util.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
-import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet;
 import com.google.devtools.build.lib.packages.AttributeMap;
@@ -29,14 +28,12 @@
 import java.util.List;
 import java.util.Set;
 
-/**
- * Utility methods for use by ConfiguredTarget implementations.
- */
+/** Utility methods for use by ConfiguredTarget implementations. */
 public abstract class Util {
 
   private Util() {}
 
-  //---------- Label and Target related methods
+  // ---------- Label and Target related methods
 
   /**
    * Returns the workspace-relative path of the specified target (file or rule).
@@ -57,8 +54,8 @@
   }
 
   /**
-   * Returns the workspace-relative path of the specified target (file or rule),
-   * prepending a prefix and appending a suffix.
+   * Returns the workspace-relative path of the specified target (file or rule), prepending a prefix
+   * and appending a suffix.
    *
    * <p>For example, "//foo/bar:wiz" and "//foo:bar/wiz" both result in "foo/bar/wiz".
    */
@@ -66,9 +63,7 @@
     return target.getLabel().getPackageFragment().getRelative(prefix + target.getName() + suffix);
   }
 
-  /**
-   * Checks if a PathFragment contains a '-'.
-   */
+  /** Checks if a PathFragment contains a '-'. */
   public static boolean containsHyphen(PathFragment path) {
     return path.getPathString().indexOf('-') >= 0;
   }
@@ -76,12 +71,14 @@
   // ---------- Implicit dependency extractor
 
   /*
-   * Given a RuleContext, find all the implicit deps aka deps that weren't explicitly set in the
-   * build file and all toolchain deps.
+   * Given a RuleContext, find all the implicit attribute deps aka deps that weren't explicitly set
+   * in the build file but are attached behind the scenes to some attribute. This means this
+   * function does *not* cover deps attached other ways e.g. toolchain-related implicit deps
+   * (see {@link PostAnalysisQueryEnvironment#targetifyValues} for more info on further implicit
+   * deps filtering).
    * note: nodes that are depended on both implicitly and explicitly are considered explicit.
    */
   public static ImmutableSet<ConfiguredTargetKey> findImplicitDeps(RuleContext ruleContext) {
-    // (1) Consider rule attribute dependencies.
     Set<ConfiguredTargetKey> maybeImplicitDeps = CompactHashSet.create();
     Set<ConfiguredTargetKey> explicitDeps = CompactHashSet.create();
     AttributeMap attributes = ruleContext.attributes();
@@ -97,18 +94,6 @@
         }
       }
     }
-    // (2) Consider toolchain dependencies
-    ToolchainContext toolchainContext = ruleContext.getToolchainContext();
-    if (toolchainContext != null) {
-      BuildConfiguration config = ruleContext.getConfiguration();
-      for (Label toolchain : toolchainContext.resolvedToolchainLabels()) {
-        maybeImplicitDeps.add(ConfiguredTargetKey.of(toolchain, config));
-      }
-      maybeImplicitDeps.add(
-          ConfiguredTargetKey.of(toolchainContext.executionPlatform().label(), config));
-      maybeImplicitDeps.add(
-          ConfiguredTargetKey.of(toolchainContext.targetPlatform().label(), config));
-    }
     return ImmutableSet.copyOf(Sets.difference(maybeImplicitDeps, explicitDeps));
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
index 50db0b9c..d670db1 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
@@ -13,7 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.lib.query2;
 
-
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -70,7 +69,6 @@
 import com.google.devtools.build.skyframe.WalkableGraph;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -239,8 +237,8 @@
     for (T target : targets) {
       targetsByKey.put(getSkyKey(target), target);
     }
-    Map<SkyKey, Collection<T>> directDeps =
-        targetifyValues(graph.getDirectDeps(targetsByKey.keySet()));
+    Map<SkyKey, ImmutableList<ClassifiedDependency<T>>> directDeps =
+        targetifyValues(targetsByKey, graph.getDirectDeps(targetsByKey.keySet()));
     if (targetsByKey.size() != directDeps.size()) {
       Iterable<ConfiguredTargetKey> missingTargets =
           Sets.difference(targetsByKey.keySet(), directDeps.keySet()).stream()
@@ -249,7 +247,7 @@
       eventHandler.handle(Event.warn("Targets were missing from graph: " + missingTargets));
     }
     ThreadSafeMutableSet<T> result = createThreadSafeMutableSet();
-    for (Map.Entry<SkyKey, Collection<T>> entry : directDeps.entrySet()) {
+    for (Map.Entry<SkyKey, ImmutableList<ClassifiedDependency<T>>> entry : directDeps.entrySet()) {
       result.addAll(filterFwdDeps(targetsByKey.get(entry.getKey()), entry.getValue()));
     }
     return result;
@@ -261,9 +259,10 @@
     return getFwdDeps(targets);
   }
 
-  private Collection<T> filterFwdDeps(T configTarget, Collection<T> rawFwdDeps) {
+  private ImmutableList<T> filterFwdDeps(
+      T configTarget, ImmutableList<ClassifiedDependency<T>> rawFwdDeps) {
     if (settings.isEmpty()) {
-      return rawFwdDeps;
+      return getDependencies(rawFwdDeps);
     }
     return getAllowedDeps(configTarget, rawFwdDeps);
   }
@@ -275,8 +274,8 @@
     for (T target : targets) {
       targetsByKey.put(getSkyKey(target), target);
     }
-    Map<SkyKey, Collection<T>> reverseDepsByKey =
-        targetifyValues(graph.getReverseDeps(targetsByKey.keySet()));
+    Map<SkyKey, ImmutableList<ClassifiedDependency<T>>> reverseDepsByKey =
+        targetifyValues(targetsByKey, graph.getReverseDeps(targetsByKey.keySet()));
     if (targetsByKey.size() != reverseDepsByKey.size()) {
       Iterable<ConfiguredTargetKey> missingTargets =
           Sets.difference(targetsByKey.keySet(), reverseDepsByKey.keySet()).stream()
@@ -284,23 +283,27 @@
               .collect(Collectors.toList());
       eventHandler.handle(Event.warn("Targets were missing from graph: " + missingTargets));
     }
-    Map<T, Collection<T>> reverseDepsByCT = new HashMap<>();
-    for (Map.Entry<SkyKey, Collection<T>> entry : reverseDepsByKey.entrySet()) {
+    Map<T, ImmutableList<ClassifiedDependency<T>>> reverseDepsByCT = new HashMap<>();
+    for (Map.Entry<SkyKey, ImmutableList<ClassifiedDependency<T>>> entry :
+        reverseDepsByKey.entrySet()) {
       reverseDepsByCT.put(targetsByKey.get(entry.getKey()), entry.getValue());
     }
     return reverseDepsByCT.isEmpty() ? Collections.emptyList() : filterReverseDeps(reverseDepsByCT);
   }
 
-  private Collection<T> filterReverseDeps(Map<T, Collection<T>> rawReverseDeps) {
+  private Collection<T> filterReverseDeps(
+      Map<T, ImmutableList<ClassifiedDependency<T>>> rawReverseDeps) {
     Set<T> result = CompactHashSet.create();
-    for (Map.Entry<T, Collection<T>> targetAndRdeps : rawReverseDeps.entrySet()) {
-      ImmutableSet.Builder<T> ruleDeps = ImmutableSet.builder();
-      for (T parent : targetAndRdeps.getValue()) {
-        if (parent instanceof RuleConfiguredTarget
+    for (Map.Entry<T, ImmutableList<ClassifiedDependency<T>>> targetAndRdeps :
+        rawReverseDeps.entrySet()) {
+      ImmutableList.Builder<ClassifiedDependency<T>> ruleDeps = ImmutableList.builder();
+      for (ClassifiedDependency<T> parent : targetAndRdeps.getValue()) {
+        T dependency = parent.dependency;
+        if (parent.dependency instanceof RuleConfiguredTarget
             && dependencyFilter != DependencyFilter.ALL_DEPS) {
           ruleDeps.add(parent);
         } else {
-          result.add(parent);
+          result.add(dependency);
         }
       }
       result.addAll(getAllowedDeps(targetAndRdeps.getKey(), ruleDeps.build()));
@@ -312,7 +315,7 @@
    * @param target source target
    * @param deps next level of deps to filter
    */
-  protected Collection<T> getAllowedDeps(T target, Collection<T> deps) {
+  private ImmutableList<T> getAllowedDeps(T target, Collection<ClassifiedDependency<T>> deps) {
     // It's possible to query on a target that's configured in the host configuration. In those
     // cases if --notool_deps is turned on, we only allow reachable targets that are ALSO in the
     // host config. This is somewhat counterintuitive and subject to change in the future but seems
@@ -324,8 +327,8 @@
             deps.stream()
                 .filter(
                     dep ->
-                        getConfiguration(dep) != null
-                            && getConfiguration(dep).isToolConfiguration())
+                        getConfiguration(dep.dependency) != null
+                            && getConfiguration(dep.dependency).isToolConfiguration())
                 .collect(Collectors.toList());
       } else {
         deps =
@@ -336,53 +339,88 @@
                         // they can also appear on host-configured attributes like genrule#tools.
                         // While this may not be strictly correct, it's better to overapproximate
                         // than underapproximate the results.
-                        getConfiguration(dep) == null
-                            || !getConfiguration(dep).isToolConfiguration())
+                        getConfiguration(dep.dependency) == null
+                            || !getConfiguration(dep.dependency).isToolConfiguration())
                 .collect(Collectors.toList());
       }
     }
     if (settings.contains(Setting.NO_IMPLICIT_DEPS)) {
       RuleConfiguredTarget ruleConfiguredTarget = getRuleConfiguredTarget(target);
       if (ruleConfiguredTarget != null) {
-        Set<ConfiguredTargetKey> implicitDeps = ruleConfiguredTarget.getImplicitDeps();
-        deps =
-            deps.stream()
-                .filter(
-                    dep ->
-                        !implicitDeps.contains(
-                            ConfiguredTargetKey.of(getCorrectLabel(dep), getConfiguration(dep))))
-                .collect(Collectors.toList());
+        deps = deps.stream().filter(dep -> !dep.implicit).collect(Collectors.toList());
       }
     }
-    return deps;
+    return getDependencies(deps);
   }
 
   protected abstract RuleConfiguredTarget getRuleConfiguredTarget(T target);
 
-  protected Collection<T> targetifyValues(Iterable<SkyKey> dependencies)
-      throws InterruptedException {
-    Collection<T> values = new ArrayList<>();
-    for (SkyKey key : dependencies) {
-      if (key.functionName().equals(SkyFunctions.CONFIGURED_TARGET)) {
-        values.add(getValueFromKey(key));
-      } else if (key.functionName().equals(SkyFunctions.TOOLCHAIN_RESOLUTION)) {
-        // Also fetch these dependencies.
-        Collection<T> toolchainDeps = targetifyValues(graph.getDirectDeps(key));
-        values.addAll(toolchainDeps);
+  /**
+   * Returns targetified dependencies wrapped as {@link ClassifiedDependency} objects which include
+   * information on if the target is an implicit or explicit dependency.
+   *
+   * @param parent Parent target that knows about its attribute-attached implicit deps. If this is
+   *     null, that is a signal from the caller that all dependencies should be considered implicit.
+   * @param dependencies dependencies to targetify
+   */
+  private ImmutableList<ClassifiedDependency<T>> targetifyValues(
+      @Nullable T parent, Iterable<SkyKey> dependencies) throws InterruptedException {
+    Collection<ConfiguredTargetKey> implicitDeps = null;
+    if (parent != null) {
+      RuleConfiguredTarget ruleConfiguredTarget = getRuleConfiguredTarget(parent);
+      if (ruleConfiguredTarget != null) {
+        implicitDeps = ruleConfiguredTarget.getImplicitDeps();
       }
     }
-    return values;
+
+    ImmutableList.Builder<ClassifiedDependency<T>> values = ImmutableList.builder();
+    for (SkyKey key : dependencies) {
+      if (key.functionName().equals(SkyFunctions.CONFIGURED_TARGET)) {
+        T dependency = getValueFromKey(key);
+        boolean implicit =
+            implicitDeps == null
+                || implicitDeps.contains(
+                    ConfiguredTargetKey.of(
+                        getCorrectLabel(dependency), getConfiguration(dependency)));
+        values.add(new ClassifiedDependency<>(dependency, implicit));
+      } else if (key.functionName().equals(SkyFunctions.TOOLCHAIN_RESOLUTION)) {
+        // Also fetch these dependencies.
+        values.addAll(targetifyValues(null, graph.getDirectDeps(key)));
+      }
+    }
+    return values.build();
   }
 
-  protected Map<SkyKey, Collection<T>> targetifyValues(
-      Map<SkyKey, ? extends Iterable<SkyKey>> input) throws InterruptedException {
-    Map<SkyKey, Collection<T>> result = new HashMap<>();
+  private Map<SkyKey, ImmutableList<ClassifiedDependency<T>>> targetifyValues(
+      Map<SkyKey, T> fromTargetsByKey, Map<SkyKey, ? extends Iterable<SkyKey>> input)
+      throws InterruptedException {
+    Map<SkyKey, ImmutableList<ClassifiedDependency<T>>> result = new HashMap<>();
     for (Map.Entry<SkyKey, ? extends Iterable<SkyKey>> entry : input.entrySet()) {
-      result.put(entry.getKey(), targetifyValues(entry.getValue()));
+      SkyKey fromKey = entry.getKey();
+      result.put(fromKey, targetifyValues(fromTargetsByKey.get(fromKey), entry.getValue()));
     }
     return result;
   }
 
+  /** A class to store a dependency with some information. */
+  private static class ClassifiedDependency<T> {
+    // True if this dependency is attached implicitly.
+    boolean implicit;
+    T dependency;
+
+    private ClassifiedDependency(T dependency, boolean implicit) {
+      this.implicit = implicit;
+      this.dependency = dependency;
+    }
+  }
+
+  private static <T> ImmutableList<T> getDependencies(
+      Collection<ClassifiedDependency<T>> classifiedDependencies) {
+    return classifiedDependencies.stream()
+        .map(dep -> dep.dependency)
+        .collect(ImmutableList.toImmutableList());
+  }
+
   @Nullable
   protected abstract BuildConfiguration getConfiguration(T target);
 
diff --git a/src/test/java/com/google/devtools/build/lib/query2/testutil/PostAnalysisQueryTest.java b/src/test/java/com/google/devtools/build/lib/query2/testutil/PostAnalysisQueryTest.java
index 9a261e4..c984d1a 100644
--- a/src/test/java/com/google/devtools/build/lib/query2/testutil/PostAnalysisQueryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/query2/testutil/PostAnalysisQueryTest.java
@@ -258,6 +258,31 @@
         .doesNotContain(evalToListOfStrings(implicits));
   }
 
+  // Regression test for b/148550864
+  @Test
+  public void testNoImplicitDeps_platformDeps() throws Exception {
+    MockRule simpleRule = () -> MockRule.define("simple_rule");
+    helper.useRuleClassProvider(setRuleClassProviders(simpleRule).build());
+
+    writeFile(
+        "test/BUILD",
+        "simple_rule(name = 'my_rule')",
+        "platform(name = 'host_platform')",
+        "platform(name = 'execution_platform')");
+
+    ((PostAnalysisQueryHelper<T>) helper)
+        .useConfiguration(
+            "--host_platform=//test:host_platform",
+            "--extra_execution_platforms=//test:execution_platform");
+
+    // Check for platform dependencies
+    assertThat(evalToListOfStrings("deps(//test:my_rule)"))
+        .containsAtLeastElementsIn(
+            evalToListOfStrings("//test:execution_platform + //test:host_platform"));
+    helper.setQuerySettings(Setting.NO_IMPLICIT_DEPS);
+    assertThat(evalToListOfStrings("deps(//test:my_rule)")).containsExactly("//test:my_rule");
+  }
+
   @Test
   public void testNoImplicitDeps_computedDefault() throws Exception {
     MockRule computedDefaultRule =