Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Syntax] Allow to mutate syntax trees
Summary: This patch adds facilities to mutate the syntax trees and produce corresponding text replacements. The public interface of the syntax library now includes facilities to: 1. perform type-safe modifications of syntax trees, 2. compute textual replacements to apply the modifications, 3. create syntax trees not backed by the source code. For each of the three, we only add a few example transformations in this patch to illustrate the idea, support for more kinds of nodes and transformations will be done in follow-up patches. The high-level mutation operations are implemented on top of operations that allow to arbitrarily change the trees. They are considered to be implementation details and are not available to the users of the library. Reviewers: sammccall, gribozavr2 Reviewed By: gribozavr2 Subscribers: merge_guards_bot, mgorny, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D64573
- Loading branch information
1 parent
bb1b0bc
commit 1ad1504
Showing
13 changed files
with
572 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
//===- Mutations.h - mutate syntax trees --------------------*- C++ ---*-=====// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// Defines high-level APIs for transforming syntax trees and producing the | ||
// corresponding textual replacements. | ||
//===----------------------------------------------------------------------===// | ||
#ifndef LLVM_CLANG_TOOLING_SYNTAX_MUTATIONS_H | ||
#define LLVM_CLANG_TOOLING_SYNTAX_MUTATIONS_H | ||
|
||
#include "clang/Tooling/Core/Replacement.h" | ||
#include "clang/Tooling/Syntax/Nodes.h" | ||
#include "clang/Tooling/Syntax/Tree.h" | ||
|
||
namespace clang { | ||
namespace syntax { | ||
|
||
/// Computes textual replacements required to mimic the tree modifications made | ||
/// to the syntax tree. | ||
tooling::Replacements computeReplacements(const Arena &A, | ||
const syntax::TranslationUnit &TU); | ||
|
||
/// Removes a statement or replaces it with an empty statement where one is | ||
/// required syntactically. E.g., in the following example: | ||
/// if (cond) { foo(); } else bar(); | ||
/// One can remove `foo();` completely and to remove `bar();` we would need to | ||
/// replace it with an empty statement. | ||
/// EXPECTS: S->canModify() == true | ||
void removeStatement(syntax::Arena &A, syntax::Statement *S); | ||
|
||
} // namespace syntax | ||
} // namespace clang | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
//===- ComputeReplacements.cpp --------------------------------*- C++ -*-=====// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
#include "clang/Tooling/Core/Replacement.h" | ||
#include "clang/Tooling/Syntax/Mutations.h" | ||
#include "clang/Tooling/Syntax/Tokens.h" | ||
#include "llvm/Support/Error.h" | ||
|
||
using namespace clang; | ||
|
||
namespace { | ||
using ProcessTokensFn = llvm::function_ref<void(llvm::ArrayRef<syntax::Token>, | ||
bool /*IsOriginal*/)>; | ||
/// Enumerates spans of tokens from the tree consecutively laid out in memory. | ||
void enumerateTokenSpans(const syntax::Tree *Root, ProcessTokensFn Callback) { | ||
struct Enumerator { | ||
Enumerator(ProcessTokensFn Callback) | ||
: SpanBegin(nullptr), SpanEnd(nullptr), SpanIsOriginal(false), | ||
Callback(Callback) {} | ||
|
||
void run(const syntax::Tree *Root) { | ||
process(Root); | ||
// Report the last span to the user. | ||
if (SpanBegin) | ||
Callback(llvm::makeArrayRef(SpanBegin, SpanEnd), SpanIsOriginal); | ||
} | ||
|
||
private: | ||
void process(const syntax::Node *N) { | ||
if (auto *T = dyn_cast<syntax::Tree>(N)) { | ||
for (auto *C = T->firstChild(); C != nullptr; C = C->nextSibling()) | ||
process(C); | ||
return; | ||
} | ||
|
||
auto *L = cast<syntax::Leaf>(N); | ||
if (SpanEnd == L->token() && SpanIsOriginal == L->isOriginal()) { | ||
// Extend the current span. | ||
++SpanEnd; | ||
return; | ||
} | ||
// Report the current span to the user. | ||
if (SpanBegin) | ||
Callback(llvm::makeArrayRef(SpanBegin, SpanEnd), SpanIsOriginal); | ||
// Start recording a new span. | ||
SpanBegin = L->token(); | ||
SpanEnd = SpanBegin + 1; | ||
SpanIsOriginal = L->isOriginal(); | ||
} | ||
|
||
const syntax::Token *SpanBegin; | ||
const syntax::Token *SpanEnd; | ||
bool SpanIsOriginal; | ||
ProcessTokensFn Callback; | ||
}; | ||
|
||
return Enumerator(Callback).run(Root); | ||
} | ||
|
||
syntax::FileRange rangeOfExpanded(const syntax::Arena &A, | ||
llvm::ArrayRef<syntax::Token> Expanded) { | ||
auto &Buffer = A.tokenBuffer(); | ||
auto &SM = A.sourceManager(); | ||
|
||
// Check that \p Expanded actually points into expanded tokens. | ||
assert(Buffer.expandedTokens().begin() <= Expanded.begin()); | ||
assert(Expanded.end() < Buffer.expandedTokens().end()); | ||
|
||
if (Expanded.empty()) | ||
// (!) empty tokens must always point before end(). | ||
return syntax::FileRange( | ||
SM, SM.getExpansionLoc(Expanded.begin()->location()), /*Length=*/0); | ||
|
||
auto Spelled = Buffer.spelledForExpanded(Expanded); | ||
assert(Spelled && "could not find spelled tokens for expanded"); | ||
return syntax::Token::range(SM, Spelled->front(), Spelled->back()); | ||
} | ||
} // namespace | ||
|
||
tooling::Replacements | ||
syntax::computeReplacements(const syntax::Arena &A, | ||
const syntax::TranslationUnit &TU) { | ||
auto &Buffer = A.tokenBuffer(); | ||
auto &SM = A.sourceManager(); | ||
|
||
tooling::Replacements Replacements; | ||
// Text inserted by the replacement we are building now. | ||
std::string Replacement; | ||
auto emitReplacement = [&](llvm::ArrayRef<syntax::Token> ReplacedRange) { | ||
if (ReplacedRange.empty() && Replacement.empty()) | ||
return; | ||
llvm::cantFail(Replacements.add(tooling::Replacement( | ||
SM, rangeOfExpanded(A, ReplacedRange).toCharRange(SM), Replacement))); | ||
Replacement = ""; | ||
}; | ||
|
||
const syntax::Token *NextOriginal = Buffer.expandedTokens().begin(); | ||
enumerateTokenSpans( | ||
&TU, [&](llvm::ArrayRef<syntax::Token> Tokens, bool IsOriginal) { | ||
if (!IsOriginal) { | ||
Replacement += | ||
syntax::Token::range(SM, Tokens.front(), Tokens.back()).text(SM); | ||
return; | ||
} | ||
assert(NextOriginal <= Tokens.begin()); | ||
// We are looking at a span of original tokens. | ||
if (NextOriginal != Tokens.begin()) { | ||
// There is a gap, record a replacement or deletion. | ||
emitReplacement(llvm::makeArrayRef(NextOriginal, Tokens.begin())); | ||
} else { | ||
// No gap, but we may have pending insertions. Emit them now. | ||
emitReplacement(llvm::makeArrayRef(NextOriginal, /*Length=*/0)); | ||
} | ||
NextOriginal = Tokens.end(); | ||
}); | ||
|
||
// We might have pending replacements at the end of file. If so, emit them. | ||
emitReplacement(llvm::makeArrayRef( | ||
NextOriginal, Buffer.expandedTokens().drop_back().end())); | ||
|
||
return Replacements; | ||
} |
Oops, something went wrong.