Create the skeleton of the C++ -> Rust migration tool.
PiperOrigin-RevId: 448016954
diff --git a/common/file_io.cc b/common/file_io.cc
index 2975dd3..613b642 100644
--- a/common/file_io.cc
+++ b/common/file_io.cc
@@ -4,10 +4,21 @@
#include "common/file_io.h"
+#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
namespace crubit {
+absl::StatusOr<std::string> GetFileContents(absl::string_view path) {
+ llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> err_or_buffer =
+ llvm::MemoryBuffer::getFileOrSTDIN(path.data(), /* IsText= */ true);
+ if (std::error_code err = err_or_buffer.getError()) {
+ return absl::Status(absl::StatusCode::kInternal, err.message());
+ }
+
+ return std::string((*err_or_buffer)->getBuffer());
+}
+
absl::Status SetFileContents(absl::string_view path,
absl::string_view contents) {
std::error_code error_code;
diff --git a/common/file_io.h b/common/file_io.h
index 7ab9ff8..b2924d6 100644
--- a/common/file_io.h
+++ b/common/file_io.h
@@ -11,6 +11,8 @@
namespace crubit {
+absl::StatusOr<std::string> GetFileContents(absl::string_view path);
+
absl::Status SetFileContents(absl::string_view path,
absl::string_view contents);
diff --git a/migrator/rs_from_cc/BUILD b/migrator/rs_from_cc/BUILD
new file mode 100644
index 0000000..834ae10
--- /dev/null
+++ b/migrator/rs_from_cc/BUILD
@@ -0,0 +1,94 @@
+"""Generates equivalent Rust code from C++ code."""
+
+licenses(["notice"])
+
+cc_binary(
+ name = "rs_from_cc",
+ srcs = ["rs_from_cc.cc"],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":rs_from_cc_lib",
+ "@absl//flags:flag",
+ "@absl//flags:parse",
+ "@absl//status",
+ "@absl//status:statusor",
+ "@absl//strings",
+ "//common:check",
+ "//common:file_io",
+ "@llvm///llvm:Support",
+ "@rust//support:rust_okay_here",
+ ],
+)
+
+cc_library(
+ name = "frontend_action",
+ srcs = ["frontend_action.cc"],
+ hdrs = ["frontend_action.h"],
+ deps = [
+ ":ast_consumer",
+ ":converter",
+ "//lifetime_annotations",
+ "@llvm///clang:ast",
+ "@llvm///clang:frontend",
+ ],
+)
+
+cc_library(
+ name = "ast_consumer",
+ srcs = ["ast_consumer.cc"],
+ hdrs = ["ast_consumer.h"],
+ deps = [
+ ":converter",
+ "//common:check",
+ "@llvm///clang:ast",
+ "@llvm///clang:frontend",
+ ],
+)
+
+cc_library(
+ name = "converter",
+ srcs = ["converter.cc"],
+ hdrs = ["converter.h"],
+ deps = [
+ "@absl//container:flat_hash_map",
+ "@absl//container:flat_hash_set",
+ "@absl//status:statusor",
+ "@absl//strings",
+ "@absl//types:span",
+ "//lifetime_annotations",
+ "@llvm///clang:ast",
+ "@llvm///clang:basic",
+ "@llvm///clang:sema",
+ "//third_party/re2",
+ ],
+)
+
+cc_test(
+ name = "rs_from_cc_test",
+ srcs = ["rs_from_cc_lib_test.cc"],
+ deps = [
+ ":rs_from_cc_lib",
+ "//testing/base/public:gunit_main",
+ "@absl//status",
+ "@absl//strings",
+ "@llvm///clang:ast",
+ ],
+)
+
+cc_library(
+ name = "rs_from_cc_lib",
+ srcs = ["rs_from_cc_lib.cc"],
+ hdrs = ["rs_from_cc_lib.h"],
+ deps = [
+ ":converter",
+ ":frontend_action",
+ "@absl//container:flat_hash_map",
+ "@absl//status",
+ "@absl//status:statusor",
+ "@absl//strings",
+ "@absl//types:span",
+ "@llvm///clang:basic",
+ "@llvm///clang:frontend",
+ "@llvm///clang:tooling",
+ ],
+)
diff --git a/migrator/rs_from_cc/README.md b/migrator/rs_from_cc/README.md
new file mode 100644
index 0000000..309cf96
--- /dev/null
+++ b/migrator/rs_from_cc/README.md
@@ -0,0 +1,8 @@
+# rs_from_cc
+
+Disclaimer: This project is experimental, under heavy development, and should
+not be used yet.
+
+`:rs_from_cc` converts C++ code into equivalent Rust code.
+
+The relevant design docs are [here](./docs/).
diff --git a/migrator/rs_from_cc/ast_consumer.cc b/migrator/rs_from_cc/ast_consumer.cc
new file mode 100644
index 0000000..c41bbad
--- /dev/null
+++ b/migrator/rs_from_cc/ast_consumer.cc
@@ -0,0 +1,27 @@
+// Part of the Crubit 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 "migrator/rs_from_cc/ast_consumer.h"
+
+#include "common/check.h"
+#include "migrator/rs_from_cc/converter.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Frontend/CompilerInstance.h"
+
+namespace crubit_rs_from_cc {
+
+void AstConsumer::HandleTranslationUnit(clang::ASTContext& ast_context) {
+ if (ast_context.getDiagnostics().hasErrorOccurred()) {
+ // We do not need to process partially incorrect headers, we assume all
+ // input is valid C++. If there is an error Clang already printed it to
+ // stderr; the user will be informed about the cause of the failure.
+ // There is nothing more for us to do here.
+ return;
+ }
+ CRUBIT_CHECK(instance_.hasSema());
+ Converter converter(invocation_, ast_context);
+ converter.Convert(ast_context.getTranslationUnitDecl());
+}
+
+} // namespace crubit_rs_from_cc
diff --git a/migrator/rs_from_cc/ast_consumer.h b/migrator/rs_from_cc/ast_consumer.h
new file mode 100644
index 0000000..7d934ab
--- /dev/null
+++ b/migrator/rs_from_cc/ast_consumer.h
@@ -0,0 +1,32 @@
+// Part of the Crubit 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 CRUBIT_MIGRATOR_RS_FROM_CC_AST_CONSUMER_H_
+#define CRUBIT_MIGRATOR_RS_FROM_CC_AST_CONSUMER_H_
+
+#include "migrator/rs_from_cc/converter.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Frontend/CompilerInstance.h"
+
+namespace crubit_rs_from_cc {
+
+// Consumes the Clang AST created from the invocation's entry header and
+// generates the intermediate representation (`IR`) in the invocation object.
+class AstConsumer : public clang::ASTConsumer {
+ public:
+ explicit AstConsumer(clang::CompilerInstance& instance,
+ Converter::Invocation& invocation)
+ : instance_(instance), invocation_(invocation) {}
+
+ void HandleTranslationUnit(clang::ASTContext& context) override;
+
+ private:
+ clang::CompilerInstance& instance_;
+ Converter::Invocation& invocation_;
+}; // class AstConsumer
+
+} // namespace crubit_rs_from_cc
+
+#endif // CRUBIT_MIGRATOR_RS_FROM_CC_AST_CONSUMER_H_
diff --git a/migrator/rs_from_cc/converter.cc b/migrator/rs_from_cc/converter.cc
new file mode 100644
index 0000000..7cec407
--- /dev/null
+++ b/migrator/rs_from_cc/converter.cc
@@ -0,0 +1,45 @@
+// Part of the Crubit 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 "migrator/rs_from_cc/converter.h"
+
+#include <string>
+
+#include "absl/strings/str_split.h"
+#include "clang/AST/CXXInheritance.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/RecordLayout.h"
+#include "clang/Basic/FileManager.h"
+#include "third_party/re2/re2.h"
+
+namespace crubit_rs_from_cc {
+
+void Converter::Convert(
+ const clang::TranslationUnitDecl* translation_unit_decl) {
+ ConvertUnsupported(translation_unit_decl);
+}
+
+void Converter::ConvertUnsupported(const clang::Decl* decl) {
+ std::string ast;
+ llvm::raw_string_ostream os(ast);
+ decl->dump(os);
+ os.flush();
+ result_ += "\n";
+ result_ += "// Unsupported decl:\n//\n";
+ // Remove addresses since they're not useful and add non-determinism that
+ // would break golden testing.
+ // Also remove spaces at the end of each line, those are a pain in golden
+ // tests since IDEs often strip spaces at end of line.
+ RE2::GlobalReplace(&ast, "(?m) 0x[a-z0-9]+| +$", "");
+ for (auto line : absl::StrSplit(ast, '\n')) {
+ if (line.empty()) {
+ continue;
+ }
+ result_ += "// ";
+ result_ += line;
+ result_ += '\n';
+ }
+}
+
+} // namespace crubit_rs_from_cc
diff --git a/migrator/rs_from_cc/converter.h b/migrator/rs_from_cc/converter.h
new file mode 100644
index 0000000..25b447c
--- /dev/null
+++ b/migrator/rs_from_cc/converter.h
@@ -0,0 +1,61 @@
+// Part of the Crubit 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 CRUBIT_MIGRATOR_RS_FROM_CC_CONVERTER_H_
+#define CRUBIT_MIGRATOR_RS_FROM_CC_CONVERTER_H_
+
+#include <memory>
+#include <optional>
+#include <set>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/status/statusor.h"
+#include "absl/types/span.h"
+#include "lifetime_annotations/lifetime_annotations.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Mangle.h"
+#include "clang/AST/RawCommentList.h"
+#include "clang/AST/Type.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/Specifiers.h"
+#include "clang/Sema/Sema.h"
+
+namespace crubit_rs_from_cc {
+
+// Visits the C++ AST and generates the corresponding Rust code in the
+// Invocation object.
+class Converter {
+ public:
+ // Top-level parameters as well as return value of a migrator invocation.
+ class Invocation {
+ public:
+ std::string rs_code_;
+ };
+
+ explicit Converter(Invocation& invocation, clang::ASTContext& ctx)
+ : result_(invocation.rs_code_), ctx_(ctx) {}
+
+ // Import all visible declarations from a translation unit.
+ void Convert(const clang::TranslationUnitDecl* decl);
+
+ // "converts" a not-yet-supported declaration to Rust by dumping the C++ AST
+ // into a comment.
+ void ConvertUnsupported(const clang::Decl* decl);
+
+ private:
+ // The main output of the conversion process (Rust code).
+ std::string& result_;
+
+ clang::ASTContext& ctx_;
+}; // class Converter
+
+} // namespace crubit_rs_from_cc
+
+#endif // CRUBIT_MIGRATOR_RS_FROM_CC_CONVERTER_H_
diff --git a/migrator/rs_from_cc/frontend_action.cc b/migrator/rs_from_cc/frontend_action.cc
new file mode 100644
index 0000000..7fbe5f8
--- /dev/null
+++ b/migrator/rs_from_cc/frontend_action.cc
@@ -0,0 +1,20 @@
+// Part of the Crubit 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 "migrator/rs_from_cc/frontend_action.h"
+
+#include <memory>
+
+#include "migrator/rs_from_cc/ast_consumer.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/Frontend/CompilerInstance.h"
+
+namespace crubit_rs_from_cc {
+
+std::unique_ptr<clang::ASTConsumer> FrontendAction::CreateASTConsumer(
+ clang::CompilerInstance& instance, llvm::StringRef) {
+ return std::make_unique<AstConsumer>(instance, invocation_);
+}
+
+} // namespace crubit_rs_from_cc
diff --git a/migrator/rs_from_cc/frontend_action.h b/migrator/rs_from_cc/frontend_action.h
new file mode 100644
index 0000000..42c6779
--- /dev/null
+++ b/migrator/rs_from_cc/frontend_action.h
@@ -0,0 +1,34 @@
+// Part of the Crubit 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 CRUBIT_MIGRATOR_RS_FROM_CC_FRONTEND_ACTION_H_
+#define CRUBIT_MIGRATOR_RS_FROM_CC_FRONTEND_ACTION_H_
+
+#include <memory>
+
+#include "lifetime_annotations/lifetime_annotations.h"
+#include "migrator/rs_from_cc/converter.h"
+#include "clang/AST/ASTConsumer.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Frontend/FrontendAction.h"
+
+namespace crubit_rs_from_cc {
+
+// Creates an `ASTConsumer` that generates the Rust code in the invocation
+// object.
+class FrontendAction : public clang::ASTFrontendAction {
+ public:
+ explicit FrontendAction(Converter::Invocation& invocation)
+ : invocation_(invocation) {}
+
+ std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
+ clang::CompilerInstance& instance, llvm::StringRef) override;
+
+ private:
+ Converter::Invocation& invocation_;
+};
+
+} // namespace crubit_rs_from_cc
+
+#endif // CRUBIT_MIGRATOR_RS_FROM_CC_FRONTEND_ACTION_H_
diff --git a/migrator/rs_from_cc/rs_from_cc.cc b/migrator/rs_from_cc/rs_from_cc.cc
new file mode 100644
index 0000000..40e7cfd
--- /dev/null
+++ b/migrator/rs_from_cc/rs_from_cc.cc
@@ -0,0 +1,56 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Parses C++ code and generates an equivalent Rust source file.
+
+#include <utility>
+#include <vector>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "common/check.h"
+#include "common/file_io.h"
+#include "migrator/rs_from_cc/rs_from_cc_lib.h"
+#include "llvm/Support/FileSystem.h"
+
+ABSL_FLAG(std::string, cc_in, "",
+ "input path for the C++ source file (it may or may not be a header)");
+ABSL_FLAG(std::string, rs_out, "",
+ "output path for the Rust source file; will be overwritten if it "
+ "already exists");
+
+int main(int argc, char* argv[]) {
+ auto args = absl::ParseCommandLine(argc, argv);
+
+ auto cc_in = absl::GetFlag(FLAGS_cc_in);
+ if (cc_in.empty()) {
+ std::cerr << "please specify --cc_in" << std::endl;
+ return 1;
+ }
+ auto rs_out = absl::GetFlag(FLAGS_rs_out);
+ if (rs_out.empty()) {
+ std::cerr << "please specify --rs_out" << std::endl;
+ return 1;
+ }
+
+ auto status_or_cc_file_content = crubit::GetFileContents(cc_in);
+ CRUBIT_CHECK(status_or_cc_file_content.ok());
+ std::string cc_file_content = std::move(*status_or_cc_file_content);
+
+ // Skip $0.
+ ++argv;
+
+ absl::StatusOr<std::string> rs_code = crubit_rs_from_cc::RsFromCc(
+ cc_file_content, cc_in,
+ std::vector<absl::string_view>(argv, argv + argc));
+ if (!rs_code.ok()) {
+ CRUBIT_CHECK(rs_code.ok());
+ }
+
+ CRUBIT_CHECK(crubit::SetFileContents(rs_out, *rs_code).ok());
+ return 0;
+}
diff --git a/migrator/rs_from_cc/rs_from_cc_lib.cc b/migrator/rs_from_cc/rs_from_cc_lib.cc
new file mode 100644
index 0000000..d25a258
--- /dev/null
+++ b/migrator/rs_from_cc/rs_from_cc_lib.cc
@@ -0,0 +1,48 @@
+// Part of the Crubit 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 "migrator/rs_from_cc/rs_from_cc_lib.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/substitute.h"
+#include "absl/types/span.h"
+#include "migrator/rs_from_cc/converter.h"
+#include "migrator/rs_from_cc/frontend_action.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/FileSystemOptions.h"
+#include "clang/Frontend/FrontendAction.h"
+#include "clang/Tooling/Tooling.h"
+
+namespace crubit_rs_from_cc {
+
+absl::StatusOr<std::string> RsFromCc(const absl::string_view cc_file_content,
+ const absl::string_view cc_file_name,
+ absl::Span<const absl::string_view> args) {
+ std::vector<std::string> args_as_strings{
+ // Parse non-doc comments that are used as documention
+ "-fparse-all-comments"};
+ args_as_strings.insert(args_as_strings.end(), args.begin(), args.end());
+
+ Converter::Invocation invocation;
+ if (clang::tooling::runToolOnCodeWithArgs(
+ std::make_unique<FrontendAction>(invocation), cc_file_content,
+ args_as_strings, cc_file_name, "rs_from_cc",
+ std::make_shared<clang::PCHContainerOperations>(),
+ clang::tooling::FileContentMappings())) {
+ return invocation.rs_code_;
+ } else {
+ return absl::Status(absl::StatusCode::kInvalidArgument,
+ "Could not compile source file contents");
+ }
+}
+
+} // namespace crubit_rs_from_cc
diff --git a/migrator/rs_from_cc/rs_from_cc_lib.h b/migrator/rs_from_cc/rs_from_cc_lib.h
new file mode 100644
index 0000000..8eed34f
--- /dev/null
+++ b/migrator/rs_from_cc/rs_from_cc_lib.h
@@ -0,0 +1,32 @@
+// Part of the Crubit 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 CRUBIT_MIGRATOR_RS_FROM_CC_RS_FROM_CC_LIB_H_
+#define CRUBIT_MIGRATOR_RS_FROM_CC_RS_FROM_CC_LIB_H_
+
+#include <string>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
+namespace crubit_rs_from_cc {
+
+// Converts C++ source code into Rust.
+//
+// Parameters:
+// * `cc_file_content`: a string with the C++ source code to convert.
+// * `cc_file_name`: name of the C++ file we're converting. Can be omitted for
+// tests.
+// * `args`: additional command line arguments for Clang (if any)
+//
+absl::StatusOr<std::string> RsFromCc(
+ absl::string_view cc_file_content,
+ absl::string_view cc_file_name = "testing/file_name.cc",
+ absl::Span<const absl::string_view> args = {});
+
+} // namespace crubit_rs_from_cc
+
+#endif // CRUBIT_MIGRATOR_RS_FROM_CC_RS_FROM_CC_LIB_H_
diff --git a/migrator/rs_from_cc/rs_from_cc_lib_test.cc b/migrator/rs_from_cc/rs_from_cc_lib_test.cc
new file mode 100644
index 0000000..e20826e
--- /dev/null
+++ b/migrator/rs_from_cc/rs_from_cc_lib_test.cc
@@ -0,0 +1,102 @@
+// Part of the Crubit 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 "migrator/rs_from_cc/rs_from_cc_lib.h"
+
+#include <variant>
+
+#include "testing/base/public/gmock.h"
+#include "testing/base/public/gunit.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "clang/AST/ASTContext.h"
+
+namespace crubit_rs_from_cc {
+namespace {
+
+using ::testing::Eq;
+using ::testing::status::StatusIs;
+
+TEST(RsFromCcTest, Noop) {
+ // Nothing interesting there, but also not empty, so that the header gets
+ // generated.
+ ASSERT_OK_AND_ASSIGN(std::string rs_code, RsFromCc(" "));
+
+ EXPECT_THAT(rs_code, Eq(R"end_of_string(
+// Unsupported decl:
+//
+// TranslationUnitDecl <<invalid sloc>> <invalid sloc>
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
+// | `-BuiltinType '__int128'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
+// | `-BuiltinType 'unsigned __int128'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __NSConstantString '__NSConstantString_tag'
+// | `-RecordType '__NSConstantString_tag'
+// | `-CXXRecord '__NSConstantString_tag'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
+// | `-PointerType 'char *'
+// | `-BuiltinType 'char'
+// `-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag[1]'
+// `-ConstantArrayType '__va_list_tag[1]' 1
+// `-RecordType '__va_list_tag'
+// `-CXXRecord '__va_list_tag'
+)end_of_string"));
+}
+
+TEST(RsFromCcTest, Comment) {
+ ASSERT_OK_AND_ASSIGN(std::string rs_code, RsFromCc("// This is a comment"));
+
+ EXPECT_THAT(rs_code, Eq(R"end_of_string(
+// Unsupported decl:
+//
+// TranslationUnitDecl <<invalid sloc>> <invalid sloc>
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
+// | `-BuiltinType '__int128'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
+// | `-BuiltinType 'unsigned __int128'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __NSConstantString '__NSConstantString_tag'
+// | `-RecordType '__NSConstantString_tag'
+// | `-CXXRecord '__NSConstantString_tag'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
+// | `-PointerType 'char *'
+// | `-BuiltinType 'char'
+// `-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag[1]'
+// `-ConstantArrayType '__va_list_tag[1]' 1
+// `-RecordType '__va_list_tag'
+// `-CXXRecord '__va_list_tag'
+)end_of_string"));
+}
+
+TEST(RsFromCcTest, ErrorOnInvalidInput) {
+ ASSERT_THAT(RsFromCc("int foo(); But this is not C++"),
+ StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(RsFromCcTest, FunctionDeclaration) {
+ ASSERT_OK_AND_ASSIGN(std::string rs_code, RsFromCc("void f();"));
+
+ EXPECT_THAT(rs_code, Eq(R"end_of_string(
+// Unsupported decl:
+//
+// TranslationUnitDecl <<invalid sloc>> <invalid sloc>
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
+// | `-BuiltinType '__int128'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
+// | `-BuiltinType 'unsigned __int128'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __NSConstantString '__NSConstantString_tag'
+// | `-RecordType '__NSConstantString_tag'
+// | `-CXXRecord '__NSConstantString_tag'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __builtin_ms_va_list 'char *'
+// | `-PointerType 'char *'
+// | `-BuiltinType 'char'
+// |-TypedefDecl <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag[1]'
+// | `-ConstantArrayType '__va_list_tag[1]' 1
+// | `-RecordType '__va_list_tag'
+// | `-CXXRecord '__va_list_tag'
+// `-FunctionDecl <testing/file_name.cc:1:1, col:8> col:6 f 'void ()'
+)end_of_string"));
+}
+
+} // namespace
+} // namespace crubit_rs_from_cc