Преглед изворни кода

Carbon fuzzing 2/3: added logic for converting carbon.proto to a Carbon source string. (#1155)

* Carbon fuzzing 2/3: added logic for converting carbon.proto to a Carbon source string.

* adjusted to proto changes in proto PR

* addressed todos

* removed unnecessary has_xx() calls

* minor fix

* sync from part 1

* Update executable_semantics/fuzzing/proto_to_carbon_test.cpp

Co-authored-by: Geoff Romer <gromer@google.com>

* review comments

* Update common/fuzzing/proto_to_carbon.cpp

Co-authored-by: Jon Meow <jperkins@google.com>

* Update common/fuzzing/proto_to_carbon.cpp

Co-authored-by: Jon Meow <jperkins@google.com>

* code review

* code review

* Update common/fuzzing/proto_to_carbon.cpp

Co-authored-by: Jon Meow <jperkins@google.com>

* code review

* merged + added support for type_params in class

* use <> includes for protobuf

* review comments

* code review comments

Co-authored-by: Geoff Romer <gromer@google.com>
Co-authored-by: Jon Meow <jperkins@google.com>
pk19604014 пре 4 година
родитељ
комит
f8ded337fb

+ 10 - 0
common/fuzzing/BUILD

@@ -16,3 +16,13 @@ cc_proto_library(
     name = "carbon_cc_proto",
     deps = [":carbon_proto"],
 )
+
+cc_library(
+    name = "proto_to_carbon_lib",
+    srcs = ["proto_to_carbon.cpp"],
+    hdrs = ["proto_to_carbon.h"],
+    deps = [
+        ":carbon_cc_proto",
+        "@llvm-project//llvm:Support",
+    ],
+)

+ 2 - 0
common/fuzzing/carbon.proto

@@ -218,6 +218,7 @@ message WhileStatement {
 message MatchClause {
   optional Pattern pattern = 1;
   optional Statement statement = 2;
+  optional bool is_default = 3;
 }
 
 message MatchStatement {
@@ -282,6 +283,7 @@ message FunctionDeclaration {
 message ClassDeclaration {
   optional string name = 1;
   repeated Declaration members = 2;
+  optional TuplePattern type_params = 3;
 }
 
 message AlternativeSignature {

+ 705 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -0,0 +1,705 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "common/fuzzing/proto_to_carbon.h"
+
+#include <string_view>
+
+#include "common/fuzzing/carbon.pb.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace Carbon {
+
+static auto ExpressionToCarbon(const Fuzzing::Expression& expression,
+                               llvm::raw_ostream& out) -> void;
+static auto PatternToCarbon(const Fuzzing::Pattern& pattern,
+                            llvm::raw_ostream& out) -> void;
+static auto StatementToCarbon(const Fuzzing::Statement& statement,
+                              llvm::raw_ostream& out) -> void;
+static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
+                                llvm::raw_ostream& out) -> void;
+
+// Produces a valid Carbon identifier, which must match the regex
+// `[A-Za-z_][A-Za-z0-9_]*`. In the case when `s` is generated by the
+// fuzzing framework, it might contain invalid/non-printable characters.
+static auto IdentifierToCarbon(std::string_view s, llvm::raw_ostream& out)
+    -> void {
+  if (s.empty()) {
+    out << "EmptyIdentifier";
+  } else {
+    if (!llvm::isAlpha(s[0]) && s[0] != '_') {
+      // Ensures that identifier starts with a valid character.
+      out << 'x';
+    }
+    for (const char c : s) {
+      if (llvm::isAlnum(c) || c == '_') {
+        out << c;
+      } else {
+        out << llvm::toHex(c);
+      }
+    }
+  }
+}
+
+static auto StringLiteralToCarbon(std::string_view s, llvm::raw_ostream& out) {
+  out << '"';
+  out.write_escaped(s);
+  out << '"';
+}
+
+static auto LibraryNameToCarbon(const Fuzzing::LibraryName& library,
+                                llvm::raw_ostream& out) -> void {
+  IdentifierToCarbon(library.package_name(), out);
+
+  // Path is optional.
+  if (library.has_path()) {
+    out << " library ";
+    // library.path() is a string literal.
+    StringLiteralToCarbon(library.path(), out);
+  }
+}
+
+static auto PrefixUnaryOperatorToCarbon(std::string_view op,
+                                        const Fuzzing::Expression& arg,
+                                        llvm::raw_ostream& out) -> void {
+  out << op;
+  ExpressionToCarbon(arg, out);
+}
+
+static auto PostfixUnaryOperatorToCarbon(const Fuzzing::Expression& arg,
+                                         std::string_view op,
+                                         llvm::raw_ostream& out) -> void {
+  ExpressionToCarbon(arg, out);
+  out << op;
+}
+
+static auto BinaryOperatorToCarbon(const Fuzzing::Expression& lhs,
+                                   std::string_view op,
+                                   const Fuzzing::Expression& rhs,
+                                   llvm::raw_ostream& out) -> void {
+  ExpressionToCarbon(lhs, out);
+  out << op;
+  ExpressionToCarbon(rhs, out);
+}
+
+static auto PrimitiveOperatorToCarbon(
+    const Fuzzing::PrimitiveOperatorExpression& primitive_operator,
+    llvm::raw_ostream& out) -> void {
+  const Fuzzing::Expression& arg0 =
+      primitive_operator.arguments().size() > 0
+          ? primitive_operator.arguments(0)
+          : Fuzzing::Expression::default_instance();
+  const Fuzzing::Expression& arg1 =
+      primitive_operator.arguments().size() > 1
+          ? primitive_operator.arguments(1)
+          : Fuzzing::Expression::default_instance();
+  out << "(";
+  switch (primitive_operator.op()) {
+    case Fuzzing::PrimitiveOperatorExpression::UnknownOperator:
+      // `-` is an arbitrary default to avoid getting invalid syntax.
+      PrefixUnaryOperatorToCarbon("-", arg0, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::AddressOf:
+      PrefixUnaryOperatorToCarbon("&", arg0, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Deref:
+      PrefixUnaryOperatorToCarbon("*", arg0, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Mul:
+      BinaryOperatorToCarbon(arg0, " * ", arg1, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Ptr:
+      PostfixUnaryOperatorToCarbon(arg0, "*", out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Neg:
+      PrefixUnaryOperatorToCarbon("-", arg0, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Sub:
+      BinaryOperatorToCarbon(arg0, " - ", arg1, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Not:
+      // Needs a space to 'unglue' from the operand.
+      PrefixUnaryOperatorToCarbon("not ", arg0, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Add:
+      BinaryOperatorToCarbon(arg0, " + ", arg1, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::And:
+      BinaryOperatorToCarbon(arg0, " and ", arg1, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Eq:
+      BinaryOperatorToCarbon(arg0, " == ", arg1, out);
+      break;
+
+    case Fuzzing::PrimitiveOperatorExpression::Or:
+      BinaryOperatorToCarbon(arg0, " or ", arg1, out);
+      break;
+  }
+  out << ")";
+}
+
+static auto FieldInitializerToCarbon(const Fuzzing::FieldInitializer& field,
+                                     std::string_view separator,
+                                     llvm::raw_ostream& out) -> void {
+  out << ".";
+  IdentifierToCarbon(field.name(), out);
+  out << " " << separator << " ";
+  ExpressionToCarbon(field.expression(), out);
+}
+
+static auto TupleLiteralExpressionToCarbon(
+    const Fuzzing::TupleLiteralExpression& tuple_literal,
+    llvm::raw_ostream& out) -> void {
+  out << "(";
+  llvm::ListSeparator sep;
+  for (const auto& field : tuple_literal.fields()) {
+    out << sep;
+    ExpressionToCarbon(field, out);
+  }
+  if (tuple_literal.fields_size() == 1) {
+    // Adding a trailing comma so that generated source will be parsed as a
+    // tuple expression. See docs/design/tuples.md.
+    out << ", ";
+  }
+  out << ")";
+}
+
+static auto ExpressionToCarbon(const Fuzzing::Expression& expression,
+                               llvm::raw_ostream& out) -> void {
+  switch (expression.kind_case()) {
+    case Fuzzing::Expression::KIND_NOT_SET:
+      // Arbitrary default for missing expressions to avoid invalid syntax.
+      out << "true";
+      break;
+
+    case Fuzzing::Expression::kCall: {
+      const auto& call = expression.call();
+      ExpressionToCarbon(call.function(), out);
+      if (call.argument().kind_case() == Fuzzing::Expression::kTupleLiteral) {
+        TupleLiteralExpressionToCarbon(call.argument().tuple_literal(), out);
+      } else {
+        out << "(";
+        ExpressionToCarbon(call.argument(), out);
+        out << ")";
+      }
+      break;
+    }
+
+    case Fuzzing::Expression::kFunctionType: {
+      const auto& fun_type = expression.function_type();
+      out << "__Fn";
+      ExpressionToCarbon(fun_type.parameter(), out);
+      out << " -> ";
+      ExpressionToCarbon(fun_type.return_type(), out);
+      break;
+    }
+
+    case Fuzzing::Expression::kFieldAccess: {
+      const auto& field_access = expression.field_access();
+      ExpressionToCarbon(field_access.aggregate(), out);
+      out << ".";
+      IdentifierToCarbon(field_access.field(), out);
+      break;
+    }
+
+    case Fuzzing::Expression::kIndex: {
+      const auto& index = expression.index();
+      ExpressionToCarbon(index.aggregate(), out);
+      out << "[";
+      ExpressionToCarbon(index.offset(), out);
+      out << "]";
+      break;
+    }
+
+    case Fuzzing::Expression::kPrimitiveOperator:
+      PrimitiveOperatorToCarbon(expression.primitive_operator(), out);
+      break;
+
+    case Fuzzing::Expression::kTupleLiteral: {
+      TupleLiteralExpressionToCarbon(expression.tuple_literal(), out);
+      break;
+    }
+
+    case Fuzzing::Expression::kStructLiteral: {
+      const auto& struct_literal = expression.struct_literal();
+      out << "{";
+      llvm::ListSeparator sep;
+      for (const auto& field : struct_literal.fields()) {
+        out << sep;
+        FieldInitializerToCarbon(field, "=", out);
+      }
+      out << "}";
+      break;
+    }
+
+    case Fuzzing::Expression::kStructTypeLiteral: {
+      const auto& struct_type_literal = expression.struct_type_literal();
+      out << "{";
+      llvm::ListSeparator sep;
+      for (const auto& field : struct_type_literal.fields()) {
+        out << sep;
+        FieldInitializerToCarbon(field, ":", out);
+      }
+      out << "}";
+      break;
+    }
+
+    case Fuzzing::Expression::kIdentifier: {
+      const auto& identifier = expression.identifier();
+      IdentifierToCarbon(identifier.name(), out);
+      break;
+    }
+
+    case Fuzzing::Expression::kIntrinsic: {
+      const auto& intrinsic = expression.intrinsic();
+      switch (intrinsic.intrinsic()) {
+        case Fuzzing::IntrinsicExpression::UnknownIntrinsic:
+          // Arbitrary default to avoid getting invalid syntax.
+          out << "__intrinsic_print";
+          break;
+
+        case Fuzzing::IntrinsicExpression::Print:
+          out << "__intrinsic_print";
+          break;
+      }
+      TupleLiteralExpressionToCarbon(intrinsic.argument(), out);
+    } break;
+
+    case Fuzzing::Expression::kIfExpression: {
+      const auto& if_expression = expression.if_expression();
+      out << "if ";
+      ExpressionToCarbon(if_expression.condition(), out);
+      out << " then ";
+      ExpressionToCarbon(if_expression.then_expression(), out);
+      out << " else ";
+      ExpressionToCarbon(if_expression.else_expression(), out);
+      break;
+    }
+
+    case Fuzzing::Expression::kBoolTypeLiteral:
+      out << "Bool";
+      break;
+
+    case Fuzzing::Expression::kBoolLiteral: {
+      const auto& bool_literal = expression.bool_literal();
+      out << (bool_literal.value() ? "true" : "false");
+      break;
+    }
+
+    case Fuzzing::Expression::kIntTypeLiteral:
+      out << "i32";
+      break;
+
+    case Fuzzing::Expression::kIntLiteral: {
+      out << expression.int_literal().value();
+      break;
+    }
+
+    case Fuzzing::Expression::kStringLiteral:
+      StringLiteralToCarbon(expression.string_literal().value(), out);
+      break;
+
+    case Fuzzing::Expression::kStringTypeLiteral:
+      out << "String";
+      break;
+
+    case Fuzzing::Expression::kContinuationTypeLiteral:
+      out << "__Continuation";
+      break;
+
+    case Fuzzing::Expression::kTypeTypeLiteral:
+      out << "Type";
+      break;
+
+    case Fuzzing::Expression::kUnimplementedExpression:
+      // Not really supported.
+      // This is an arbitrary default to avoid getting invalid syntax.
+      out << "1 __unimplemented_example_infix 2";
+      break;
+  }
+}
+
+static auto BindingPatternToCarbon(const Fuzzing::BindingPattern& pattern,
+                                   llvm::raw_ostream& out) -> void {
+  IdentifierToCarbon(pattern.name(), out);
+  out << ": ";
+  PatternToCarbon(pattern.type(), out);
+}
+
+static auto GenericBindingToCarbon(
+    const Fuzzing::GenericBinding& generic_binding, llvm::raw_ostream& out) {
+  IdentifierToCarbon(generic_binding.name(), out);
+  out << ":! ";
+  ExpressionToCarbon(generic_binding.type(), out);
+}
+
+static auto TuplePatternToCarbon(const Fuzzing::TuplePattern& tuple_pattern,
+                                 llvm::raw_ostream& out) -> void {
+  out << "(";
+  llvm::ListSeparator sep;
+  for (const auto& field : tuple_pattern.fields()) {
+    out << sep;
+    PatternToCarbon(field, out);
+  }
+  if (tuple_pattern.fields_size() == 1) {
+    // Adding a trailing comma so that generated source will be parsed as a
+    // tuple pattern expression. See docs/design/tuples.md.
+    out << ", ";
+  }
+  out << ")";
+}
+
+static auto PatternToCarbon(const Fuzzing::Pattern& pattern,
+                            llvm::raw_ostream& out) -> void {
+  switch (pattern.kind_case()) {
+    case Fuzzing::Pattern::KIND_NOT_SET:
+      // Arbitrary default to avoid getting invalid syntax.
+      out << "auto";
+      break;
+
+    case Fuzzing::Pattern::kBindingPattern:
+      BindingPatternToCarbon(pattern.binding_pattern(), out);
+      break;
+
+    case Fuzzing::Pattern::kTuplePattern:
+      TuplePatternToCarbon(pattern.tuple_pattern(), out);
+      break;
+
+    case Fuzzing::Pattern::kAlternativePattern: {
+      const auto& alternative_pattern = pattern.alternative_pattern();
+      ExpressionToCarbon(alternative_pattern.choice_type(), out);
+      out << ".";
+      IdentifierToCarbon(alternative_pattern.alternative_name(), out);
+      TuplePatternToCarbon(alternative_pattern.arguments(), out);
+      break;
+    }
+
+    // Arbitrary expression.
+    case Fuzzing::Pattern::kExpressionPattern: {
+      const auto& expression_pattern = pattern.expression_pattern();
+      ExpressionToCarbon(expression_pattern.expression(), out);
+      break;
+    }
+
+    case Fuzzing::Pattern::kAutoPattern:
+      out << "auto";
+      break;
+
+    case Fuzzing::Pattern::kVarPattern:
+      out << "var ";
+      PatternToCarbon(pattern.var_pattern().pattern(), out);
+      break;
+
+    case Fuzzing::Pattern::kGenericBinding:
+      GenericBindingToCarbon(pattern.generic_binding(), out);
+      break;
+  }
+}
+
+static auto BlockStatementToCarbon(const Fuzzing::BlockStatement& block,
+                                   llvm::raw_ostream& out) -> void {
+  out << "{\n";
+  for (const auto& statement : block.statements()) {
+    StatementToCarbon(statement, out);
+    out << "\n";
+  }
+  out << "}\n";
+}
+
+static auto StatementToCarbon(const Fuzzing::Statement& statement,
+                              llvm::raw_ostream& out) -> void {
+  switch (statement.kind_case()) {
+    case Fuzzing::Statement::KIND_NOT_SET:
+      // Arbitrary default to avoid getting invalid syntax.
+      out << "true;\n";
+      break;
+
+    case Fuzzing::Statement::kExpressionStatement: {
+      const auto& expression_statement = statement.expression_statement();
+      ExpressionToCarbon(expression_statement.expression(), out);
+      out << ";";
+      break;
+    }
+
+    case Fuzzing::Statement::kAssign: {
+      const auto& assign_statement = statement.assign();
+      ExpressionToCarbon(assign_statement.lhs(), out);
+      out << " = ";
+      ExpressionToCarbon(assign_statement.rhs(), out);
+      out << ";";
+      break;
+    }
+
+    case Fuzzing::Statement::kVariableDefinition: {
+      const auto& def = statement.variable_definition();
+      out << "var ";
+      PatternToCarbon(def.pattern(), out);
+      out << " = ";
+      ExpressionToCarbon(def.init(), out);
+      out << ";";
+      break;
+    }
+
+    case Fuzzing::Statement::kIfStatement: {
+      const auto& if_statement = statement.if_statement();
+      out << "if (";
+      ExpressionToCarbon(if_statement.condition(), out);
+      out << ") ";
+      BlockStatementToCarbon(if_statement.then_block(), out);
+      // `else` is optional.
+      if (if_statement.has_else_block()) {
+        out << " else ";
+        BlockStatementToCarbon(if_statement.else_block(), out);
+      }
+      break;
+    }
+
+    case Fuzzing::Statement::kReturnStatement: {
+      const auto& ret = statement.return_statement();
+      out << "return";
+      if (!ret.is_omitted_expression()) {
+        out << " ";
+        ExpressionToCarbon(ret.expression(), out);
+      }
+      out << ";";
+      break;
+    }
+
+    case Fuzzing::Statement::kBlock:
+      BlockStatementToCarbon(statement.block(), out);
+      break;
+
+    case Fuzzing::Statement::kWhileStatement: {
+      const auto& while_statement = statement.while_statement();
+      out << "while (";
+      ExpressionToCarbon(while_statement.condition(), out);
+      out << ") ";
+      BlockStatementToCarbon(while_statement.body(), out);
+      break;
+    }
+
+    case Fuzzing::Statement::kMatch: {
+      const auto& match = statement.match();
+      out << "match (";
+      ExpressionToCarbon(match.expression(), out);
+      out << ") {";
+      for (const auto& clause : match.clauses()) {
+        if (clause.is_default()) {
+          out << "default";
+        } else {
+          out << "case ";
+          PatternToCarbon(clause.pattern(), out);
+        }
+        out << " => ";
+        StatementToCarbon(clause.statement(), out);
+      }
+      out << "}";
+      break;
+    }
+
+    case Fuzzing::Statement::kContinuation: {
+      const auto& continuation = statement.continuation();
+      out << "__continuation ";
+      IdentifierToCarbon(continuation.name(), out);
+      BlockStatementToCarbon(continuation.body(), out);
+      break;
+    }
+
+    case Fuzzing::Statement::kRun: {
+      const auto& run = statement.run();
+      out << "__run ";
+      ExpressionToCarbon(run.argument(), out);
+      out << ";";
+      break;
+    }
+
+    case Fuzzing::Statement::kAwait:
+      out << "__await;";
+      break;
+
+    case Fuzzing::Statement::kBreakStatement:
+      out << "break;";
+      break;
+
+    case Fuzzing::Statement::kContinueStatement:
+      out << "continue;";
+      break;
+  }
+}
+
+static auto ReturnTermToCarbon(const Fuzzing::ReturnTerm& return_term,
+                               llvm::raw_ostream& out) -> void {
+  switch (return_term.kind()) {
+    case Fuzzing::ReturnTerm::UnknownReturnKind:
+    case Fuzzing::ReturnTerm::Omitted:
+      break;
+    case Fuzzing::ReturnTerm::Auto:
+      out << " -> auto";
+      break;
+    case Fuzzing::ReturnTerm::Expression:
+      out << " -> ";
+      ExpressionToCarbon(return_term.type(), out);
+      break;
+  }
+}
+
+static auto DeclarationToCarbon(const Fuzzing::Declaration& declaration,
+                                llvm::raw_ostream& out) -> void {
+  switch (declaration.kind_case()) {
+    case Fuzzing::Declaration::KIND_NOT_SET:
+      // Arbitrary default to avoid getting invalid syntax.
+      out << "var x: i32;";
+      break;
+
+    case Fuzzing::Declaration::kFunction: {
+      const auto& function = declaration.function();
+      out << "fn ";
+      IdentifierToCarbon(function.name(), out);
+
+      if (!function.deduced_parameters().empty() || function.has_me_pattern()) {
+        out << "[";
+        llvm::ListSeparator sep;
+        for (const Fuzzing::GenericBinding& p : function.deduced_parameters()) {
+          out << sep;
+          GenericBindingToCarbon(p, out);
+        }
+        if (function.has_me_pattern()) {
+          // This is a class method.
+          out << sep;
+          BindingPatternToCarbon(function.me_pattern(), out);
+        }
+        out << "]";
+      }
+      TuplePatternToCarbon(function.param_pattern(), out);
+      ReturnTermToCarbon(function.return_term(), out);
+
+      // Body is optional.
+      if (function.has_body()) {
+        out << "\n";
+        BlockStatementToCarbon(function.body(), out);
+      } else {
+        out << ";";
+      }
+      break;
+    }
+
+    case Fuzzing::Declaration::kClassDeclaration: {
+      const auto& class_declaration = declaration.class_declaration();
+      out << "class ";
+      IdentifierToCarbon(class_declaration.name(), out);
+
+      // type_params is optional.
+      if (class_declaration.has_type_params()) {
+        TuplePatternToCarbon(class_declaration.type_params(), out);
+      }
+
+      out << "{\n";
+      for (const auto& member : class_declaration.members()) {
+        DeclarationToCarbon(member, out);
+        out << "\n";
+      }
+      out << "}";
+      break;
+    }
+
+    case Fuzzing::Declaration::kChoice: {
+      const auto& choice = declaration.choice();
+      out << "choice ";
+      IdentifierToCarbon(choice.name(), out);
+
+      out << "{";
+      llvm::ListSeparator sep;
+      for (const auto& alternative : choice.alternatives()) {
+        out << sep;
+        IdentifierToCarbon(alternative.name(), out);
+        ExpressionToCarbon(alternative.signature(), out);
+      }
+      out << "}";
+      break;
+    }
+
+    case Fuzzing::Declaration::kVariable: {
+      const auto& var = declaration.variable();
+      out << "var ";
+      BindingPatternToCarbon(var.binding(), out);
+
+      // Initializer is optional.
+      if (var.has_initializer()) {
+        out << " = ";
+        ExpressionToCarbon(var.initializer(), out);
+      }
+      out << ";";
+      break;
+    }
+
+    case Fuzzing::Declaration::kInterface: {
+      const auto& interface = declaration.interface();
+      out << "interface ";
+      IdentifierToCarbon(interface.name(), out);
+      out << " {\n";
+      for (const auto& member : interface.members()) {
+        DeclarationToCarbon(member, out);
+        out << "\n";
+      }
+      out << "}";
+      // TODO: need to handle interface.self()?
+      break;
+    }
+
+    case Fuzzing::Declaration::kImpl: {
+      const auto& impl = declaration.impl();
+      if (impl.kind() == Fuzzing::ImplDeclaration::ExternalImpl) {
+        out << "external ";
+      }
+      out << "impl ";
+      ExpressionToCarbon(impl.impl_type(), out);
+      out << " as ";
+      ExpressionToCarbon(impl.interface(), out);
+      out << " {\n";
+      for (const auto& member : impl.members()) {
+        DeclarationToCarbon(member, out);
+        out << "\n";
+      }
+      out << "}";
+      break;
+    }
+  }
+}
+
+static auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit,
+                          llvm::raw_ostream& out) -> void {
+  out << "// Generated by proto_to_carbon.\n\n";
+  out << "package ";
+  LibraryNameToCarbon(compilation_unit.package_statement(), out);
+  out << (compilation_unit.is_api() ? " api" : " impl") << ";\n";
+
+  if (!compilation_unit.declarations().empty()) {
+    out << "\n";
+    for (const auto& declaration : compilation_unit.declarations()) {
+      DeclarationToCarbon(declaration, out);
+      out << "\n";
+    }
+  }
+}
+
+auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit)
+    -> std::string {
+  std::string source;
+  llvm::raw_string_ostream out(source);
+  ProtoToCarbon(compilation_unit, out);
+  return source;
+}
+
+}  // namespace Carbon

+ 22 - 0
common/fuzzing/proto_to_carbon.h

@@ -0,0 +1,22 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef COMMON_FUZZING_PROTO_TO_CARBON_H_
+#define COMMON_FUZZING_PROTO_TO_CARBON_H_
+
+#include "common/fuzzing/carbon.pb.h"
+
+namespace Carbon {
+
+// Builds a Carbon source from `compilation_unit`. The logic tries to produce a
+// syntactially valid Carbon source for all cases, even if the input protocol
+// buffer is invalid (like a variable declaration with an empty `name` field).
+// This is done to reduce the number of inputs the fuzzer framework generates
+// when trying to produce lexically valid source.
+auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit)
+    -> std::string;
+
+}  // namespace Carbon
+
+#endif  // COMMON_FUZZING_PROTO_TO_CARBON_H_

+ 22 - 0
executable_semantics/fuzzing/BUILD

@@ -35,3 +35,25 @@ cc_test(
         "@llvm-project//llvm:Support",
     ],
 )
+
+cc_test(
+    name = "proto_to_carbon_test",
+    srcs = ["proto_to_carbon_test.cpp"],
+    args = [
+        "$(locations //executable_semantics:standard_libraries)",
+        "$(locations //executable_semantics/testdata:carbon_files)",
+    ],
+    data = [
+        "//executable_semantics:standard_libraries",
+        "//executable_semantics/testdata:carbon_files",
+    ],
+    deps = [
+        ":ast_to_proto_lib",
+        "//common/fuzzing:carbon_cc_proto",
+        "//common/fuzzing:proto_to_carbon_lib",
+        "//executable_semantics/syntax",
+        "@com_google_googletest//:gtest",
+        "@com_google_protobuf//:protobuf_headers",
+        "@llvm-project//llvm:Support",
+    ],
+)

+ 12 - 1
executable_semantics/fuzzing/ast_to_proto.cpp

@@ -380,7 +380,14 @@ static auto StatementToProto(const Statement& statement) -> Fuzzing::Statement {
           ExpressionToProto(match.expression());
       for (const Match::Clause& clause : match.clauses()) {
         auto* clause_proto = match_proto->add_clauses();
-        *clause_proto->mutable_pattern() = PatternToProto(clause.pattern());
+        const bool is_default_clause =
+            clause.pattern().kind() == PatternKind::BindingPattern &&
+            cast<BindingPattern>(clause.pattern()).name() == AnonymousName;
+        if (is_default_clause) {
+          clause_proto->set_is_default(true);
+        } else {
+          *clause_proto->mutable_pattern() = PatternToProto(clause.pattern());
+        }
         *clause_proto->mutable_statement() =
             StatementToProto(clause.statement());
       }
@@ -466,6 +473,10 @@ static auto DeclarationToProto(const Declaration& declaration)
       const auto& class_decl = cast<ClassDeclaration>(declaration);
       auto* class_proto = declaration_proto.mutable_class_declaration();
       class_proto->set_name(class_decl.name());
+      if (class_decl.type_params().has_value()) {
+        *class_proto->mutable_type_params() =
+            TuplePatternToProto(**class_decl.type_params());
+      }
       for (Nonnull<const Declaration*> member : class_decl.members()) {
         *class_proto->add_members() = DeclarationToProto(*member);
       }

+ 1 - 1
executable_semantics/fuzzing/ast_to_proto_test.cpp

@@ -5,6 +5,7 @@
 #include "executable_semantics/fuzzing/ast_to_proto.h"
 
 #include <gmock/gmock.h>
+#include <google/protobuf/descriptor.h>
 #include <gtest/gtest.h>
 
 #include <filesystem>
@@ -13,7 +14,6 @@
 #include <variant>
 
 #include "executable_semantics/syntax/parse.h"
-#include "google/protobuf/descriptor.h"
 #include "llvm/Support/Error.h"
 
 namespace Carbon::Testing {

+ 63 - 0
executable_semantics/fuzzing/proto_to_carbon_test.cpp

@@ -0,0 +1,63 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "common/fuzzing/proto_to_carbon.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "executable_semantics/fuzzing/ast_to_proto.h"
+#include "executable_semantics/syntax/parse.h"
+
+namespace Carbon::Testing {
+namespace {
+
+static std::vector<llvm::StringRef>* carbon_files = nullptr;
+
+// Returns a string representation of `ast`.
+auto AstToString(const AST& ast) -> std::string {
+  std::string s;
+  llvm::raw_string_ostream out(s);
+  out << "package " << ast.package.package << (ast.is_api ? "api" : "impl")
+      << ";\n";
+  for (auto* declaration : ast.declarations) {
+    out << *declaration << "\n";
+  }
+  return s;
+}
+
+TEST(ProtoToCarbonTest, Roundtrip) {
+  int parsed_ok_count = 0;
+  for (const llvm::StringRef f : *carbon_files) {
+    Carbon::Arena arena;
+    const ErrorOr<AST> ast = Carbon::Parse(&arena, f, /*trace=*/false);
+    if (ast.ok()) {
+      ++parsed_ok_count;
+      const std::string source_from_proto = ProtoToCarbon(AstToProto(*ast));
+      SCOPED_TRACE(testing::Message()
+                   << "Carbon file: " << f << ", source from proto:\n"
+                   << source_from_proto);
+      const ErrorOr<AST> ast_from_proto = Carbon::ParseFromString(
+          &arena, f, source_from_proto, /*trace=*/false);
+
+      if (ast_from_proto.ok()) {
+        EXPECT_EQ(AstToString(*ast), AstToString(*ast_from_proto));
+      } else {
+        ADD_FAILURE() << "Parse error " << ast_from_proto.error().message();
+      }
+    }
+  }
+  // Makes sure files were actually processed.
+  EXPECT_GT(parsed_ok_count, 0);
+}
+
+}  // namespace
+}  // namespace Carbon::Testing
+
+auto main(int argc, char** argv) -> int {
+  ::testing::InitGoogleTest(&argc, argv);
+  Carbon::Testing::carbon_files =
+      new std::vector<llvm::StringRef>(&argv[1], &argv[argc]);
+  return RUN_ALL_TESTS();
+}