Skip to content

Commit

Permalink
[clangd] Rename: merge index/AST refs path-insensitively where needed
Browse files Browse the repository at this point in the history
If you have c:\foo open, and C:\foo indexed (case difference) then these
need to be considered the same file. Otherwise we emit edits to both,
and editors do... something that isn't pretty.

Maybe more centralized normalization is called for, but it's not trivial
to do this while also being case-preserving. see
clangd/clangd#108

Fixes clangd/clangd#665

Differential Revision: https://reviews.llvm.org/D95759

(cherry picked from commit b63cd4d)
  • Loading branch information
sam-mccall authored and tstellar committed Feb 22, 2021
1 parent 76e4c93 commit 8eeb3d9
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 16 deletions.
14 changes: 0 additions & 14 deletions clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
Expand Up @@ -396,20 +396,6 @@ DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
return None;
}

// For platforms where paths are case-insensitive (but case-preserving),
// we need to do case-insensitive comparisons and use lowercase keys.
// FIXME: Make Path a real class with desired semantics instead.
// This class is not the only place this problem exists.
// FIXME: Mac filesystems default to case-insensitive, but may be sensitive.

static std::string maybeCaseFoldPath(PathRef Path) {
#if defined(_WIN32) || defined(__APPLE__)
return Path.lower();
#else
return std::string(Path);
#endif
}

std::vector<DirectoryBasedGlobalCompilationDatabase::DirectoryCache *>
DirectoryBasedGlobalCompilationDatabase::getDirectoryCaches(
llvm::ArrayRef<llvm::StringRef> Dirs) const {
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/refactor/Rename.cpp
Expand Up @@ -68,7 +68,7 @@ llvm::Optional<std::string> getOtherRefFile(const Decl &D, StringRef MainFile,
if (OtherFile)
return;
if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
if (*RefFilePath != MainFile)
if (!pathEqual(*RefFilePath, MainFile))
OtherFile = *RefFilePath;
}
});
Expand Down Expand Up @@ -474,7 +474,7 @@ findOccurrencesOutsideFile(const NamedDecl &RenameDecl,
if ((R.Kind & RefKind::Spelled) == RefKind::Unknown)
return;
if (auto RefFilePath = filePath(R.Location, /*HintFilePath=*/MainFile)) {
if (*RefFilePath != MainFile)
if (!pathEqual(*RefFilePath, MainFile))
AffectedFiles[*RefFilePath].push_back(toRange(R.Location));
}
});
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/support/CMakeLists.txt
Expand Up @@ -23,6 +23,7 @@ add_clang_library(clangdSupport
Logger.cpp
Markup.cpp
MemoryTree.cpp
Path.cpp
Shutdown.cpp
Threading.cpp
ThreadsafeFS.cpp
Expand Down
30 changes: 30 additions & 0 deletions clang-tools-extra/clangd/support/Path.cpp
@@ -0,0 +1,30 @@
//===--- Path.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 "support/Path.h"
namespace clang {
namespace clangd {

std::string maybeCaseFoldPath(PathRef Path) {
#if defined(_WIN32) || defined(__APPLE__)
return Path.lower();
#else
return std::string(Path);
#endif
}

bool pathEqual(PathRef A, PathRef B) {
#if defined(_WIN32) || defined(__APPLE__)
return A.equals_lower(B);
#else
return A == B;
#endif
}

} // namespace clangd
} // namespace clang
6 changes: 6 additions & 0 deletions clang-tools-extra/clangd/support/Path.h
Expand Up @@ -22,6 +22,12 @@ using Path = std::string;
/// signatures.
using PathRef = llvm::StringRef;

// For platforms where paths are case-insensitive (but case-preserving),
// we need to do case-insensitive comparisons and use lowercase keys.
// FIXME: Make Path a real class with desired semantics instead.
std::string maybeCaseFoldPath(PathRef Path);
bool pathEqual(PathRef, PathRef);

} // namespace clangd
} // namespace clang

Expand Down
46 changes: 46 additions & 0 deletions clang-tools-extra/clangd/unittests/RenameTests.cpp
Expand Up @@ -1067,6 +1067,52 @@ TEST(RenameTest, Renameable) {
}
}

MATCHER_P(newText, T, "") { return arg.newText == T; }

TEST(RenameTest, IndexMergeMainFile) {
Annotations Code("int ^x();");
TestTU TU = TestTU::withCode(Code.code());
TU.Filename = "main.cc";
auto AST = TU.build();

auto Main = testPath("main.cc");

auto Rename = [&](const SymbolIndex *Idx) {
auto GetDirtyBuffer = [&](PathRef Path) -> llvm::Optional<std::string> {
return Code.code().str(); // Every file has the same content.
};
RenameOptions Opts;
Opts.AllowCrossFile = true;
RenameInputs Inputs{Code.point(), "xPrime", AST, Main,
Idx, Opts, GetDirtyBuffer};
auto Results = rename(Inputs);
EXPECT_TRUE(bool(Results)) << llvm::toString(Results.takeError());
return std::move(*Results);
};

// We do not expect to see duplicated edits from AST vs index.
auto Results = Rename(TU.index().get());
EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main));
EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(),
ElementsAre(newText("xPrime")));

// Sanity check: we do expect to see index results!
TU.Filename = "other.cc";
Results = Rename(TU.index().get());
EXPECT_THAT(Results.GlobalChanges.keys(),
UnorderedElementsAre(Main, testPath("other.cc")));

#if defined(_WIN32) || defined(__APPLE__)
// On case-insensitive systems, no duplicates if AST vs index case differs.
// https://github.com/clangd/clangd/issues/665
TU.Filename = "MAIN.CC";
Results = Rename(TU.index().get());
EXPECT_THAT(Results.GlobalChanges.keys(), ElementsAre(Main));
EXPECT_THAT(Results.GlobalChanges[Main].asTextEdits(),
ElementsAre(newText("xPrime")));
#endif
}

TEST(RenameTest, MainFileReferencesOnly) {
// filter out references not from main file.
llvm::StringRef Test =
Expand Down

0 comments on commit 8eeb3d9

Please sign in to comment.