Remove legacy migration prototype (#4887)

Although migration tooling is intended for Carbon, this tooling was
primarily a proof-of-concept prototype. The last time it got significant
work was in 2022
(https://github.com/carbon-language/carbon-lang/commits/trunk/migrate_cpp);
since then, we've mainly been doing small cleanups to keep it building.
We're now hitting an issue in #4886 that `TypeNodes.inc` isn't exposed
as a `#include`.

We already had thoughts about rewriting this as a `RecursiveASTVisitor`,
per `rewriter.cpp`/`rewriter.h`. That may justify a significantly
different approach than had been set up in `cpp_refactoring`. But, it's
hard to tell.

Either way, my sense is that rather than incrementally trying to keep
this code building, we should revisit it when we're ready and have a
long-term strategy for how migration should work.
This commit is contained in:
Jon Ross-Perkins
2025-02-03 19:57:15 +00:00
committed by GitHub
parent 77ea777b13
commit a8aca3ce71
24 changed files with 0 additions and 2250 deletions
-2
View File
@@ -15,8 +15,6 @@ filegroup(
data = [
"//explorer",
"//installers/local:carbon",
"//migrate_cpp:rewriter",
"//migrate_cpp/cpp_refactoring",
"//toolchain/install:carbon-busybox",
# The tree sitter rules can't be queried; evaluation fails on
# @platforms.
-60
View File
@@ -1,60 +0,0 @@
# 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
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load("@rules_python//python:defs.bzl", "py_binary")
py_binary(
name = "migrate_cpp",
srcs = ["migrate_cpp.py"],
data = [
":clang_tidy.yaml",
"//migrate_cpp/cpp_refactoring",
],
python_version = "PY3",
)
cc_library(
name = "output_segment",
hdrs = ["output_segment.h"],
deps = [
"//common:check",
"@llvm-project//clang:ast",
],
)
cc_library(
name = "rewriter",
srcs = ["rewriter.cpp"],
hdrs = ["rewriter.h"],
visibility = [
# For dependency checking. Remove once it has a binary using it.
"//bazel/check_deps:__pkg__",
],
deps = [
":output_segment",
"//common:check",
"@llvm-project//clang:ast",
"@llvm-project//clang:basic",
"@llvm-project//clang:frontend",
"@llvm-project//clang:lex",
"@llvm-project//clang:tooling",
"@llvm-project//clang:tooling_core",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "rewriter_test",
size = "small",
srcs = ["rewriter_test.cpp"],
deps = [
":rewriter",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//clang:ast",
"@llvm-project//clang:frontend",
"@llvm-project//clang:tooling",
],
)
-28
View File
@@ -1,28 +0,0 @@
# C++ migration tooling
<!--
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
-->
<!-- toc -->
## Table of contents
- [Overview](#overview)
- [Structure](#structure)
<!-- tocstop -->
## Overview
`migrate_cpp` assists in migration of C++ code to Carbon. It's currently being
assembled; more documentation will be added later.
## Structure
The `migrate_cpp` tool uses a `clang::RecursiveASTVisitor` to traverse Clang's
AST and, to each node, associate replacements. Each node's replacement is a
sequence of text, or a reference to some other node that should be used to
replace it.
-16
View File
@@ -1,16 +0,0 @@
# 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
# Use clang-tidy to standardize syntax. This configuration focuses on
# modernizations, not casing-related choices, in order to avoid changes which
# would affect non-migrated callers.
---
Checks:
-*, bugprone-*, -bugprone-reserved-identifier, google-*,
-google-readability-todo, misc-definitions-in-headers, misc-misplaced-const,
misc-redundant-expression, misc-static-assert,
misc-unconventional-assign-operator, misc-uniqueptr-reset-release,
misc-unused-*, modernize-*, -modernize-avoid-c-arrays, performance-*,
readability-braces-around-statements
-120
View File
@@ -1,120 +0,0 @@
# 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
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
package(default_visibility = ["//visibility:public"])
cc_binary(
name = "cpp_refactoring",
srcs = ["main.cpp"],
deps = [
":fn_inserter",
":for_range",
":matcher",
":var_decl",
"@llvm-project//clang:tooling",
],
)
cc_library(
name = "matcher",
srcs = ["matcher.cpp"],
hdrs = [
"matcher.h",
"matcher_manager.h",
],
deps = [
"@llvm-project//clang:ast_matchers",
"@llvm-project//clang:basic",
"@llvm-project//clang:lex",
"@llvm-project//clang:tooling_core",
],
)
cc_library(
name = "matcher_test_base",
testonly = 1,
hdrs = ["matcher_test_base.h"],
deps = [
":matcher",
"@googletest//:gtest",
"@llvm-project//clang:ast_matchers",
"@llvm-project//clang:tooling",
"@llvm-project//clang:tooling_core",
],
)
# Individual matchers
cc_library(
name = "fn_inserter",
srcs = ["fn_inserter.cpp"],
hdrs = ["fn_inserter.h"],
deps = [
":matcher",
"@llvm-project//clang:ast_matchers",
],
)
cc_test(
name = "fn_inserter_test",
size = "small",
srcs = ["fn_inserter_test.cpp"],
deps = [
":fn_inserter",
":matcher_test_base",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//clang:tooling",
],
)
cc_library(
name = "for_range",
srcs = ["for_range.cpp"],
hdrs = ["for_range.h"],
deps = [
":matcher",
"@llvm-project//clang:ast_matchers",
],
)
cc_test(
name = "for_range_test",
size = "small",
srcs = ["for_range_test.cpp"],
deps = [
":for_range",
":matcher_test_base",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//clang:tooling",
],
)
cc_library(
name = "var_decl",
srcs = ["var_decl.cpp"],
hdrs = ["var_decl.h"],
deps = [
":matcher",
"@llvm-project//clang:ast_matchers",
"@llvm-project//clang:type_nodes_gen",
"@llvm-project//llvm:Support",
],
)
cc_test(
name = "var_decl_test",
size = "small",
srcs = ["var_decl_test.cpp"],
deps = [
":matcher_test_base",
":var_decl",
"//testing/base:gtest_main",
"@googletest//:gtest",
"@llvm-project//clang:tooling",
],
)
@@ -1,50 +0,0 @@
// 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 "migrate_cpp/cpp_refactoring/fn_inserter.h"
#include "clang/ASTMatchers/ASTMatchers.h"
// NOLINTNEXTLINE(readability-identifier-naming)
namespace cam = ::clang::ast_matchers;
namespace Carbon {
static constexpr char Label[] = "FnInserter";
void FnInserter::Run() {
const auto& decl = GetNodeAsOrDie<clang::FunctionDecl>(Label);
// For names like "Class::Method", replace up to "Class" not "Method".
clang::NestedNameSpecifierLoc qual_loc = decl.getQualifierLoc();
clang::SourceLocation name_begin_loc =
qual_loc.hasQualifier() ? qual_loc.getBeginLoc() : decl.getLocation();
auto range =
clang::CharSourceRange::getCharRange(decl.getBeginLoc(), name_begin_loc);
// In order to handle keywords like "virtual" in "virtual auto Foo() -> ...",
// scan the replaced text and only drop auto/void entries.
llvm::SmallVector<llvm::StringRef> split;
GetSourceText(range).split(split, ' ', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
std::string new_text = "fn ";
for (llvm::StringRef t : split) {
if (t != "auto" && t != "void") {
new_text += t.str() + " ";
}
}
AddReplacement(range, new_text);
}
void FnInserterFactory::AddMatcher(cam::MatchFinder* finder,
cam::MatchFinder::MatchCallback* callback) {
finder->addMatcher(
cam::functionDecl(cam::anyOf(cam::hasTrailingReturn(),
cam::returns(cam::asString("void"))),
cam::unless(cam::anyOf(cam::cxxConstructorDecl(),
cam::cxxDestructorDecl())))
.bind(Label),
callback);
}
} // namespace Carbon
-28
View File
@@ -1,28 +0,0 @@
// 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 CARBON_MIGRATE_CPP_CPP_REFACTORING_FN_INSERTER_H_
#define CARBON_MIGRATE_CPP_CPP_REFACTORING_FN_INSERTER_H_
#include "migrate_cpp/cpp_refactoring/matcher.h"
namespace Carbon {
// Inserts `fn` for functions and methods.
class FnInserter : public Matcher {
public:
using Matcher::Matcher;
void Run() override;
};
class FnInserterFactory : public MatcherFactoryBase<FnInserter> {
public:
void AddMatcher(
clang::ast_matchers::MatchFinder* finder,
clang::ast_matchers::MatchFinder::MatchCallback* callback) override;
};
} // namespace Carbon
#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_FN_INSERTER_H_
@@ -1,93 +0,0 @@
// 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 "migrate_cpp/cpp_refactoring/fn_inserter.h"
#include "migrate_cpp/cpp_refactoring/matcher_test_base.h"
namespace Carbon::Testing {
namespace {
class FnInserterTest : public MatcherTestBase<FnInserterFactory> {};
TEST_F(FnInserterTest, TrailingReturn) {
constexpr char Before[] = "auto A() -> int;";
constexpr char After[] = "fn A() -> int;";
ExpectReplacement(Before, After);
}
TEST_F(FnInserterTest, Inline) {
constexpr char Before[] = "inline auto A() -> int;";
constexpr char After[] = "fn inline A() -> int;";
ExpectReplacement(Before, After);
}
TEST_F(FnInserterTest, Void) {
constexpr char Before[] = "void A();";
constexpr char After[] = "fn A();";
ExpectReplacement(Before, After);
}
TEST_F(FnInserterTest, Methods) {
constexpr char Before[] = R"cpp(
class Shape {
public:
virtual void Draw() = 0;
virtual auto NumSides() -> int = 0;
};
class Circle : public Shape {
public:
void Draw() override;
auto NumSides() -> int override;
auto Radius() -> double { return radius_; }
private:
double radius_;
};
void Shape::Draw() {}
)cpp";
constexpr char After[] = R"(
class Shape {
public:
fn virtual Draw() = 0;
fn virtual NumSides() -> int = 0;
};
class Circle : public Shape {
public:
fn Draw() override;
fn NumSides() -> int override;
fn Radius() -> double { return radius_; }
private:
double radius_;
};
fn Shape::Draw() {}
)";
ExpectReplacement(Before, After);
}
TEST_F(FnInserterTest, ConstructorDestructor) {
constexpr char Before[] = R"cpp(
class Shape {
public:
Shape() {}
~Shape() {}
};
)cpp";
ExpectReplacement(Before, Before);
}
TEST_F(FnInserterTest, LegacyReturn) {
// Code should be migrated to trailing returns by clang-tidy, so this is okay
// to miss.
constexpr char Before[] = "int A();";
ExpectReplacement(Before, Before);
}
} // namespace
} // namespace Carbon::Testing
-30
View File
@@ -1,30 +0,0 @@
// 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 "migrate_cpp/cpp_refactoring/for_range.h"
#include "clang/ASTMatchers/ASTMatchers.h"
// NOLINTNEXTLINE(readability-identifier-naming)
namespace cam = ::clang::ast_matchers;
namespace Carbon {
static constexpr char Label[] = "ForRange";
void ForRange::Run() {
const auto& stmt = GetNodeAsOrDie<clang::CXXForRangeStmt>(Label);
// Wrap `in` with spaces so that `for (auto i:items)` has valid results.
AddReplacement(clang::CharSourceRange::getTokenRange(stmt.getColonLoc(),
stmt.getColonLoc()),
" in ");
}
void ForRangeFactory::AddMatcher(cam::MatchFinder* finder,
cam::MatchFinder::MatchCallback* callback) {
finder->addMatcher(cam::cxxForRangeStmt().bind(Label), callback);
}
} // namespace Carbon
-31
View File
@@ -1,31 +0,0 @@
// 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 CARBON_MIGRATE_CPP_CPP_REFACTORING_FOR_RANGE_H_
#define CARBON_MIGRATE_CPP_CPP_REFACTORING_FOR_RANGE_H_
#include "migrate_cpp/cpp_refactoring/matcher.h"
namespace Carbon {
// Updates variable declarations for `var name: type`.
class ForRange : public Matcher {
public:
using Matcher::Matcher;
void Run() override;
private:
auto GetTypeStr(const clang::VarDecl& decl) -> std::string;
};
class ForRangeFactory : public MatcherFactoryBase<ForRange> {
public:
void AddMatcher(
clang::ast_matchers::MatchFinder* finder,
clang::ast_matchers::MatchFinder::MatchCallback* callback) override;
};
} // namespace Carbon
#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_FOR_RANGE_H_
@@ -1,52 +0,0 @@
// 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 "migrate_cpp/cpp_refactoring/for_range.h"
#include "migrate_cpp/cpp_refactoring/matcher_test_base.h"
namespace Carbon::Testing {
namespace {
class ForRangeTest : public MatcherTestBase<ForRangeFactory> {};
TEST_F(ForRangeTest, Basic) {
constexpr char Before[] = R"cpp(
void Foo() {
int items[] = {1};
for (int i : items) {
}
}
)cpp";
constexpr char After[] = R"(
void Foo() {
int items[] = {1};
for (int i in items) {
}
}
)";
ExpectReplacement(Before, After);
}
TEST_F(ForRangeTest, NoSpace) {
// Do not mark `cpp` so that clang-format won't "fix" the `:` spacing.
constexpr char Before[] = R"(
void Foo() {
int items[] = {1};
for (int i:items) {
}
}
)";
constexpr char After[] = R"(
void Foo() {
int items[] = {1};
for (int i in items) {
}
}
)";
ExpectReplacement(Before, After);
}
} // namespace
} // namespace Carbon::Testing
-44
View File
@@ -1,44 +0,0 @@
// 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 "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "migrate_cpp/cpp_refactoring/fn_inserter.h"
#include "migrate_cpp/cpp_refactoring/for_range.h"
#include "migrate_cpp/cpp_refactoring/matcher_manager.h"
#include "migrate_cpp/cpp_refactoring/var_decl.h"
using clang::tooling::RefactoringTool;
// Initialize the files in replacements. Matcher will restrict replacements to
// initialized files.
static void InitReplacements(RefactoringTool* tool) {
clang::FileManager& files = tool->getFiles();
Carbon::Matcher::ReplacementMap& repl = tool->getReplacements();
for (const std::string& path : tool->getSourcePaths()) {
llvm::Expected<clang::FileEntryRef> file = files.getFileRef(path);
if (!file) {
llvm::report_fatal_error(llvm::Twine("Error accessing `") + path +
"`: " + llvm::toString(file.takeError()) + "\n");
}
repl.insert({files.getCanonicalName(*file).str(), {}});
}
}
auto main(int argc, const char** argv) -> int {
llvm::cl::OptionCategory category("C++ refactoring options");
auto parser =
clang::tooling::CommonOptionsParser::create(argc, argv, category);
RefactoringTool tool(parser->getCompilations(), parser->getSourcePathList());
InitReplacements(&tool);
// Set up AST matcher callbacks.
Carbon::MatcherManager matchers(&tool.getReplacements());
matchers.Register(std::make_unique<Carbon::FnInserterFactory>());
matchers.Register(std::make_unique<Carbon::ForRangeFactory>());
matchers.Register(std::make_unique<Carbon::VarDeclFactory>());
return tool.runAndSave(
clang::tooling::newFrontendActionFactory(matchers.GetFinder()).get());
}
-46
View File
@@ -1,46 +0,0 @@
// 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 "migrate_cpp/cpp_refactoring/matcher.h"
#include "clang/Basic/SourceManager.h"
namespace Carbon {
void Matcher::AddReplacement(clang::CharSourceRange range,
llvm::StringRef replacement_text) {
if (!range.isValid()) {
// Invalid range.
return;
}
const auto& source_manager = GetSourceManager();
if (source_manager.getDecomposedLoc(range.getBegin()).first !=
source_manager.getDecomposedLoc(range.getEnd()).first) {
// Range spans macro expansions.
return;
}
if (source_manager.getFileID(range.getBegin()) !=
source_manager.getFileID(range.getEnd())) {
// Range spans files.
return;
}
clang::tooling::Replacement rep(source_manager,
source_manager.getExpansionRange(range),
replacement_text);
auto entry = replacements->find(std::string(rep.getFilePath()));
if (entry == replacements->end()) {
// The replacement was in a file which isn't being updated, such as a system
// header.
return;
}
llvm::Error err = entry->second.add(rep);
if (err) {
llvm::errs() << "Error with replacement `" << rep.toString()
<< "`: " << llvm::toString(std::move(err)) << "\n";
}
}
} // namespace Carbon
-95
View File
@@ -1,95 +0,0 @@
// 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 CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_H_
#define CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_H_
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/Core/Replacement.h"
namespace Carbon {
// This is an abstract class with helpers to make it easier to write matchers.
// Note a MatcherFactory (below) is also typically required.
class Matcher {
public:
using ReplacementMap = std::map<std::string, clang::tooling::Replacements>;
Matcher(const clang::ast_matchers::MatchFinder::MatchResult* in_match_result,
ReplacementMap* in_replacements)
: match_result(in_match_result), replacements(in_replacements) {}
virtual ~Matcher() = default;
// Performs main execution of the matcher when a result is found.
virtual void Run() = 0;
protected:
// Replaces the given range with the specified text.
void AddReplacement(clang::CharSourceRange range,
llvm::StringRef replacement_text);
// Returns a matched node by ID, exiting if not present.
template <typename NodeType>
auto GetNodeAsOrDie(llvm::StringRef id) -> const NodeType& {
auto* node = match_result->Nodes.getNodeAs<NodeType>(id);
if (!node) {
llvm::report_fatal_error(std::string("getNodeAs failed for ") + id);
}
return *node;
}
// Returns the language options.
auto GetLangOpts() -> const clang::LangOptions& {
return match_result->Context->getLangOpts();
}
// Returns the full source manager.
auto GetSourceManager() -> const clang::SourceManager& {
return *match_result->SourceManager;
}
// Returns the source text for a given range.
auto GetSourceText(clang::CharSourceRange range) -> llvm::StringRef {
return clang::Lexer::getSourceText(range, GetSourceManager(),
GetLangOpts());
}
private:
const clang::ast_matchers::MatchFinder::MatchResult* const match_result;
ReplacementMap* const replacements;
};
// A factory used to instantiate per-MatchResult Matchers, to be registered with
// the MatcherManager.
class MatcherFactory {
public:
virtual ~MatcherFactory() = default;
virtual auto CreateMatcher(
const clang::ast_matchers::MatchFinder::MatchResult* match_result,
Matcher::ReplacementMap* replacements) -> std::unique_ptr<Matcher> = 0;
// Adds the Matcher to the finder with the provided callback.
virtual void AddMatcher(
clang::ast_matchers::MatchFinder* finder,
clang::ast_matchers::MatchFinder::MatchCallback* callback) = 0;
};
// A convenience factory that implements CreateMatcher for Matchers that have a
// standard constructor.
template <typename MatcherType>
class MatcherFactoryBase : public MatcherFactory {
public:
auto CreateMatcher(
const clang::ast_matchers::MatchFinder::MatchResult* match_result,
Matcher::ReplacementMap* replacements)
-> std::unique_ptr<Matcher> override {
return std::make_unique<MatcherType>(match_result, replacements);
}
};
} // namespace Carbon
#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_H_
@@ -1,58 +0,0 @@
// 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 CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_MANAGER_H_
#define CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_MANAGER_H_
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/Core/Replacement.h"
#include "migrate_cpp/cpp_refactoring/matcher.h"
namespace Carbon {
// Manages registration of AST matchers.
class MatcherManager {
public:
explicit MatcherManager(Matcher::ReplacementMap* in_replacements)
: replacements(in_replacements) {}
// Registers Matcher implementations.
void Register(std::unique_ptr<MatcherFactory> factory) {
matchers.push_back(std::make_unique<MatchCallbackWrapper>(
&finder, std::move(factory), replacements));
}
auto GetFinder() -> clang::ast_matchers::MatchFinder* { return &finder; }
private:
// Adapts Matcher for use with MatchCallback.
class MatchCallbackWrapper
: public clang::ast_matchers::MatchFinder::MatchCallback {
public:
explicit MatchCallbackWrapper(clang::ast_matchers::MatchFinder* finder,
std::unique_ptr<MatcherFactory> in_factory,
Matcher::ReplacementMap* in_replacements)
: factory(std::move(in_factory)), replacements(in_replacements) {
factory->AddMatcher(finder, this);
}
void run(const clang::ast_matchers::MatchFinder::MatchResult& match_result)
override {
factory->CreateMatcher(&match_result, replacements)->Run();
}
private:
std::unique_ptr<MatcherFactory> factory;
Matcher::ReplacementMap* const replacements;
};
Matcher::ReplacementMap* const replacements;
clang::ast_matchers::MatchFinder finder;
std::vector<std::unique_ptr<MatcherFactory>> factories;
std::vector<std::unique_ptr<MatchCallbackWrapper>> matchers;
};
} // namespace Carbon
#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_MANAGER_H_
@@ -1,67 +0,0 @@
// 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 CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_TEST_BASE_H_
#define CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_TEST_BASE_H_
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Tooling.h"
#include "migrate_cpp/cpp_refactoring/matcher_manager.h"
namespace Carbon::Testing {
// Matcher test framework.
template <typename MatcherFactoryType>
class MatcherTestBase : public ::testing::Test {
protected:
MatcherTestBase() : matchers_(&replacements_) {
matchers_.Register(std::make_unique<MatcherFactoryType>());
}
// Expects that the replacements produced by running the finder result in
// the specified code transformation.
void ExpectReplacement(llvm::StringRef before, llvm::StringRef after) {
auto factory =
clang::tooling::newFrontendActionFactory(matchers_.GetFinder());
constexpr char Filename[] = "test.cc";
replacements_.clear();
replacements_.insert({Filename, {}});
ASSERT_TRUE(clang::tooling::runToolOnCodeWithArgs(
factory->create(), before, {}, Filename, "clang-tool",
std::make_shared<clang::PCHContainerOperations>(),
clang::tooling::FileContentMappings()));
EXPECT_THAT(replacements_, testing::ElementsAre(testing::Key(Filename)));
llvm::Expected<std::string> actual =
clang::tooling::applyAllReplacements(before, replacements_[Filename]);
// Make a specific note if the matcher didn't make any changes.
std::string unchanged;
if (before == *actual) {
unchanged = "NOTE: Actual matches original text, no changes made.";
}
if (after.find('\n') == std::string::npos) {
EXPECT_THAT(*actual, testing::Eq(after.str())) << unchanged;
} else {
// Split lines to get gmock to get an easier-to-read error.
llvm::SmallVector<llvm::StringRef, 0> actual_lines;
llvm::SplitString(*actual, actual_lines, "\n");
llvm::SmallVector<llvm::StringRef, 0> after_lines;
llvm::SplitString(after, after_lines, "\n");
EXPECT_THAT(actual_lines, testing::ContainerEq(after_lines)) << unchanged;
}
}
private:
Matcher::ReplacementMap replacements_;
MatcherManager matchers_;
};
} // namespace Carbon::Testing
#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_TEST_BASE_H_
-144
View File
@@ -1,144 +0,0 @@
// 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 "migrate_cpp/cpp_refactoring/var_decl.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/Support/FormatVariadic.h"
// NOLINTNEXTLINE(readability-identifier-naming)
namespace cam = ::clang::ast_matchers;
namespace Carbon {
static constexpr char Label[] = "VarDecl";
// Helper function for printing TypeLocClass. Useful for debugging.
LLVM_ATTRIBUTE_UNUSED
static auto TypeLocClassToString(clang::TypeLoc::TypeLocClass c)
-> std::string {
switch (c) {
// Mirrors the definition in clang/AST/TypeLoc.h in order to print names.
#define ABSTRACT_TYPE(Class, Base)
#define TYPE(Class, Base) \
case clang::TypeLoc::Class: \
return #Class;
#include "clang/AST/TypeNodes.inc"
case clang::TypeLoc::Qualified:
return "Qualified";
}
}
// Returns a string for the type.
auto VarDecl::GetTypeStr(const clang::VarDecl& decl) -> std::string {
// Built a vector of class information, because we'll be traversing reverse
// order to construct the final type.
auto type_loc = decl.getTypeSourceInfo()->getTypeLoc();
std::vector<std::pair<clang::TypeLoc::TypeLocClass, std::string>> segments;
while (!type_loc.isNull()) {
auto qualifiers = type_loc.getType().getLocalQualifiers();
std::string qual_str;
if (!qualifiers.empty()) {
qual_str = qualifiers.getAsString();
}
auto range =
clang::CharSourceRange::getTokenRange(type_loc.getLocalSourceRange());
std::string range_str = GetSourceText(range).str();
// Make a list of segments with their TypeLocClass for reconstruction of the
// string. Locally, we will have a qualifier (such as `const`) and a type
// string (such as `int`) which is also used.
auto type_loc_class = type_loc.getTypeLocClass();
if (qual_str.empty()) {
segments.push_back({type_loc_class, range_str});
} else if (range_str.empty()) {
segments.push_back({type_loc_class, qual_str});
} else {
segments.push_back(
{type_loc_class, llvm::formatv("{0} {1}", qual_str, range_str)});
}
type_loc = type_loc.getNextTypeLoc();
}
// Construct the final type based on the class of each step. This reverses to
// start from the "inside" of the type and go "out" when constructing
// type_str.
std::string type_str;
auto prev_class = clang::TypeLoc::Auto; // Placeholder class, used in loop.
for (const auto& [type_loc_class, text] : llvm::reverse(segments)) {
switch (type_loc_class) {
case clang::TypeLoc::Elaborated:
type_str.insert(0, text);
break;
case clang::TypeLoc::Qualified:
if (prev_class == clang::TypeLoc::Pointer) {
type_str += " " + text;
} else {
if (!type_str.empty()) {
type_str.insert(0, " ");
}
type_str.insert(0, text);
}
break;
default:
type_str += text;
break;
}
prev_class = type_loc_class;
}
return type_str;
}
void VarDecl::Run() {
const auto& decl = GetNodeAsOrDie<clang::VarDecl>(Label);
if (decl.getTypeSourceInfo() == nullptr) {
// TODO: Need to understand what's happening in this case. Not sure if we
// need to address it.
return;
}
std::string after;
if (decl.getType().isConstQualified()) {
after = "let ";
} else if (!llvm::isa<clang::ParmVarDecl>(&decl)) {
// Start the replacement with "var" unless it's a parameter.
after = "var ";
}
// Add "identifier: type" to the replacement.
after += decl.getNameAsString() + ": " + GetTypeStr(decl);
// This decides the range to replace. Normally the entire decl is replaced,
// but for code like `int i, j` we need to detect the comma between the
// declared names. That case currently results in `var i: int, var j: int`.
// If there's a comma, this range will be non-empty.
auto type_loc = decl.getTypeSourceInfo()->getTypeLoc();
clang::SourceLocation after_type_loc = clang::Lexer::getLocForEndOfToken(
type_loc.getEndLoc(), 0, GetSourceManager(), GetLangOpts());
llvm::StringRef comma_source_text = GetSourceText(
clang::CharSourceRange::getCharRange(after_type_loc, decl.getLocation()));
clang::SourceLocation replace_start = !comma_source_text.trim().empty()
? decl.getLocation()
: decl.getBeginLoc();
// Figure out where the replacement ends and initialization begins. For
// example, `int i` the end is the identifier, `int i[4]` the end is the `[4]`
// type qualifier.
clang::SourceLocation identifier_end = clang::Lexer::getLocForEndOfToken(
decl.getLocation(), 0, GetSourceManager(), GetLangOpts());
clang::SourceLocation replace_end = std::max(identifier_end, after_type_loc);
AddReplacement(
clang::CharSourceRange::getCharRange(replace_start, replace_end), after);
}
void VarDeclFactory::AddMatcher(cam::MatchFinder* finder,
cam::MatchFinder::MatchCallback* callback) {
finder->addMatcher(cam::varDecl(cam::unless(cam::hasParent(cam::declStmt(
cam::hasParent(cam::cxxForRangeStmt())))))
.bind(Label),
callback);
}
} // namespace Carbon
-31
View File
@@ -1,31 +0,0 @@
// 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 CARBON_MIGRATE_CPP_CPP_REFACTORING_VAR_DECL_H_
#define CARBON_MIGRATE_CPP_CPP_REFACTORING_VAR_DECL_H_
#include "migrate_cpp/cpp_refactoring/matcher.h"
namespace Carbon {
// Updates variable declarations for `var name: type`.
class VarDecl : public Matcher {
public:
using Matcher::Matcher;
void Run() override;
private:
auto GetTypeStr(const clang::VarDecl& decl) -> std::string;
};
class VarDeclFactory : public MatcherFactoryBase<VarDecl> {
public:
void AddMatcher(
clang::ast_matchers::MatchFinder* finder,
clang::ast_matchers::MatchFinder::MatchCallback* callback) override;
};
} // namespace Carbon
#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_VAR_DECL_H_
@@ -1,257 +0,0 @@
// 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 "migrate_cpp/cpp_refactoring/var_decl.h"
#include "migrate_cpp/cpp_refactoring/matcher_test_base.h"
namespace Carbon::Testing {
namespace {
class VarDeclTest : public MatcherTestBase<VarDeclFactory> {};
TEST_F(VarDeclTest, Declaration) {
constexpr char Before[] = "int i;";
constexpr char After[] = "var i: int;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationArray) {
constexpr char Before[] = "int i[4];";
constexpr char After[] = "var i: int[4];";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationConstArray) {
constexpr char Before[] = "const int i[] = {0, 1};";
constexpr char After[] = "let i: const int[] = {0, 1};";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationConstPointer) {
// TODO: Fix j replacement location.
constexpr char Before[] = R"cpp(
int i = 0;
int* const j = &i;
const int* k = &i;
)cpp";
constexpr char After[] = R"(
var i: int = 0;
int* const let j: int* const = &i;
var k: const int* = &i;
)";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationComma) {
// TODO: Maybe replace the comma with a `;`.
constexpr char Before[] = "int i, j;";
constexpr char After[] = "var i: int, var j: int;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationCommaAssignment) {
// TODO: Maybe replace the comma with a `;`.
constexpr char Before[] = "int i = 0, j = 0;";
constexpr char After[] = "var i: int = 0, var j: int = 0;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationCommaArray) {
// TODO: Maybe replace the comma with a `;`.
// TODO: Need to handle j's array.
constexpr char Before[] = "int i[4], j[4];";
constexpr char After[] = "var i: int[4], j[4];";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationCommaArrayAssignment) {
// TODO: Maybe replace the comma with a `;`.
// TODO: Need to handle j's array.
constexpr char Before[] = "int i[] = {0}, j[] = {1};";
constexpr char After[] = "var i: int[] = {0}, j[] = {1};";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationCommaPointers) {
// TODO: Maybe replace the comma with a `;`.
// TODO: Need to handle j's pointer.
// constexpr char After[] = "var i: int *, var j: int *;";
constexpr char Before[] = "int *i, *j;";
constexpr char After[] = "var i: int*, *j;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, DeclarationCommaPointersAssignment) {
// TODO: Maybe replace the comma with a `;`.
// TODO: Need to handle j's pointer.
// constexpr char After[] = "var i: int *, var j: int *;";
constexpr char Before[] = "int *i = nullptr, *j = i;";
constexpr char After[] = "var i: int* = nullptr, *j = i;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, Assignment) {
constexpr char Before[] = "int i = 0;";
constexpr char After[] = "var i: int = 0;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, Auto) {
constexpr char Before[] = "auto i = 0;";
constexpr char After[] = "var i: auto = 0;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, AutoRef) {
constexpr char Before[] = R"cpp(
auto i = 0;
const auto& j = i;
)cpp";
constexpr char After[] = R"(
var i: auto = 0;
var j: const auto& = i;
)";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, Const) {
constexpr char Before[] = "const int i = 0;";
constexpr char After[] = "let i: const int = 0;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, ConstPointer) {
constexpr char Before[] = "const int* i;";
constexpr char After[] = "var i: const int*;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, Namespace) {
constexpr char Before[] = R"cpp(
namespace Foo {
typedef int Bar;
}
Foo::Bar x;
)cpp";
constexpr char After[] = R"(
namespace Foo {
typedef int Bar;
}
var x: Foo::Bar;
)";
ExpectReplacement(Before, After);
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, Params) {
constexpr char Before[] = "auto Foo(int i) -> int;";
constexpr char After[] = "auto Foo(i: int) -> int;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, ParamsDefault) {
constexpr char Before[] = "auto Foo(int i = 0) -> int;";
constexpr char After[] = "auto Foo(i: int = 0) -> int;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, ParamsConst) {
constexpr char Before[] = "auto Foo(const int i) -> int;";
constexpr char After[] = "auto Foo(let i: const int) -> int;";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, ParamStruct) {
// This is to ensure the 'struct' keyword doesn't get added to the qualified
// type.
constexpr char Before[] = R"cpp(
struct Circle {};
auto Draw(int times, const Circle& circle) -> bool;
)cpp";
constexpr char After[] = R"(
struct Circle {};
auto Draw(times: int, circle: const Circle&) -> bool;
)";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, Member) {
// TODO: Handle member variables.
constexpr char Before[] = R"cpp(
struct Circle {
Circle() : x(0), y(0), radius(1) {}
int x;
int y;
int radius;
};
)cpp";
ExpectReplacement(Before, Before);
}
TEST_F(VarDeclTest, Constructor) {
constexpr char Before[] = R"cpp(
struct Index {
Index(int i) : i(i) {}
int i;
};
Index x(0);
)cpp";
constexpr char After[] = R"(
struct Index {
Index(i: int) : i(i) {}
int i;
};
var x: Index(0);
)";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, RangeFor) {
constexpr char Before[] = R"cpp(
void Foo() {
int items[] = {1};
for (int i : items) {
int j;
}
}
)cpp";
constexpr char After[] = R"(
void Foo() {
var items: int[] = {1};
for (int i : items) {
var j: int;
}
}
)";
ExpectReplacement(Before, After);
}
TEST_F(VarDeclTest, Template) {
constexpr char Before[] = R"cpp(
template <typename T>
struct R {};
template <typename T>
struct S {};
R<S<int>> x;
)cpp";
constexpr char After[] = R"(
template <typename T>
struct R {};
template <typename T>
struct S {};
var x: R<S<int>>;
)";
ExpectReplacement(Before, After);
}
} // namespace
} // namespace Carbon::Testing
-118
View File
@@ -1,118 +0,0 @@
"""Migrates C++ code to Carbon."""
__copyright__ = """
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
"""
import argparse
import glob
import os
import subprocess
import sys
from typing import Optional
_CPP_REFACTORING = "./cpp_refactoring/cpp_refactoring"
_H_EXTS = {".h", ".hpp"}
_CPP_EXTS = {".c", ".cc", ".cpp", ".cxx"}
class _Workflow:
_parsed_args: argparse.Namespace
_data_dir: str
_cpp_files: Optional[list[str]]
def __init__(self) -> None:
"""Parses command-line arguments and flags."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"dir",
type=str,
help="A directory containing C++ files to migrate to Carbon.",
)
parsed_args = parser.parse_args()
self._parsed_args = parsed_args
self._data_dir = os.path.dirname(sys.argv[0])
# Validate arguments.
if not os.path.isdir(parsed_args.dir):
sys.exit("%r must point to a directory." % parsed_args.dir)
def run(self) -> None:
"""Runs the migration workflow."""
try:
self._gather_files()
self._clang_tidy()
self._cpp_refactoring()
self._rename_files()
self._print_header("Done!")
except subprocess.CalledProcessError as e:
# Discard the stack for subprocess errors.
sys.exit(str(e))
def _data_file(self, relative_path: str) -> str:
"""Returns the path to a data file."""
return os.path.join(self._data_dir, relative_path)
@staticmethod
def _print_header(header: str) -> None:
print("*" * 79)
print("* %-75s *" % header)
print("*" * 79)
def _gather_files(self) -> None:
"""Returns the list of C++ files to convert."""
self._print_header("Gathering C++ files...")
all_files = glob.glob(
os.path.join(self._parsed_args.dir, "**/*.*"), recursive=True
)
exts = _CPP_EXTS.union(_H_EXTS)
cpp_files = [f for f in all_files if os.path.splitext(f)[1] in exts]
if not cpp_files:
sys.exit(
"%r doesn't contain any C++ files to convert."
% self._parsed_args.dir
)
self._cpp_files = sorted(cpp_files)
print("%d files found." % len(self._cpp_files))
def _clang_tidy(self) -> None:
"""Runs clang-tidy to fix C++ files in a directory."""
self._print_header("Running clang-tidy...")
with open(self._data_file("clang_tidy.yaml")) as f:
config = f.read()
subprocess.run(
["run-clang-tidy.py", "-fix", "-config", config],
check=True,
)
def _cpp_refactoring(self) -> None:
"""Runs cpp_refactoring to migrate C++ files towards Carbon syntax."""
self._print_header("Running cpp_refactoring...")
cpp_refactoring = self._data_file(_CPP_REFACTORING)
assert self._cpp_files is not None
subprocess.run([cpp_refactoring] + self._cpp_files, check=True)
def _rename_files(self) -> None:
"""Renames C++ files to the destination Carbon filenames."""
api_renames = 0
impl_renames = 0
assert self._cpp_files is not None
for f in self._cpp_files:
parts = os.path.splitext(f)
if parts[1] in _H_EXTS:
os.rename(f, parts[0] + ".carbon")
api_renames += 1
else:
os.rename(f, parts[0] + ".impl.carbon")
impl_renames += 1
print(
"Renaming resulted in %d API files and %d impl files."
% (api_renames, impl_renames)
)
if __name__ == "__main__":
_Workflow().run()
-89
View File
@@ -1,89 +0,0 @@
// 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 CARBON_MIGRATE_CPP_OUTPUT_SEGMENT_H_
#define CARBON_MIGRATE_CPP_OUTPUT_SEGMENT_H_
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include "clang/AST/ASTTypeTraits.h"
#include "common/check.h"
namespace Carbon {
namespace Internal {
// Checks that the type T is an acceptable node type from which an
// OutputSegment can be constructed. We intentionally do not want to support
// `clang::Type` because we support traversing through `clang::TypeLoc`
// instead. However, most other types we intend to support as they become
// necessary.
template <typename T>
concept IsSupportedClangASTNodeType = std::convertible_to<T*, clang::Stmt*> ||
std::convertible_to<T*, clang::Decl*>;
} // namespace Internal
// Represents a segment of the output string. `OutputSegment`s come in two
// flavors: Text and Node. A text segment holds string text that should be used
// to be added to the output. A node segment holds a node in Clang's AST and
// indicates that the output associated to that node should be the output
// segment that the `RewriteBuilder` (defined below) has attached to that AST
// node.
//
// For example, the output for a binary operator node corresponding to the C++
// code snippet `f() + 3 * 5`, would be the sequence of three output segments:
//
// {Node(lhs), Text(" + "), Node(rhs)}
//
// The left-hand side and right-hand side can then be queried recursively to
// determine what their output should be.
class OutputSegment {
public:
// Creates a text-based `OutputSegment`.
explicit OutputSegment(std::string content) : content_(std::move(content)) {}
explicit OutputSegment(llvm::StringRef content) : content_(content.str()) {}
explicit OutputSegment(const char* content) : content_(content) {}
// Creates a node-based `OutputSegment` from `node`.
explicit OutputSegment(const clang::DynTypedNode& node) : content_(node) {}
template <typename T>
requires Internal::IsSupportedClangASTNodeType<T>
explicit OutputSegment(const T* node);
// Creates a TypeLoc-based `OutputSegment` from `type_loc`.
explicit OutputSegment(clang::TypeLoc type_loc)
: content_(PassThroughQualifiedTypeLoc(type_loc)) {}
private:
friend struct OutputWriter;
template <typename T>
auto AssertNotNull(T* ptr) -> T& {
CARBON_CHECK(ptr != nullptr);
return *ptr;
}
// Traversals for TypeLocs have some sharp corners. In particular,
// QualifiedTypeLocs are silently passed through to their unqualified part.
// This means that when constructing output segments we also need to match
// this behavior.
static auto PassThroughQualifiedTypeLoc(clang::TypeLoc type_loc)
-> clang::TypeLoc {
auto qtl = type_loc.getAs<clang::QualifiedTypeLoc>();
return qtl.isNull() ? type_loc : qtl.getUnqualifiedLoc();
}
std::variant<std::string, clang::DynTypedNode, clang::TypeLoc> content_;
};
template <typename T>
requires Internal::IsSupportedClangASTNodeType<T>
OutputSegment::OutputSegment(const T* node)
: content_(clang::DynTypedNode::create(AssertNotNull(node))) {}
} // namespace Carbon
#endif // CARBON_MIGRATE_CPP_OUTPUT_SEGMENT_H_
-378
View File
@@ -1,378 +0,0 @@
// 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 "migrate_cpp/rewriter.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/FormatVariadic.h"
namespace Carbon {
static constexpr const char CppPlaceholder[] = "__cpp__{ ... }";
auto OutputWriter::Write(clang::SourceLocation loc,
const OutputSegment& segment) const -> bool {
return std::visit(
[&](auto& content) {
using Type = std::decay_t<decltype(content)>;
auto [begin, end] = bounds;
if constexpr (std::is_same_v<Type, std::string>) {
auto begin_offset = source_manager.getDecomposedLoc(loc).second;
// Append the string replacement if the node being replaced falls
// within `bounds`.
if (begin <= begin_offset && begin_offset < end) {
output.append(content);
}
} else if constexpr (std::is_same_v<Type, clang::DynTypedNode> ||
std::is_same_v<Type, clang::TypeLoc>) {
auto content_loc = content.getSourceRange().getBegin();
auto begin_offset =
source_manager.getDecomposedLoc(content_loc).second;
// If the node we're considering a replacement for is already beyond
// the region for which we want to make a replacement, exit early
// declaring that we have completed replacements (by returning false).
// Otherwise proceed. Note that we do not exit early or skip anything
// if the node comes before the relevant region. This is because many
// nodes in Clang's AST have a starting source location but a
// meaningless end location, and while the start of the segment may
// not be in the range, as we recurse, sub-segments may indeed end up
// being printed.
if (begin_offset >= end) {
return false;
}
if (auto iter = map.find(content); iter == map.end()) {
output.append(CppPlaceholder);
} else {
for (const auto& output_segment : iter->second) {
if (!Write(content.getSourceRange().getBegin(), output_segment)) {
return false;
}
}
}
} else {
static_assert(std::is_void_v<Type>,
"Failed to handle a case in the `std::variant`.");
}
return true;
},
segment.content_);
}
auto MigrationConsumer::HandleTranslationUnit(clang::ASTContext& context)
-> void {
RewriteBuilder rewriter(context, segment_map_);
rewriter.TraverseAST(context);
auto translation_unit_node =
clang::DynTypedNode::create(*context.getTranslationUnitDecl());
auto iter = segment_map_.find(translation_unit_node);
if (iter == segment_map_.end()) {
result_.append(CppPlaceholder);
} else {
OutputWriter w{
.map = segment_map_,
.bounds = output_range_,
.source_manager = context.getSourceManager(),
.output = result_,
};
for (const auto& output_segment : iter->second) {
w.Write(translation_unit_node.getSourceRange().getBegin(),
output_segment);
}
}
}
auto RewriteBuilder::TextFor(clang::SourceLocation begin,
clang::SourceLocation end) const
-> llvm::StringRef {
auto range = clang::CharSourceRange::getCharRange(begin, end);
return clang::Lexer::getSourceText(range, context_.getSourceManager(),
context_.getLangOpts());
}
auto RewriteBuilder::TextForTokenAt(clang::SourceLocation loc) const
-> llvm::StringRef {
auto& source_manager = context_.getSourceManager();
auto [file_id, offset] = source_manager.getDecomposedLoc(loc);
llvm::StringRef file = source_manager.getBufferData(file_id);
clang::Lexer lexer(source_manager.getLocForStartOfFile(file_id),
context_.getLangOpts(), file.begin(), file.data() + offset,
file.end());
clang::Token token;
lexer.LexFromRawLexer(token);
return TextFor(loc, loc.getLocWithOffset(token.getLength()));
}
// TODO: The output written in this member function needs to be
// architecture-dependent. Moreover, even if the output is correct in the sense
// that the types match and are interoperable between Carbon and C++, they may
// not be semantically correct: If the C++ code specifies the type `long`, and
// on the platform for which the migration is occurring `long` has 64-bits, we
// may not want to use `i64` as the replacement: The C++ code may be intended to
// operate in environments where `long` is only 32-bits wide. We need to develop
// a strategy for determining builtin-type replacements that addresses these
// issues.
auto RewriteBuilder::VisitBuiltinTypeLoc(clang::BuiltinTypeLoc type_loc)
-> bool {
llvm::StringRef content;
switch (type_loc.getTypePtr()->getKind()) {
case clang::BuiltinType::Bool:
content = "bool";
break;
case clang::BuiltinType::Char_U:
content = "char";
break;
case clang::BuiltinType::UChar:
content = "u8";
break;
case clang::BuiltinType::UShort:
content = "u16";
break;
case clang::BuiltinType::UInt:
content = "u32";
break;
case clang::BuiltinType::ULong:
content = "u64";
break;
case clang::BuiltinType::ULongLong:
content = "u64";
break;
case clang::BuiltinType::UInt128:
content = "u128";
break;
case clang::BuiltinType::Char_S:
content = "char";
break;
case clang::BuiltinType::SChar:
content = "i8";
break;
case clang::BuiltinType::Short:
content = "i16";
break;
case clang::BuiltinType::Int:
content = "i32";
break;
case clang::BuiltinType::Long:
content = "i64";
break;
case clang::BuiltinType::LongLong:
content = "i64";
break;
case clang::BuiltinType::Int128:
content = "i128";
break;
case clang::BuiltinType::Float:
content = "f32";
break;
case clang::BuiltinType::Double:
content = "f64";
break;
case clang::BuiltinType::Void:
content = "()";
break;
default:
// In this case we do not know what the output should be so we do not
// write any.
return true;
}
SetReplacement(type_loc, OutputSegment(content));
return true;
}
auto RewriteBuilder::VisitCXXBoolLiteralExpr(clang::CXXBoolLiteralExpr* expr)
-> bool {
SetReplacement(expr, OutputSegment(expr->getValue() ? "true" : "false"));
return true;
}
auto RewriteBuilder::VisitDeclRefExpr(clang::DeclRefExpr* expr) -> bool {
SetReplacement(expr, OutputSegment(TextForTokenAt(expr->getBeginLoc())));
return true;
}
auto RewriteBuilder::VisitDeclStmt(clang::DeclStmt* stmt) -> bool {
std::vector<OutputSegment> segments;
for (clang::Decl* decl : stmt->decls()) {
segments.push_back(OutputSegment(decl));
segments.push_back(OutputSegment(";\n"));
}
SetReplacement(stmt, std::move(segments));
return true;
}
auto RewriteBuilder::VisitImplicitCastExpr(clang::ImplicitCastExpr* expr)
-> bool {
SetReplacement(expr, OutputSegment(expr->getSubExpr()));
return true;
}
auto RewriteBuilder::VisitIntegerLiteral(clang::IntegerLiteral* expr) -> bool {
// TODO: Replace suffixes.
std::string text(TextForTokenAt(expr->getBeginLoc()));
for (char& c : text) {
// Carbon uses underscores for digit separators whereas C++ uses single
// quotation marks. Convert all `'` to `_`.
if (c == '\'') {
c = '_';
}
}
SetReplacement(expr, OutputSegment(std::move(text)));
return true;
}
auto RewriteBuilder::VisitParmVarDecl(clang::ParmVarDecl* decl) -> bool {
llvm::StringRef name = decl->getName();
std::vector<OutputSegment> segments = {
OutputSegment(llvm::formatv("{0}: ", name.empty() ? "_" : name.str())),
OutputSegment(decl->getTypeSourceInfo()->getTypeLoc()),
};
if (clang::Expr* init = decl->getInit()) {
segments.push_back(OutputSegment(" = "));
segments.push_back(OutputSegment(init));
}
SetReplacement(decl, std::move(segments));
return true;
}
auto RewriteBuilder::VisitPointerTypeLoc(clang::PointerTypeLoc type_loc)
-> bool {
SetReplacement(type_loc,
{OutputSegment(type_loc.getPointeeLoc()), OutputSegment("*")});
return true;
}
auto RewriteBuilder::VisitReturnStmt(clang::ReturnStmt* stmt) -> bool {
SetReplacement(
stmt, {OutputSegment("return "), OutputSegment(stmt->getRetValue())});
return true;
}
auto RewriteBuilder::VisitTranslationUnitDecl(clang::TranslationUnitDecl* decl)
-> bool {
std::vector<OutputSegment> segments;
// Clang starts each translation unit with some initial `TypeDefDecl`s that
// are not part of the written text. We want to skip past these initial
// declarations, which we do by ignoring any node of type `TypeDefDecl` which
// has an invalid source location.
auto iter = decl->decls_begin();
while (iter != decl->decls_end() && llvm::isa<clang::TypedefDecl>(*iter) &&
(*iter)->getLocation().isInvalid()) {
++iter;
}
for (; iter != decl->decls_end(); ++iter) {
clang::Decl* d = *iter;
segments.push_back(OutputSegment(d));
// Function definitions do not need semicolons.
bool needs_semicolon = !(llvm::isa<clang::FunctionDecl>(d) &&
llvm::cast<clang::FunctionDecl>(d)->hasBody());
segments.push_back(OutputSegment(needs_semicolon ? ";\n" : "\n"));
}
SetReplacement(decl, std::move(segments));
return true;
}
auto RewriteBuilder::VisitUnaryOperator(clang::UnaryOperator* expr) -> bool {
switch (expr->getOpcode()) {
case clang::UO_AddrOf:
SetReplacement(expr,
{OutputSegment("&"), OutputSegment(expr->getSubExpr())});
break;
default:
// TODO: Finish implementing cases.
break;
}
return true;
}
// NOLINTNEXTLINE(misc-no-recursion): Recursion may be okay for migration.
auto RewriteBuilder::TraverseFunctionDecl(clang::FunctionDecl* decl) -> bool {
clang::TypeLoc return_type_loc = decl->getFunctionTypeLoc().getReturnLoc();
if (!TraverseTypeLoc(return_type_loc)) {
return false;
}
std::vector<OutputSegment> segments;
segments.push_back(
OutputSegment(llvm::formatv("fn {0}(", decl->getNameAsString())));
size_t i = 0;
for (; i + 1 < decl->getNumParams(); ++i) {
clang::ParmVarDecl* param = decl->getParamDecl(i);
if (!TraverseDecl(param)) {
return false;
}
segments.push_back(OutputSegment(param));
segments.push_back(OutputSegment(", "));
}
if (i + 1 == decl->getNumParams()) {
clang::ParmVarDecl* param = decl->getParamDecl(i);
if (!TraverseDecl(param)) {
return false;
}
segments.push_back(OutputSegment(param));
}
segments.push_back(OutputSegment(") -> "));
segments.push_back(OutputSegment(return_type_loc));
if (decl->hasBody()) {
segments.push_back(OutputSegment(" {\n"));
auto* stmts = llvm::dyn_cast<clang::CompoundStmt>(decl->getBody());
for (clang::Stmt* stmt : stmts->body()) {
if (!TraverseStmt(stmt)) {
return false;
}
segments.push_back(OutputSegment(stmt));
segments.push_back(OutputSegment(";\n"));
}
segments.push_back(OutputSegment("}"));
}
SetReplacement(decl, std::move(segments));
return true;
}
// NOLINTNEXTLINE(misc-no-recursion): Recursion may be okay for migration.
auto RewriteBuilder::TraverseVarDecl(clang::VarDecl* decl) -> bool {
clang::TypeLoc loc = decl->getTypeSourceInfo()->getTypeLoc();
if (!TraverseTypeLoc(loc)) {
return false;
}
// TODO: Check storage class. Determine what happens for static local
// variables.
bool is_const = decl->getType().isConstQualified();
std::vector<OutputSegment> segments = {
OutputSegment(llvm::formatv("{0} {1}: ", is_const ? "let" : "var",
decl->getNameAsString())),
OutputSegment(decl->getTypeSourceInfo()->getTypeLoc()),
};
if (clang::Expr* init = decl->getInit()) {
if (!TraverseStmt(init)) {
return false;
}
segments.push_back(OutputSegment(" = "));
segments.push_back(OutputSegment(init));
}
SetReplacement(decl, std::move(segments));
return true;
}
} // namespace Carbon
-223
View File
@@ -1,223 +0,0 @@
// 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 CARBON_MIGRATE_CPP_REWRITER_H_
#define CARBON_MIGRATE_CPP_REWRITER_H_
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "llvm/ADT/DenseMap.h"
#include "migrate_cpp/output_segment.h"
namespace Carbon {
namespace Internal {
struct Empty {
friend auto operator==(Empty /*unused*/, Empty /*unused*/) -> bool {
return true;
}
};
struct Tombstone {
friend auto operator==(Tombstone /*unused*/, Tombstone /*unused*/) -> bool {
return true;
}
};
// Type alias for the variant representing any of the values that can be
// written with OutputWriter.
using KeyType =
std::variant<clang::DynTypedNode, clang::TypeLoc, Empty, Tombstone>;
// `KeyInfo` is used as a template argument to `llvm::DenseMap` to specify how
// to equality-compare and hash `KeyType`.
struct KeyInfo {
static auto isEqual(const KeyType& lhs, const KeyType& rhs) -> bool {
return lhs == rhs;
}
static auto getHashValue(const KeyType& x) -> unsigned {
return std::visit(
[](auto x) -> unsigned {
using Type = std::decay_t<decltype(x)>;
if constexpr (std::is_same_v<Type, clang::DynTypedNode>) {
return clang::DynTypedNode::DenseMapInfo::getHashValue(x);
} else if constexpr (std::is_same_v<Type, clang::TypeLoc>) {
// TODO: Improve this.
return reinterpret_cast<uintptr_t>(x.getTypePtr());
} else {
return 0;
}
},
x);
}
static auto getEmptyKey() -> KeyType { return Empty{}; }
static auto getTombstoneKey() -> KeyType { return Tombstone{}; }
};
} // namespace Internal
// `OutputWriter` is responsible for traversing the tree of `OutputSegment`s
// and writing the correct data to its member `output`.
struct OutputWriter {
using SegmentMapType =
llvm::DenseMap<Internal::KeyType, std::vector<OutputSegment>,
Internal::KeyInfo>;
auto Write(clang::SourceLocation loc, const OutputSegment& segment) const
-> bool;
const SegmentMapType& map;
// Bounds represent the offsets into the primary file (multi-file refactorings
// are not yet supported) that should be output. While primarily this is a
// mechanism to make testing more robust, it can also be used to make local
// changes to sections of C++ code.
std::pair<size_t, size_t> bounds;
clang::SourceManager& source_manager;
std::string& output;
};
// `RewriteBuilder` is a recursive AST visitor. For each node, it computes and
// stores a sequence of `OutputSegment`s describing how this node should be
// replaced.
class RewriteBuilder : public clang::RecursiveASTVisitor<RewriteBuilder> {
public:
using SegmentMapType = typename OutputWriter::SegmentMapType;
// Constructs a `RewriteBuilder` which can read the AST from `context` and
// will write results into `segments`.
explicit RewriteBuilder(clang::ASTContext& context, SegmentMapType& segments)
: context_(context), segments_(segments) {}
// By default, traverse children nodes before their parent. Called by the CRTP
// base class to determine traversal order.
auto shouldTraversePostOrder() const -> bool { return true; }
// Visitor member functions, defining how each node should be processed.
auto VisitBuiltinTypeLoc(clang::BuiltinTypeLoc type_loc) -> bool;
auto VisitCXXBoolLiteralExpr(clang::CXXBoolLiteralExpr* expr) -> bool;
auto VisitDeclRefExpr(clang::DeclRefExpr* expr) -> bool;
auto VisitDeclStmt(clang::DeclStmt* stmt) -> bool;
auto VisitImplicitCastExpr(clang::ImplicitCastExpr* expr) -> bool;
auto VisitIntegerLiteral(clang::IntegerLiteral* expr) -> bool;
auto VisitParmVarDecl(clang::ParmVarDecl* decl) -> bool;
auto VisitPointerTypeLoc(clang::PointerTypeLoc type_loc) -> bool;
auto VisitReturnStmt(clang::ReturnStmt* stmt) -> bool;
auto VisitTranslationUnitDecl(clang::TranslationUnitDecl* decl) -> bool;
auto VisitUnaryOperator(clang::UnaryOperator* expr) -> bool;
auto TraverseFunctionDecl(clang::FunctionDecl* decl) -> bool;
auto TraverseVarDecl(clang::VarDecl* decl) -> bool;
auto segments() const -> const SegmentMapType& { return segments_; }
auto segments() -> SegmentMapType& { return segments_; }
private:
// Associates `output_segments` in the output map `this->segments()` with the
// key `node`, so as to declare that, when output is being written, `node`
// should be replaced with the sequence of outputs described by
// `output_segments`.
auto SetReplacement(clang::DynTypedNode node,
std::vector<OutputSegment> output_segments) -> void {
segments_.try_emplace(node, std::move(output_segments));
}
auto SetReplacement(clang::TypeLoc node,
std::vector<OutputSegment> output_segments) -> void {
segments_.try_emplace(node, std::move(output_segments));
}
template <typename T>
auto SetReplacement(const T* node, std::vector<OutputSegment> output_segments)
-> void {
segments_.try_emplace(clang::DynTypedNode::create(*node),
std::move(output_segments));
}
// Invokes the overload of `SetReplacement` defined above. Equivalent to
// `this->SetReplacement(node, std::vector<OutputSegment>(1, segment))`.
template <typename T>
auto SetReplacement(const T* node, OutputSegment segment) -> void {
std::vector<OutputSegment> node_segments;
node_segments.push_back(std::move(segment));
SetReplacement(node, std::move(node_segments));
}
auto SetReplacement(clang::TypeLoc type_loc, OutputSegment segment) -> void {
std::vector<OutputSegment> node_segments;
node_segments.push_back(std::move(segment));
SetReplacement(type_loc, std::move(node_segments));
}
// Returns a `llvm::StringRef` into the source text corresponding to the
// half-open interval starting at `begin` (inclusive) and ending at `end`
// (exclusive).
auto TextFor(clang::SourceLocation begin, clang::SourceLocation end) const
-> llvm::StringRef;
// Returns a `llvm::StringRef` into the source text for the single token
// located at `loc`.
auto TextForTokenAt(clang::SourceLocation loc) const -> llvm::StringRef;
clang::ASTContext& context_;
SegmentMapType& segments_;
};
// An `ASTConsumer` which, when executed, populates a `std::string` with the
// text of a Carbon source file which is a best approximation of the
// semantics of the corresponding C++ translation unit defined by the consumed
// AST.
class MigrationConsumer : public clang::ASTConsumer {
public:
explicit MigrationConsumer(std::string& result,
std::pair<size_t, size_t> output_range)
: result_(result), output_range_(std::move(output_range)) {}
auto HandleTranslationUnit(clang::ASTContext& context) -> void override;
private:
RewriteBuilder::SegmentMapType segment_map_;
std::string& result_;
std::pair<size_t, size_t> output_range_;
};
// An `ASTFrontendAction` which constructs a `MigrationConsumer` and invokes it
// on an AST, populating a `std::string` with the text of a Carbon source file
// which is a best approximation of the semantics of the corresponding C++
// translation unit defined by the consumed AST.
class MigrationAction : public clang::ASTFrontendAction {
public:
// Constructs the `MigrationAction`. The parameter `result` is a reference to
// the `std::string` where output will be written. Only output corresponding
// to text at offsets that fall in between `output_range.first` and
// `output_range.second` will be written.
explicit MigrationAction(std::string& result,
std::pair<size_t, size_t> output_range)
: result_(result), output_range_(std::move(output_range)) {}
// Returns a `std::unique_ptr` to a `clang::MigrationConsumer` which populates
// the output `result`.
auto CreateASTConsumer(clang::CompilerInstance& /*CI*/,
llvm::StringRef /*InFile*/)
-> std::unique_ptr<clang::ASTConsumer> override {
return std::make_unique<MigrationConsumer>(result_, output_range_);
}
private:
std::string& result_;
std::pair<size_t, size_t> output_range_;
};
} // namespace Carbon
#endif // CARBON_MIGRATE_CPP_REWRITER_H_
-190
View File
@@ -1,190 +0,0 @@
// 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 "migrate_cpp/rewriter.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"
namespace Carbon::Testing {
namespace {
// Represents C++ source code with at most one region enclosed in $[[...]]$ as
// an annotated range.
class Annotations {
public:
explicit Annotations(llvm::StringRef annotated_source) {
size_t index = annotated_source.find("$[[");
if (index == llvm::StringRef::npos) {
source_code_ = std::string(annotated_source);
return;
}
start_ = index;
end_ = annotated_source.find("]]$", index);
CARBON_CHECK(end_ != llvm::StringRef::npos,
"Found `$[[` but no matching `]]$`");
source_code_ = (llvm::Twine(annotated_source.substr(0, start_)) +
annotated_source.substr(start_ + 3, end_ - start_ - 3) +
annotated_source.substr(end_ + 3))
.str();
// Update `end_` so that it is relative to the unannotated source (which
// means three characters earlier due to the `$[[` being removed.
end_ -= 3;
}
// Returns a view into the unannotated source.
auto source() const -> llvm::StringRef { return source_code_; }
// Returns the offsets in the file representing the annotated range if they
// exist and `{0, std::numeric_limits<size_t>::max()}` otherwise.
auto range() const -> std::pair<size_t, size_t> {
return std::pair(start_, end_);
}
private:
std::string source_code_;
size_t start_ = 0;
size_t end_ = std::numeric_limits<size_t>::max();
};
// Rewrites the `cpp_code`, return the Carbon equivalent. If the text has no
// source range annotated with $[[...]]$, the entire translation unit will be
// migrated and output. Otherwise, only the migrated output corresponding to the
// annotated range will be output. No more than one range may be annotated at
// all.
//
// This annotation mechanism is useful in that it allows us to specifically test
// the migration associated with specific nodes even when they require some
// additional context that we do not wish to be covered by the test.
auto RewriteText(llvm::StringRef cpp_code) -> std::string {
std::string result;
Annotations annotated_cpp_code(cpp_code);
bool success = clang::tooling::runToolOnCodeWithArgs(
std::make_unique<MigrationAction>(result, annotated_cpp_code.range()),
annotated_cpp_code.source(), {}, "test.cc", "clang-tool",
std::make_shared<clang::PCHContainerOperations>(),
clang::tooling::FileContentMappings());
return success ? result : "";
}
TEST(Rewriter, BoolLiteral) {
EXPECT_EQ(RewriteText("bool x = $[[true]]$;"), "true");
EXPECT_EQ(RewriteText("bool x = $[[false]]$;"), "false");
}
TEST(Rewriter, IntegerLiteral) {
EXPECT_EQ(RewriteText("int x = $[[0]]$;"), "0");
EXPECT_EQ(RewriteText("int x = $[[1]]$;"), "1");
EXPECT_EQ(RewriteText("int x = $[[1234]]$;"), "1234");
EXPECT_EQ(RewriteText("int x = $[[12'34]]$;"), "12_34");
EXPECT_EQ(RewriteText("int x = $[[12'3'4]]$;"), "12_3_4");
}
TEST(Rewriter, SingleDeclaration) {
EXPECT_EQ(RewriteText("bool b;"), "var b: bool;\n");
EXPECT_EQ(RewriteText("int i;"), "var i: i32;\n");
EXPECT_EQ(RewriteText("const bool b = false;"), "let b: bool = false;\n");
EXPECT_EQ(RewriteText("const int i = 17;"), "let i: i32 = 17;\n");
EXPECT_EQ(RewriteText("bool const b = false;"), "let b: bool = false;\n");
EXPECT_EQ(RewriteText("int const i = 1234;"), "let i: i32 = 1234;\n");
}
TEST(Rewriter, Pointers) {
// TODO: Add tests for pointers-to-const when the syntax is nailed down.
EXPECT_EQ(RewriteText("bool b;\n"
"$[[bool *p = &b]]$;"),
"var p: bool* = &b");
EXPECT_EQ(RewriteText("bool b;\n"
"$[[bool * const p = &b]]$;"),
"let p: bool* = &b");
// Pointers and non-pointers on the same DeclStmt.
EXPECT_EQ(RewriteText("bool b, *p;\n"),
"var b: bool;\n"
"var p: bool*;\n");
EXPECT_EQ(RewriteText("bool b, *p = &b;\n"),
"var b: bool;\n"
"var p: bool* = &b;\n");
}
TEST(Rewriter, DeclarationComma) {
EXPECT_EQ(RewriteText("int x, y;"),
"var x: i32;\n"
"var y: i32;\n");
EXPECT_EQ(RewriteText("int x = 7, y;"),
"var x: i32 = 7;\n"
"var y: i32;\n");
EXPECT_EQ(RewriteText("const int x = 1, y = 2;"),
"let x: i32 = 1;\n"
"let y: i32 = 2;\n");
EXPECT_EQ(RewriteText("int const x = 1234, y = 5678;"),
"let x: i32 = 1234;\n"
"let y: i32 = 5678;\n");
}
TEST(Rewriter, FunctionDeclaration) {
// Function declarations and definitions returning void.
EXPECT_EQ(RewriteText("void f();"), "fn f() -> ();\n");
EXPECT_EQ(RewriteText("void f() {}"),
"fn f() -> () {\n"
"}\n");
// Function declarations and definitions returning int.
EXPECT_EQ(RewriteText("int f();"), "fn f() -> i32;\n");
EXPECT_EQ(RewriteText("int f() { return 0; }"),
"fn f() -> i32 {\n"
"return 0;\n"
"}\n");
// Function declarations and definitions with a single parameter.
EXPECT_EQ(RewriteText("int f(bool);"), "fn f(_: bool) -> i32;\n");
EXPECT_EQ(RewriteText("int f(bool b);"), "fn f(b: bool) -> i32;\n");
EXPECT_EQ(RewriteText("int f(bool) { return 0; }"),
"fn f(_: bool) -> i32 {\n"
"return 0;\n"
"}\n");
EXPECT_EQ(RewriteText("int f(bool b) { return 0; }"),
"fn f(b: bool) -> i32 {\n"
"return 0;\n"
"}\n");
// Function declarations and definitions with a multiple parameters.
EXPECT_EQ(RewriteText("int f(bool, int);"),
"fn f(_: bool, _: i32) -> i32;\n");
EXPECT_EQ(RewriteText("int f(bool b, int n);"),
"fn f(b: bool, n: i32) -> i32;\n");
EXPECT_EQ(RewriteText("int f(bool, int n) { return 0; }"),
"fn f(_: bool, n: i32) -> i32 {\n"
"return 0;\n"
"}\n");
EXPECT_EQ(RewriteText("int f(bool b, int n) { return 0; }"),
"fn f(b: bool, n: i32) -> i32 {\n"
"return 0;\n"
"}\n");
EXPECT_EQ(RewriteText("int f(bool b, int n = 3) { return n; }"),
"fn f(b: bool, n: i32 = 3) -> i32 {\n"
"return n;\n"
"}\n");
// Function declarations with trailing-return syntax.
EXPECT_EQ(RewriteText("auto f(bool b, int n = 3) -> int;"),
"fn f(b: bool, n: i32 = 3) -> i32;\n");
EXPECT_EQ(RewriteText("auto f(bool b, int n = 3) -> int { return n; }"),
"fn f(b: bool, n: i32 = 3) -> i32 {\n"
"return n;\n"
"}\n");
}
} // namespace
} // namespace Carbon::Testing