mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
This was failing to build for me locally with some arbitrary Clang HEAD
host compiler:
```
migrate_cpp/rewriter.cpp:225:3: error: call to member function 'SetReplacement' is ambiguous
225 | SetReplacement(expr, {OutputSegment(std::move(text))});
| ^~~~~~~~~~~~~~
./migrate_cpp/rewriter.h:141:8: note: candidate function [with T = clang::IntegerLiteral]
141 | auto SetReplacement(const T* node, std::vector<OutputSegment> output_segments)
| ^
./migrate_cpp/rewriter.h:150:8: note: candidate function [with T = clang::IntegerLiteral]
150 | auto SetReplacement(const T* node, OutputSegment segment) -> void {
| ^
```
No idea if that's a bug in clang HEAD, but it seemed like removing the
{} simplified the code anyway - so here's that.
377 lines
12 KiB
C++
377 lines
12 KiB
C++
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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
|