diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -140,7 +140,7 @@ // If the file is open in user's editor, make sure the version we // saw and current version are compatible as this is the text that // will be replaced by editors. - if (!It.second.canApplyTo(Draft->Contents)) { + if (!It.second.canApplyTo(*Draft->Contents)) { ++InvalidFileCount; LastInvalidFile = It.first(); } @@ -518,7 +518,7 @@ llvm::Optional WithOffsetEncoding; if (Opts.Encoding) WithOffsetEncoding.emplace(kCurrentOffsetEncoding, *Opts.Encoding); - Server.emplace(*CDB, TFS, Opts, + Server.emplace(*CDB, TFS, DraftMgr.getDraftFS(), Opts, static_cast(this)); } applyConfiguration(Params.initializationOptions.ConfigSettings); @@ -695,7 +695,7 @@ return; } - Server->addDocument(File, Draft->Contents, encodeVersion(Draft->Version), + Server->addDocument(File, *Draft->Contents, encodeVersion(Draft->Version), WantDiags, Params.forceRebuild); } @@ -889,7 +889,7 @@ "onDocumentOnTypeFormatting called for non-added file", ErrorCode::InvalidParams)); - Server->formatOnType(File, Code->Contents, Params.position, Params.ch, + Server->formatOnType(File, *Code->Contents, Params.position, Params.ch, std::move(Reply)); } @@ -904,11 +904,11 @@ ErrorCode::InvalidParams)); Server->formatRange( - File, Code->Contents, Params.range, + File, *Code->Contents, Params.range, [Code = Code->Contents, Reply = std::move(Reply)]( llvm::Expected Result) mutable { if (Result) - Reply(replacementsToEdits(Code, Result.get())); + Reply(replacementsToEdits(*Code, Result.get())); else Reply(Result.takeError()); }); @@ -924,11 +924,11 @@ "onDocumentFormatting called for non-added file", ErrorCode::InvalidParams)); - Server->formatFile(File, Code->Contents, + Server->formatFile(File, *Code->Contents, [Code = Code->Contents, Reply = std::move(Reply)]( llvm::Expected Result) mutable { if (Result) - Reply(replacementsToEdits(Code, Result.get())); + Reply(replacementsToEdits(*Code, Result.get())); else Reply(Result.takeError()); }); @@ -1468,7 +1468,8 @@ BackgroundContext(Context::current().clone()), Transp(Transp), MsgHandler(new MessageHandler(*this)), TFS(TFS), SupportedSymbolKinds(defaultSymbolKinds()), - SupportedCompletionItemKinds(defaultCompletionItemKinds()), Opts(Opts) { + SupportedCompletionItemKinds(defaultCompletionItemKinds()), DraftMgr(TFS), + Opts(Opts) { // clang-format off MsgHandler->bind("initialize", &ClangdLSPServer::onInitialize); @@ -1567,14 +1568,14 @@ auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); if (!Code) return true; // completion code will log the error for untracked doc. - auto Offset = positionToOffset(Code->Contents, Params.position, + auto Offset = positionToOffset(*Code->Contents, Params.position, /*AllowColumnsBeyondLineLength=*/false); if (!Offset) { vlog("could not convert position '{0}' to offset for file '{1}'", Params.position, Params.textDocument.uri.file()); return true; } - return allowImplicitCompletion(Code->Contents, *Offset); + return allowImplicitCompletion(*Code->Contents, *Offset); } void ClangdLSPServer::onHighlightingsReady( @@ -1715,7 +1716,7 @@ for (const Path &FilePath : DraftMgr.getActiveFiles()) if (Filter(FilePath)) if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race? - Server->addDocument(FilePath, std::move(Draft->Contents), + Server->addDocument(FilePath, *Draft->Contents, encodeVersion(Draft->Version), WantDiagnostics::Auto); } diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -170,7 +170,12 @@ /// those arguments for subsequent reparses. However, ClangdServer will check /// if compilation arguments changed on calls to forceReparse(). ClangdServer(const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, - const Options &Opts, Callbacks *Callbacks = nullptr); + const ThreadsafeFS &DirtyFS, const Options &Opts, + Callbacks *Callbacks = nullptr); + + ClangdServer(const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, + const Options &Opts, Callbacks *Callbacks = nullptr) + : ClangdServer(CDB, TFS, TFS, Opts, Callbacks) {} /// Add a \p File to the list of tracked C++ files or update the contents if /// \p File is already tracked. Also schedules parsing of the AST for it on a @@ -363,6 +368,8 @@ config::Provider *ConfigProvider = nullptr; const ThreadsafeFS &TFS; + /// A File system that overlays open documents over the underlying filesystem. + const ThreadsafeFS &DirtyFS; Callbacks *ServerCallbacks = nullptr; mutable std::mutex ConfigDiagnosticsMu; diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -137,9 +137,10 @@ } ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, - const ThreadsafeFS &TFS, const Options &Opts, - Callbacks *Callbacks) - : ConfigProvider(Opts.ConfigProvider), TFS(TFS), ServerCallbacks(Callbacks), + const ThreadsafeFS &TFS, const ThreadsafeFS &DirtyFS, + const Options &Opts, Callbacks *Callbacks) + : ConfigProvider(Opts.ConfigProvider), TFS(TFS), DirtyFS(DirtyFS), + ServerCallbacks(Callbacks), DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex(Opts.HeavyweightDynamicSymbolIndex, Opts.CollectMainFileRefs) @@ -426,7 +427,8 @@ // May generate several candidate selections, due to SelectionTree ambiguity. // vector of pointers because GCC doesn't like non-copyable Selection. static llvm::Expected>> -tweakSelection(const Range &Sel, const InputsAndAST &AST) { +tweakSelection(const Range &Sel, const InputsAndAST &AST, + llvm::vfs::FileSystem *FS) { auto Begin = positionToOffset(AST.Inputs.Contents, Sel.start); if (!Begin) return Begin.takeError(); @@ -438,7 +440,7 @@ AST.AST.getASTContext(), AST.AST.getTokens(), *Begin, *End, [&](SelectionTree T) { Result.push_back(std::make_unique( - AST.Inputs.Index, AST.AST, *Begin, *End, std::move(T))); + AST.Inputs.Index, AST.AST, *Begin, *End, std::move(T), FS)); return false; }); assert(!Result.empty() && "Expected at least one SelectionTree"); @@ -451,12 +453,14 @@ // Tracks number of times a tweak has been offered. static constexpr trace::Metric TweakAvailable( "tweak_available", trace::Metric::Counter, "tweak_id"); - auto Action = [File = File.str(), Sel, CB = std::move(CB), + auto Action = [File = File.str(), Sel, CB = std::move(CB), &TFS = this->TFS, Filter = std::move(Filter)](Expected InpAST) mutable { if (!InpAST) return CB(InpAST.takeError()); - auto Selections = tweakSelection(Sel, *InpAST); + // FIXME: Should we use the dirty fs here? + auto FS = TFS.view(llvm::None); + auto Selections = tweakSelection(Sel, *InpAST, FS.get()); if (!Selections) return CB(Selections.takeError()); std::vector Res; @@ -494,7 +498,8 @@ this](Expected InpAST) mutable { if (!InpAST) return CB(InpAST.takeError()); - auto Selections = tweakSelection(Sel, *InpAST); + auto FS = DirtyFS.view(llvm::None); + auto Selections = tweakSelection(Sel, *InpAST, FS.get()); if (!Selections) return CB(Selections.takeError()); llvm::Optional> Effect; diff --git a/clang-tools-extra/clangd/DraftStore.h b/clang-tools-extra/clangd/DraftStore.h --- a/clang-tools-extra/clangd/DraftStore.h +++ b/clang-tools-extra/clangd/DraftStore.h @@ -11,6 +11,7 @@ #include "Protocol.h" #include "support/Path.h" +#include "support/ThreadsafeFS.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/StringMap.h" #include @@ -20,6 +21,20 @@ namespace clang { namespace clangd { +/// A Reference counted string that is used to hold the contents of open files. +class RefCntString : public llvm::ThreadSafeRefCountedBase { +public: + RefCntString(std::string Str) : Data(std::move(Str)) {} + + StringRef str() const { return Data; } + + operator const std::string &() const { return Data; } + operator StringRef() const { return str(); } + +private: + std::string Data; +}; + /// A thread-safe container for files opened in a workspace, addressed by /// filenames. The contents are owned by the DraftStore. This class supports /// both whole and incremental updates of the documents. @@ -28,10 +43,14 @@ class DraftStore { public: struct Draft { - std::string Contents; + llvm::IntrusiveRefCntPtr Contents; int64_t Version = -1; }; + DraftStore(const ThreadsafeFS &BaseFS); + + DraftStore() = default; + /// \return Contents of the stored document. /// For untracked files, a llvm::None is returned. llvm::Optional getDraft(PathRef File) const; @@ -59,9 +78,20 @@ /// Remove the draft from the store. void removeDraft(PathRef File); + const ThreadsafeFS &getDraftFS() const { + assert(DraftFS && "No draft fs has been set up"); + return *DraftFS; + } + private: + struct DraftAndTime { + Draft Draft; + llvm::sys::TimePoint<> MTime; + }; + class DraftstoreFS; + std::unique_ptr DraftFS; mutable std::mutex Mutex; - llvm::StringMap Drafts; + llvm::StringMap Drafts; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/DraftStore.cpp b/clang-tools-extra/clangd/DraftStore.cpp --- a/clang-tools-extra/clangd/DraftStore.cpp +++ b/clang-tools-extra/clangd/DraftStore.cpp @@ -9,7 +9,9 @@ #include "DraftStore.h" #include "SourceCode.h" #include "support/Logger.h" +#include "llvm/Support/Chrono.h" #include "llvm/Support/Errc.h" +#include namespace clang { namespace clangd { @@ -21,7 +23,7 @@ if (It == Drafts.end()) return None; - return It->second; + return It->second.Draft; } std::vector DraftStore::getActiveFiles() const { @@ -47,14 +49,19 @@ } } +static void updateTime(llvm::sys::TimePoint<> &Time) { + Time = llvm::sys::TimePoint<>::clock::now(); +} + int64_t DraftStore::addDraft(PathRef File, llvm::Optional Version, llvm::StringRef Contents) { std::lock_guard Lock(Mutex); - Draft &D = Drafts[File]; - updateVersion(D, Version); - D.Contents = Contents.str(); - return D.Version; + DraftAndTime &D = Drafts[File]; + updateVersion(D.Draft, Version); + updateTime(D.MTime); + D.Draft.Contents = llvm::makeIntrusiveRefCnt(Contents.str()); + return D.Draft.Version; } llvm::Expected DraftStore::updateDraft( @@ -68,8 +75,8 @@ "Trying to do incremental update on non-added document: {0}", File); } - Draft &D = EntryIt->second; - std::string Contents = EntryIt->second.Contents; + DraftAndTime &D = EntryIt->second; + std::string Contents = *D.Draft.Contents; for (const TextDocumentContentChangeEvent &Change : Changes) { if (!Change.range) { @@ -120,9 +127,11 @@ Contents = std::move(NewContents); } - updateVersion(D, Version); - D.Contents = std::move(Contents); - return D; + updateVersion(D.Draft, Version); + updateTime(D.MTime); + D.Draft.Contents = + llvm::makeIntrusiveRefCnt(std::move(Contents)); + return D.Draft; } void DraftStore::removeDraft(PathRef File) { @@ -131,5 +140,173 @@ Drafts.erase(File); } +namespace { +class RefCntStringMemBuffer final : public llvm::MemoryBuffer { +public: + static std::unique_ptr + create(IntrusiveRefCntPtr Data, StringRef Name) { + // Allocate space for the FileContentMemBuffer and its name with null + // terminator. + static_assert(alignof(RefCntStringMemBuffer) <= sizeof(void *), + "Alignment requirements can't be greater than pointer"); + auto MemSize = sizeof(RefCntStringMemBuffer) + Name.size() + 1; + return std::unique_ptr(new (operator new( + MemSize)) RefCntStringMemBuffer(std::move(Data), Name)); + } + + BufferKind getBufferKind() const override { + return MemoryBuffer::MemoryBuffer_Malloc; + } + + StringRef getBufferIdentifier() const override { + return StringRef(nameBegin(), NameSize); + } + +private: + RefCntStringMemBuffer(IntrusiveRefCntPtr Data, StringRef Name) + : BufferContents(std::move(Data)), NameSize(Name.size()) { + MemoryBuffer::init(BufferContents->str().begin(), + BufferContents->str().end(), true); + memcpy(nameBegin(), Name.begin(), Name.size()); + nameBegin()[Name.size()] = '\0'; + } + + char *nameBegin() { return (char *)&this[1]; } + const char *nameBegin() const { return (const char *)&this[1]; } + + IntrusiveRefCntPtr BufferContents; + size_t NameSize; +}; +class DraftStoreFile : public llvm::vfs::File { +public: + struct FileData { + IntrusiveRefCntPtr Contents; + llvm::sys::fs::UniqueID UID; + llvm::sys::TimePoint<> MTime; + }; + DraftStoreFile(std::string RequestedName, FileData Data) + : RequestedName(std::move(RequestedName)), Data(std::move(Data)) {} + + llvm::ErrorOr status() override { + return llvm::vfs::Status( + RequestedName, Data.UID, Data.MTime, 0, 0, Data.Contents->str().size(), + llvm::sys::fs::file_type::regular_file, llvm::sys::fs::all_all); + } + + llvm::ErrorOr> + getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator, + bool IsVolatile) override { + return RefCntStringMemBuffer::create(Data.Contents, RequestedName); + } + + std::error_code close() override { return {}; } + + ~DraftStoreFile() override { close(); } + +private: + std::string RequestedName; + FileData Data; +}; + +class DraftStoreFileSystem : public llvm::vfs::FileSystem { +public: + DraftStoreFileSystem(llvm::StringMap Contents) + : Contents(std::move(Contents)) {} + + llvm::ErrorOr status(const Twine &Path) override { + SmallString<256> PathStorage; + Path.toVector(PathStorage); + auto EC = makeAbsolute(PathStorage); + assert(!EC); + (void)EC; + auto Iter = Contents.find(PathStorage); + if (Iter == Contents.end()) + return llvm::errc::no_such_file_or_directory; + return DraftStoreFile(std::string(PathStorage), Iter->getValue()).status(); + } + + llvm::ErrorOr> + openFileForRead(const Twine &Path) override { + SmallString<256> PathStorage; + Path.toVector(PathStorage); + auto EC = makeAbsolute(PathStorage); + assert(!EC); + (void)EC; + auto Iter = Contents.find(PathStorage); + if (Iter == Contents.end()) + return llvm::errc::no_such_file_or_directory; + return std::make_unique(std::string(PathStorage), + Iter->getValue()); + } + + llvm::ErrorOr getCurrentWorkingDirectory() const override { + return WorkingDirectory; + } + + std::error_code setCurrentWorkingDirectory(const Twine &P) override { + SmallString<128> Path; + P.toVector(Path); + + // Fix up relative paths. This just prepends the current working + // directory. + std::error_code EC = makeAbsolute(Path); + assert(!EC); + (void)EC; + + llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true); + + if (!Path.empty()) + WorkingDirectory = std::string(Path); + return {}; + } + llvm::vfs::directory_iterator dir_begin(const Twine &Dir, + std::error_code &EC) override { + EC = llvm::errc::no_such_file_or_directory; + return llvm::vfs::directory_iterator(); + } + +private: + std::string WorkingDirectory; + llvm::StringMap Contents; +}; + +} // namespace + +class DraftStore::DraftstoreFS : public ThreadsafeFS { +public: + DraftstoreFS(const ThreadsafeFS &Base, const DraftStore &DS) + : Base(Base), DS(DS) {} + + llvm::IntrusiveRefCntPtr viewImpl() const override { + auto BaseView = Base.view(llvm::None); + llvm::StringMap Contents; + std::lock_guard Guard(DS.Mutex); + for (const auto &KV : DS.Drafts) { + // Query the base filesystem for file uniqueids. + auto BaseStatus = BaseView->status(KV.getKey()); + if (!BaseStatus) { + elog("Couldn't find file {0} when building DirtyFS", KV.getKey()); + continue; + } + // log("Adding dirty file {0} to dirty buffer", KV.getKey()); + Contents.insert(std::make_pair( + KV.getKey(), DraftStoreFile::FileData{KV.getValue().Draft.Contents, + BaseStatus->getUniqueID(), + KV.getValue().MTime})); + } + auto OFS = llvm::makeIntrusiveRefCnt( + std::move(BaseView)); + OFS->pushOverlay( + llvm::makeIntrusiveRefCnt(std::move(Contents))); + return OFS; + } + +private: + const ThreadsafeFS &Base; + const DraftStore &DS; +}; + +DraftStore::DraftStore(const ThreadsafeFS &BaseFS) + : DraftFS(std::make_unique(BaseFS, *this)) {} } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/refactor/Tweak.h b/clang-tools-extra/clangd/refactor/Tweak.h --- a/clang-tools-extra/clangd/refactor/Tweak.h +++ b/clang-tools-extra/clangd/refactor/Tweak.h @@ -48,7 +48,8 @@ /// Input to prepare and apply tweaks. struct Selection { Selection(const SymbolIndex *Index, ParsedAST &AST, unsigned RangeBegin, - unsigned RangeEnd, SelectionTree ASTSelection); + unsigned RangeEnd, SelectionTree ASTSelection, + llvm::vfs::FileSystem *VFS); /// The text of the active document. llvm::StringRef Code; /// The Index for handling codebase related queries. @@ -64,6 +65,11 @@ unsigned SelectionEnd; /// The AST nodes that were selected. SelectionTree ASTSelection; + /// File system used to access source code (for cross-file tweaks). + /// This can be used to overlay the "dirty" contents of files open in the + /// editor, which (in case of headers) may not match the saved contents used + /// for building the AST. + llvm::vfs::FileSystem *FS = nullptr; // FIXME: provide a way to get sources and ASTs for other files. }; diff --git a/clang-tools-extra/clangd/refactor/Tweak.cpp b/clang-tools-extra/clangd/refactor/Tweak.cpp --- a/clang-tools-extra/clangd/refactor/Tweak.cpp +++ b/clang-tools-extra/clangd/refactor/Tweak.cpp @@ -47,9 +47,12 @@ Tweak::Selection::Selection(const SymbolIndex *Index, ParsedAST &AST, unsigned RangeBegin, unsigned RangeEnd, - SelectionTree ASTSelection) + SelectionTree ASTSelection, + llvm::vfs::FileSystem *FS) : Index(Index), AST(&AST), SelectionBegin(RangeBegin), - SelectionEnd(RangeEnd), ASTSelection(std::move(ASTSelection)) { + SelectionEnd(RangeEnd), ASTSelection(std::move(ASTSelection)), + FS(FS ? FS + : &AST.getSourceManager().getFileManager().getVirtualFileSystem()) { auto &SM = AST.getSourceManager(); Code = SM.getBufferData(SM.getMainFileID()); Cursor = SM.getComposedLoc(SM.getMainFileID(), RangeBegin); diff --git a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp --- a/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp @@ -63,10 +63,9 @@ } llvm::Optional getSourceFile(llvm::StringRef FileName, - const Tweak::Selection &Sel) { - if (auto Source = getCorrespondingHeaderOrSource( - FileName, - &Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem())) + const Tweak::Selection &Sel, + llvm::vfs::FileSystem *FS) { + if (auto Source = getCorrespondingHeaderOrSource(FileName, FS)) return *Source; return getCorrespondingHeaderOrSource(FileName, *Sel.AST, Sel.Index); } @@ -403,13 +402,14 @@ if (!MainFileName) return error("Couldn't get absolute path for main file."); - auto CCFile = getSourceFile(*MainFileName, Sel); + auto *FS = Sel.FS; + assert(FS && "FS Must be set in apply"); + + auto CCFile = getSourceFile(*MainFileName, Sel, FS); + if (!CCFile) return error("Couldn't find a suitable implementation file."); - - auto &FS = - Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem(); - auto Buffer = FS.getBufferForFile(*CCFile); + auto Buffer = FS->getBufferForFile(*CCFile); // FIXME: Maybe we should consider creating the implementation file if it // doesn't exist? if (!Buffer) diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp --- a/clang-tools-extra/clangd/tool/Check.cpp +++ b/clang-tools-extra/clangd/tool/Check.cpp @@ -203,7 +203,8 @@ vlog(" {0} {1}", Pos, Tok.text(AST->getSourceManager())); auto Tree = SelectionTree::createRight(AST->getASTContext(), AST->getTokens(), Start, End); - Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree)); + Tweak::Selection Selection(&Index, *AST, Start, End, std::move(Tree), + nullptr); for (const auto &T : prepareTweaks(Selection, Opts.TweakFilter)) { auto Result = T->apply(Selection); if (!Result) { diff --git a/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp b/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp --- a/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp +++ b/clang-tools-extra/clangd/unittests/DraftStoreTests.cpp @@ -52,8 +52,8 @@ llvm::Expected Result = DS.updateDraft(Path, llvm::None, {Event}); ASSERT_TRUE(!!Result); - EXPECT_EQ(Result->Contents, SrcAfter.code()); - EXPECT_EQ(DS.getDraft(Path)->Contents, SrcAfter.code()); + EXPECT_EQ(*Result->Contents, SrcAfter.code()); + EXPECT_EQ(*DS.getDraft(Path)->Contents, SrcAfter.code()); EXPECT_EQ(Result->Version, static_cast(i)); } } @@ -84,8 +84,8 @@ DS.updateDraft(Path, llvm::None, Changes); ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError()); - EXPECT_EQ(Result->Contents, FinalSrc.code()); - EXPECT_EQ(DS.getDraft(Path)->Contents, FinalSrc.code()); + EXPECT_EQ(*Result->Contents, FinalSrc.code()); + EXPECT_EQ(*DS.getDraft(Path)->Contents, FinalSrc.code()); EXPECT_EQ(Result->Version, 1); } @@ -345,7 +345,7 @@ Optional Contents = DS.getDraft(File); EXPECT_TRUE(Contents); - EXPECT_EQ(Contents->Contents, OriginalContents); + EXPECT_EQ(*Contents->Contents, OriginalContents); EXPECT_EQ(Contents->Version, 0); } diff --git a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp --- a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp +++ b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp @@ -74,7 +74,8 @@ SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), Range.first, Range.second, [&](SelectionTree ST) { Tweak::Selection S(Index, AST, Range.first, - Range.second, std::move(ST)); + Range.second, std::move(ST), + nullptr); if (auto T = prepareTweak(TweakID, S)) { Result = (*T)->apply(S); return true;