---
layout: contribute
title: Implementing Beautiful Error Messages (Loading Phase)
---

# Implementing Beautiful Error Messages (Loading Phase)

__Author__: [laurentlb@google.com](mailto:laurentlb@google.com)

__Status__: unimplemented

__Related__: ["Beautiful error messages"](/designs/2016/05/23/beautiful-error-messages.html)

## Introduction

This is a followup to the document ["Beautiful error messages"](/designs/2016/05/23/beautiful-error-messages.html).

The purpose of this document is to outline a design for some plumbing that will
allow the sort of errors described in that document to be emitted by Blaze for
loading time `BUILD` file errors.

## Review: What needs to be done

In the ["Beautiful error messages"](/designs/2016/05/23/beautiful-error-messages.html)
document, four characteristics of error messages
are enumerated:

1.  **Context**: The erroneous text in the `BUILD` file in question is shown,
    with a caret pointing at the exact expression in question.

2.  **Colors**: Everyone loves colors.

3.  **Suggestions**: A guess of how the error should be fixed is shown.

4.  **Links**: Documentation is referenced directly in error messages.

This document covers (1) and (3).

## Current Error Infrastructure

In the loading phase, Bazel parses `BUILD` files into a tree of [ASTNode]
(https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/syntax/ASTNode.java)
instances, with a [BuildFileAST]
(https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/syntax/BuildFileAST.java)
at the root and `Statement` instances at the leaves. Each statement implements
doExec, which can throw an [EvalException]
(https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/syntax/EvalException.java).
That exception is translated to the error printed to the terminal.

An `EvalException` encapsulates the information given in an error. Fleshing out
the contents of this exception type is a good starting point for implementing
new error features.

## Implementation: Context

An `EvalException` contains a reference to a `Location`, which encapsulates the
line/character information currently specified in a Bazel error.

Generally, an `ASTNode` will have a location instance that is populated by the
parser as it moves from token to token ([example]
(https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/syntax/Parser.java#L607)).
Right now, since the location only contains pure syntactic information, the
parser [calls into the lexer]
(https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/syntax/Parser.java#L413)
to create the location instance. While the parser perhaps *could* also encode
AST information into the `Location`, that shouldn’t be necessary to provide
"context" in the form of a printed line and a carat. It seems that the
lexer maintains `BUILD` file info as a buffer, and should be able to
parameterize `Location` instances with the actual contents of the line in
question. If this is true, then implementing (1) above shouldn’t involve much
more than fleshing out [LocationInfo]
(https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java#L72).

## Implementation: Suggestions

While data tracked by the lexer should be sufficient to encode an offending
line from a `BUILD` file into an error, providing suggestions will probably
require semantic information only retrievable from the parsed AST. Furthermore,
we require a mechanism in `EvalException` to perform computation on AST data in
order to generate suggestions.

It seems an unlikely solution to encode AST information in `Location` instances,
since those instances are produced by the parser before the AST, or even the AST
node in question, is necessarily complete.

Instead, here are a couple proposals:

1. An abstract subclass of `EvalException` (e.g. `ContextualEvalException`)
that knows how to create an error message with suggestions given unimplemented
suggestion generation logic.

2. A further group of exceptions that are parameterized with a particular sort
of `ASTNode` and know how to generate suggestions. As an example, this
exception type could report typos in rule names.

```java
public class IdentifierEvalException extends ContextualEvalException {
 public static final Map<String, String> TYPOS = Map.of(
    "cclibrary", "cc_library",
    ect...
  )
  public IdentifierEvalException(Identifier identifier, Location loc) {...}
  @Override
  protected Suggestion generateSuggestion() {
    if (TYPOS.keySet().contains(identifier.getName()) {
      return TypoSuggestion(identifier, TYPOS.get(identifier.getName()));
    }
  }
}
```

Once the plumbing around (1) is in place, we can add subclasses at our leisure
to provide suggestions.

Furthermore, a `ContextualEvalException` can be made to have enough information
to not only provide suggestions, but also to return context-aware error
messages. Consider this example, from the
["Beautiful error messages"](/designs/2016/05/23/beautiful-error-messages.html)
document:

```python
my_obj = select({
  ":something": [1],
  "other": [2],
})

t = [x for x in my_obj]
```

<pre>
<font color="red">ERROR:</font> /test/BUILD:6:5: type 'select' is not iterable.
</pre>
<pre>
<font color="red">
ERROR: /test/BUILD:6:16:</font> <b>my_obj of type 'select' is not iterable.</b>
You can iterate only on strings, lists, tuples or dicts.
t = [x for x in my_obj]
                ^
Related documentation: http://documentation#select
</pre>

We can imagine a `NotIterableEvalException` that knows not only about the type
`select`, but is also parameterized with the erroneous expression ```my_objc```.

## Problem: Serialization

The above proposal hinges on the ability to store a file pointer in the
`Location` object, to be dereferenced at error-time to obtain the entire `BUILD`
file. This opens the door to some issues:

1. A `Location` instance is stored for every node in the parse tree of every
`BUILD` file. Even a file pointer in each `Location` may have substantial
memory/speed impact.
<p>This impact is easily measurable and likely tolerable in order to achieve
better error messages. However, it is clear that storing anything much larger
than a file pointer (like a fragment of the file, or the file itself) in each
`Location` would be untenable.

2. The `Location` object is serialized, since the AST is part of a `SkyValue`.
This pointer, then, must be serializable.
<p>This in particular is troubling because, unlike the java heap in a local
Bazel execution, the `SkyValue` containing the AST does not necessarily have the
`BUILD` file. However, it is not clear to me that this means the file must be
copied into each `Location` object. The AST presumably resides in a single
`SkyValue` - one copy of the file in that `SkyValue`, with a pointer to that
file in the `Location`, would be sufficient, it seems.
<p>The nature of the `SkyValue` that contains that AST must be determined,
thinking about if and how to embed the `BUILD` file into that `SkyValue`, and
strategizing about a good serialization for a `Location` object that
contains a file pointer.
