Factor out filtering functions from universe in rdeps(u, x, 1).
For example, the query `rdeps(kind('binary', ...), //foo:bar, 1)` will be
first rewritten as `kind('binary', rdeps(..., //foo:bar, 1))`.
This rewriting is not much of an optimization on its own, but it may make the
query eligible for the RdepsToAllRdepsQueryExpressionMapper, which can rewrite
the example intermediate query as `kind('binary', allrdeps(//foo:bar, 1))`.
RELNOTES:
None.
PiperOrigin-RevId: 260773356
diff --git a/src/main/java/com/google/devtools/build/lib/query2/FilteredDirectRdepsInUniverseExpressionMapper.java b/src/main/java/com/google/devtools/build/lib/query2/FilteredDirectRdepsInUniverseExpressionMapper.java
new file mode 100644
index 0000000..a18535e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/query2/FilteredDirectRdepsInUniverseExpressionMapper.java
@@ -0,0 +1,177 @@
+// Copyright 2019 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.query2;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.cmdline.TargetPattern;
+import com.google.devtools.build.lib.query2.engine.BinaryOperatorExpression;
+import com.google.devtools.build.lib.query2.engine.FunctionExpression;
+import com.google.devtools.build.lib.query2.engine.LetExpression;
+import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Argument;
+import com.google.devtools.build.lib.query2.engine.QueryEnvironment.ArgumentType;
+import com.google.devtools.build.lib.query2.engine.QueryEnvironment.FilteringQueryFunction;
+import com.google.devtools.build.lib.query2.engine.QueryExpression;
+import com.google.devtools.build.lib.query2.engine.QueryExpressionMapper;
+import com.google.devtools.build.lib.query2.engine.QueryExpressionVisitor;
+import com.google.devtools.build.lib.query2.engine.RdepsFunction;
+import com.google.devtools.build.lib.query2.engine.SetExpression;
+import com.google.devtools.build.lib.query2.engine.TargetLiteral;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link QueryExpressionMapper} that transforms each occurrence of an expression of the form
+ * {@literal 'rdeps(filterfunc(<pat>, <universeScope>), <T>, 1)'} to {@literal 'filterfunc(<pat>,
+ * rdeps(<universeScope>, <T>, 1))'}.
+ *
+ * <p>By factoring the filterfunc out of the rdeps universe, we prepare the query for transformation
+ * by the {@link RdepsToAllRdepsQueryExpressionMapper}.
+ *
+ * <p><em>Note:</em> we require a max depth of 1 because the transformation is only sound with a
+ * depth of 1. Consider a query of depth 2.
+ *
+ * <ol>
+ * <li>{@literal 'rdeps(kind(^foo$, //...), //base:b, 2)'} yields these two disjoint sets:
+ * <ol>
+ * <li>targets of kind 'foo' that depend directly on //base:b
+ * <li>targets of kind 'foo' that do not depend directly on //base:b, and depend on a target
+ * of kind 'foo' that itself depends directly on //base:b
+ * </ol>
+ * <li>{@literal 'kind(^foo$, rdeps(//..., //base:b, 2))'} yields these two disjoint sets:
+ * <ol>
+ * <li>targets of kind 'foo' that depend directly on //base:b
+ * <li>targets of kind 'foo' that do not depend directly on //base:b, and depend on a target
+ * of <em>any</em> kind that itself depends directly on //base:b
+ * </ol>
+ * </ol>
+ *
+ * With a depth of 1, the rdeps operator in both queries would return only the first set: proof the
+ * transformation is sound. With a depth of 2, we see the rdeps operator with a filtered universe
+ * returns a strictly smaller set.
+ */
+public class FilteredDirectRdepsInUniverseExpressionMapper extends QueryExpressionMapper<Void> {
+ private final TargetPattern.Parser targetPatternParser;
+ private final String absoluteUniverseScopePattern;
+
+ FilteredDirectRdepsInUniverseExpressionMapper(
+ TargetPattern.Parser targetPatternParser, String universeScopePattern) {
+ this.targetPatternParser = targetPatternParser;
+ this.absoluteUniverseScopePattern = targetPatternParser.absolutize(universeScopePattern);
+ }
+
+ @Override
+ public QueryExpression visit(FunctionExpression functionExpression, Void context) {
+ if (functionExpression.getFunction().getName().equals(new RdepsFunction().getName())) {
+ List<Argument> args = functionExpression.getArgs();
+ // This transformation only applies to the 3-arg form of rdeps, with depth == 1.
+ if (args.size() == 3 && args.get(2).getInteger() == 1) {
+ List<FunctionExpression> universeFilteringFunctions =
+ args.get(0).getExpression().accept(new ExtractFilteringFunctionsFromUniverseVisitor());
+ // If we get back a non-empty list of FunctionExpressions, these are filtering functions
+ // that can be safely factored out.
+ if (!universeFilteringFunctions.isEmpty()) {
+ QueryExpression curFunction = makeUnfilteredRdepsWithDepthOne(args.get(1));
+ for (FunctionExpression filterExpr : universeFilteringFunctions) {
+ curFunction = wrapExprWithFilter(curFunction, filterExpr);
+ }
+ return curFunction;
+ }
+ }
+ }
+ return super.visit(functionExpression, context);
+ }
+
+ private FunctionExpression makeUnfilteredRdepsWithDepthOne(Argument target) {
+ return new FunctionExpression(
+ new RdepsFunction(),
+ ImmutableList.of(
+ Argument.of(new TargetLiteral(absoluteUniverseScopePattern)), target, Argument.of(1)));
+ }
+
+ private static QueryExpression wrapExprWithFilter(
+ QueryExpression curFunction, FunctionExpression filterExpr) {
+ List<Argument> filterExprArgs = filterExpr.getArgs();
+ FilteringQueryFunction filteringFunction = filterExpr.getFunction().asFilteringFunction();
+ List<Argument> rewrittenArgs = new ArrayList<>();
+ for (int i = 0; i < filterExprArgs.size(); i++) {
+ if (i == filteringFunction.getExpressionToFilterIndex()) {
+ rewrittenArgs.add(Argument.of(curFunction));
+ } else {
+ rewrittenArgs.add(filterExprArgs.get(i));
+ }
+ }
+ return new FunctionExpression(filteringFunction, rewrittenArgs);
+ }
+
+ /**
+ * Internal visitor applied to the universe argument of all QueryExpressions of the form {@literal
+ * rdeps(u, x, 1)}.
+ */
+ private class ExtractFilteringFunctionsFromUniverseVisitor
+ implements QueryExpressionVisitor<List<FunctionExpression>, Void> {
+
+ @Override
+ public List<FunctionExpression> visit(TargetLiteral targetLiteral, Void context) {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public List<FunctionExpression> visit(
+ BinaryOperatorExpression binaryOperatorExpression, Void context) {
+ return ImmutableList.of();
+ }
+
+ @SuppressWarnings("MixedMutabilityReturnType")
+ @Override
+ public List<FunctionExpression> visit(FunctionExpression functionExpression, Void context) {
+ FilteringQueryFunction filteringFunction =
+ functionExpression.getFunction().asFilteringFunction();
+ if (filteringFunction == null) {
+ return ImmutableList.of();
+ }
+ Argument filteredArgument =
+ functionExpression.getArgs().get(filteringFunction.getExpressionToFilterIndex());
+ Preconditions.checkArgument(filteredArgument.getType() == ArgumentType.EXPRESSION);
+ QueryExpression filteredExpression = filteredArgument.getExpression();
+
+ ArrayList<FunctionExpression> results = new ArrayList<>();
+ if (filteredExpression instanceof TargetLiteral) {
+ TargetLiteral literalUniverseExpression = (TargetLiteral) filteredExpression;
+ String absolutizedUniverseExpression =
+ targetPatternParser.absolutize(literalUniverseExpression.getPattern());
+ if (absolutizedUniverseExpression.equals(absoluteUniverseScopePattern)) {
+ results.add(functionExpression);
+ }
+ } else if (filteredExpression instanceof FunctionExpression) {
+ List<FunctionExpression> nestedFilters = filteredExpression.accept(this);
+ if (!nestedFilters.isEmpty()) {
+ results.addAll(nestedFilters);
+ results.add(functionExpression);
+ }
+ }
+ return results;
+ }
+
+ @Override
+ public List<FunctionExpression> visit(LetExpression letExpression, Void context) {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public List<FunctionExpression> visit(SetExpression setExpression, Void context) {
+ return ImmutableList.of();
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
index ddc9ded..b58c9be 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
@@ -355,7 +355,10 @@
}
TargetPattern.Parser targetPatternParser = new TargetPattern.Parser(parserPrefix);
String universeScopePattern = Iterables.getOnlyElement(universeScope);
- return new RdepsToAllRdepsQueryExpressionMapper(targetPatternParser, universeScopePattern);
+ return QueryExpressionMapper.compose(
+ new RdepsToAllRdepsQueryExpressionMapper(targetPatternParser, universeScopePattern),
+ new FilteredDirectRdepsInUniverseExpressionMapper(
+ targetPatternParser, universeScopePattern));
}
@Override