Path: blob/main/contrib/llvm-project/clang/lib/Tooling/Refactoring/ASTSelection.cpp
35271 views
//===--- ASTSelection.cpp - Clang refactoring library ---------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Tooling/Refactoring/ASTSelection.h"9#include "clang/AST/LexicallyOrderedRecursiveASTVisitor.h"10#include "clang/Lex/Lexer.h"11#include "llvm/Support/SaveAndRestore.h"12#include <optional>1314using namespace clang;15using namespace tooling;1617namespace {1819CharSourceRange getLexicalDeclRange(Decl *D, const SourceManager &SM,20const LangOptions &LangOpts) {21if (!isa<ObjCImplDecl>(D))22return CharSourceRange::getTokenRange(D->getSourceRange());23// Objective-C implementation declarations end at the '@' instead of the 'end'24// keyword. Use the lexer to find the location right after 'end'.25SourceRange R = D->getSourceRange();26SourceLocation LocAfterEnd = Lexer::findLocationAfterToken(27R.getEnd(), tok::raw_identifier, SM, LangOpts,28/*SkipTrailingWhitespaceAndNewLine=*/false);29return LocAfterEnd.isValid()30? CharSourceRange::getCharRange(R.getBegin(), LocAfterEnd)31: CharSourceRange::getTokenRange(R);32}3334/// Constructs the tree of selected AST nodes that either contain the location35/// of the cursor or overlap with the selection range.36class ASTSelectionFinder37: public LexicallyOrderedRecursiveASTVisitor<ASTSelectionFinder> {38public:39ASTSelectionFinder(SourceRange Selection, FileID TargetFile,40const ASTContext &Context)41: LexicallyOrderedRecursiveASTVisitor(Context.getSourceManager()),42SelectionBegin(Selection.getBegin()),43SelectionEnd(Selection.getBegin() == Selection.getEnd()44? SourceLocation()45: Selection.getEnd()),46TargetFile(TargetFile), Context(Context) {47// The TU decl is the root of the selected node tree.48SelectionStack.push_back(49SelectedASTNode(DynTypedNode::create(*Context.getTranslationUnitDecl()),50SourceSelectionKind::None));51}5253std::optional<SelectedASTNode> getSelectedASTNode() {54assert(SelectionStack.size() == 1 && "stack was not popped");55SelectedASTNode Result = std::move(SelectionStack.back());56SelectionStack.pop_back();57if (Result.Children.empty())58return std::nullopt;59return std::move(Result);60}6162bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {63// Avoid traversing the semantic expressions. They should be handled by64// looking through the appropriate opaque expressions in order to build65// a meaningful selection tree.66llvm::SaveAndRestore LookThrough(LookThroughOpaqueValueExprs, true);67return TraverseStmt(E->getSyntacticForm());68}6970bool TraverseOpaqueValueExpr(OpaqueValueExpr *E) {71if (!LookThroughOpaqueValueExprs)72return true;73llvm::SaveAndRestore LookThrough(LookThroughOpaqueValueExprs, false);74return TraverseStmt(E->getSourceExpr());75}7677bool TraverseDecl(Decl *D) {78if (isa<TranslationUnitDecl>(D))79return LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D);80if (D->isImplicit())81return true;8283// Check if this declaration is written in the file of interest.84const SourceRange DeclRange = D->getSourceRange();85const SourceManager &SM = Context.getSourceManager();86SourceLocation FileLoc;87if (DeclRange.getBegin().isMacroID() && !DeclRange.getEnd().isMacroID())88FileLoc = DeclRange.getEnd();89else90FileLoc = SM.getSpellingLoc(DeclRange.getBegin());91if (SM.getFileID(FileLoc) != TargetFile)92return true;9394SourceSelectionKind SelectionKind =95selectionKindFor(getLexicalDeclRange(D, SM, Context.getLangOpts()));96SelectionStack.push_back(97SelectedASTNode(DynTypedNode::create(*D), SelectionKind));98LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D);99popAndAddToSelectionIfSelected(SelectionKind);100101if (DeclRange.getEnd().isValid() &&102SM.isBeforeInTranslationUnit(SelectionEnd.isValid() ? SelectionEnd103: SelectionBegin,104DeclRange.getEnd())) {105// Stop early when we've reached a declaration after the selection.106return false;107}108return true;109}110111bool TraverseStmt(Stmt *S) {112if (!S)113return true;114if (auto *Opaque = dyn_cast<OpaqueValueExpr>(S))115return TraverseOpaqueValueExpr(Opaque);116// Avoid selecting implicit 'this' expressions.117if (auto *TE = dyn_cast<CXXThisExpr>(S)) {118if (TE->isImplicit())119return true;120}121// FIXME (Alex Lorenz): Improve handling for macro locations.122SourceSelectionKind SelectionKind =123selectionKindFor(CharSourceRange::getTokenRange(S->getSourceRange()));124SelectionStack.push_back(125SelectedASTNode(DynTypedNode::create(*S), SelectionKind));126LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S);127popAndAddToSelectionIfSelected(SelectionKind);128return true;129}130131private:132void popAndAddToSelectionIfSelected(SourceSelectionKind SelectionKind) {133SelectedASTNode Node = std::move(SelectionStack.back());134SelectionStack.pop_back();135if (SelectionKind != SourceSelectionKind::None || !Node.Children.empty())136SelectionStack.back().Children.push_back(std::move(Node));137}138139SourceSelectionKind selectionKindFor(CharSourceRange Range) {140SourceLocation End = Range.getEnd();141const SourceManager &SM = Context.getSourceManager();142if (Range.isTokenRange())143End = Lexer::getLocForEndOfToken(End, 0, SM, Context.getLangOpts());144if (!SourceLocation::isPairOfFileLocations(Range.getBegin(), End))145return SourceSelectionKind::None;146if (!SelectionEnd.isValid()) {147// Do a quick check when the selection is of length 0.148if (SM.isPointWithin(SelectionBegin, Range.getBegin(), End))149return SourceSelectionKind::ContainsSelection;150return SourceSelectionKind::None;151}152bool HasStart = SM.isPointWithin(SelectionBegin, Range.getBegin(), End);153bool HasEnd = SM.isPointWithin(SelectionEnd, Range.getBegin(), End);154if (HasStart && HasEnd)155return SourceSelectionKind::ContainsSelection;156if (SM.isPointWithin(Range.getBegin(), SelectionBegin, SelectionEnd) &&157SM.isPointWithin(End, SelectionBegin, SelectionEnd))158return SourceSelectionKind::InsideSelection;159// Ensure there's at least some overlap with the 'start'/'end' selection160// types.161if (HasStart && SelectionBegin != End)162return SourceSelectionKind::ContainsSelectionStart;163if (HasEnd && SelectionEnd != Range.getBegin())164return SourceSelectionKind::ContainsSelectionEnd;165166return SourceSelectionKind::None;167}168169const SourceLocation SelectionBegin, SelectionEnd;170FileID TargetFile;171const ASTContext &Context;172std::vector<SelectedASTNode> SelectionStack;173/// Controls whether we can traverse through the OpaqueValueExpr. This is174/// typically enabled during the traversal of syntactic form for175/// PseudoObjectExprs.176bool LookThroughOpaqueValueExprs = false;177};178179} // end anonymous namespace180181std::optional<SelectedASTNode>182clang::tooling::findSelectedASTNodes(const ASTContext &Context,183SourceRange SelectionRange) {184assert(SelectionRange.isValid() &&185SourceLocation::isPairOfFileLocations(SelectionRange.getBegin(),186SelectionRange.getEnd()) &&187"Expected a file range");188FileID TargetFile =189Context.getSourceManager().getFileID(SelectionRange.getBegin());190assert(Context.getSourceManager().getFileID(SelectionRange.getEnd()) ==191TargetFile &&192"selection range must span one file");193194ASTSelectionFinder Visitor(SelectionRange, TargetFile, Context);195Visitor.TraverseDecl(Context.getTranslationUnitDecl());196return Visitor.getSelectedASTNode();197}198199static const char *selectionKindToString(SourceSelectionKind Kind) {200switch (Kind) {201case SourceSelectionKind::None:202return "none";203case SourceSelectionKind::ContainsSelection:204return "contains-selection";205case SourceSelectionKind::ContainsSelectionStart:206return "contains-selection-start";207case SourceSelectionKind::ContainsSelectionEnd:208return "contains-selection-end";209case SourceSelectionKind::InsideSelection:210return "inside";211}212llvm_unreachable("invalid selection kind");213}214215static void dump(const SelectedASTNode &Node, llvm::raw_ostream &OS,216unsigned Indent = 0) {217OS.indent(Indent * 2);218if (const Decl *D = Node.Node.get<Decl>()) {219OS << D->getDeclKindName() << "Decl";220if (const auto *ND = dyn_cast<NamedDecl>(D))221OS << " \"" << ND->getDeclName() << '"';222} else if (const Stmt *S = Node.Node.get<Stmt>()) {223OS << S->getStmtClassName();224}225OS << ' ' << selectionKindToString(Node.SelectionKind) << "\n";226for (const auto &Child : Node.Children)227dump(Child, OS, Indent + 1);228}229230void SelectedASTNode::dump(llvm::raw_ostream &OS) const { ::dump(*this, OS); }231232/// Returns true if the given node has any direct children with the following233/// selection kind.234///235/// Note: The direct children also include children of direct children with the236/// "None" selection kind.237static bool hasAnyDirectChildrenWithKind(const SelectedASTNode &Node,238SourceSelectionKind Kind) {239assert(Kind != SourceSelectionKind::None && "invalid predicate!");240for (const auto &Child : Node.Children) {241if (Child.SelectionKind == Kind)242return true;243if (Child.SelectionKind == SourceSelectionKind::None)244return hasAnyDirectChildrenWithKind(Child, Kind);245}246return false;247}248249namespace {250struct SelectedNodeWithParents {251SelectedASTNode::ReferenceType Node;252llvm::SmallVector<SelectedASTNode::ReferenceType, 8> Parents;253254/// Canonicalizes the given selection by selecting different related AST nodes255/// when it makes sense to do so.256void canonicalize();257};258259enum SelectionCanonicalizationAction { KeepSelection, SelectParent };260261/// Returns the canonicalization action which should be applied to the262/// selected statement.263SelectionCanonicalizationAction264getSelectionCanonizalizationAction(const Stmt *S, const Stmt *Parent) {265// Select the parent expression when:266// - The string literal in ObjC string literal is selected, e.g.:267// @"test" becomes @"test"268// ~~~~~~ ~~~~~~~269if (isa<StringLiteral>(S) && isa<ObjCStringLiteral>(Parent))270return SelectParent;271// The entire call should be selected when just the member expression272// that refers to the method or the decl ref that refers to the function273// is selected.274// f.call(args) becomes f.call(args)275// ~~~~ ~~~~~~~~~~~~276// func(args) becomes func(args)277// ~~~~ ~~~~~~~~~~278else if (const auto *CE = dyn_cast<CallExpr>(Parent)) {279if ((isa<MemberExpr>(S) || isa<DeclRefExpr>(S)) &&280CE->getCallee()->IgnoreImpCasts() == S)281return SelectParent;282}283// FIXME: Syntactic form -> Entire pseudo-object expr.284return KeepSelection;285}286287} // end anonymous namespace288289void SelectedNodeWithParents::canonicalize() {290const Stmt *S = Node.get().Node.get<Stmt>();291assert(S && "non statement selection!");292const Stmt *Parent = Parents[Parents.size() - 1].get().Node.get<Stmt>();293if (!Parent)294return;295296// Look through the implicit casts in the parents.297unsigned ParentIndex = 1;298for (; (ParentIndex + 1) <= Parents.size() && isa<ImplicitCastExpr>(Parent);299++ParentIndex) {300const Stmt *NewParent =301Parents[Parents.size() - ParentIndex - 1].get().Node.get<Stmt>();302if (!NewParent)303break;304Parent = NewParent;305}306307switch (getSelectionCanonizalizationAction(S, Parent)) {308case SelectParent:309Node = Parents[Parents.size() - ParentIndex];310for (; ParentIndex != 0; --ParentIndex)311Parents.pop_back();312break;313case KeepSelection:314break;315}316}317318/// Finds the set of bottom-most selected AST nodes that are in the selection319/// tree with the specified selection kind.320///321/// For example, given the following selection tree:322///323/// FunctionDecl "f" contains-selection324/// CompoundStmt contains-selection [#1]325/// CallExpr inside326/// ImplicitCastExpr inside327/// DeclRefExpr inside328/// IntegerLiteral inside329/// IntegerLiteral inside330/// FunctionDecl "f2" contains-selection331/// CompoundStmt contains-selection [#2]332/// CallExpr inside333/// ImplicitCastExpr inside334/// DeclRefExpr inside335/// IntegerLiteral inside336/// IntegerLiteral inside337///338/// This function will find references to nodes #1 and #2 when searching for the339/// \c ContainsSelection kind.340static void findDeepestWithKind(341const SelectedASTNode &ASTSelection,342llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,343SourceSelectionKind Kind,344llvm::SmallVectorImpl<SelectedASTNode::ReferenceType> &ParentStack) {345if (ASTSelection.Node.get<DeclStmt>()) {346// Select the entire decl stmt when any of its child declarations is the347// bottom-most.348for (const auto &Child : ASTSelection.Children) {349if (!hasAnyDirectChildrenWithKind(Child, Kind)) {350MatchingNodes.push_back(SelectedNodeWithParents{351std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}});352return;353}354}355} else {356if (!hasAnyDirectChildrenWithKind(ASTSelection, Kind)) {357// This node is the bottom-most.358MatchingNodes.push_back(SelectedNodeWithParents{359std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}});360return;361}362}363// Search in the children.364ParentStack.push_back(std::cref(ASTSelection));365for (const auto &Child : ASTSelection.Children)366findDeepestWithKind(Child, MatchingNodes, Kind, ParentStack);367ParentStack.pop_back();368}369370static void findDeepestWithKind(371const SelectedASTNode &ASTSelection,372llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes,373SourceSelectionKind Kind) {374llvm::SmallVector<SelectedASTNode::ReferenceType, 16> ParentStack;375findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack);376}377378std::optional<CodeRangeASTSelection>379CodeRangeASTSelection::create(SourceRange SelectionRange,380const SelectedASTNode &ASTSelection) {381// Code range is selected when the selection range is not empty.382if (SelectionRange.getBegin() == SelectionRange.getEnd())383return std::nullopt;384llvm::SmallVector<SelectedNodeWithParents, 4> ContainSelection;385findDeepestWithKind(ASTSelection, ContainSelection,386SourceSelectionKind::ContainsSelection);387// We are looking for a selection in one body of code, so let's focus on388// one matching result.389if (ContainSelection.size() != 1)390return std::nullopt;391SelectedNodeWithParents &Selected = ContainSelection[0];392if (!Selected.Node.get().Node.get<Stmt>())393return std::nullopt;394const Stmt *CodeRangeStmt = Selected.Node.get().Node.get<Stmt>();395if (!isa<CompoundStmt>(CodeRangeStmt)) {396Selected.canonicalize();397return CodeRangeASTSelection(Selected.Node, Selected.Parents,398/*AreChildrenSelected=*/false);399}400// FIXME (Alex L): First selected SwitchCase means that first case statement.401// is selected actually402// (See https://github.com/apple/swift-clang & CompoundStmtRange).403404// FIXME (Alex L): Tweak selection rules for compound statements, see:405// https://github.com/apple/swift-clang/blob/swift-4.1-branch/lib/Tooling/406// Refactor/ASTSlice.cpp#L513407// The user selected multiple statements in a compound statement.408Selected.Parents.push_back(Selected.Node);409return CodeRangeASTSelection(Selected.Node, Selected.Parents,410/*AreChildrenSelected=*/true);411}412413static bool isFunctionLikeDeclaration(const Decl *D) {414// FIXME (Alex L): Test for BlockDecl.415return isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D);416}417418bool CodeRangeASTSelection::isInFunctionLikeBodyOfCode() const {419bool IsPrevCompound = false;420// Scan through the parents (bottom-to-top) and check if the selection is421// contained in a compound statement that's a body of a function/method422// declaration.423for (const auto &Parent : llvm::reverse(Parents)) {424const DynTypedNode &Node = Parent.get().Node;425if (const auto *D = Node.get<Decl>()) {426if (isFunctionLikeDeclaration(D))427return IsPrevCompound;428// Stop the search at any type declaration to avoid returning true for429// expressions in type declarations in functions, like:430// function foo() { struct X {431// int m = /*selection:*/ 1 + 2 /*selection end*/; }; };432if (isa<TypeDecl>(D))433return false;434}435IsPrevCompound = Node.get<CompoundStmt>() != nullptr;436}437return false;438}439440const Decl *CodeRangeASTSelection::getFunctionLikeNearestParent() const {441for (const auto &Parent : llvm::reverse(Parents)) {442const DynTypedNode &Node = Parent.get().Node;443if (const auto *D = Node.get<Decl>()) {444if (isFunctionLikeDeclaration(D))445return D;446}447}448return nullptr;449}450451452