diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -15,8 +15,12 @@ #include "index/SymbolCollector.h" #include "support/Logger.h" #include "support/Trace.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTTypeTraits.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/AST/Stmt.h" #include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/None.h" @@ -318,13 +322,72 @@ return Results; } +// Detect name conflict with othter DeclStmts in the same enclosing scope. +const NamedDecl *lookupSiblingWithinEnclosingScope(ASTContext &Ctx, + const NamedDecl &RenamedDecl, + StringRef NewName) { + DynTypedNodeList Parents = Ctx.getParents(RenamedDecl); + if (Parents.size() != 1 || !Parents.begin()->get()) + return nullptr; + Parents = Ctx.getParents(*Parents.begin()->get()); + if (Parents.size() != 1) + return nullptr; + // For now, only CompoundStmts are supported; + const auto *ParentNode = Parents.begin(); + const CompoundStmt *EnclosingScope = ParentNode->get(); + if (const auto *If = ParentNode->get()) + if (const auto *Then = dyn_cast(If->getThen())) + EnclosingScope = Then; + if (const auto *While = ParentNode->get()) + if (const auto *Body = dyn_cast(While->getBody())) + EnclosingScope = Body; + if (const auto *For = ParentNode->get()) + if (const auto *Body = dyn_cast(For->getBody())) + EnclosingScope = Body; + if (!EnclosingScope) + return nullptr; + // This helper checks if any statement within DeclStmt has NewName. + auto CheckDeclStmt = [&](const DeclStmt *DS) -> const NamedDecl * { + for (const auto &Child : DS->getDeclGroup()) + if (const auto *VD = dyn_cast(Child)) + if (VD != &RenamedDecl && VD->getName() == NewName) + return VD; + return nullptr; + }; + for (const auto *Node : EnclosingScope->children()) + if (const auto *DS = dyn_cast(Node)) + if (const auto *Result = CheckDeclStmt(DS)) + return Result; + Parents = Ctx.getParents(*EnclosingScope); + if (Parents.size() != 1) + return nullptr; + // This helper checks if there is a condition variable has NewName. + auto CheckConditionVariable = [&](const auto *Scope) -> const NamedDecl * { + if (!Scope) + return nullptr; + if (const auto *ConditionDS = Scope->getConditionVariableDeclStmt()) + if (const auto *Result = CheckDeclStmt(ConditionDS)) + return Result; + return nullptr; + }; + const auto *Parent = Parents.begin(); + if (const auto *Result = CheckConditionVariable(Parent->get())) + return Result; + if (const auto *Result = CheckConditionVariable(Parent->get())) + return Result; + if (const auto *For = Parent->get()) + if (const auto *Init = dyn_cast(For->getInit())) + if (const auto *Result = CheckDeclStmt(Init)) + return Result; + return nullptr; +} + // Lookup the declarations (if any) with the given Name in the context of // RenameDecl. -const NamedDecl *lookupSiblingWithName(const ASTContext &Ctx, - const NamedDecl &RenamedDecl, - llvm::StringRef Name) { - trace::Span Tracer("LookupSiblingWithName"); - const auto &II = Ctx.Idents.get(Name); +const NamedDecl *lookupSiblingsWithinContext(ASTContext &Ctx, + const NamedDecl &RenamedDecl, + llvm::StringRef NewName) { + const auto &II = Ctx.Idents.get(NewName); DeclarationName LookupName(&II); DeclContextLookupResult LookupResult; const auto *DC = RenamedDecl.getDeclContext(); @@ -356,6 +419,16 @@ return nullptr; } +const NamedDecl *lookupSiblingWithName(ASTContext &Ctx, + const NamedDecl &RenamedDecl, + llvm::StringRef NewName) { + trace::Span Tracer("LookupSiblingWithName"); + if (const auto *Result = + lookupSiblingsWithinContext(Ctx, RenamedDecl, NewName)) + return Result; + return lookupSiblingWithinEnclosingScope(Ctx, RenamedDecl, NewName); +} + struct InvalidName { enum Kind { Keywords, diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -1010,13 +1010,41 @@ )cpp", "conflict", !HeaderFile, nullptr, "Conflict"}, - {R"cpp(// FIXME: detecting local variables is not supported yet. + {R"cpp( void func() { int Conflict; - int [[V^ar]]; + int V^ar; + } + )cpp", + "conflict", !HeaderFile, nullptr, "Conflict"}, + + {R"cpp( + void func() { + if (int Conflict = 42) { + int V^ar; + } } )cpp", - nullptr, !HeaderFile, nullptr, "Conflict"}, + "conflict", !HeaderFile, nullptr, "Conflict"}, + + {R"cpp( + void func() { + while (int V^ar = 10) { + bool Conflict = true; + } + } + )cpp", + "conflict", !HeaderFile, nullptr, "Conflict"}, + + {R"cpp( + void func() { + for (int Something = 9000, Anything = 14, Conflict = 42; Anything > 9; + ++Something) { + int V^ar; + } + } + )cpp", + "conflict", !HeaderFile, nullptr, "Conflict"}, {R"cpp(// Trying to rename into the same name, SameName == SameName. void func() {