Add fine grained detail to QueryExpressions and propagate the failure details.

Add additional query related exception codes in QueryExpressions. These values will then get bubbled up to the QueryCommand and then handled by Blaze. These errors include "VARIABLE_UNDEFINED", and "BUILDFILES_AND_LOADFILES_CANNOT_USE_OUTPUT_LOCATION_ERROR".

RELNOTES: None.
PiperOrigin-RevId: 319862408
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryException.java b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryException.java
index 3d92ba3..df09b3e 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryException.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryException.java
@@ -41,7 +41,7 @@
   public QueryException(QueryException e, QueryExpression toplevel) {
     super(describeFailedQuery(e, toplevel), e);
     this.expression = null;
-    this.failureDetail = Optional.empty();
+    this.failureDetail = e.getFailureDetail();
   }
 
   public QueryException(QueryExpression expression, String message) {
diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java b/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java
index afeb24a..63789df 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/engine/TargetLiteral.java
@@ -16,6 +16,7 @@
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryTaskFuture;
+import com.google.devtools.build.lib.server.FailureDetails.Query;
 import java.util.Collection;
 import java.util.Set;
 
@@ -50,7 +51,8 @@
     Set<T> value = context.get(varName);
     if (value == null) {
       return env.immediateFailedFuture(
-          new QueryException(this, "undefined variable '" + varName + "'"));
+          new QueryException(
+              this, "undefined variable '" + varName + "'", Query.Code.VARIABLE_UNDEFINED));
     }
     try {
       callback.process(value);
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/BUILD b/src/main/java/com/google/devtools/build/lib/query2/query/output/BUILD
index 0881fec..eaa5132 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/BUILD
@@ -31,6 +31,7 @@
         "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
         "//src/main/java/com/google/devtools/common/options",
         "//src/main/protobuf:build_java_proto",
+        "//src/main/protobuf:failure_details_java_proto",
         "//third_party:flogger",
         "//third_party:guava",
         "//third_party:jsr305",
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/output/LocationOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/query/output/LocationOutputFormatter.java
index 8421e45..1ade9dc 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/output/LocationOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/output/LocationOutputFormatter.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.query2.engine.QueryExpression;
 import com.google.devtools.build.lib.query2.engine.SynchronizedDelegatingOutputFormatterCallback;
 import com.google.devtools.build.lib.query2.engine.ThreadSafeOutputFormatterCallback;
+import com.google.devtools.build.lib.server.FailureDetails.Query;
 import com.google.devtools.build.lib.syntax.Location;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -54,7 +55,8 @@
     if (expr.accept(noteBuildFilesAndLoadLilesVisitor)) {
       throw new QueryException(
           "Query expressions involving 'buildfiles' or 'loadfiles' cannot be used with "
-          + "--output=location");
+              + "--output=location",
+          Query.Code.BUILDFILES_AND_LOADFILES_CANNOT_USE_OUTPUT_LOCATION_ERROR);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
index f39c973..0427d36 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
@@ -81,18 +81,14 @@
     } catch (QueryException e) {
       String message = "Error while parsing '" + query + "': " + e.getMessage();
       env.getReporter().handle(Event.error(null, message));
-      return e.getFailureDetail().isPresent()
-          ? Either.ofLeft(
-              BlazeCommandResult.detailedExitCode(
-                  DetailedExitCode.of(ExitCode.COMMAND_LINE_ERROR, e.getFailureDetail().get())))
-          : Either.ofLeft(BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR));
+      return Either.ofLeft(finalizeBlazeCommandResult(ExitCode.COMMAND_LINE_ERROR, e));
     }
 
     try {
       formatter.verifyCompatible(queryEnv, expr);
     } catch (QueryException e) {
       env.getReporter().handle(Event.error(e.getMessage()));
-      return Either.ofLeft(BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR));
+      return Either.ofLeft(finalizeBlazeCommandResult(ExitCode.COMMAND_LINE_ERROR, e));
     }
 
     expr = queryEnv.transformParsedQuery(expr);
@@ -133,7 +129,7 @@
             // TODO(bazel-team): this is a kludge to fix a bug observed in the wild. We should make
             // sure no null error messages ever get in.
             .handle(Event.error(e.getMessage() == null ? e.toString() : e.getMessage()));
-        return Either.ofLeft(BlazeCommandResult.exitCode(ExitCode.ANALYSIS_FAILURE));
+        return Either.ofLeft(finalizeBlazeCommandResult(ExitCode.ANALYSIS_FAILURE, e));
       } catch (InterruptedException e) {
         catastrophe = false;
         IOException ioException = callback.getIoException();
@@ -211,4 +207,13 @@
         BlazeCommandResult.detailedExitCode(
             InterruptedFailureDetails.detailedExitCode(message, Interrupted.Code.QUERY)));
   }
+
+  private static BlazeCommandResult finalizeBlazeCommandResult(
+      ExitCode exitCode, QueryException e) {
+    if (e.getFailureDetail().isPresent()) {
+      return BlazeCommandResult.detailedExitCode(
+          DetailedExitCode.of(exitCode, e.getFailureDetail().get()));
+    }
+    return BlazeCommandResult.exitCode(exitCode);
+  }
 }
diff --git a/src/main/protobuf/failure_details.proto b/src/main/protobuf/failure_details.proto
index 1b59140..fb09dc2 100644
--- a/src/main/protobuf/failure_details.proto
+++ b/src/main/protobuf/failure_details.proto
@@ -598,6 +598,9 @@
     QUERY_RESULTS_FLUSH_FAILURE = 15 [(metadata) = { exit_code: 36 }];
     UNCLOSED_QUOTATION_EXPRESSION_ERROR = 16 [(metadata) = { exit_code: 2 }];
     VARIABLE_NAME_INVALID = 17 [(metadata) = { exit_code: 7 }];
+    VARIABLE_UNDEFINED = 18 [(metadata) = { exit_code: 7 }];
+    BUILDFILES_AND_LOADFILES_CANNOT_USE_OUTPUT_LOCATION_ERROR = 19
+        [(metadata) = { exit_code: 2 }];
 
     reserved 7 to 12; // For internal use
   }