// 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 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 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_