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,14 @@ #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/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/None.h" @@ -318,13 +324,83 @@ 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; + // This helper checks if any statement within DeclStmt has NewName. + auto CheckDeclStmt = [&](const DeclStmt *DS) -> const NamedDecl * { + if (!DS) + return nullptr; + for (const auto &Child : DS->getDeclGroup()) + if (const auto *ND = dyn_cast(Child)) + if (ND != &RenamedDecl && ND->getName() == NewName) + return ND; + return nullptr; + }; + auto CheckCompoundStmt = [&](const CompoundStmt *CS) -> const NamedDecl * { + if (!CS) + return nullptr; + for (const auto *Node : CS->children()) + if (const auto *Result = CheckDeclStmt(dyn_cast(Node))) + return Result; + 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 *Result = + CheckDeclStmt(Scope->getConditionVariableDeclStmt())) + return Result; + return nullptr; + }; + const auto *ParentNode = Parents.begin(); + if (const auto *EnclosingCS = ParentNode->get()) { + if (const auto *Result = CheckCompoundStmt(EnclosingCS)) + return Result; + Parents = Ctx.getParents(*EnclosingCS); + if (Parents.size() != 1) + 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 *Result = + CheckDeclStmt(dyn_cast(For->getInit()))) + return Result; + if (const auto *Function = Parent->get()) + for (const auto *Parameter : Function->parameters()) + if (Parameter->getName() == NewName) + return Parameter; + } + if (const auto *EnclosingIf = ParentNode->get()) { + if (const auto *Result = CheckCompoundStmt( + dyn_cast_or_null(EnclosingIf->getElse()))) + return Result; + return CheckCompoundStmt(dyn_cast(EnclosingIf->getThen())); + } + if (const auto *EnclosingWhile = ParentNode->get()) + return CheckCompoundStmt(dyn_cast(EnclosingWhile->getBody())); + if (const auto *EnclosingFor = ParentNode->get()) + return CheckCompoundStmt(dyn_cast(EnclosingFor->getBody())); + 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 +432,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,68 @@ )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", + "conflict", !HeaderFile, nullptr, "Conflict"}, + + {R"cpp( + void func() { + if (int Conflict = 42) { + } else { + bool V^ar; + } + } + )cpp", + "conflict", !HeaderFile, nullptr, "Conflict"}, + + {R"cpp( + void func() { + if (int V^ar = 42) { + } else { + bool Conflict; + } } )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( + void func(int Conflict) { + bool V^ar; + } + )cpp", + "conflict", !HeaderFile, nullptr, "Conflict"}, {R"cpp(// Trying to rename into the same name, SameName == SameName. void func() {