mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 22:02:23 +01:00
This does some more work to the run_clang_tidy.py wrapper script, and runs an example pass. "again" because it's really the proto fuzzer changes that broke it, it had been working before. "mostly" because there's still an issue within the proto fuzzer that it can't find "port/protobuf.h", i.e. https://github.com/google/libprotobuf-mutator/tree/master/port, but I'm still hesitant to add an include path there.
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
|