[clangd] Add "Deprecated" field to Symbol and CodeCompletion.

Summary: Also set "deprecated" field in LSP CompletionItem.

Reviewers: sammccall, kadircet

Reviewed By: sammccall

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, cfe-commits

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

llvm-svn: 341576
This commit is contained in:
Eric Liu 2018-09-06 18:52:26 +00:00
parent 9e5c163154
commit 6df66001ee
16 changed files with 119 additions and 25 deletions

View File

@ -353,6 +353,8 @@ struct CodeCompletionBuilder {
return std::tie(X.range.start.line, X.range.start.character) <
std::tie(Y.range.start.line, Y.range.start.character);
});
Completion.Deprecated |=
(C.SemaResult->Availability == CXAvailability_Deprecated);
}
if (C.IndexResult) {
Completion.Origin |= C.IndexResult->Origin;
@ -362,6 +364,7 @@ struct CodeCompletionBuilder {
Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind);
if (Completion.Name.empty())
Completion.Name = C.IndexResult->Name;
Completion.Deprecated |= (C.IndexResult->Flags & Symbol::Deprecated);
}
// Turn absolute path into a literal string that can be #included.
@ -1625,6 +1628,7 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const {
LSP.kind = Kind;
LSP.detail = BundleSize > 1 ? llvm::formatv("[{0} overloads]", BundleSize)
: ReturnType;
LSP.deprecated = Deprecated;
if (InsertInclude)
LSP.detail += "\n" + InsertInclude->Header;
LSP.documentation = Documentation;
@ -1656,6 +1660,7 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const {
: InsertTextFormat::PlainText;
if (InsertInclude && InsertInclude->Insertion)
LSP.additionalTextEdits.push_back(*InsertInclude->Insertion);
return LSP;
}

View File

@ -177,6 +177,9 @@ struct CodeCompletion {
};
Scores Score;
/// Indicates if this item is deprecated.
bool Deprecated = false;
// Serialize this to an LSP completion item. This is a lossy operation.
CompletionItem render(const CodeCompleteOptions &) const;
};

View File

@ -517,6 +517,8 @@ json::Value toJSON(const CompletionItem &CI) {
Result["textEdit"] = *CI.textEdit;
if (!CI.additionalTextEdits.empty())
Result["additionalTextEdits"] = json::Array(CI.additionalTextEdits);
if (CI.deprecated)
Result["deprecated"] = CI.deprecated;
return std::move(Result);
}

View File

@ -768,6 +768,9 @@ struct CompletionItem {
/// themselves.
std::vector<TextEdit> additionalTextEdits;
/// Indicates if this item is deprecated.
bool deprecated = false;
// TODO(krasimir): The following optional fields defined by the language
// server protocol are unsupported:
//

View File

@ -167,9 +167,7 @@ static bool isInstanceMember(const index::SymbolInfo &D) {
}
void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
if (SemaCCResult.Availability == CXAvailability_Deprecated)
Deprecated = true;
Deprecated |= (SemaCCResult.Availability == CXAvailability_Deprecated);
Category = categorize(SemaCCResult);
if (SemaCCResult.Declaration) {
@ -180,6 +178,7 @@ void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
}
void SymbolQualitySignals::merge(const Symbol &IndexResult) {
Deprecated |= (IndexResult.Flags & Symbol::Deprecated);
References = std::max(IndexResult.References, References);
Category = categorize(IndexResult.SymInfo);
ReservedName = ReservedName || isReserved(IndexResult.Name);

View File

@ -55,6 +55,17 @@ raw_ostream &operator<<(raw_ostream &OS, SymbolOrigin O) {
return OS;
}
raw_ostream &operator<<(raw_ostream &OS, Symbol::SymbolFlag F) {
if (F == Symbol::None)
return OS << "None";
std::string s;
if (F & Symbol::Deprecated)
s += "deprecated|";
if (F & Symbol::IndexedForCodeCompletion)
s += "completion|";
return OS << StringRef(s).rtrim('|');
}
raw_ostream &operator<<(raw_ostream &OS, const Symbol &S) {
return OS << S.Scope << S.Name;
}

View File

@ -201,9 +201,6 @@ struct Symbol {
// The number of translation units that reference this symbol from their main
// file. This number is only meaningful if aggregated in an index.
unsigned References = 0;
/// Whether or not this symbol is meant to be used for the code completion.
/// See also isIndexedForCodeCompletion().
bool IsIndexedForCodeCompletion = false;
/// Where this symbol came from. Usually an index provides a constant value.
SymbolOrigin Origin = SymbolOrigin::Unknown;
/// A brief description of the symbol that can be appended in the completion
@ -244,9 +241,27 @@ struct Symbol {
/// any definition.
llvm::SmallVector<IncludeHeaderWithReferences, 1> IncludeHeaders;
// FIXME: add extra fields for index scoring signals.
enum SymbolFlag : uint8_t {
None = 0,
/// Whether or not this symbol is meant to be used for the code completion.
/// See also isIndexedForCodeCompletion().
IndexedForCodeCompletion = 1 << 0,
/// Indicates if the symbol is deprecated.
Deprecated = 1 << 1,
};
SymbolFlag Flags = SymbolFlag::None;
/// FIXME: also add deprecation message and fixit?
};
inline Symbol::SymbolFlag operator|(Symbol::SymbolFlag A, Symbol::SymbolFlag B) {
return static_cast<Symbol::SymbolFlag>(static_cast<uint8_t>(A) |
static_cast<uint8_t>(B));
}
inline Symbol::SymbolFlag &operator|=(Symbol::SymbolFlag &A, Symbol::SymbolFlag B) {
return A = A | B;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Symbol &S);
raw_ostream &operator<<(raw_ostream &, Symbol::SymbolFlag);
// Invokes Callback with each StringRef& contained in the Symbol.
// Useful for deduplicating backing strings.

View File

@ -35,7 +35,8 @@ bool MemIndex::fuzzyFind(
// Exact match against all possible scopes.
if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope))
continue;
if (Req.RestrictForCodeCompletion && !Sym->IsIndexedForCodeCompletion)
if (Req.RestrictForCodeCompletion &&
!(Sym->Flags & Symbol::IndexedForCodeCompletion))
continue;
if (auto Score = Filter.match(Sym->Name))

View File

@ -150,6 +150,7 @@ Symbol mergeSymbol(const Symbol &L, const Symbol &R) {
}
S.Origin |= O.Origin | SymbolOrigin::Merge;
S.Flags |= O.Flags;
return S;
}

View File

@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//
#include "Serialization.h"
#include "../RIFF.h"
#include "Index.h"
#include "llvm/Support/Compression.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Error.h"
@ -200,7 +201,7 @@ void writeSymbol(const Symbol &Sym, const StringTableOut &Strings,
}
}
writeVar(Sym.References, OS);
OS.write(Sym.IsIndexedForCodeCompletion);
OS.write(static_cast<uint8_t>(Sym.Flags));
OS.write(static_cast<uint8_t>(Sym.Origin));
writeVar(Strings.index(Sym.Signature), OS);
writeVar(Strings.index(Sym.CompletionSnippetSuffix), OS);
@ -274,7 +275,7 @@ Expected<Symbol> readSymbol(StringRef &Data, const StringTableIn &Strings) {
}
}
Sym.References = consumeVar(Data);
Sym.IsIndexedForCodeCompletion = consume8(Data);
Sym.Flags = static_cast<Symbol::SymbolFlag>(consume8(Data));
Sym.Origin = static_cast<SymbolOrigin>(consume8(Data));
READ_STRING(Sym.Signature);
READ_STRING(Sym.CompletionSnippetSuffix);
@ -305,7 +306,7 @@ Expected<Symbol> readSymbol(StringRef &Data, const StringTableIn &Strings) {
// The current versioning scheme is simple - non-current versions are rejected.
// If you make a breaking change, bump this version number to invalidate stored
// data. Later we may want to support some backward compatibility.
constexpr static uint32_t Version = 2;
constexpr static uint32_t Version = 3;
Expected<IndexFileIn> readIndexFile(StringRef Data) {
auto RIFF = riff::readFile(Data);

View File

@ -396,7 +396,7 @@ bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name,
Symbol S;
S.ID = std::move(*ID);
S.Name = Name->getName();
S.IsIndexedForCodeCompletion = true;
S.Flags |= Symbol::IndexedForCodeCompletion;
S.SymInfo = index::getSymbolInfoForMacro(*MI);
std::string FileURI;
if (auto DeclLoc = getTokenLocation(MI->getDefinitionLoc(), SM, Opts,
@ -491,7 +491,8 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
// FIXME: this returns foo:bar: for objective-C methods, we prefer only foo:
// for consistency with CodeCompletionString and a clean name/signature split.
S.IsIndexedForCodeCompletion = isIndexedForCodeCompletion(ND, Ctx);
if (isIndexedForCodeCompletion(ND, Ctx))
S.Flags |= Symbol::IndexedForCodeCompletion;
S.SymInfo = index::getSymbolInfo(&ND);
std::string FileURI;
if (auto DeclLoc = getTokenLocation(findNameLoc(&ND), SM, Opts,
@ -531,6 +532,8 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
S.IncludeHeaders.emplace_back(Include, 1);
S.Origin = Opts.Origin;
if (ND.getAvailability() == AR_Deprecated)
S.Flags |= Symbol::Deprecated;
Symbols.insert(S);
return Symbols.find(S.ID);
}

View File

@ -17,6 +17,7 @@
#include "llvm/Support/Errc.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include <cstdint>
LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(clang::clangd::Symbol)
LLVM_YAML_IS_SEQUENCE_VECTOR(clang::clangd::Symbol::IncludeHeaderWithReferences)
@ -48,6 +49,19 @@ struct NormalizedSymbolID {
std::string HexString;
};
struct NormalizedSymbolFlag {
NormalizedSymbolFlag(IO &) {}
NormalizedSymbolFlag(IO &, Symbol::SymbolFlag F) {
Flag = static_cast<uint8_t>(F);
}
Symbol::SymbolFlag denormalize(IO &) {
return static_cast<Symbol::SymbolFlag>(Flag);
}
uint8_t Flag = 0;
};
template <> struct MappingTraits<SymbolLocation::Position> {
static void mapping(IO &IO, SymbolLocation::Position &Value) {
IO.mapRequired("Line", Value.Line);
@ -83,6 +97,8 @@ struct MappingTraits<clang::clangd::Symbol::IncludeHeaderWithReferences> {
template <> struct MappingTraits<Symbol> {
static void mapping(IO &IO, Symbol &Sym) {
MappingNormalization<NormalizedSymbolID, SymbolID> NSymbolID(IO, Sym.ID);
MappingNormalization<NormalizedSymbolFlag, Symbol::SymbolFlag> NSymbolFlag(
IO, Sym.Flags);
IO.mapRequired("ID", NSymbolID->HexString);
IO.mapRequired("Name", Sym.Name);
IO.mapRequired("Scope", Sym.Scope);
@ -91,8 +107,7 @@ template <> struct MappingTraits<Symbol> {
SymbolLocation());
IO.mapOptional("Definition", Sym.Definition, SymbolLocation());
IO.mapOptional("References", Sym.References, 0u);
IO.mapOptional("IsIndexedForCodeCompletion", Sym.IsIndexedForCodeCompletion,
false);
IO.mapOptional("Flags", NSymbolFlag->Flag);
IO.mapOptional("Signature", Sym.Signature);
IO.mapOptional("CompletionSnippetSuffix", Sym.CompletionSnippetSuffix);
IO.mapOptional("Documentation", Sym.Documentation);

View File

@ -77,6 +77,7 @@ Matcher<const std::vector<CodeCompletion> &> Has(std::string Name,
return Contains(AllOf(Named(std::move(Name)), Kind(K)));
}
MATCHER(IsDocumented, "") { return !arg.Documentation.empty(); }
MATCHER(Deprecated, "") { return arg.Deprecated; }
std::unique_ptr<SymbolIndex> memIndex(std::vector<Symbol> Symbols) {
SymbolSlab::Builder Slab;
@ -161,7 +162,7 @@ Symbol sym(StringRef QName, index::SymbolKind Kind, StringRef USRFormat) {
USR += Regex("^.*$").sub(USRFormat, Sym.Name); // e.g. func -> @F@func#
Sym.ID = SymbolID(USR);
Sym.SymInfo.Kind = Kind;
Sym.IsIndexedForCodeCompletion = true;
Sym.Flags |= Symbol::IndexedForCodeCompletion;
Sym.Origin = SymbolOrigin::Static;
return Sym;
}
@ -720,9 +721,11 @@ TEST(CompletionTest, Documentation) {
TEST(CompletionTest, GlobalCompletionFiltering) {
Symbol Class = cls("XYZ");
Class.IsIndexedForCodeCompletion = false;
Class.Flags = static_cast<Symbol::SymbolFlag>(
Class.Flags & ~(Symbol::IndexedForCodeCompletion));
Symbol Func = func("XYZ::foooo");
Func.IsIndexedForCodeCompletion = false;
Func.Flags = static_cast<Symbol::SymbolFlag>(
Func.Flags & ~(Symbol::IndexedForCodeCompletion));
auto Results = completions(R"(// void f() {
XYZ::foooo^
@ -1374,6 +1377,7 @@ TEST(CompletionTest, Render) {
EXPECT_EQ(R.documentation, "This is x().");
EXPECT_THAT(R.additionalTextEdits, IsEmpty());
EXPECT_EQ(R.sortText, sortText(1.0, "x"));
EXPECT_FALSE(R.deprecated);
Opts.EnableSnippets = true;
R = C.render(Opts);
@ -1392,6 +1396,10 @@ TEST(CompletionTest, Render) {
C.BundleSize = 2;
R = C.render(Opts);
EXPECT_EQ(R.detail, "[2 overloads]\n\"foo.h\"");
C.Deprecated = true;
R = C.render(Opts);
EXPECT_TRUE(R.deprecated);
}
TEST(CompletionTest, IgnoreRecoveryResults) {
@ -1891,12 +1899,24 @@ TEST(CompletionTest, MergeMacrosFromIndexAndSema) {
Sym.Name = "Clangd_Macro_Test";
Sym.ID = SymbolID("c:foo.cpp@8@macro@Clangd_Macro_Test");
Sym.SymInfo.Kind = index::SymbolKind::Macro;
Sym.IsIndexedForCodeCompletion = true;
Sym.Flags |= Symbol::IndexedForCodeCompletion;
EXPECT_THAT(completions("#define Clangd_Macro_Test\nClangd_Macro_T^", {Sym})
.Completions,
UnorderedElementsAre(Named("Clangd_Macro_Test")));
}
TEST(CompletionTest, DeprecatedResults) {
std::string Body = R"cpp(
void TestClangd();
void TestClangc() __attribute__((deprecated("", "")));
)cpp";
EXPECT_THAT(
completions(Body + "int main() { TestClang^ }").Completions,
UnorderedElementsAre(AllOf(Named("TestClangd"), Not(Deprecated())),
AllOf(Named("TestClangc"), Deprecated())));
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@ -59,7 +59,7 @@ TEST(QualityTests, SymbolQualitySignalExtraction) {
F.References = 24; // TestTU doesn't count references, so fake it.
Quality = {};
Quality.merge(F);
EXPECT_FALSE(Quality.Deprecated); // FIXME: Include deprecated bit in index.
EXPECT_TRUE(Quality.Deprecated);
EXPECT_FALSE(Quality.ReservedName);
EXPECT_EQ(Quality.References, 24u);
EXPECT_EQ(Quality.Category, SymbolQualitySignals::Function);

View File

@ -35,7 +35,7 @@ CanonicalDeclaration:
End:
Line: 1
Column: 1
IsIndexedForCodeCompletion: true
Flags: 1
Documentation: 'Foo doc'
ReturnType: 'int'
IncludeHeaders:
@ -62,7 +62,7 @@ CanonicalDeclaration:
End:
Line: 1
Column: 1
IsIndexedForCodeCompletion: false
Flags: 2
Signature: '-sig'
CompletionSnippetSuffix: '-snippet'
...
@ -82,7 +82,8 @@ TEST(SerializationTest, YAMLConversions) {
EXPECT_EQ(Sym1.Documentation, "Foo doc");
EXPECT_EQ(Sym1.ReturnType, "int");
EXPECT_EQ(Sym1.CanonicalDeclaration.FileURI, "file:///path/foo.h");
EXPECT_TRUE(Sym1.IsIndexedForCodeCompletion);
EXPECT_TRUE(Sym1.Flags & Symbol::IndexedForCodeCompletion);
EXPECT_FALSE(Sym1.Flags & Symbol::Deprecated);
EXPECT_THAT(Sym1.IncludeHeaders,
UnorderedElementsAre(IncludeHeaderWithRef("include1", 7u),
IncludeHeaderWithRef("include2", 3u)));
@ -94,7 +95,8 @@ TEST(SerializationTest, YAMLConversions) {
EXPECT_EQ(Sym2.Signature, "-sig");
EXPECT_EQ(Sym2.ReturnType, "");
EXPECT_EQ(Sym2.CanonicalDeclaration.FileURI, "file:///path/bar.h");
EXPECT_FALSE(Sym2.IsIndexedForCodeCompletion);
EXPECT_FALSE(Sym2.Flags & Symbol::IndexedForCodeCompletion);
EXPECT_TRUE(Sym2.Flags & Symbol::Deprecated);
std::string ConcatenatedYAML;
{

View File

@ -80,8 +80,10 @@ MATCHER_P(DefRange, Pos, "") {
}
MATCHER_P(RefCount, R, "") { return int(arg.References) == R; }
MATCHER_P(ForCodeCompletion, IsIndexedForCodeCompletion, "") {
return arg.IsIndexedForCodeCompletion == IsIndexedForCodeCompletion;
return static_cast<bool>(arg.Flags & Symbol::IndexedForCodeCompletion) ==
IsIndexedForCodeCompletion;
}
MATCHER(Deprecated, "") { return arg.Flags & Symbol::Deprecated; }
MATCHER(RefRange, "") {
const Ref &Pos = testing::get<0>(arg);
const Range &Range = testing::get<1>(arg);
@ -1014,6 +1016,17 @@ TEST_F(SymbolCollectorTest, CollectMacros) {
DeclRange(Header.range("used")))));
}
TEST_F(SymbolCollectorTest, DeprecatedSymbols) {
const std::string Header = R"(
void TestClangc() __attribute__((deprecated("", "")));
void TestClangd();
)";
runSymbolCollector(Header, /**/ "");
EXPECT_THAT(Symbols, UnorderedElementsAre(
AllOf(QName("TestClangc"), Deprecated()),
AllOf(QName("TestClangd"), Not(Deprecated()))));
}
} // namespace
} // namespace clangd
} // namespace clang