mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 18:31:08 +01:00
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:
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user