diff --git a/bazel/check_deps/BUILD b/bazel/check_deps/BUILD index 97f01b5b3c1ca..3788b79e16a32 100644 --- a/bazel/check_deps/BUILD +++ b/bazel/check_deps/BUILD @@ -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. diff --git a/migrate_cpp/BUILD b/migrate_cpp/BUILD deleted file mode 100644 index 3236bf0d9ca51..0000000000000 --- a/migrate_cpp/BUILD +++ /dev/null @@ -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", - ], -) diff --git a/migrate_cpp/README.md b/migrate_cpp/README.md deleted file mode 100644 index b4b7dacf6a8ff..0000000000000 --- a/migrate_cpp/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# C++ migration tooling - - - - - -## Table of contents - -- [Overview](#overview) -- [Structure](#structure) - - - -## 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. diff --git a/migrate_cpp/clang_tidy.yaml b/migrate_cpp/clang_tidy.yaml deleted file mode 100644 index daae94868aafb..0000000000000 --- a/migrate_cpp/clang_tidy.yaml +++ /dev/null @@ -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 diff --git a/migrate_cpp/cpp_refactoring/BUILD b/migrate_cpp/cpp_refactoring/BUILD deleted file mode 100644 index 656a623a63587..0000000000000 --- a/migrate_cpp/cpp_refactoring/BUILD +++ /dev/null @@ -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", - ], -) diff --git a/migrate_cpp/cpp_refactoring/fn_inserter.cpp b/migrate_cpp/cpp_refactoring/fn_inserter.cpp deleted file mode 100644 index f370ffce160eb..0000000000000 --- a/migrate_cpp/cpp_refactoring/fn_inserter.cpp +++ /dev/null @@ -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(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 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 diff --git a/migrate_cpp/cpp_refactoring/fn_inserter.h b/migrate_cpp/cpp_refactoring/fn_inserter.h deleted file mode 100644 index 5e75b0a809cd5..0000000000000 --- a/migrate_cpp/cpp_refactoring/fn_inserter.h +++ /dev/null @@ -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 { - 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_ diff --git a/migrate_cpp/cpp_refactoring/fn_inserter_test.cpp b/migrate_cpp/cpp_refactoring/fn_inserter_test.cpp deleted file mode 100644 index 5fe8a248f20c1..0000000000000 --- a/migrate_cpp/cpp_refactoring/fn_inserter_test.cpp +++ /dev/null @@ -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 {}; - -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 diff --git a/migrate_cpp/cpp_refactoring/for_range.cpp b/migrate_cpp/cpp_refactoring/for_range.cpp deleted file mode 100644 index eef4946fa0fe9..0000000000000 --- a/migrate_cpp/cpp_refactoring/for_range.cpp +++ /dev/null @@ -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(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 diff --git a/migrate_cpp/cpp_refactoring/for_range.h b/migrate_cpp/cpp_refactoring/for_range.h deleted file mode 100644 index 50576b89a335f..0000000000000 --- a/migrate_cpp/cpp_refactoring/for_range.h +++ /dev/null @@ -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 { - 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_ diff --git a/migrate_cpp/cpp_refactoring/for_range_test.cpp b/migrate_cpp/cpp_refactoring/for_range_test.cpp deleted file mode 100644 index 4a3150c8f610a..0000000000000 --- a/migrate_cpp/cpp_refactoring/for_range_test.cpp +++ /dev/null @@ -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 {}; - -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 diff --git a/migrate_cpp/cpp_refactoring/main.cpp b/migrate_cpp/cpp_refactoring/main.cpp deleted file mode 100644 index d316434b0ee68..0000000000000 --- a/migrate_cpp/cpp_refactoring/main.cpp +++ /dev/null @@ -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 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()); - matchers.Register(std::make_unique()); - matchers.Register(std::make_unique()); - - return tool.runAndSave( - clang::tooling::newFrontendActionFactory(matchers.GetFinder()).get()); -} diff --git a/migrate_cpp/cpp_refactoring/matcher.cpp b/migrate_cpp/cpp_refactoring/matcher.cpp deleted file mode 100644 index 12891df58f33f..0000000000000 --- a/migrate_cpp/cpp_refactoring/matcher.cpp +++ /dev/null @@ -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 diff --git a/migrate_cpp/cpp_refactoring/matcher.h b/migrate_cpp/cpp_refactoring/matcher.h deleted file mode 100644 index f6eab1264209a..0000000000000 --- a/migrate_cpp/cpp_refactoring/matcher.h +++ /dev/null @@ -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; - - 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 - auto GetNodeAsOrDie(llvm::StringRef id) -> const NodeType& { - auto* node = match_result->Nodes.getNodeAs(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 = 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 -class MatcherFactoryBase : public MatcherFactory { - public: - auto CreateMatcher( - const clang::ast_matchers::MatchFinder::MatchResult* match_result, - Matcher::ReplacementMap* replacements) - -> std::unique_ptr override { - return std::make_unique(match_result, replacements); - } -}; - -} // namespace Carbon - -#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_H_ diff --git a/migrate_cpp/cpp_refactoring/matcher_manager.h b/migrate_cpp/cpp_refactoring/matcher_manager.h deleted file mode 100644 index c9f4d4075dc15..0000000000000 --- a/migrate_cpp/cpp_refactoring/matcher_manager.h +++ /dev/null @@ -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 factory) { - matchers.push_back(std::make_unique( - &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 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 factory; - Matcher::ReplacementMap* const replacements; - }; - - Matcher::ReplacementMap* const replacements; - clang::ast_matchers::MatchFinder finder; - std::vector> factories; - std::vector> matchers; -}; - -} // namespace Carbon - -#endif // CARBON_MIGRATE_CPP_CPP_REFACTORING_MATCHER_MANAGER_H_ diff --git a/migrate_cpp/cpp_refactoring/matcher_test_base.h b/migrate_cpp/cpp_refactoring/matcher_test_base.h deleted file mode 100644 index 81c38972ec285..0000000000000 --- a/migrate_cpp/cpp_refactoring/matcher_test_base.h +++ /dev/null @@ -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 -#include - -#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 -class MatcherTestBase : public ::testing::Test { - protected: - MatcherTestBase() : matchers_(&replacements_) { - matchers_.Register(std::make_unique()); - } - - // 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::tooling::FileContentMappings())); - EXPECT_THAT(replacements_, testing::ElementsAre(testing::Key(Filename))); - llvm::Expected 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 actual_lines; - llvm::SplitString(*actual, actual_lines, "\n"); - llvm::SmallVector 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_ diff --git a/migrate_cpp/cpp_refactoring/var_decl.cpp b/migrate_cpp/cpp_refactoring/var_decl.cpp deleted file mode 100644 index 7e586c618ba35..0000000000000 --- a/migrate_cpp/cpp_refactoring/var_decl.cpp +++ /dev/null @@ -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> 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(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(&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 diff --git a/migrate_cpp/cpp_refactoring/var_decl.h b/migrate_cpp/cpp_refactoring/var_decl.h deleted file mode 100644 index abe0d33ec01db..0000000000000 --- a/migrate_cpp/cpp_refactoring/var_decl.h +++ /dev/null @@ -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 { - 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_ diff --git a/migrate_cpp/cpp_refactoring/var_decl_test.cpp b/migrate_cpp/cpp_refactoring/var_decl_test.cpp deleted file mode 100644 index 6376e2db703b9..0000000000000 --- a/migrate_cpp/cpp_refactoring/var_decl_test.cpp +++ /dev/null @@ -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 {}; - -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 - struct R {}; - - template - struct S {}; - - R> x; - )cpp"; - constexpr char After[] = R"( - template - struct R {}; - - template - struct S {}; - - var x: R>; - )"; - ExpectReplacement(Before, After); -} - -} // namespace -} // namespace Carbon::Testing diff --git a/migrate_cpp/migrate_cpp.py b/migrate_cpp/migrate_cpp.py deleted file mode 100644 index 8c10ac074651a..0000000000000 --- a/migrate_cpp/migrate_cpp.py +++ /dev/null @@ -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() diff --git a/migrate_cpp/output_segment.h b/migrate_cpp/output_segment.h deleted file mode 100644 index 1fd6a458d3e61..0000000000000 --- a/migrate_cpp/output_segment.h +++ /dev/null @@ -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 -#include -#include -#include - -#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 -concept IsSupportedClangASTNodeType = std::convertible_to || - std::convertible_to; -} // 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 - requires Internal::IsSupportedClangASTNodeType - 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 - 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(); - return qtl.isNull() ? type_loc : qtl.getUnqualifiedLoc(); - } - - std::variant content_; -}; - -template - requires Internal::IsSupportedClangASTNodeType -OutputSegment::OutputSegment(const T* node) - : content_(clang::DynTypedNode::create(AssertNotNull(node))) {} - -} // namespace Carbon - -#endif // CARBON_MIGRATE_CPP_OUTPUT_SEGMENT_H_ diff --git a/migrate_cpp/rewriter.cpp b/migrate_cpp/rewriter.cpp deleted file mode 100644 index 35f0e7c6b2279..0000000000000 --- a/migrate_cpp/rewriter.cpp +++ /dev/null @@ -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; - auto [begin, end] = bounds; - - if constexpr (std::is_same_v) { - 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 || - std::is_same_v) { - 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, - "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 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 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 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(*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(d) && - llvm::cast(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 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(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 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 diff --git a/migrate_cpp/rewriter.h b/migrate_cpp/rewriter.h deleted file mode 100644 index 381fa413cd010..0000000000000 --- a/migrate_cpp/rewriter.h +++ /dev/null @@ -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 -#include -#include -#include - -#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; - -// `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; - if constexpr (std::is_same_v) { - return clang::DynTypedNode::DenseMapInfo::getHashValue(x); - } else if constexpr (std::is_same_v) { - // TODO: Improve this. - return reinterpret_cast(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::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 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 { - 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 output_segments) -> void { - segments_.try_emplace(node, std::move(output_segments)); - } - - auto SetReplacement(clang::TypeLoc node, - std::vector output_segments) -> void { - segments_.try_emplace(node, std::move(output_segments)); - } - - template - auto SetReplacement(const T* node, std::vector 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(1, segment))`. - template - auto SetReplacement(const T* node, OutputSegment segment) -> void { - std::vector 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 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 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 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 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 override { - return std::make_unique(result_, output_range_); - } - - private: - std::string& result_; - std::pair output_range_; -}; - -} // namespace Carbon - -#endif // CARBON_MIGRATE_CPP_REWRITER_H_ diff --git a/migrate_cpp/rewriter_test.cpp b/migrate_cpp/rewriter_test.cpp deleted file mode 100644 index f60569f4cc10e..0000000000000 --- a/migrate_cpp/rewriter_test.cpp +++ /dev/null @@ -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 -#include - -#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::max()}` otherwise. - auto range() const -> std::pair { - return std::pair(start_, end_); - } - - private: - std::string source_code_; - size_t start_ = 0; - size_t end_ = std::numeric_limits::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(result, annotated_cpp_code.range()), - annotated_cpp_code.source(), {}, "test.cc", "clang-tool", - std::make_shared(), - 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