diff --git a/common/fuzzing/BUILD b/common/fuzzing/BUILD index 492dca495c7e..be6e36bdf2a8 100644 --- a/common/fuzzing/BUILD +++ b/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", + ], +) diff --git a/common/fuzzing/carbon.proto b/common/fuzzing/carbon.proto index 998ad9c37d89..cb645f9a7fdf 100644 --- a/common/fuzzing/carbon.proto +++ b/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 { diff --git a/common/fuzzing/proto_to_carbon.cpp b/common/fuzzing/proto_to_carbon.cpp new file mode 100644 index 000000000000..7132460015af --- /dev/null +++ b/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 + +#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 diff --git a/common/fuzzing/proto_to_carbon.h b/common/fuzzing/proto_to_carbon.h new file mode 100644 index 000000000000..d5939408d04d --- /dev/null +++ b/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_ diff --git a/executable_semantics/fuzzing/BUILD b/executable_semantics/fuzzing/BUILD index 4ffa439f5b27..f231592ccbde 100644 --- a/executable_semantics/fuzzing/BUILD +++ b/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", + ], +) diff --git a/executable_semantics/fuzzing/ast_to_proto.cpp b/executable_semantics/fuzzing/ast_to_proto.cpp index 43757e8c5f4c..837cd4465bda 100644 --- a/executable_semantics/fuzzing/ast_to_proto.cpp +++ b/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(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(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 member : class_decl.members()) { *class_proto->add_members() = DeclarationToProto(*member); } diff --git a/executable_semantics/fuzzing/ast_to_proto_test.cpp b/executable_semantics/fuzzing/ast_to_proto_test.cpp index 1471376b81ef..f86b444565af 100644 --- a/executable_semantics/fuzzing/ast_to_proto_test.cpp +++ b/executable_semantics/fuzzing/ast_to_proto_test.cpp @@ -5,6 +5,7 @@ #include "executable_semantics/fuzzing/ast_to_proto.h" #include +#include #include #include @@ -13,7 +14,6 @@ #include #include "executable_semantics/syntax/parse.h" -#include "google/protobuf/descriptor.h" #include "llvm/Support/Error.h" namespace Carbon::Testing { diff --git a/executable_semantics/fuzzing/proto_to_carbon_test.cpp b/executable_semantics/fuzzing/proto_to_carbon_test.cpp new file mode 100644 index 000000000000..7ce922d6d2ed --- /dev/null +++ b/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 +#include + +#include "executable_semantics/fuzzing/ast_to_proto.h" +#include "executable_semantics/syntax/parse.h" + +namespace Carbon::Testing { +namespace { + +static std::vector* 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 = 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_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(&argv[1], &argv[argc]); + return RUN_ALL_TESTS(); +}