[fidl][errors] Error support in fidlc

This adds support for the syntax introduced in FTP-014. If the error
syntax is used then structs and unions are generated to support them.

TEST=added compiler test and example

Change-Id: Iaf71052d2f3493a44a3917121ad3dfd21f61691e
diff --git a/system/host/fidl/examples/errors.fidl b/system/host/fidl/examples/errors.fidl
new file mode 100644
index 0000000..8eb9769
--- /dev/null
+++ b/system/host/fidl/examples/errors.fidl
@@ -0,0 +1,18 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+library fidl.examples.errors;
+
+enum ErrorCode : int32 {
+    kBad = 1;
+    kReallyBad = 2;
+    kOMGSoTerrible = 3;
+};
+
+interface EpicFail {
+    1: IntegerError(string x) -> (string y) error int32;
+    2: UnsignedError(string x) -> (string y) error uint32;
+    3: EnumError(string x) -> (string y) error ErrorCode;
+    4: EmptyResult(string x) -> () error ErrorCode;
+};
diff --git a/system/host/fidl/include/fidl/flat_ast.h b/system/host/fidl/include/fidl/flat_ast.h
index d4bd4de..acedfa5 100644
--- a/system/host/fidl/include/fidl/flat_ast.h
+++ b/system/host/fidl/include/fidl/flat_ast.h
@@ -22,6 +22,7 @@
 #include "error_reporter.h"
 #include "raw_ast.h"
 #include "type_shape.h"
+#include "virtual_source_file.h"
 
 namespace fidl {
 namespace flat {
@@ -1094,6 +1095,8 @@
                                        const raw::AttributeList* attributes);
 
     Name NextAnonymousName();
+    Name GeneratedName(const std::string& name);
+    Name DerivedName(const std::vector<StringView>& components);
 
     bool CompileCompoundIdentifier(const raw::CompoundIdentifier* compound_identifier,
                                    SourceLocation location, Name* out_name);
@@ -1111,8 +1114,9 @@
     bool ConsumeEnumDeclaration(std::unique_ptr<raw::EnumDeclaration> enum_declaration);
     bool
     ConsumeInterfaceDeclaration(std::unique_ptr<raw::InterfaceDeclaration> interface_declaration);
-    bool ConsumeParameterList(std::unique_ptr<raw::ParameterList> parameter_list,
-                              Struct** out_struct_decl);
+    bool ConsumeParameterList(Name name, std::unique_ptr<raw::ParameterList> parameter_list,
+                              bool anonymous, Struct** out_struct_decl);
+    bool CreateMethodResult(const Name& interface_name, raw::InterfaceMethod* method, Struct* in_response, Struct** out_response);
     bool ConsumeStructDeclaration(std::unique_ptr<raw::StructDeclaration> struct_declaration);
     bool ConsumeTableDeclaration(std::unique_ptr<raw::TableDeclaration> table_declaration);
     bool ConsumeUnionDeclaration(std::unique_ptr<raw::UnionDeclaration> union_declaration);
@@ -1121,6 +1125,7 @@
     bool TypeCanBeConst(const Type* type);
     const Type* TypeResolve(const Type* type);
     bool TypeIsConvertibleTo(const Type* from_type, const Type* to_type);
+    std::unique_ptr<IdentifierType> IdentifierTypeForDecl(const Decl* decl, types::Nullability nullability);
 
     // Given a const declaration of the form
     //     const type foo = name;
@@ -1224,6 +1229,8 @@
     Typespace* typespace_;
 
     uint32_t anon_counter_ = 0;
+
+    VirtualSourceFile generated_source_file_{"generated"};
 };
 
 } // namespace flat
diff --git a/system/host/fidl/include/fidl/raw_ast.h b/system/host/fidl/include/fidl/raw_ast.h
index 8ebe8ad..21e1e24 100644
--- a/system/host/fidl/include/fidl/raw_ast.h
+++ b/system/host/fidl/include/fidl/raw_ast.h
@@ -396,10 +396,12 @@
                     std::unique_ptr<Ordinal> ordinal,
                     std::unique_ptr<Identifier> identifier,
                     std::unique_ptr<ParameterList> maybe_request,
-                    std::unique_ptr<ParameterList> maybe_response)
+                    std::unique_ptr<ParameterList> maybe_response,
+                    std::unique_ptr<Type> maybe_error)
         : SourceElement(element), attributes(std::move(attributes)),
           ordinal(std::move(ordinal)), identifier(std::move(identifier)),
-          maybe_request(std::move(maybe_request)), maybe_response(std::move(maybe_response)) {}
+          maybe_request(std::move(maybe_request)), maybe_response(std::move(maybe_response)),
+          maybe_error(std::move(maybe_error)) { }
 
     void Accept(TreeVisitor& visitor);
 
@@ -408,6 +410,7 @@
     std::unique_ptr<Identifier> identifier;
     std::unique_ptr<ParameterList> maybe_request;
     std::unique_ptr<ParameterList> maybe_response;
+    std::unique_ptr<Type> maybe_error;
 };
 
 class InterfaceDeclaration : public SourceElement {
diff --git a/system/host/fidl/include/fidl/source_file.h b/system/host/fidl/include/fidl/source_file.h
index 8b9f9d5..41798a6 100644
--- a/system/host/fidl/include/fidl/source_file.h
+++ b/system/host/fidl/include/fidl/source_file.h
@@ -16,7 +16,7 @@
 class SourceFile {
 public:
     SourceFile(std::string filename, std::string data);
-    ~SourceFile();
+    virtual ~SourceFile();
 
     StringView filename() const { return filename_; }
     StringView data() const { return data_; }
@@ -28,7 +28,7 @@
         int column;
     };
 
-    StringView LineContaining(StringView view, Position* position_out) const;
+    virtual StringView LineContaining(StringView view, Position* position_out) const;
 
 private:
     std::string filename_;
diff --git a/system/host/fidl/include/fidl/token_definitions.inc b/system/host/fidl/include/fidl/token_definitions.inc
index a9cb6aa..6aa8881 100644
--- a/system/host/fidl/include/fidl/token_definitions.inc
+++ b/system/host/fidl/include/fidl/token_definitions.inc
@@ -63,6 +63,8 @@
 KEYWORD(Union, "union")
 KEYWORD(XUnion, "xunion")
 
+KEYWORD(Error, "error")
+
 KEYWORD(True, "true")
 KEYWORD(False, "false")
 
diff --git a/system/host/fidl/include/fidl/virtual_source_file.h b/system/host/fidl/include/fidl/virtual_source_file.h
new file mode 100644
index 0000000..a371df3
--- /dev/null
+++ b/system/host/fidl/include/fidl/virtual_source_file.h
@@ -0,0 +1,33 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ZIRCON_SYSTEM_HOST_FIDL_INCLUDE_FIDL_VIRTUAL_SOURCE_FILE_H_
+#define ZIRCON_SYSTEM_HOST_FIDL_INCLUDE_FIDL_VIRTUAL_SOURCE_FILE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "source_file.h"
+#include "source_location.h"
+
+namespace fidl {
+
+class VirtualSourceFile : public SourceFile {
+public:
+    VirtualSourceFile(std::string filename) : SourceFile(filename, "") {}
+    virtual ~VirtualSourceFile() = default;
+
+    virtual StringView LineContaining(StringView view, Position* position_out) const;
+
+    SourceLocation AddLine(const std::string& line);
+
+private:
+    std::vector<std::unique_ptr<std::string>> virtual_lines_;
+};
+
+}  // namespace fidl
+
+#endif // ZIRCON_SYSTEM_HOST_FIDL_INCLUDE_FIDL_VIRTUAL_SOURCE_FILE_H_
diff --git a/system/host/fidl/lib/flat_ast.cpp b/system/host/fidl/lib/flat_ast.cpp
index 7ef4253..5c2e6aa 100644
--- a/system/host/fidl/lib/flat_ast.cpp
+++ b/system/host/fidl/lib/flat_ast.cpp
@@ -681,6 +681,11 @@
         "SocketControl",
         "OvernetStream",
     }));
+    AddAttributeSchema("Result", AttributeSchema({
+        AttributeSchema::Placement::kUnionDecl,
+    }, {
+        "",
+    }));
     // clang-format on
 }
 
@@ -857,7 +862,15 @@
     std::ostringstream data;
     data << "SomeLongAnonymousPrefix";
     data << anon_counter_++;
-    return Name(this, StringView(data.str()));
+    return GeneratedName(data.str());
+}
+
+Name Library::GeneratedName(const std::string& name) {
+    return Name(this, generated_source_file_.AddLine(name));
+}
+
+Name Library::DerivedName(const std::vector<StringView>& components) {
+  return GeneratedName(StringJoin(components, "_"));
 }
 
 bool Library::CompileCompoundIdentifier(const raw::CompoundIdentifier* compound_identifier,
@@ -1128,6 +1141,77 @@
     return true;
 }
 
+bool Library::CreateMethodResult(const Name& interface_name,
+                                raw::InterfaceMethod* method,
+                                Struct* in_response,
+                                Struct** out_response) {
+    // Compile the error type.
+    auto error_location = method->maybe_error->location();
+    std::unique_ptr<Type> error_type;
+    if (!ConsumeType(std::move(method->maybe_error), error_location, &error_type))
+        return false;
+
+    const PrimitiveType* error_primitive = nullptr;
+    if (error_type->kind == Type::Kind::kPrimitive) {
+        error_primitive = static_cast<const PrimitiveType*>(error_type.get());
+    } else if (error_type->kind == Type::Kind::kIdentifier) {
+        auto identifier_type = static_cast<const IdentifierType*>(error_type.get());
+        Decl* decl = LookupDeclByName(identifier_type->name);
+        if (decl && decl->kind == Decl::Kind::kEnum) {
+            Enum* error_enum = static_cast<Enum*>(decl);
+            if (!error_enum->type) {
+                if (!CompileEnum(error_enum))
+                    return false;
+            }
+            error_primitive = error_enum->type;
+        }
+    }
+    if (!error_primitive ||
+        (error_primitive->subtype != types::PrimitiveSubtype::kInt32 &&
+         error_primitive->subtype != types::PrimitiveSubtype::kUint32)) {
+        return Fail(error_location,
+                    "invalid error type: must be int32, uint32 or an enum therof");
+    }
+
+    // Make the Result union containing the response struct and the
+    // error type.
+    Union::Member response_member{
+        IdentifierTypeForDecl(in_response, types::Nullability::kNonnullable),
+        GeneratedName("response").source_location(),
+        nullptr};
+    Union::Member error_member{
+        std::move(error_type),
+        GeneratedName("err").source_location(),
+        nullptr};
+    SourceLocation method_name = method->identifier->location();
+    Name result_name = DerivedName({interface_name.name_part(), method_name.data(), "Result"});
+    std::vector<Union::Member> result_members;
+    result_members.push_back(std::move(response_member));
+    result_members.push_back(std::move(error_member));
+    std::vector<std::unique_ptr<raw::Attribute>> result_attributes;
+    result_attributes.emplace_back(std::make_unique<raw::Attribute>(*method, "Result", ""));
+    auto result_attributelist = std::make_unique<raw::AttributeList>(*method,
+                                                                     std::move(result_attributes));
+    union_declarations_.push_back(std::make_unique<Union>(std::move(result_attributelist),
+                                                          std::move(result_name),
+                                                          std::move(result_members)));
+    auto result_decl = union_declarations_.back().get();
+    if (!RegisterDecl(result_decl))
+        return false;
+
+    // Make a new response struct for the method containing just the
+    // result union.
+    std::vector<Struct::Member> response_members;
+    response_members.push_back(Struct::Member(IdentifierTypeForDecl(result_decl, types::Nullability::kNonnullable),
+                                              GeneratedName("result").source_location(), nullptr, nullptr));
+    struct_declarations_.push_back(std::make_unique<Struct>(nullptr, NextAnonymousName(), std::move(response_members), true));
+    *out_response = struct_declarations_.back().get();
+    if (!RegisterDecl(*out_response))
+        return false;
+
+    return true;
+}
+
 bool Library::ConsumeInterfaceDeclaration(
     std::unique_ptr<raw::InterfaceDeclaration> interface_declaration) {
     auto attributes = std::move(interface_declaration->attributes);
@@ -1153,16 +1237,25 @@
 
         Struct* maybe_request = nullptr;
         if (method->maybe_request != nullptr) {
-            if (!ConsumeParameterList(std::move(method->maybe_request), &maybe_request))
+            Name request_name = NextAnonymousName();
+            if (!ConsumeParameterList(std::move(request_name), std::move(method->maybe_request), true, &maybe_request))
                 return false;
         }
 
+        bool has_error = (method->maybe_error != nullptr);
+
         Struct* maybe_response = nullptr;
         if (method->maybe_response != nullptr) {
-            if (!ConsumeParameterList(std::move(method->maybe_response), &maybe_response))
+            Name response_name = has_error ? DerivedName({name.name_part(), method_name.data(), "Response"}) : NextAnonymousName();
+            if (!ConsumeParameterList(std::move(response_name), std::move(method->maybe_response), !has_error, &maybe_response))
                 return false;
         }
 
+        if (has_error) {
+          if (!CreateMethodResult(name, method.get(), maybe_response, &maybe_response))
+            return false;
+        }
+
         assert(maybe_request != nullptr || maybe_response != nullptr);
         methods.emplace_back(std::move(attributes),
                              std::move(ordinal_literal),
@@ -1177,8 +1270,12 @@
     return RegisterDecl(interface_declarations_.back().get());
 }
 
-bool Library::ConsumeParameterList(std::unique_ptr<raw::ParameterList> parameter_list,
-                                   Struct** out_struct_decl) {
+std::unique_ptr<IdentifierType> Library::IdentifierTypeForDecl(const Decl* decl, types::Nullability nullability) {
+    return std::make_unique<IdentifierType>(Name(decl->name.library(), decl->name.name_part()), nullability);
+}
+
+bool Library::ConsumeParameterList(Name name, std::unique_ptr<raw::ParameterList> parameter_list,
+                                   bool anonymous, Struct** out_struct_decl) {
     std::vector<Struct::Member> members;
     for (auto& parameter : parameter_list->parameter_list) {
         const SourceLocation name = parameter->identifier->location();
@@ -1191,10 +1288,9 @@
             nullptr /* attributes */);
     }
 
-    auto name = NextAnonymousName();
     struct_declarations_.push_back(
         std::make_unique<Struct>(nullptr /* attributes */, std::move(name), std::move(members),
-                                 true /* anonymous */));
+                                 anonymous));
 
     auto struct_decl = struct_declarations_.back().get();
     if (!RegisterDecl(struct_decl))
diff --git a/system/host/fidl/lib/parser.cpp b/system/host/fidl/lib/parser.cpp
index feb592a..6a19935 100644
--- a/system/host/fidl/lib/parser.cpp
+++ b/system/host/fidl/lib/parser.cpp
@@ -690,6 +690,7 @@
     std::unique_ptr<raw::Identifier> method_name;
     std::unique_ptr<raw::ParameterList> maybe_request;
     std::unique_ptr<raw::ParameterList> maybe_response;
+    std::unique_ptr<raw::Type> maybe_error;
 
     auto parse_params = [this](std::unique_ptr<raw::ParameterList>* params_out) {
         ConsumeToken(OfKind(Token::Kind::kLeftParen));
@@ -722,6 +723,11 @@
                 return Fail();
             if (!parse_params(&maybe_response))
                 return Fail();
+            if (MaybeConsumeToken(IdentifierOfSubkind(Token::Subkind::kError))) {
+                maybe_error = ParseType();
+                if (!Ok())
+                    return Fail();
+            }
         }
     }
 
@@ -733,7 +739,8 @@
                                                   std::move(ordinal),
                                                   std::move(method_name),
                                                   std::move(maybe_request),
-                                                  std::move(maybe_response));
+                                                  std::move(maybe_response),
+                                                  std::move(maybe_error));
 }
 
 std::unique_ptr<raw::InterfaceDeclaration>
diff --git a/system/host/fidl/lib/virtual_source_file.cpp b/system/host/fidl/lib/virtual_source_file.cpp
new file mode 100644
index 0000000..0bd6a8e
--- /dev/null
+++ b/system/host/fidl/lib/virtual_source_file.cpp
@@ -0,0 +1,30 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "fidl/virtual_source_file.h"
+
+namespace fidl {
+
+SourceLocation VirtualSourceFile::AddLine(const std::string& line) {
+    return SourceLocation(*virtual_lines_.emplace_back(std::make_unique<std::string>(line)), *this);
+}
+
+StringView VirtualSourceFile::LineContaining(StringView view, Position* position_out) const {
+    for (int i = 0; i < virtual_lines_.size(); i++) {
+        const std::string& line = *virtual_lines_[i];
+        const char* line_begin = &*line.cbegin();
+        const char* line_end = &*line.cend();
+        if (view.data() < line_begin || view.data() + view.size() > line_end)
+            continue;
+        if (position_out != nullptr) {
+            auto column = view.data() - line_begin;
+            assert(column < std::numeric_limits<int>::max());
+            *position_out = {i + 1, (int)column};
+        }
+        return StringView(line);
+    }
+    return StringView();
+}
+
+}  // namespace fidl
diff --git a/system/host/fidl/rules.mk b/system/host/fidl/rules.mk
index c649a59..0bcd772 100644
--- a/system/host/fidl/rules.mk
+++ b/system/host/fidl/rules.mk
@@ -30,6 +30,7 @@
     $(LOCAL_DIR)/lib/source_manager.cpp \
     $(LOCAL_DIR)/lib/tables_generator.cpp \
     $(LOCAL_DIR)/lib/tree_visitor.cpp \
+    $(LOCAL_DIR)/lib/virtual_source_file.cpp \
     $(BUILDGEN_DIR)/lib/json_schema.cpp \
 
 $(BUILDGEN_DIR)/lib/json_schema.cpp: $(LOCAL_DIR)/schema.json
diff --git a/system/utest/fidl-compiler/errors_tests.cpp b/system/utest/fidl-compiler/errors_tests.cpp
new file mode 100644
index 0000000..50b59c0
--- /dev/null
+++ b/system/utest/fidl-compiler/errors_tests.cpp
@@ -0,0 +1,210 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <unittest/unittest.h>
+
+#include "test_library.h"
+
+namespace {
+
+bool GoodError() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+
+interface Example {
+    Method() -> (string foo) error int32;
+};
+
+)FIDL");
+
+    ASSERT_TRUE(library.Compile());
+
+    auto methods = &library.LookupInterface("Example")->methods;
+    ASSERT_EQ(methods->size(), 1);
+    auto method = &methods->at(0);
+    auto response = method->maybe_response;
+    ASSERT_NOT_NULL(response);
+    ASSERT_EQ(response->members.size(), 1);
+    auto response_member = &response->members.at(0);
+    ASSERT_EQ(response_member->type->kind, fidl::flat::Type::Kind::kIdentifier);
+    auto result_identifier = static_cast<fidl::flat::IdentifierType*>(response_member->type.get());
+    const fidl::flat::Union* result_union = library.LookupUnion(result_identifier->name.name_part());
+    ASSERT_NOT_NULL(result_union);
+    ASSERT_NOT_NULL(result_union->attributes);
+    ASSERT_TRUE(result_union->attributes->HasAttribute("Result"));
+    ASSERT_EQ(result_union->members.size(), 2);
+
+    ASSERT_STR_EQ("response", std::string(result_union->members.at(0).name.data()).c_str());
+
+    const fidl::flat::Union::Member& error = result_union->members.at(1);
+    ASSERT_STR_EQ("err", std::string(error.name.data()).c_str());
+
+    ASSERT_NOT_NULL(error.type);
+    ASSERT_EQ(error.type->kind, fidl::flat::Type::Kind::kPrimitive);
+    auto primitive_type = static_cast<fidl::flat::PrimitiveType*>(error.type.get());
+    ASSERT_EQ(primitive_type->subtype, fidl::types::PrimitiveSubtype::kInt32);
+
+    END_TEST;
+}
+
+bool GoodErrorUnsigned() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+
+interface Example {
+    Method() -> (string foo) error uint32;
+};
+
+)FIDL");
+
+    ASSERT_TRUE(library.Compile());
+    END_TEST;
+}
+
+bool GoodErrorEnum() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+
+enum ErrorType : int32 {
+    GOOD = 1;
+    BAD = 2;
+    UGLY = 3;
+};
+
+interface Example {
+    Method() -> (string foo) error ErrorType;
+};
+
+)FIDL");
+
+    ASSERT_TRUE(library.Compile());
+    END_TEST;
+}
+
+bool GoodErrorEnumAfter() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+
+interface Example {
+    Method() -> (string foo) error ErrorType;
+};
+
+enum ErrorType : int32 {
+    GOOD = 1;
+    BAD = 2;
+    UGLY = 3;
+};
+
+)FIDL");
+
+    ASSERT_TRUE(library.Compile());
+    END_TEST;
+}
+
+bool BadErrorUnknownIdentifier() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+
+interface Example {
+    Method() -> (string foo) error ErrorType;
+};
+)FIDL");
+
+    ASSERT_FALSE(library.Compile());
+    auto errors = library.errors();
+    ASSERT_EQ(errors.size(), 1);
+    ASSERT_STR_STR(errors[0].c_str(), "error: invalid error type");
+    END_TEST;
+}
+
+bool BadErrorWrongPrimitive() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+
+interface Example {
+    Method() -> (string foo) error float32;
+};
+)FIDL");
+
+    ASSERT_FALSE(library.Compile());
+    auto errors = library.errors();
+    ASSERT_EQ(errors.size(), 1);
+    ASSERT_STR_STR(errors[0].c_str(), "error: invalid error type");
+    END_TEST;
+}
+
+bool BadErrorMissingType() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+interface Example {
+    Method() -> (int32 flub) error;
+};
+)FIDL");
+    ASSERT_FALSE(library.Compile());
+    auto errors = library.errors();
+    ASSERT_EQ(errors.size(), 1);
+    ASSERT_STR_STR(errors[0].c_str(), "error: found unexpected token");
+    END_TEST;
+}
+
+bool BadErrorNotAType() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+interface Example {
+    Method() -> (int32 flub) error "hello";
+};
+)FIDL");
+    ASSERT_FALSE(library.Compile());
+    auto errors = library.errors();
+    ASSERT_EQ(errors.size(), 1);
+    ASSERT_STR_STR(errors[0].c_str(), "error: found unexpected token");
+    END_TEST;
+}
+
+bool BadErrorNoResponse() {
+    BEGIN_TEST;
+
+    TestLibrary library(R"FIDL(
+library example;
+interface Example {
+    Method() -> error int32;
+};
+)FIDL");
+    ASSERT_FALSE(library.Compile());
+    auto errors = library.errors();
+    ASSERT_EQ(errors.size(), 1);
+    ASSERT_STR_STR(errors[0].c_str(), "error: unexpected token \"error\"");
+    END_TEST;
+}
+} // namespace
+
+BEGIN_TEST_CASE(errors_tests);
+
+RUN_TEST(GoodError);
+RUN_TEST(GoodErrorUnsigned);
+RUN_TEST(GoodErrorEnum);
+RUN_TEST(GoodErrorEnumAfter);
+RUN_TEST(BadErrorUnknownIdentifier);
+RUN_TEST(BadErrorWrongPrimitive);
+RUN_TEST(BadErrorMissingType);
+RUN_TEST(BadErrorNotAType);
+RUN_TEST(BadErrorNoResponse);
+
+END_TEST_CASE(errors_tests);
diff --git a/system/utest/fidl-compiler/json_generator_tests.cpp b/system/utest/fidl-compiler/json_generator_tests.cpp
index 6143085..b23476f 100644
--- a/system/utest/fidl-compiler/json_generator_tests.cpp
+++ b/system/utest/fidl-compiler/json_generator_tests.cpp
@@ -835,6 +835,154 @@
 
     END_TEST;
 }
+
+bool json_generator_test_error() {
+    BEGIN_TEST;
+
+    for (int i = 0; i < kRepeatTestCount; i++) {
+        EXPECT_TRUE(checkJSONGenerator(R"FIDL(
+library fidl.test.json;
+
+interface Example {
+   foo(string s) -> (int64 y) error uint32;
+};
+
+)FIDL",
+                                       R"JSON({
+  "version": "0.0.1",
+  "name": "fidl.test.json",
+  "library_dependencies": [],
+  "const_declarations": [],
+  "enum_declarations": [],
+  "interface_declarations": [
+    {
+      "name": "fidl.test.json/Example",
+      "methods": [
+        {
+          "ordinal": 1369693400,
+          "generated_ordinal": 1369693400,
+          "name": "foo",
+          "has_request": true,
+          "maybe_request": [
+            {
+              "type": {
+                "kind": "string",
+                "nullable": false
+              },
+              "name": "s",
+              "size": 16,
+              "max_out_of_line": 4294967295,
+              "alignment": 8,
+              "offset": 16,
+              "max_handles": 0
+            }
+          ],
+          "maybe_request_size": 32,
+          "maybe_request_alignment": 8,
+          "has_response": true,
+          "maybe_response": [
+            {
+              "type": {
+                "kind": "identifier",
+                "identifier": "fidl.test.json/Example_foo_Result",
+                "nullable": false
+              },
+              "name": "result",
+              "size": 16,
+              "max_out_of_line": 0,
+              "alignment": 8,
+              "offset": 16,
+              "max_handles": 0
+            }
+          ],
+          "maybe_response_size": 32,
+          "maybe_response_alignment": 8
+        }
+      ]
+    }
+  ],
+  "struct_declarations": [
+    {
+      "name": "fidl.test.json/Example_foo_Response",
+      "anonymous": false,
+      "members": [
+        {
+          "type": {
+            "kind": "primitive",
+            "subtype": "int64"
+          },
+          "name": "y",
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "offset": 0,
+          "max_handles": 0
+        }
+      ],
+      "size": 8,
+      "max_out_of_line": 0,
+      "alignment": 8,
+      "max_handles": 0
+    }
+  ],
+  "table_declarations": [],
+  "union_declarations": [
+    {
+      "name": "fidl.test.json/Example_foo_Result",
+      "maybe_attributes": [
+        {
+          "name": "Result",
+          "value": ""
+        }
+      ],
+      "members": [
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.json/Example_foo_Response",
+            "nullable": false
+          },
+          "name": "response",
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "offset": 8
+        },
+        {
+          "type": {
+            "kind": "primitive",
+            "subtype": "uint32"
+          },
+          "name": "err",
+          "size": 4,
+          "max_out_of_line": 0,
+          "alignment": 4,
+          "offset": 8
+        }
+      ],
+      "size": 16,
+      "max_out_of_line": 0,
+      "alignment": 8,
+      "max_handles": 0
+    }
+  ],
+  "xunion_declarations": [],
+  "declaration_order": [
+    "fidl.test.json/Example_foo_Response",
+    "fidl.test.json/Example_foo_Result",
+    "fidl.test.json/Example"
+  ],
+  "declarations": {
+    "fidl.test.json/Example": "interface",
+    "fidl.test.json/Example_foo_Response": "struct",
+    "fidl.test.json/Example_foo_Result": "union"
+  }
+})JSON"));
+    }
+
+    END_TEST;
+}
+
 } // namespace
 
 BEGIN_TEST_CASE(json_generator_tests);
@@ -845,4 +993,5 @@
 RUN_TEST(json_generator_test_xunion);
 RUN_TEST(json_generator_test_inheritance);
 RUN_TEST(json_generator_test_inheritance_with_recursive_decl);
+RUN_TEST(json_generator_test_error);
 END_TEST_CASE(json_generator_tests);
diff --git a/system/utest/fidl-compiler/rules.mk b/system/utest/fidl-compiler/rules.mk
index 1f2a4ca..dfbbb73 100644
--- a/system/utest/fidl-compiler/rules.mk
+++ b/system/utest/fidl-compiler/rules.mk
@@ -21,6 +21,7 @@
     $(EXAMPLE_DIR)/empty.fidl \
     $(EXAMPLE_DIR)/enums.fidl \
     $(EXAMPLE_DIR)/events.fidl \
+    $(EXAMPLE_DIR)/errors.fidl \
     $(EXAMPLE_DIR)/example-0.fidl \
     $(EXAMPLE_DIR)/example-1.fidl \
     $(EXAMPLE_DIR)/example-2.fidl \
@@ -66,6 +67,7 @@
     $(LOCAL_DIR)/consts_tests.cpp \
     $(LOCAL_DIR)/declaration_order_tests.cpp \
     $(LOCAL_DIR)/enums_tests.cpp \
+    $(LOCAL_DIR)/errors_tests.cpp \
     $(LOCAL_DIR)/flat_ast_tests.cpp \
     $(LOCAL_DIR)/formatter_unittests.cpp \
     $(LOCAL_DIR)/json_generator_tests.cpp \
@@ -80,6 +82,7 @@
     $(LOCAL_DIR)/table_tests.cpp \
     $(LOCAL_DIR)/types_tests.cpp \
     $(LOCAL_DIR)/using_tests.cpp \
+    $(LOCAL_DIR)/virtual_source_tests.cpp \
     $(LOCAL_DIR)/visitor_unittests.cpp \
     $(LOCAL_DIR)/xunion_tests.cpp \
     $(BUILDGEN_DIR)/examples.cpp \
diff --git a/system/utest/fidl-compiler/virtual_source_tests.cpp b/system/utest/fidl-compiler/virtual_source_tests.cpp
new file mode 100644
index 0000000..7131c31
--- /dev/null
+++ b/system/utest/fidl-compiler/virtual_source_tests.cpp
@@ -0,0 +1,53 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <unittest/unittest.h>
+
+#include "test_library.h"
+#include "fidl/source_location.h"
+
+namespace {
+
+bool AddLine() {
+    BEGIN_TEST;
+
+    fidl::VirtualSourceFile file("imaginary-test-file");
+
+    fidl::SourceLocation one = file.AddLine("one");
+    fidl::SourceLocation two = file.AddLine("two");
+    fidl::SourceLocation three = file.AddLine("three");
+
+    EXPECT_STR_EQ(std::string(one.data()).c_str(), "one");
+    EXPECT_STR_EQ(std::string(two.data()).c_str(), "two");
+    EXPECT_STR_EQ(std::string(three.data()).c_str(), "three");
+
+    END_TEST;
+}
+
+bool LineContaining() {
+    BEGIN_TEST;
+
+    fidl::VirtualSourceFile file("imaginary-test-file");
+
+    file.AddLine("one");
+    fidl::SourceLocation two = file.AddLine("two");
+    file.AddLine("three");
+
+    fidl::SourceFile::Position pos{};
+    file.LineContaining(two.data(), &pos);
+    EXPECT_EQ(pos.line, 2);
+    EXPECT_EQ(pos.column, 0);
+
+    END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(virtual_source_tests);
+
+RUN_TEST(AddLine);
+RUN_TEST(LineContaining);
+
+END_TEST_CASE(virtual_source_tests);
+