
This is a major change on how we represent nested name qualifications in the AST. * The nested name specifier itself and how it's stored is changed. The prefixes for types are handled within the type hierarchy, which makes canonicalization for them super cheap, no memory allocation required. Also translating a type into nested name specifier form becomes a no-op. An identifier is stored as a DependentNameType. The nested name specifier gains a lightweight handle class, to be used instead of passing around pointers, which is similar to what is implemented for TemplateName. There is still one free bit available, and this handle can be used within a PointerUnion and PointerIntPair, which should keep bit-packing aficionados happy. * The ElaboratedType node is removed, all type nodes in which it could previously apply to can now store the elaborated keyword and name qualifier, tail allocating when present. * TagTypes can now point to the exact declaration found when producing these, as opposed to the previous situation of there only existing one TagType per entity. This increases the amount of type sugar retained, and can have several applications, for example in tracking module ownership, and other tools which care about source file origins, such as IWYU. These TagTypes are lazily allocated, in order to limit the increase in AST size. This patch offers a great performance benefit. It greatly improves compilation time for [stdexec](https://github.com/NVIDIA/stdexec). For one datapoint, for `test_on2.cpp` in that project, which is the slowest compiling test, this patch improves `-c` compilation time by about 7.2%, with the `-fsyntax-only` improvement being at ~12%. This has great results on compile-time-tracker as well:  This patch also further enables other optimziations in the future, and will reduce the performance impact of template specialization resugaring when that lands. It has some other miscelaneous drive-by fixes. About the review: Yes the patch is huge, sorry about that. Part of the reason is that I started by the nested name specifier part, before the ElaboratedType part, but that had a huge performance downside, as ElaboratedType is a big performance hog. I didn't have the steam to go back and change the patch after the fact. There is also a lot of internal API changes, and it made sense to remove ElaboratedType in one go, versus removing it from one type at a time, as that would present much more churn to the users. Also, the nested name specifier having a different API avoids missing changes related to how prefixes work now, which could make existing code compile but not work. How to review: The important changes are all in `clang/include/clang/AST` and `clang/lib/AST`, with also important changes in `clang/lib/Sema/TreeTransform.h`. The rest and bulk of the changes are mostly consequences of the changes in API. PS: TagType::getDecl is renamed to `getOriginalDecl` in this patch, just for easier to rebasing. I plan to rename it back after this lands. Fixes #136624 Fixes https://github.com/llvm/llvm-project/issues/43179 Fixes https://github.com/llvm/llvm-project/issues/68670 Fixes https://github.com/llvm/llvm-project/issues/92757
4609 lines
136 KiB
C++
4609 lines
136 KiB
C++
//===-- HoverTests.cpp ----------------------------------------------------===//
|
|
//
|
|
// 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 "AST.h"
|
|
#include "Annotations.h"
|
|
#include "Config.h"
|
|
#include "Hover.h"
|
|
#include "Protocol.h"
|
|
#include "TestFS.h"
|
|
#include "TestIndex.h"
|
|
#include "TestTU.h"
|
|
#include "index/MemIndex.h"
|
|
#include "clang/AST/Attr.h"
|
|
#include "clang/Format/Format.h"
|
|
#include "clang/Index/IndexSymbol.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/Twine.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
using PassMode = HoverInfo::PassType::PassMode;
|
|
|
|
std::string guard(llvm::StringRef Code) {
|
|
return "#pragma once\n" + Code.str();
|
|
}
|
|
|
|
TEST(Hover, Structured) {
|
|
struct {
|
|
const char *const Code;
|
|
const std::function<void(HoverInfo &)> ExpectedBuilder;
|
|
} Cases[] = {
|
|
// Global scope.
|
|
{R"cpp(
|
|
// Best foo ever.
|
|
void [[fo^o]]() {}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Documentation = "Best foo ever.";
|
|
HI.Definition = "void foo()";
|
|
HI.ReturnType = "void";
|
|
HI.Type = "void ()";
|
|
HI.Parameters.emplace();
|
|
}},
|
|
// Inside namespace
|
|
{R"cpp(
|
|
namespace ns1 { namespace ns2 {
|
|
/// Best foo ever.
|
|
void [[fo^o]]() {}
|
|
}}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "ns1::ns2::";
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Documentation = "Best foo ever.";
|
|
HI.Definition = "void foo()";
|
|
HI.ReturnType = "void";
|
|
HI.Type = "void ()";
|
|
HI.Parameters.emplace();
|
|
}},
|
|
// Field
|
|
{R"cpp(
|
|
namespace ns1 { namespace ns2 {
|
|
class Foo {
|
|
char [[b^ar]];
|
|
double y[2];
|
|
};
|
|
}}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "ns1::ns2::";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Name = "bar";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.Definition = "char bar";
|
|
HI.Type = "char";
|
|
HI.Offset = 0;
|
|
HI.Size = 8;
|
|
HI.Padding = 56;
|
|
HI.Align = 8;
|
|
HI.AccessSpecifier = "private";
|
|
}},
|
|
// Union field
|
|
{R"cpp(
|
|
union Foo {
|
|
char [[b^ar]];
|
|
double y[2];
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Name = "bar";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.Definition = "char bar";
|
|
HI.Type = "char";
|
|
HI.Size = 8;
|
|
HI.Padding = 120;
|
|
HI.Align = 8;
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
// Bitfield
|
|
{R"cpp(
|
|
struct Foo {
|
|
int [[^x]] : 1;
|
|
int y : 1;
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.Definition = "int x : 1";
|
|
HI.Type = "int";
|
|
HI.Offset = 0;
|
|
HI.Size = 1;
|
|
HI.Align = 32;
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
// Local to class method.
|
|
{R"cpp(
|
|
namespace ns1 { namespace ns2 {
|
|
struct Foo {
|
|
void foo() {
|
|
int [[b^ar]];
|
|
}
|
|
};
|
|
}}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "ns1::ns2::";
|
|
HI.LocalScope = "Foo::foo::";
|
|
HI.Name = "bar";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Definition = "int bar";
|
|
HI.Type = "int";
|
|
}},
|
|
// Predefined variable
|
|
{R"cpp(
|
|
void foo() {
|
|
[[__f^unc__]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "__func__";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Documentation =
|
|
"Name of the current function (predefined variable)";
|
|
HI.Value = "\"foo\"";
|
|
HI.Type = "const char[4]";
|
|
}},
|
|
// Predefined variable (dependent)
|
|
{R"cpp(
|
|
template<int> void foo() {
|
|
[[__f^unc__]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "__func__";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Documentation =
|
|
"Name of the current function (predefined variable)";
|
|
HI.Type = "const char[]";
|
|
}},
|
|
// Anon namespace and local scope.
|
|
{R"cpp(
|
|
namespace ns1 { namespace {
|
|
struct {
|
|
char [[b^ar]];
|
|
} T;
|
|
}}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "ns1::";
|
|
HI.LocalScope = "(anonymous struct)::";
|
|
HI.Name = "bar";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.Definition = "char bar";
|
|
HI.Type = "char";
|
|
HI.Offset = 0;
|
|
HI.Size = 8;
|
|
HI.Align = 8;
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
// Struct definition shows size.
|
|
{R"cpp(
|
|
struct [[^X]]{};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "X";
|
|
HI.Kind = index::SymbolKind::Struct;
|
|
HI.Definition = "struct X {}";
|
|
HI.Size = 8;
|
|
HI.Align = 8;
|
|
}},
|
|
// Variable with template type
|
|
{R"cpp(
|
|
template <typename T, class... Ts> class Foo { public: Foo(int); };
|
|
Foo<int, char, bool> [[fo^o]] = Foo<int, char, bool>(5);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Definition = "Foo<int, char, bool> foo = Foo<int, char, bool>(5)";
|
|
HI.Type = "Foo<int, char, bool>";
|
|
}},
|
|
// Implicit template instantiation
|
|
{R"cpp(
|
|
template <typename T> class vector{};
|
|
[[vec^tor]]<int> foo;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "vector<int>";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.Definition = "template <> class vector<int> {}";
|
|
}},
|
|
// Class template
|
|
{R"cpp(
|
|
template <template<typename, bool...> class C,
|
|
typename = char,
|
|
int = 0,
|
|
bool Q = false,
|
|
class... Ts> class Foo final {};
|
|
template <template<typename, bool...> class T>
|
|
[[F^oo]]<T> foo;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "Foo";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.Definition =
|
|
R"cpp(template <template <typename, bool...> class C, typename = char, int = 0,
|
|
bool Q = false, class... Ts>
|
|
class Foo final {})cpp";
|
|
HI.TemplateParameters = {
|
|
{{"template <typename, bool...> class"},
|
|
std::string("C"),
|
|
std::nullopt},
|
|
{{"typename"}, std::nullopt, std::string("char")},
|
|
{{"int"}, std::nullopt, std::string("0")},
|
|
{{"bool"}, std::string("Q"), std::string("false")},
|
|
{{"class..."}, std::string("Ts"), std::nullopt},
|
|
};
|
|
}},
|
|
// Function template
|
|
{R"cpp(
|
|
template <template<typename, bool...> class C,
|
|
typename = char,
|
|
int = 0,
|
|
bool Q = false,
|
|
class... Ts> void foo();
|
|
template<typename, bool...> class Foo;
|
|
|
|
void bar() {
|
|
[[fo^o]]<Foo>();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Definition = "template <> void foo<Foo, char, 0, false, <>>()";
|
|
HI.ReturnType = "void";
|
|
HI.Type = "void ()";
|
|
HI.Parameters.emplace();
|
|
}},
|
|
// Function decl
|
|
{R"cpp(
|
|
template<typename, bool...> class Foo {};
|
|
Foo<bool, true, false> foo(int, bool T = false);
|
|
|
|
void bar() {
|
|
[[fo^o]](3);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Definition = "Foo<bool, true, false> foo(int, bool T = false)";
|
|
HI.ReturnType = "Foo<bool, true, false>";
|
|
HI.Type = "Foo<bool, true, false> (int, bool)";
|
|
HI.Parameters = {
|
|
{{"int"}, std::nullopt, std::nullopt},
|
|
{{"bool"}, std::string("T"), std::string("false")},
|
|
};
|
|
}},
|
|
// Pointers to lambdas
|
|
{R"cpp(
|
|
void foo() {
|
|
auto lamb = [](int T, bool B) -> bool { return T && B; };
|
|
auto *b = &lamb;
|
|
auto *[[^c]] = &b;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Name = "c";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Definition = "auto *c = &b";
|
|
HI.Type = "(lambda) **";
|
|
HI.ReturnType = "bool";
|
|
HI.Parameters = {
|
|
{{"int"}, std::string("T"), std::nullopt},
|
|
{{"bool"}, std::string("B"), std::nullopt},
|
|
};
|
|
return HI;
|
|
}},
|
|
// Lambda parameter with decltype reference
|
|
{R"cpp(
|
|
auto lamb = [](int T, bool B) -> bool { return T && B; };
|
|
void foo(decltype(lamb)& bar) {
|
|
[[ba^r]](0, false);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Name = "bar";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.Definition = "decltype(lamb) &bar";
|
|
HI.Type = {"decltype(lamb) &", "(lambda) &"};
|
|
HI.ReturnType = "bool";
|
|
HI.Parameters = {
|
|
{{"int"}, std::string("T"), std::nullopt},
|
|
{{"bool"}, std::string("B"), std::nullopt},
|
|
};
|
|
return HI;
|
|
}},
|
|
// Lambda parameter with decltype
|
|
{R"cpp(
|
|
auto lamb = [](int T, bool B) -> bool { return T && B; };
|
|
void foo(decltype(lamb) bar) {
|
|
[[ba^r]](0, false);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Name = "bar";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.Definition = "decltype(lamb) bar";
|
|
HI.Type = "class (lambda)";
|
|
HI.ReturnType = "bool";
|
|
HI.Parameters = {
|
|
{{"int"}, std::string("T"), std::nullopt},
|
|
{{"bool"}, std::string("B"), std::nullopt},
|
|
};
|
|
HI.Value = "false";
|
|
return HI;
|
|
}},
|
|
// Lambda variable
|
|
{R"cpp(
|
|
void foo() {
|
|
int bar = 5;
|
|
auto lamb = [&bar](int T, bool B) -> bool { return T && B && bar; };
|
|
bool res = [[lam^b]](bar, false);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Name = "lamb";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Definition = "auto lamb = [&bar](int T, bool B) -> bool {}";
|
|
HI.Type = "class (lambda)";
|
|
HI.ReturnType = "bool";
|
|
HI.Parameters = {
|
|
{{"int"}, std::string("T"), std::nullopt},
|
|
{{"bool"}, std::string("B"), std::nullopt},
|
|
};
|
|
return HI;
|
|
}},
|
|
// Local variable in lambda
|
|
{R"cpp(
|
|
void foo() {
|
|
auto lamb = []{int [[te^st]];};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::(anonymous class)::operator()::";
|
|
HI.Name = "test";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Definition = "int test";
|
|
HI.Type = "int";
|
|
}},
|
|
// Partially-specialized class template. (formerly type-parameter-0-0)
|
|
{R"cpp(
|
|
template <typename T> class X;
|
|
template <typename T> class [[^X]]<T*> {};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "X<T *>";
|
|
HI.NamespaceScope = "";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.Definition = "template <typename T> class X<T *> {}";
|
|
}},
|
|
// Constructor of partially-specialized class template
|
|
{R"cpp(
|
|
template<typename, typename=void> struct X;
|
|
template<typename T> struct X<T*>{ [[^X]](); };
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "X";
|
|
HI.LocalScope = "X<T *>::"; // FIXME: X<T *, void>::
|
|
HI.Kind = index::SymbolKind::Constructor;
|
|
HI.Definition = "X()";
|
|
HI.Parameters.emplace();
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{"class X { [[^~]]X(); };", // FIXME: Should be [[~X]]()
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "~X";
|
|
HI.LocalScope = "X::";
|
|
HI.Kind = index::SymbolKind::Destructor;
|
|
HI.Definition = "~X()";
|
|
HI.Parameters.emplace();
|
|
HI.AccessSpecifier = "private";
|
|
}},
|
|
{"class X { [[op^erator]] int(); };",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "operator int";
|
|
HI.LocalScope = "X::";
|
|
HI.Kind = index::SymbolKind::ConversionFunction;
|
|
HI.Definition = "operator int()";
|
|
HI.Parameters.emplace();
|
|
HI.AccessSpecifier = "private";
|
|
}},
|
|
{"class X { operator [[^X]](); };",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "X";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.Definition = "class X {}";
|
|
}},
|
|
|
|
// auto on structured bindings
|
|
{R"cpp(
|
|
void foo() {
|
|
struct S { int x; float y; };
|
|
[[au^to]] [x, y] = S();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "S";
|
|
}},
|
|
// undeduced auto
|
|
{R"cpp(
|
|
template<typename T>
|
|
void foo() {
|
|
[[au^to]] x = T{};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "/* not deduced */";
|
|
}},
|
|
// constrained auto
|
|
{R"cpp(
|
|
template <class T> concept F = true;
|
|
F [[au^to]] x = 1;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{R"cpp(
|
|
template <class T> concept F = true;
|
|
[[^F]] auto x = 1;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "F";
|
|
HI.Kind = index::SymbolKind::Concept;
|
|
HI.Definition = "template <class T>\nconcept F = true";
|
|
}},
|
|
// auto on lambda
|
|
{R"cpp(
|
|
void foo() {
|
|
[[au^to]] lamb = []{};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "class(lambda)";
|
|
}},
|
|
// auto on template instantiation
|
|
{R"cpp(
|
|
template<typename T> class Foo{};
|
|
void foo() {
|
|
[[au^to]] x = Foo<int>();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Foo<int>";
|
|
}},
|
|
// auto on specialized template
|
|
{R"cpp(
|
|
template<typename T> class Foo{};
|
|
template<> class Foo<int>{};
|
|
void foo() {
|
|
[[au^to]] x = Foo<int>();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Foo<int>";
|
|
}},
|
|
// constrained template parameter
|
|
{R"cpp(
|
|
template<class T> concept Fooable = true;
|
|
template<[[Foo^able]] T>
|
|
void bar(T t) {}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "Fooable";
|
|
HI.Kind = index::SymbolKind::Concept;
|
|
HI.Definition = "template <class T>\nconcept Fooable = true";
|
|
}},
|
|
{R"cpp(
|
|
template<class T> concept Fooable = true;
|
|
template<Fooable [[T^T]]>
|
|
void bar(TT t) {}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "TT";
|
|
HI.Type = "class";
|
|
HI.AccessSpecifier = "public";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "bar::";
|
|
HI.Kind = index::SymbolKind::TemplateTypeParm;
|
|
HI.Definition = "Fooable TT";
|
|
}},
|
|
{R"cpp(
|
|
template<class T> concept Fooable = true;
|
|
void bar([[Foo^able]] auto t) {}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "Fooable";
|
|
HI.Kind = index::SymbolKind::Concept;
|
|
HI.Definition = "template <class T>\nconcept Fooable = true";
|
|
}},
|
|
// concept reference
|
|
{R"cpp(
|
|
template<class T> concept Fooable = true;
|
|
auto X = [[Fooa^ble]]<int>;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "Fooable";
|
|
HI.Kind = index::SymbolKind::Concept;
|
|
HI.Definition = "template <class T>\nconcept Fooable = true";
|
|
HI.Value = "true";
|
|
}},
|
|
|
|
// empty macro
|
|
{R"cpp(
|
|
#define MACRO
|
|
[[MAC^RO]]
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MACRO";
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Definition = "#define MACRO";
|
|
}},
|
|
|
|
// object-like macro
|
|
{R"cpp(
|
|
#define MACRO 41
|
|
int x = [[MAC^RO]];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MACRO";
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Value = "41 (0x29)";
|
|
HI.Type = "int";
|
|
HI.Definition = "#define MACRO 41\n\n"
|
|
"// Expands to\n"
|
|
"41";
|
|
}},
|
|
|
|
// function-like macro
|
|
{R"cpp(
|
|
// Best MACRO ever.
|
|
#define MACRO(x,y,z) void foo(x, y, z)
|
|
[[MAC^RO]](int, double d, bool z = false);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MACRO";
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Definition = "#define MACRO(x, y, z) void foo(x, y, z)\n\n"
|
|
"// Expands to\n"
|
|
"void foo(int, double d, bool z = false)";
|
|
}},
|
|
|
|
// nested macro
|
|
{R"cpp(
|
|
#define STRINGIFY_AUX(s) #s
|
|
#define STRINGIFY(s) STRINGIFY_AUX(s)
|
|
#define DECL_STR(NAME, VALUE) const char *v_##NAME = STRINGIFY(VALUE)
|
|
#define FOO 41
|
|
|
|
[[DECL^_STR]](foo, FOO);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "DECL_STR";
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Type = HoverInfo::PrintedType("const char *");
|
|
HI.Definition = "#define DECL_STR(NAME, VALUE) const char *v_##NAME = "
|
|
"STRINGIFY(VALUE)\n\n"
|
|
"// Expands to\n"
|
|
"const char *v_foo = \"41\"";
|
|
}},
|
|
|
|
// constexprs
|
|
{R"cpp(
|
|
constexpr int add(int a, int b) { return a + b; }
|
|
int [[b^ar]] = add(1, 2);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "bar";
|
|
HI.Definition = "int bar = add(1, 2)";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = "int";
|
|
HI.NamespaceScope = "";
|
|
HI.Value = "3";
|
|
}},
|
|
{R"cpp(
|
|
int [[b^ar]] = sizeof(char);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "bar";
|
|
HI.Definition = "int bar = sizeof(char)";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = "int";
|
|
HI.NamespaceScope = "";
|
|
HI.Value = "1";
|
|
}},
|
|
{R"cpp(
|
|
template<int a, int b> struct Add {
|
|
static constexpr int result = a + b;
|
|
};
|
|
int [[ba^r]] = Add<1, 2>::result;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "bar";
|
|
HI.Definition = "int bar = Add<1, 2>::result";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = "int";
|
|
HI.NamespaceScope = "";
|
|
HI.Value = "3";
|
|
}},
|
|
{R"cpp(
|
|
enum Color { RED = -123, GREEN = 5, };
|
|
Color x = [[GR^EEN]];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "GREEN";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Color::";
|
|
HI.Definition = "GREEN = 5";
|
|
HI.Kind = index::SymbolKind::EnumConstant;
|
|
HI.Type = "enum Color";
|
|
HI.Value = "5"; // Numeric on the enumerator name, no hex as small.
|
|
}},
|
|
{R"cpp(
|
|
enum Color { RED = -123, GREEN = 5, };
|
|
Color x = RED;
|
|
Color y = [[^x]];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "Color x = RED";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = "Color";
|
|
HI.Value = "RED (0xffffff85)"; // Symbolic on an expression.
|
|
}},
|
|
{R"cpp(
|
|
template<int a, int b> struct Add {
|
|
static constexpr int result = a + b;
|
|
};
|
|
int bar = Add<1, 2>::[[resu^lt]];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "result";
|
|
HI.Definition = "static constexpr int result = a + b";
|
|
HI.Kind = index::SymbolKind::StaticProperty;
|
|
HI.Type = "const int";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Add<1, 2>::";
|
|
HI.Value = "3";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{R"cpp(
|
|
using my_int = int;
|
|
constexpr my_int answer() { return 40 + 2; }
|
|
int x = [[ans^wer]]();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "answer";
|
|
HI.Definition = "constexpr my_int answer()";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Type = {"my_int ()", "int ()"};
|
|
HI.ReturnType = {"my_int", "int"};
|
|
HI.Parameters.emplace();
|
|
HI.NamespaceScope = "";
|
|
HI.Value = "42 (0x2a)";
|
|
}},
|
|
{R"cpp(
|
|
const char *[[ba^r]] = "1234";
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "bar";
|
|
HI.Definition = "const char *bar = \"1234\"";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = "const char *";
|
|
HI.NamespaceScope = "";
|
|
HI.Value = "&\"1234\"[0]";
|
|
}},
|
|
{R"cpp(// Should not crash
|
|
template <typename T>
|
|
struct Tmpl {
|
|
Tmpl(int name);
|
|
};
|
|
|
|
template <typename A>
|
|
void boom(int name) {
|
|
new Tmpl<A>([[na^me]]);
|
|
})cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "name";
|
|
HI.Definition = "int name";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.Type = "int";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "boom::";
|
|
}},
|
|
{
|
|
R"cpp(// Should not print inline or anon namespaces.
|
|
namespace ns {
|
|
inline namespace in_ns {
|
|
namespace a {
|
|
namespace {
|
|
namespace b {
|
|
inline namespace in_ns2 {
|
|
class Foo {};
|
|
} // in_ns2
|
|
} // b
|
|
} // anon
|
|
} // a
|
|
} // in_ns
|
|
} // ns
|
|
void foo() {
|
|
ns::a::b::[[F^oo]] x;
|
|
(void)x;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "Foo";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.NamespaceScope = "ns::a::b::";
|
|
HI.Definition = "class Foo {}";
|
|
}},
|
|
{
|
|
R"cpp(
|
|
template <typename T> class Foo {};
|
|
class X;
|
|
void foo() {
|
|
[[^auto]] x = Foo<X>();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Foo<X>";
|
|
}},
|
|
{// Falls back to primary template, when the type is not instantiated.
|
|
R"cpp(
|
|
// comment from primary
|
|
template <typename T> class Foo {};
|
|
// comment from specialization
|
|
template <typename T> class Foo<T*> {};
|
|
void foo() {
|
|
[[Fo^o]]<int*> *x = nullptr;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "Foo<int *>";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "template <> class Foo<int *>";
|
|
// FIXME: Maybe force instantiation to make use of real template
|
|
// pattern.
|
|
HI.Documentation = "comment from primary";
|
|
}},
|
|
{// Template Type Parameter
|
|
R"cpp(
|
|
template <typename [[^T]] = int> void foo();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "T";
|
|
HI.Kind = index::SymbolKind::TemplateTypeParm;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "typename T = int";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "typename";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// TemplateTemplate Type Parameter
|
|
R"cpp(
|
|
template <template<typename> class [[^T]]> void foo();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "T";
|
|
HI.Kind = index::SymbolKind::TemplateTemplateParm;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "template <typename> class T";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "template <typename> class";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// NonType Template Parameter
|
|
R"cpp(
|
|
template <int [[^T]] = 5> void foo();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "T";
|
|
HI.Kind = index::SymbolKind::NonTypeTemplateParm;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int T = 5";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "int";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
|
|
{// Getter
|
|
R"cpp(
|
|
struct X { int Y; float [[^y]]() { return Y; } };
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "y";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "float y()";
|
|
HI.LocalScope = "X::";
|
|
HI.Documentation = "Trivial accessor for `Y`.";
|
|
HI.Type = "float ()";
|
|
HI.ReturnType = "float";
|
|
HI.Parameters.emplace();
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// Setter
|
|
R"cpp(
|
|
struct X { int Y; void [[^setY]](float v) { Y = v; } };
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "setY";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "void setY(float v)";
|
|
HI.LocalScope = "X::";
|
|
HI.Documentation = "Trivial setter for `Y`.";
|
|
HI.Type = "void (float)";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters.emplace();
|
|
HI.Parameters->emplace_back();
|
|
HI.Parameters->back().Type = "float";
|
|
HI.Parameters->back().Name = "v";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// Setter (builder)
|
|
R"cpp(
|
|
struct X { int Y; X& [[^setY]](float v) { Y = v; return *this; } };
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "setY";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "X &setY(float v)";
|
|
HI.LocalScope = "X::";
|
|
HI.Documentation = "Trivial setter for `Y`.";
|
|
HI.Type = "X &(float)";
|
|
HI.ReturnType = "X &";
|
|
HI.Parameters.emplace();
|
|
HI.Parameters->emplace_back();
|
|
HI.Parameters->back().Type = "float";
|
|
HI.Parameters->back().Name = "v";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// Setter (move)
|
|
R"cpp(
|
|
namespace std { template<typename T> T&& move(T&& t); }
|
|
struct X { int Y; void [[^setY]](float v) { Y = std::move(v); } };
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "setY";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "void setY(float v)";
|
|
HI.LocalScope = "X::";
|
|
HI.Documentation = "Trivial setter for `Y`.";
|
|
HI.Type = "void (float)";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters.emplace();
|
|
HI.Parameters->emplace_back();
|
|
HI.Parameters->back().Type = "float";
|
|
HI.Parameters->back().Name = "v";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// Field type initializer.
|
|
R"cpp(
|
|
struct X { int x = 2; };
|
|
X ^[[x]];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "X x";
|
|
HI.Type = "X";
|
|
}},
|
|
{// Don't crash on null types.
|
|
R"cpp(auto [^[[x]]] = 1; /*error-ok*/)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "";
|
|
HI.Type = "NULL TYPE";
|
|
// Bindings are in theory public members of an anonymous struct.
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// Don't crash on invalid decl with invalid init expr.
|
|
R"cpp(
|
|
Unknown [[^abc]] = invalid;
|
|
// error-ok
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "abc";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int abc";
|
|
HI.Type = "int";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// Extra info for function call.
|
|
R"cpp(
|
|
void fun(int arg_a, int &arg_b) {};
|
|
void code() {
|
|
int a = 1, b = 2;
|
|
fun(a, [[^b]]);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "b";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int b = 2";
|
|
HI.LocalScope = "code::";
|
|
HI.Value = "2";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_b";
|
|
HI.CalleeArgInfo->Type = "int &";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Ref, false};
|
|
}},
|
|
{// make_unique-like function call
|
|
R"cpp(
|
|
struct Foo {
|
|
explicit Foo(int arg_a) {}
|
|
};
|
|
template<class T, class... Args>
|
|
T make(Args&&... args)
|
|
{
|
|
return T(args...);
|
|
}
|
|
|
|
void code() {
|
|
int a = 1;
|
|
auto foo = make<Foo>([[^a]]);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int a = 1";
|
|
HI.LocalScope = "code::";
|
|
HI.Value = "1";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_a";
|
|
HI.CalleeArgInfo->Type = "int";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, false};
|
|
}},
|
|
{
|
|
R"cpp(
|
|
void foobar(const float &arg);
|
|
int main() {
|
|
int a = 0;
|
|
foobar([[^a]]);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int a = 0";
|
|
HI.LocalScope = "main::";
|
|
HI.Value = "0";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg";
|
|
HI.CalleeArgInfo->Type = "const float &";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, true};
|
|
}},
|
|
{
|
|
R"cpp(
|
|
struct Foo {
|
|
explicit Foo(const float& arg) {}
|
|
};
|
|
int main() {
|
|
int a = 0;
|
|
Foo foo([[^a]]);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int a = 0";
|
|
HI.LocalScope = "main::";
|
|
HI.Value = "0";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg";
|
|
HI.CalleeArgInfo->Type = "const float &";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, true};
|
|
}},
|
|
{// Literal passed to function call
|
|
R"cpp(
|
|
void fun(int arg_a, const int &arg_b) {};
|
|
void code() {
|
|
int a = 1;
|
|
fun(a, [[^2]]);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "literal";
|
|
HI.Kind = index::SymbolKind::Unknown;
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_b";
|
|
HI.CalleeArgInfo->Type = "const int &";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::ConstRef, false};
|
|
}},
|
|
{// Expression passed to function call
|
|
R"cpp(
|
|
void fun(int arg_a, const int &arg_b) {};
|
|
void code() {
|
|
int a = 1;
|
|
fun(a, 1 [[^+]] 2);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "expression";
|
|
HI.Kind = index::SymbolKind::Unknown;
|
|
HI.Type = "int";
|
|
HI.Value = "3";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_b";
|
|
HI.CalleeArgInfo->Type = "const int &";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::ConstRef, false};
|
|
}},
|
|
{
|
|
R"cpp(
|
|
int add(int lhs, int rhs);
|
|
int main() {
|
|
add(1 [[^+]] 2, 3);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "expression";
|
|
HI.Kind = index::SymbolKind::Unknown;
|
|
HI.Type = "int";
|
|
HI.Value = "3";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "lhs";
|
|
HI.CalleeArgInfo->Type = "int";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, false};
|
|
}},
|
|
{
|
|
R"cpp(
|
|
void foobar(const float &arg);
|
|
int main() {
|
|
foobar([[^0]]);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "literal";
|
|
HI.Kind = index::SymbolKind::Unknown;
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg";
|
|
HI.CalleeArgInfo->Type = "const float &";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, true};
|
|
}},
|
|
{// Extra info for method call.
|
|
R"cpp(
|
|
class C {
|
|
public:
|
|
void fun(int arg_a = 3, int arg_b = 4) {}
|
|
};
|
|
void code() {
|
|
int a = 1, b = 2;
|
|
C c;
|
|
c.fun([[^a]], b);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int a = 1";
|
|
HI.LocalScope = "code::";
|
|
HI.Value = "1";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_a";
|
|
HI.CalleeArgInfo->Type = "int";
|
|
HI.CalleeArgInfo->Default = "3";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, false};
|
|
}},
|
|
{
|
|
R"cpp(
|
|
struct Foo {
|
|
Foo(const int &);
|
|
};
|
|
void foo(Foo);
|
|
void bar() {
|
|
const int x = 0;
|
|
foo([[^x]]);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "const int x = 0";
|
|
HI.LocalScope = "bar::";
|
|
HI.Value = "0";
|
|
HI.Type = "const int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Type = "Foo";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::ConstRef, true};
|
|
}},
|
|
{// Dont crash on invalid decl
|
|
R"cpp(
|
|
// error-ok
|
|
struct Foo {
|
|
Bar [[x^x]];
|
|
};)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "xx";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int xx";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{R"cpp(
|
|
// error-ok
|
|
struct Foo {
|
|
Bar xx;
|
|
int [[y^y]];
|
|
};)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "yy";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "int yy";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.AccessSpecifier = "public";
|
|
}},
|
|
{// No crash on InitListExpr.
|
|
R"cpp(
|
|
struct Foo {
|
|
int a[10];
|
|
};
|
|
constexpr Foo k2 = {
|
|
^[[{]]1} // FIXME: why the hover range is 1 character?
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "expression";
|
|
HI.Kind = index::SymbolKind::Unknown;
|
|
HI.Type = "int[10]";
|
|
HI.Value = "{1}";
|
|
}},
|
|
{// Var template decl
|
|
R"cpp(
|
|
using m_int = int;
|
|
|
|
template <int Size> m_int ^[[arr]][Size];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "arr";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = {"m_int[Size]", "int[Size]"};
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "template <int Size> m_int arr[Size]";
|
|
HI.TemplateParameters = {{{"int"}, {"Size"}, std::nullopt}};
|
|
}},
|
|
{// Var template decl specialization
|
|
R"cpp(
|
|
using m_int = int;
|
|
|
|
template <int Size> m_int arr[Size];
|
|
|
|
template <> m_int ^[[arr]]<4>[4];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "arr<4>";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = {"m_int[4]", "int[4]"};
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "m_int arr[4]";
|
|
}},
|
|
{// Canonical type
|
|
R"cpp(
|
|
template<typename T>
|
|
struct TestHover {
|
|
using Type = T;
|
|
};
|
|
|
|
void code() {
|
|
TestHover<int>::Type ^[[a]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "code::";
|
|
HI.Definition = "TestHover<int>::Type a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = {"TestHover<int>::Type", "int"};
|
|
}},
|
|
{// Canonical template type
|
|
R"cpp(
|
|
template<typename T>
|
|
void ^[[foo]](T arg) {}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "template <typename T> void foo(T arg)";
|
|
HI.Type = "void (T)";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters = {{{"T"}, std::string("arg"), std::nullopt}};
|
|
HI.TemplateParameters = {
|
|
{{"typename"}, std::string("T"), std::nullopt}};
|
|
}},
|
|
{// TypeAlias Template
|
|
R"cpp(
|
|
template<typename T>
|
|
using ^[[alias]] = T;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "alias";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "template <typename T> using alias = T";
|
|
HI.Type = "T";
|
|
HI.TemplateParameters = {
|
|
{{"typename"}, std::string("T"), std::nullopt}};
|
|
}},
|
|
{// TypeAlias Template
|
|
R"cpp(
|
|
template<typename T>
|
|
using A = T;
|
|
|
|
template<typename T>
|
|
using ^[[AA]] = A<T>;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "AA";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "template <typename T> using AA = A<T>";
|
|
HI.Type = {"A<T>", "T"};
|
|
HI.TemplateParameters = {
|
|
{{"typename"}, std::string("T"), std::nullopt}};
|
|
}},
|
|
{// Constant array
|
|
R"cpp(
|
|
using m_int = int;
|
|
|
|
m_int ^[[arr]][10];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "arr";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Definition = "m_int arr[10]";
|
|
HI.Type = {"m_int[10]", "int[10]"};
|
|
}},
|
|
{// Incomplete array
|
|
R"cpp(
|
|
using m_int = int;
|
|
|
|
extern m_int ^[[arr]][];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "arr";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Definition = "extern m_int arr[]";
|
|
HI.Type = {"m_int[]", "int[]"};
|
|
}},
|
|
{// Dependent size array
|
|
R"cpp(
|
|
using m_int = int;
|
|
|
|
template<int Size>
|
|
struct Test {
|
|
m_int ^[[arr]][Size];
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "arr";
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Test<Size>::";
|
|
HI.AccessSpecifier = "public";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.Definition = "m_int arr[Size]";
|
|
HI.Type = {"m_int[Size]", "int[Size]"};
|
|
}},
|
|
{// Bitfield offset, size and padding
|
|
R"cpp(
|
|
struct Foo {
|
|
char x;
|
|
char [[^y]] : 1;
|
|
int z;
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Name = "y";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.Definition = "char y : 1";
|
|
HI.Type = "char";
|
|
HI.Offset = 8;
|
|
HI.Size = 1;
|
|
HI.Padding = 23;
|
|
HI.Align = 8;
|
|
HI.AccessSpecifier = "public";
|
|
}}};
|
|
for (const auto &Case : Cases) {
|
|
SCOPED_TRACE(Case.Code);
|
|
|
|
Annotations T(Case.Code);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-std=c++20");
|
|
// Types might be different depending on the target triplet, we chose a
|
|
// fixed one to make sure tests passes on different platform.
|
|
TU.ExtraArgs.push_back("--target=x86_64-pc-linux-gnu");
|
|
auto AST = TU.build();
|
|
Config Cfg;
|
|
Cfg.Hover.ShowAKA = true;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
HoverInfo Expected;
|
|
Expected.SymRange = T.range();
|
|
Case.ExpectedBuilder(Expected);
|
|
|
|
EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
|
|
EXPECT_EQ(H->LocalScope, Expected.LocalScope);
|
|
EXPECT_EQ(H->Name, Expected.Name);
|
|
EXPECT_EQ(H->Kind, Expected.Kind);
|
|
EXPECT_EQ(H->Documentation, Expected.Documentation);
|
|
EXPECT_EQ(H->Definition, Expected.Definition);
|
|
EXPECT_EQ(H->Type, Expected.Type);
|
|
EXPECT_EQ(H->ReturnType, Expected.ReturnType);
|
|
EXPECT_EQ(H->Parameters, Expected.Parameters);
|
|
EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
|
|
EXPECT_EQ(H->SymRange, Expected.SymRange);
|
|
EXPECT_EQ(H->Value, Expected.Value);
|
|
EXPECT_EQ(H->Size, Expected.Size);
|
|
EXPECT_EQ(H->Offset, Expected.Offset);
|
|
EXPECT_EQ(H->Align, Expected.Align);
|
|
EXPECT_EQ(H->AccessSpecifier, Expected.AccessSpecifier);
|
|
EXPECT_EQ(H->CalleeArgInfo, Expected.CalleeArgInfo);
|
|
EXPECT_EQ(H->CallPassType, Expected.CallPassType);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, DefinitionLanuage) {
|
|
struct {
|
|
const char *const Code;
|
|
const std::string ClangLanguageFlag;
|
|
const char *const ExpectedDefinitionLanguage;
|
|
} Cases[] = {{R"cpp(
|
|
void [[some^Global]]() {}
|
|
)cpp",
|
|
"", "cpp"},
|
|
{R"cpp(
|
|
void [[some^Global]]() {}
|
|
)cpp",
|
|
"-xobjective-c++", "objective-cpp"},
|
|
{R"cpp(
|
|
void [[some^Global]]() {}
|
|
)cpp",
|
|
"-xobjective-c", "objective-c"}};
|
|
for (const auto &Case : Cases) {
|
|
SCOPED_TRACE(Case.Code);
|
|
|
|
Annotations T(Case.Code);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
if (!Case.ClangLanguageFlag.empty())
|
|
TU.ExtraArgs.push_back(Case.ClangLanguageFlag);
|
|
auto AST = TU.build();
|
|
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
|
|
EXPECT_STREQ(H->DefinitionLanguage, Case.ExpectedDefinitionLanguage);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, CallPassType) {
|
|
const llvm::StringRef CodePrefix = R"cpp(
|
|
class Base {};
|
|
class Derived : public Base {};
|
|
class CustomClass {
|
|
public:
|
|
CustomClass() {}
|
|
CustomClass(const Base &x) {}
|
|
CustomClass(int &x) {}
|
|
CustomClass(float x) {}
|
|
CustomClass(int x, int y) {}
|
|
};
|
|
|
|
void int_by_ref(int &x) {}
|
|
void int_by_const_ref(const int &x) {}
|
|
void int_by_value(int x) {}
|
|
void base_by_ref(Base &x) {}
|
|
void base_by_const_ref(const Base &x) {}
|
|
void base_by_value(Base x) {}
|
|
void float_by_value(float x) {}
|
|
void custom_by_value(CustomClass x) {}
|
|
|
|
void fun() {
|
|
int int_x;
|
|
int &int_ref = int_x;
|
|
const int &int_const_ref = int_x;
|
|
Base base;
|
|
const Base &base_const_ref = base;
|
|
Derived derived;
|
|
float float_x;
|
|
)cpp";
|
|
const llvm::StringRef CodeSuffix = "}";
|
|
|
|
struct {
|
|
const char *const Code;
|
|
HoverInfo::PassType::PassMode PassBy;
|
|
bool Converted;
|
|
} Tests[] = {
|
|
// Integer tests
|
|
{"int_by_value([[^int_x]]);", PassMode::Value, false},
|
|
{"int_by_value([[^123]]);", PassMode::Value, false},
|
|
{"int_by_ref([[^int_x]]);", PassMode::Ref, false},
|
|
{"int_by_const_ref([[^int_x]]);", PassMode::ConstRef, false},
|
|
{"int_by_const_ref([[^123]]);", PassMode::ConstRef, false},
|
|
{"int_by_value([[^int_ref]]);", PassMode::Value, false},
|
|
{"int_by_const_ref([[^int_ref]]);", PassMode::ConstRef, false},
|
|
{"int_by_const_ref([[^int_ref]]);", PassMode::ConstRef, false},
|
|
{"int_by_const_ref([[^int_const_ref]]);", PassMode::ConstRef, false},
|
|
// Custom class tests
|
|
{"base_by_ref([[^base]]);", PassMode::Ref, false},
|
|
{"base_by_const_ref([[^base]]);", PassMode::ConstRef, false},
|
|
{"base_by_const_ref([[^base_const_ref]]);", PassMode::ConstRef, false},
|
|
{"base_by_value([[^base]]);", PassMode::Value, false},
|
|
{"base_by_value([[^base_const_ref]]);", PassMode::Value, false},
|
|
{"base_by_ref([[^derived]]);", PassMode::Ref, false},
|
|
{"base_by_const_ref([[^derived]]);", PassMode::ConstRef, false},
|
|
{"base_by_value([[^derived]]);", PassMode::Value, false},
|
|
// Custom class constructor tests
|
|
{"CustomClass c1([[^base]]);", PassMode::ConstRef, false},
|
|
{"auto c2 = new CustomClass([[^base]]);", PassMode::ConstRef, false},
|
|
{"CustomClass c3([[^int_x]]);", PassMode::Ref, false},
|
|
{"CustomClass c3(int_x, [[^int_x]]);", PassMode::Value, false},
|
|
// Converted tests
|
|
{"float_by_value([[^int_x]]);", PassMode::Value, true},
|
|
{"float_by_value([[^int_ref]]);", PassMode::Value, true},
|
|
{"float_by_value([[^int_const_ref]]);", PassMode::Value, true},
|
|
{"float_by_value([[^123.0f]]);", PassMode::Value, false},
|
|
{"float_by_value([[^123]]);", PassMode::Value, true},
|
|
{"custom_by_value([[^int_x]]);", PassMode::Ref, true},
|
|
{"custom_by_value([[^float_x]]);", PassMode::Value, true},
|
|
{"custom_by_value([[^base]]);", PassMode::ConstRef, true},
|
|
};
|
|
for (const auto &Test : Tests) {
|
|
SCOPED_TRACE(Test.Code);
|
|
|
|
const auto Code = (CodePrefix + Test.Code + CodeSuffix).str();
|
|
Annotations T(Code);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-std=c++17");
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
EXPECT_EQ(H->CallPassType->PassBy, Test.PassBy);
|
|
EXPECT_EQ(H->CallPassType->Converted, Test.Converted);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, NoHover) {
|
|
llvm::StringRef Tests[] = {
|
|
"^int main() {}",
|
|
"void foo() {^}",
|
|
// FIXME: "decltype(auto)" should be a single hover
|
|
"decltype(au^to) x = 0;",
|
|
// FIXME: not supported yet
|
|
R"cpp(// Lambda auto parameter
|
|
auto lamb = [](a^uto){};
|
|
)cpp",
|
|
R"cpp(// non-named decls don't get hover. Don't crash!
|
|
^static_assert(1, "");
|
|
)cpp",
|
|
R"cpp(// non-evaluatable expr
|
|
template <typename T> void foo() {
|
|
(void)[[size^of]](T);
|
|
})cpp",
|
|
R"cpp(// should not crash on invalid semantic form of init-list-expr.
|
|
/*error-ok*/
|
|
struct Foo {
|
|
int xyz = 0;
|
|
};
|
|
class Bar {};
|
|
constexpr Foo s = ^{
|
|
.xyz = Bar(),
|
|
};
|
|
)cpp",
|
|
// literals
|
|
"auto x = t^rue;",
|
|
"auto x = ^(int){42};",
|
|
"auto x = ^42.;",
|
|
"auto x = ^42.0i;",
|
|
"auto x = ^42;",
|
|
"auto x = ^nullptr;",
|
|
};
|
|
|
|
for (const auto &Test : Tests) {
|
|
SCOPED_TRACE(Test);
|
|
|
|
Annotations T(Test);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-std=c++17");
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_FALSE(H);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, All) {
|
|
struct {
|
|
const char *const Code;
|
|
const std::function<void(HoverInfo &)> ExpectedBuilder;
|
|
} Cases[] = {
|
|
{"auto x = [['^A']]; // character literal",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "expression";
|
|
HI.Type = "char";
|
|
HI.Value = "65 (0x41)";
|
|
}},
|
|
{"auto s = ^[[\"Hello, world!\"]]; // string literal",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "string-literal";
|
|
HI.Size = 112;
|
|
HI.Type = "const char[14]";
|
|
}},
|
|
{
|
|
R"cpp(// Local variable
|
|
int main() {
|
|
int bonjour;
|
|
^[[bonjour]] = 2;
|
|
int test1 = bonjour;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "bonjour";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "main::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int bonjour";
|
|
}},
|
|
{
|
|
R"cpp(// Local variable in method
|
|
struct s {
|
|
void method() {
|
|
int bonjour;
|
|
^[[bonjour]] = 2;
|
|
}
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "bonjour";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "s::method::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int bonjour";
|
|
}},
|
|
{
|
|
R"cpp(// Struct
|
|
namespace ns1 {
|
|
struct MyClass {};
|
|
} // namespace ns1
|
|
int main() {
|
|
ns1::[[My^Class]]* Params;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MyClass";
|
|
HI.Kind = index::SymbolKind::Struct;
|
|
HI.NamespaceScope = "ns1::";
|
|
HI.Definition = "struct MyClass {}";
|
|
}},
|
|
{
|
|
R"cpp(// Class
|
|
namespace ns1 {
|
|
class MyClass {};
|
|
} // namespace ns1
|
|
int main() {
|
|
ns1::[[My^Class]]* Params;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MyClass";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.NamespaceScope = "ns1::";
|
|
HI.Definition = "class MyClass {}";
|
|
}},
|
|
{
|
|
R"cpp(// Union
|
|
namespace ns1 {
|
|
union MyUnion { int x; int y; };
|
|
} // namespace ns1
|
|
int main() {
|
|
ns1::[[My^Union]] Params;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MyUnion";
|
|
HI.Kind = index::SymbolKind::Union;
|
|
HI.NamespaceScope = "ns1::";
|
|
HI.Definition = "union MyUnion {}";
|
|
}},
|
|
{
|
|
R"cpp(// Function definition via pointer
|
|
void foo(int) {}
|
|
int main() {
|
|
auto *X = &^[[foo]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Type = "void (int)";
|
|
HI.Definition = "void foo(int)";
|
|
HI.Documentation = "Function definition via pointer";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters = {
|
|
{{"int"}, std::nullopt, std::nullopt},
|
|
};
|
|
}},
|
|
{
|
|
R"cpp(// Function declaration via call
|
|
int foo(int);
|
|
int main() {
|
|
return ^[[foo]](42);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Type = "int (int)";
|
|
HI.Definition = "int foo(int)";
|
|
HI.Documentation = "Function declaration via call";
|
|
HI.ReturnType = "int";
|
|
HI.Parameters = {
|
|
{{"int"}, std::nullopt, std::nullopt},
|
|
};
|
|
}},
|
|
{
|
|
R"cpp(// Field
|
|
struct Foo { int x; };
|
|
int main() {
|
|
Foo bar;
|
|
(void)bar.^[[x]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int x";
|
|
}},
|
|
{
|
|
R"cpp(// Field with initialization
|
|
struct Foo { int x = 5; };
|
|
int main() {
|
|
Foo bar;
|
|
(void)bar.^[[x]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int x = 5";
|
|
}},
|
|
{
|
|
R"cpp(// Static field
|
|
struct Foo { static int x; };
|
|
int main() {
|
|
(void)Foo::^[[x]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::StaticProperty;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "static int x";
|
|
}},
|
|
{
|
|
R"cpp(// Field, member initializer
|
|
struct Foo {
|
|
int x;
|
|
Foo() : ^[[x]](0) {}
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int x";
|
|
}},
|
|
{
|
|
R"cpp(// Field, GNU old-style field designator
|
|
struct Foo { int x; };
|
|
int main() {
|
|
Foo bar = { ^[[x]] : 1 };
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int x";
|
|
// FIXME: Initializer for x is a DesignatedInitListExpr, hence it is
|
|
// of struct type and omitted.
|
|
}},
|
|
{
|
|
R"cpp(// Field, field designator
|
|
struct Foo { int x; int y; };
|
|
int main() {
|
|
Foo bar = { .^[[x]] = 2, .y = 2 };
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int x";
|
|
}},
|
|
{
|
|
R"cpp(// Method call
|
|
struct Foo { int x(); };
|
|
int main() {
|
|
Foo bar;
|
|
bar.^[[x]]();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int ()";
|
|
HI.Definition = "int x()";
|
|
HI.ReturnType = "int";
|
|
HI.Parameters = std::vector<HoverInfo::Param>{};
|
|
}},
|
|
{
|
|
R"cpp(// Static method call
|
|
struct Foo { static int x(); };
|
|
int main() {
|
|
Foo::^[[x]]();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::StaticMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::";
|
|
HI.Type = "int ()";
|
|
HI.Definition = "static int x()";
|
|
HI.ReturnType = "int";
|
|
HI.Parameters = std::vector<HoverInfo::Param>{};
|
|
}},
|
|
{
|
|
R"cpp(// Typedef
|
|
typedef int Foo;
|
|
int main() {
|
|
^[[Foo]] bar;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "Foo";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "typedef int Foo";
|
|
HI.Type = "int";
|
|
HI.Documentation = "Typedef";
|
|
}},
|
|
{
|
|
R"cpp(// Typedef with embedded definition
|
|
typedef struct Bar {} Foo;
|
|
int main() {
|
|
^[[Foo]] bar;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "Foo";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "typedef struct Bar Foo";
|
|
HI.Type = "struct Bar";
|
|
HI.Documentation = "Typedef with embedded definition";
|
|
}},
|
|
{
|
|
R"cpp(// Namespace
|
|
namespace ns {
|
|
struct Foo { static void bar(); };
|
|
} // namespace ns
|
|
int main() { ^[[ns]]::Foo::bar(); }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "ns";
|
|
HI.Kind = index::SymbolKind::Namespace;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "namespace ns {}";
|
|
}},
|
|
{
|
|
R"cpp(// Anonymous namespace
|
|
namespace ns {
|
|
namespace {
|
|
int foo;
|
|
} // anonymous namespace
|
|
} // namespace ns
|
|
int main() { ns::[[f^oo]]++; }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "ns::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int foo";
|
|
}},
|
|
{
|
|
R"cpp(// Function definition via using declaration
|
|
namespace ns {
|
|
void foo();
|
|
}
|
|
int main() {
|
|
using ns::foo;
|
|
^[[foo]]();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "ns::";
|
|
HI.Type = "void ()";
|
|
HI.Definition = "void foo()";
|
|
HI.Documentation = "";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters = std::vector<HoverInfo::Param>{};
|
|
}},
|
|
{
|
|
R"cpp( // using declaration and two possible function declarations
|
|
namespace ns { void foo(int); void foo(char); }
|
|
using ns::foo;
|
|
template <typename T> void bar() { [[f^oo]](T{}); }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Using;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "using ns::foo";
|
|
}},
|
|
{
|
|
R"cpp(// Macro
|
|
#define MACRO 0
|
|
int main() { return ^[[MACRO]]; }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MACRO";
|
|
HI.Value = "0";
|
|
HI.Type = "int";
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Definition = "#define MACRO 0\n\n"
|
|
"// Expands to\n"
|
|
"0";
|
|
}},
|
|
{
|
|
R"cpp(// Macro
|
|
#define MACRO 0
|
|
#define MACRO2 ^[[MACRO]]
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MACRO";
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Definition = "#define MACRO 0";
|
|
// NOTE MACRO doesn't have expansion since it technically isn't
|
|
// expanded here
|
|
}},
|
|
{
|
|
R"cpp(// Macro
|
|
#define MACRO {\
|
|
return 0;\
|
|
}
|
|
int main() ^[[MACRO]]
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MACRO";
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Definition =
|
|
R"cpp(#define MACRO \
|
|
{ \
|
|
return 0; \
|
|
}
|
|
|
|
// Expands to
|
|
{
|
|
return 0;
|
|
})cpp";
|
|
}},
|
|
{
|
|
R"cpp(// Forward class declaration
|
|
class Foo;
|
|
class Foo {};
|
|
[[F^oo]]* foo();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "Foo";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "class Foo {}";
|
|
HI.Documentation = "Forward class declaration";
|
|
}},
|
|
{
|
|
R"cpp(// Function declaration
|
|
void foo();
|
|
void g() { [[f^oo]](); }
|
|
void foo() {}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Type = "void ()";
|
|
HI.Definition = "void foo()";
|
|
HI.Documentation = "Function declaration";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters = std::vector<HoverInfo::Param>{};
|
|
}},
|
|
{
|
|
R"cpp(// Enum declaration
|
|
enum Hello {
|
|
ONE, TWO, THREE,
|
|
};
|
|
void foo() {
|
|
[[Hel^lo]] hello = ONE;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "Hello";
|
|
HI.Kind = index::SymbolKind::Enum;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "enum Hello {}";
|
|
HI.Documentation = "Enum declaration";
|
|
}},
|
|
{
|
|
R"cpp(// Enumerator
|
|
enum Hello {
|
|
ONE, TWO, THREE,
|
|
};
|
|
void foo() {
|
|
Hello hello = [[O^NE]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "ONE";
|
|
HI.Kind = index::SymbolKind::EnumConstant;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Hello::";
|
|
HI.Type = "enum Hello";
|
|
HI.Definition = "ONE";
|
|
HI.Value = "0";
|
|
}},
|
|
{
|
|
R"cpp(// C++20's using enum
|
|
enum class Hello {
|
|
ONE, TWO, THREE,
|
|
};
|
|
void foo() {
|
|
using enum Hello;
|
|
Hello hello = [[O^NE]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "ONE";
|
|
HI.Kind = index::SymbolKind::EnumConstant;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Hello::";
|
|
HI.Type = "enum Hello";
|
|
HI.Definition = "ONE";
|
|
HI.Value = "0";
|
|
}},
|
|
{
|
|
R"cpp(// Enumerator in anonymous enum
|
|
enum {
|
|
ONE, TWO, THREE,
|
|
};
|
|
void foo() {
|
|
int hello = [[O^NE]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "ONE";
|
|
HI.Kind = index::SymbolKind::EnumConstant;
|
|
HI.NamespaceScope = "";
|
|
// FIXME: This should be `(anon enum)::`
|
|
HI.LocalScope = "";
|
|
HI.Type = "enum (unnamed)";
|
|
HI.Definition = "ONE";
|
|
HI.Value = "0";
|
|
}},
|
|
{
|
|
R"cpp(// Global variable
|
|
static int hey = 10;
|
|
void foo() {
|
|
[[he^y]]++;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "hey";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Type = "int";
|
|
HI.Definition = "static int hey = 10";
|
|
HI.Documentation = "Global variable";
|
|
// FIXME: Value shouldn't be set in this case
|
|
HI.Value = "10 (0xa)";
|
|
}},
|
|
{
|
|
R"cpp(// Global variable in namespace
|
|
namespace ns1 {
|
|
static long long hey = -36637162602497;
|
|
}
|
|
void foo() {
|
|
ns1::[[he^y]]++;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "hey";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "ns1::";
|
|
HI.Type = "long long";
|
|
HI.Definition = "static long long hey = -36637162602497";
|
|
HI.Value = "-36637162602497 (0xffffdeadbeefffff)"; // needs 64 bits
|
|
}},
|
|
{
|
|
R"cpp(// Field in anonymous struct
|
|
static struct {
|
|
int hello;
|
|
} s;
|
|
void foo() {
|
|
s.[[he^llo]]++;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "hello";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "(anonymous struct)::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int hello";
|
|
}},
|
|
{
|
|
R"cpp(// Templated function
|
|
template <typename T>
|
|
T foo() {
|
|
return 17;
|
|
}
|
|
void g() { auto x = [[f^oo]]<int>(); }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Type = "int ()";
|
|
HI.Definition = "template <> int foo<int>()";
|
|
HI.Documentation = "Templated function";
|
|
HI.ReturnType = "int";
|
|
HI.Parameters = std::vector<HoverInfo::Param>{};
|
|
// FIXME: We should populate template parameters with arguments in
|
|
// case of instantiations.
|
|
}},
|
|
{
|
|
R"cpp(// Anonymous union
|
|
struct outer {
|
|
union {
|
|
int abc, def;
|
|
} v;
|
|
};
|
|
void g() { struct outer o; o.v.[[d^ef]]++; }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "def";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "outer::(anonymous union)::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int def";
|
|
}},
|
|
{
|
|
R"cpp(// documentation from index
|
|
int nextSymbolIsAForwardDeclFromIndexWithNoLocalDocs;
|
|
void indexSymbol();
|
|
void g() { [[ind^exSymbol]](); }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "indexSymbol";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Type = "void ()";
|
|
HI.Definition = "void indexSymbol()";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters = std::vector<HoverInfo::Param>{};
|
|
HI.Documentation = "comment from index";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with auto
|
|
void foo() {
|
|
^[[auto]] i = 1;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with const auto
|
|
void foo() {
|
|
const ^[[auto]] i = 1;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with const auto&
|
|
void foo() {
|
|
const ^[[auto]]& i = 1;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with auto&
|
|
void foo() {
|
|
int x;
|
|
^[[auto]]& i = x;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with auto*
|
|
void foo() {
|
|
int a = 1;
|
|
^[[auto]]* i = &a;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with auto from pointer
|
|
void foo() {
|
|
int a = 1;
|
|
^[[auto]] i = &a;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int *";
|
|
}},
|
|
{
|
|
R"cpp(// Auto with initializer list.
|
|
namespace std
|
|
{
|
|
template<class _E>
|
|
class initializer_list { const _E *a, *b; };
|
|
}
|
|
void foo() {
|
|
^[[auto]] i = {1,2};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "std::initializer_list<int>";
|
|
}},
|
|
{
|
|
R"cpp(// User defined conversion to auto
|
|
struct Bar {
|
|
operator ^[[auto]]() const { return 10; }
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with decltype(auto)
|
|
void foo() {
|
|
^[[decltype]](auto) i = 1;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with const decltype(auto)
|
|
void foo() {
|
|
const int j = 0;
|
|
^[[decltype]](auto) i = j;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "const int";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with const& decltype(auto)
|
|
void foo() {
|
|
int k = 0;
|
|
const int& j = k;
|
|
^[[decltype]](auto) i = j;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "const int &";
|
|
}},
|
|
{
|
|
R"cpp(// Simple initialization with & decltype(auto)
|
|
void foo() {
|
|
int k = 0;
|
|
int& j = k;
|
|
^[[decltype]](auto) i = j;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int &";
|
|
}},
|
|
{
|
|
R"cpp(// simple trailing return type
|
|
^[[auto]] main() -> int {
|
|
return 0;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// auto function return with trailing type
|
|
struct Bar {};
|
|
^[[auto]] test() -> decltype(Bar()) {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation = "auto function return with trailing type";
|
|
}},
|
|
{
|
|
R"cpp(// trailing return type
|
|
struct Bar {};
|
|
auto test() -> ^[[decltype]](Bar()) {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation = "trailing return type";
|
|
}},
|
|
{
|
|
R"cpp(// auto in function return
|
|
struct Bar {};
|
|
^[[auto]] test() {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation = "auto in function return";
|
|
}},
|
|
{
|
|
R"cpp(// auto& in function return
|
|
struct Bar {};
|
|
^[[auto]]& test() {
|
|
static Bar x;
|
|
return x;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation = "auto& in function return";
|
|
}},
|
|
{
|
|
R"cpp(// auto* in function return
|
|
struct Bar {};
|
|
^[[auto]]* test() {
|
|
Bar* bar;
|
|
return bar;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation = "auto* in function return";
|
|
}},
|
|
{
|
|
R"cpp(// const auto& in function return
|
|
struct Bar {};
|
|
const ^[[auto]]& test() {
|
|
static Bar x;
|
|
return x;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation = "const auto& in function return";
|
|
}},
|
|
{
|
|
R"cpp(// decltype(auto) in function return
|
|
struct Bar {};
|
|
^[[decltype]](auto) test() {
|
|
return Bar();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation = "decltype(auto) in function return";
|
|
}},
|
|
{
|
|
R"cpp(// decltype(auto) reference in function return
|
|
^[[decltype]](auto) test() {
|
|
static int a;
|
|
return (a);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int &";
|
|
}},
|
|
{
|
|
R"cpp(// decltype lvalue reference
|
|
void foo() {
|
|
int I = 0;
|
|
^[[decltype]](I) J = I;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// decltype lvalue reference
|
|
void foo() {
|
|
int I= 0;
|
|
int &K = I;
|
|
^[[decltype]](K) J = I;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int &";
|
|
}},
|
|
{
|
|
R"cpp(// decltype lvalue reference parenthesis
|
|
void foo() {
|
|
int I = 0;
|
|
^[[decltype]]((I)) J = I;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int &";
|
|
}},
|
|
{
|
|
R"cpp(// decltype rvalue reference
|
|
void foo() {
|
|
int I = 0;
|
|
^[[decltype]](static_cast<int&&>(I)) J = static_cast<int&&>(I);
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int &&";
|
|
}},
|
|
{
|
|
R"cpp(// decltype rvalue reference function call
|
|
int && bar();
|
|
void foo() {
|
|
int I = 0;
|
|
^[[decltype]](bar()) J = bar();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int &&";
|
|
}},
|
|
{
|
|
R"cpp(// decltype of function with trailing return type.
|
|
struct Bar {};
|
|
auto test() -> decltype(Bar()) {
|
|
return Bar();
|
|
}
|
|
void foo() {
|
|
^[[decltype]](test()) i = test();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "Bar";
|
|
HI.Documentation =
|
|
"decltype of function with trailing return type.";
|
|
}},
|
|
{
|
|
R"cpp(// decltype of var with decltype.
|
|
void foo() {
|
|
int I = 0;
|
|
decltype(I) J = I;
|
|
^[[decltype]](J) K = J;
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// decltype of dependent type
|
|
template <typename T>
|
|
struct X {
|
|
using Y = ^[[decltype]](T::Z);
|
|
};
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "<dependent type>";
|
|
}},
|
|
{
|
|
R"cpp(// More complicated structured types.
|
|
int bar();
|
|
^[[auto]] (*foo)() = bar;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int";
|
|
}},
|
|
{
|
|
R"cpp(// Should not crash when evaluating the initializer.
|
|
struct Test {};
|
|
void test() { Test && [[te^st]] = {}; }
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "test";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "test::";
|
|
HI.Type = "Test &&";
|
|
HI.Definition = "Test &&test = {}";
|
|
}},
|
|
{
|
|
R"cpp(// Shouldn't crash when evaluating the initializer.
|
|
struct Bar {}; // error-ok
|
|
struct Foo { void foo(Bar x = y); }
|
|
void Foo::foo(Bar [[^x]]) {})cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "x";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "Foo::foo::";
|
|
HI.Type = "Bar";
|
|
HI.Definition = "Bar x = <recovery - expr>()";
|
|
}},
|
|
{
|
|
R"cpp(// auto on alias
|
|
typedef int int_type;
|
|
^[[auto]] x = int_type();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "int_type // aka: int";
|
|
}},
|
|
{
|
|
R"cpp(// auto on alias
|
|
struct cls {};
|
|
typedef cls cls_type;
|
|
^[[auto]] y = cls_type();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "cls_type // aka: cls";
|
|
HI.Documentation = "auto on alias";
|
|
}},
|
|
{
|
|
R"cpp(// auto on alias
|
|
template <class>
|
|
struct templ {};
|
|
^[[auto]] z = templ<int>();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "templ<int>";
|
|
HI.Documentation = "auto on alias";
|
|
}},
|
|
{
|
|
R"cpp(// Undeduced auto declaration
|
|
template<typename T>
|
|
void foo() {
|
|
^[[auto]] x = T();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "/* not deduced */";
|
|
}},
|
|
{
|
|
R"cpp(// Undeduced auto return type
|
|
template<typename T>
|
|
^[[auto]] foo() {
|
|
return T();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "/* not deduced */";
|
|
}},
|
|
{
|
|
R"cpp(// Template auto parameter
|
|
template<[[a^uto]] T>
|
|
void func() {
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
// FIXME: not sure this is what we want, but this
|
|
// is what we currently get with getDeducedType
|
|
HI.Name = "auto";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "/* not deduced */";
|
|
}},
|
|
{
|
|
R"cpp(// Undeduced decltype(auto) return type
|
|
template<typename T>
|
|
^[[decltype]](auto) foo() {
|
|
return T();
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "decltype";
|
|
HI.Kind = index::SymbolKind::TypeAlias;
|
|
HI.Definition = "/* not deduced */";
|
|
}},
|
|
{
|
|
R"cpp(// should not crash.
|
|
template <class T> struct cls {
|
|
int method();
|
|
};
|
|
|
|
auto test = cls<int>().[[m^ethod]]();
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "int method()";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "cls<int>::";
|
|
HI.Name = "method";
|
|
HI.Parameters.emplace();
|
|
HI.ReturnType = "int";
|
|
HI.Type = "int ()";
|
|
}},
|
|
{
|
|
R"cpp(// type of nested templates.
|
|
template <class T> struct cls {};
|
|
cls<cls<cls<int>>> [[fo^o]];
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "cls<cls<cls<int>>> foo";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "foo";
|
|
HI.Type = "cls<cls<cls<int>>>";
|
|
}},
|
|
{
|
|
R"cpp(// type of nested templates.
|
|
template <class T> struct cls {};
|
|
[[cl^s]]<cls<cls<int>>> foo;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "template <> struct cls<cls<cls<int>>> {}";
|
|
HI.Kind = index::SymbolKind::Struct;
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "cls<cls<cls<int>>>";
|
|
HI.Documentation = "type of nested templates.";
|
|
}},
|
|
{
|
|
R"cpp(// type with decltype
|
|
int a;
|
|
decltype(a) [[b^]] = a;)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "decltype(a) b = a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "b";
|
|
HI.Type = "int";
|
|
}},
|
|
{
|
|
R"cpp(// type with decltype
|
|
int a;
|
|
decltype(a) c;
|
|
decltype(c) [[b^]] = a;)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "decltype(c) b = a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "b";
|
|
HI.Type = "int";
|
|
}},
|
|
{
|
|
R"cpp(// type with decltype
|
|
int a;
|
|
const decltype(a) [[b^]] = a;)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "const decltype(a) b = a";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "b";
|
|
HI.Type = "int";
|
|
}},
|
|
{
|
|
R"cpp(// type with decltype
|
|
int a;
|
|
auto [[f^oo]](decltype(a) x) -> decltype(a) { return 0; })cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "auto foo(decltype(a) x) -> decltype(a)";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Name = "foo";
|
|
// FIXME: Handle composite types with decltype with a printing
|
|
// policy.
|
|
HI.Type = {"auto (decltype(a)) -> decltype(a)",
|
|
"auto (int) -> int"};
|
|
HI.ReturnType = "int";
|
|
HI.Parameters = {{{"int"}, std::string("x"), std::nullopt}};
|
|
}},
|
|
{
|
|
R"cpp(// sizeof expr
|
|
void foo() {
|
|
(void)[[size^of]](char);
|
|
})cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "expression";
|
|
HI.Type = {"__size_t", "unsigned long"};
|
|
HI.Value = "1";
|
|
}},
|
|
{
|
|
R"cpp(// alignof expr
|
|
void foo() {
|
|
(void)[[align^of]](char);
|
|
})cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "expression";
|
|
HI.Type = {"__size_t", "unsigned long"};
|
|
HI.Value = "1";
|
|
}},
|
|
{
|
|
R"cpp(
|
|
template <typename T = int>
|
|
void foo(const T& = T()) {
|
|
[[f^oo]]<>(3);
|
|
})cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foo";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Type = "void (const int &)";
|
|
HI.ReturnType = "void";
|
|
HI.Parameters = {
|
|
{{"const int &"}, std::nullopt, std::string("T()")}};
|
|
HI.Definition = "template <> void foo<int>(const int &)";
|
|
HI.NamespaceScope = "";
|
|
}},
|
|
{
|
|
R"cpp(// should not crash
|
|
@interface ObjC {
|
|
char [[da^ta]];
|
|
}@end
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "data";
|
|
HI.Type = "char";
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.LocalScope = "ObjC::";
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "char data";
|
|
}},
|
|
{
|
|
R"cpp(
|
|
@interface MYObject
|
|
@end
|
|
@interface Interface
|
|
@property(retain) [[MYOb^ject]] *x;
|
|
@end
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MYObject";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "@interface MYObject\n@end";
|
|
}},
|
|
{
|
|
R"cpp(
|
|
@interface MYObject
|
|
@end
|
|
@interface Interface
|
|
- (void)doWith:([[MYOb^ject]] *)object;
|
|
@end
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MYObject";
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "@interface MYObject\n@end";
|
|
}},
|
|
{
|
|
R"cpp(// this expr
|
|
// comment
|
|
namespace ns {
|
|
class Foo {
|
|
Foo* bar() {
|
|
return [[t^his]];
|
|
}
|
|
};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "this";
|
|
HI.Definition = "ns::Foo *";
|
|
}},
|
|
{
|
|
R"cpp(// this expr for template class
|
|
namespace ns {
|
|
template <typename T>
|
|
class Foo {
|
|
Foo* bar() const {
|
|
return [[t^his]];
|
|
}
|
|
};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "this";
|
|
HI.Definition = "const ns::Foo<T> *";
|
|
}},
|
|
{
|
|
R"cpp(// this expr for specialization class
|
|
namespace ns {
|
|
template <typename T> class Foo {};
|
|
template <>
|
|
struct Foo<int> {
|
|
Foo* bar() {
|
|
return [[thi^s]];
|
|
}
|
|
};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "this";
|
|
HI.Definition = "ns::Foo<int> *";
|
|
}},
|
|
{
|
|
R"cpp(// this expr for partial specialization struct
|
|
namespace ns {
|
|
template <typename T, typename F> struct Foo {};
|
|
template <typename F>
|
|
struct Foo<int, F> {
|
|
Foo* bar() const {
|
|
return [[thi^s]];
|
|
}
|
|
};
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "this";
|
|
HI.Definition = "const ns::Foo<int, F> *";
|
|
}},
|
|
{
|
|
R"cpp(
|
|
@interface MYObject
|
|
@end
|
|
@interface MYObject (Private)
|
|
@property(nonatomic, assign) int privateField;
|
|
@end
|
|
|
|
int someFunction() {
|
|
MYObject *obj = [MYObject sharedInstance];
|
|
return obj.[[private^Field]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "privateField";
|
|
HI.Kind = index::SymbolKind::InstanceProperty;
|
|
HI.LocalScope = "MYObject(Private)::";
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "@property(nonatomic, assign, unsafe_unretained, "
|
|
"readwrite) int privateField;";
|
|
}},
|
|
{
|
|
R"cpp(
|
|
@protocol MYProtocol
|
|
@property(nonatomic, assign) int prop1;
|
|
@end
|
|
|
|
int someFunction() {
|
|
id<MYProtocol> obj = 0;
|
|
return obj.[[pro^p1]];
|
|
}
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "prop1";
|
|
HI.Kind = index::SymbolKind::InstanceProperty;
|
|
HI.LocalScope = "MYProtocol::";
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "@property(nonatomic, assign, unsafe_unretained, "
|
|
"readwrite) int prop1;";
|
|
}},
|
|
{
|
|
R"cpp(
|
|
@protocol MYProtocol
|
|
@end
|
|
@interface MYObject
|
|
@end
|
|
|
|
@interface MYObject (Ext) <[[MYProt^ocol]]>
|
|
@end
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "MYProtocol";
|
|
HI.Kind = index::SymbolKind::Protocol;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "@protocol MYProtocol\n@end";
|
|
}},
|
|
{R"objc(
|
|
@interface Foo
|
|
@end
|
|
|
|
@implementation Foo(Private)
|
|
+ (int)somePrivateMethod {
|
|
int [[res^ult]] = 2;
|
|
return result;
|
|
}
|
|
@end
|
|
)objc",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "result";
|
|
HI.Definition = "int result = 2";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = "int";
|
|
HI.LocalScope = "+[Foo(Private) somePrivateMethod]::";
|
|
HI.NamespaceScope = "";
|
|
HI.Value = "2";
|
|
}},
|
|
{R"objc(
|
|
@interface Foo
|
|
@end
|
|
|
|
@implementation Foo
|
|
- (int)variadicArgMethod:(id)first, ... {
|
|
int [[res^ult]] = 0;
|
|
return result;
|
|
}
|
|
@end
|
|
)objc",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "result";
|
|
HI.Definition = "int result = 0";
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Type = "int";
|
|
HI.LocalScope = "-[Foo variadicArgMethod:, ...]::";
|
|
HI.NamespaceScope = "";
|
|
HI.Value = "0";
|
|
}},
|
|
// Should not crash.
|
|
{R"objc(
|
|
typedef struct MyRect {} MyRect;
|
|
|
|
@interface IFace
|
|
@property(nonatomic) MyRect frame;
|
|
@end
|
|
|
|
MyRect foobar() {
|
|
MyRect mr;
|
|
return mr;
|
|
}
|
|
void test() {
|
|
IFace *v;
|
|
v.frame = [[foo^bar]]();
|
|
}
|
|
)objc",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "foobar";
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.NamespaceScope = "";
|
|
HI.Definition = "MyRect foobar()";
|
|
HI.Type = {"MyRect ()", "struct MyRect ()"};
|
|
HI.ReturnType = {"MyRect", "struct MyRect"};
|
|
HI.Parameters.emplace();
|
|
}},
|
|
{R"cpp(
|
|
void foo(int * __attribute__(([[non^null]], noescape)) );
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "nonnull";
|
|
HI.Kind = index::SymbolKind::Unknown; // FIXME: no suitable value
|
|
HI.Definition = "__attribute__((nonnull))";
|
|
HI.Documentation = Attr::getDocumentation(attr::NonNull).str();
|
|
}},
|
|
{
|
|
R"cpp(
|
|
namespace std {
|
|
struct strong_ordering {
|
|
int n;
|
|
constexpr operator int() const { return n; }
|
|
static const strong_ordering equal, greater, less;
|
|
};
|
|
constexpr strong_ordering strong_ordering::equal = {0};
|
|
constexpr strong_ordering strong_ordering::greater = {1};
|
|
constexpr strong_ordering strong_ordering::less = {-1};
|
|
}
|
|
|
|
struct Foo
|
|
{
|
|
int x;
|
|
// Foo spaceship
|
|
auto operator<=>(const Foo&) const = default;
|
|
};
|
|
|
|
bool x = Foo(1) [[!^=]] Foo(2);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Type = "bool (const Foo &) const noexcept";
|
|
HI.Value = "true";
|
|
HI.Name = "operator==";
|
|
HI.Parameters = {{{"const Foo &"}, std::nullopt, std::nullopt}};
|
|
HI.ReturnType = "bool";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.LocalScope = "Foo::";
|
|
HI.NamespaceScope = "";
|
|
HI.Definition =
|
|
"bool operator==(const Foo &) const noexcept = default";
|
|
HI.Documentation = "";
|
|
}},
|
|
};
|
|
|
|
// Create a tiny index, so tests above can verify documentation is fetched.
|
|
Symbol IndexSym = func("indexSymbol");
|
|
IndexSym.Documentation = "comment from index";
|
|
SymbolSlab::Builder Symbols;
|
|
Symbols.insert(IndexSym);
|
|
auto Index =
|
|
MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab());
|
|
|
|
for (const auto &Case : Cases) {
|
|
SCOPED_TRACE(Case.Code);
|
|
|
|
Annotations T(Case.Code);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-std=c++20");
|
|
TU.ExtraArgs.push_back("-xobjective-c++");
|
|
|
|
TU.ExtraArgs.push_back("-Wno-gnu-designator");
|
|
// Types might be different depending on the target triplet, we chose a
|
|
// fixed one to make sure tests passes on different platform.
|
|
TU.ExtraArgs.push_back("--target=x86_64-pc-linux-gnu");
|
|
auto AST = TU.build();
|
|
Config Cfg;
|
|
Cfg.Hover.ShowAKA = true;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), Index.get());
|
|
ASSERT_TRUE(H);
|
|
HoverInfo Expected;
|
|
Expected.SymRange = T.range();
|
|
Case.ExpectedBuilder(Expected);
|
|
|
|
SCOPED_TRACE(H->present(MarkupKind::PlainText));
|
|
EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
|
|
EXPECT_EQ(H->LocalScope, Expected.LocalScope);
|
|
EXPECT_EQ(H->Name, Expected.Name);
|
|
EXPECT_EQ(H->Kind, Expected.Kind);
|
|
EXPECT_EQ(H->Documentation, Expected.Documentation);
|
|
EXPECT_EQ(H->Definition, Expected.Definition);
|
|
EXPECT_EQ(H->Type, Expected.Type);
|
|
EXPECT_EQ(H->ReturnType, Expected.ReturnType);
|
|
EXPECT_EQ(H->Parameters, Expected.Parameters);
|
|
EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
|
|
EXPECT_EQ(H->SymRange, Expected.SymRange);
|
|
EXPECT_EQ(H->Value, Expected.Value);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, Providers) {
|
|
struct {
|
|
const char *Code;
|
|
const std::function<void(HoverInfo &)> ExpectedBuilder;
|
|
} Cases[] = {{R"cpp(
|
|
struct Foo {};
|
|
Foo F = Fo^o{};
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = ""; }},
|
|
{R"cpp(
|
|
#include "foo.h"
|
|
Foo F = Fo^o{};
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }},
|
|
{R"cpp(
|
|
#include "all.h"
|
|
Foo F = Fo^o{};
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }},
|
|
{R"cpp(
|
|
#define FOO 5
|
|
int F = ^FOO;
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = ""; }},
|
|
{R"cpp(
|
|
#include "foo.h"
|
|
int F = ^FOO;
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }},
|
|
{R"cpp(
|
|
#include "all.h"
|
|
int F = ^FOO;
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }},
|
|
{R"cpp(
|
|
#include "foo.h"
|
|
Foo A;
|
|
Foo B;
|
|
Foo C = A ^+ B;
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }},
|
|
// Hover selects the underlying decl of the using decl
|
|
{R"cpp(
|
|
#include "foo.h"
|
|
namespace ns {
|
|
using ::Foo;
|
|
}
|
|
ns::F^oo d;
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = "\"foo.h\""; }},
|
|
{R"cpp(
|
|
namespace foo {};
|
|
using namespace fo^o;
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.Provider = ""; }},
|
|
};
|
|
|
|
for (const auto &Case : Cases) {
|
|
Annotations Code{Case.Code};
|
|
SCOPED_TRACE(Code.code());
|
|
|
|
TestTU TU;
|
|
TU.Filename = "foo.cpp";
|
|
TU.Code = Code.code();
|
|
TU.AdditionalFiles["foo.h"] = guard(R"cpp(
|
|
#define FOO 1
|
|
class Foo {};
|
|
Foo& operator+(const Foo, const Foo);
|
|
)cpp");
|
|
TU.AdditionalFiles["all.h"] = guard("#include \"foo.h\"");
|
|
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, Code.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
HoverInfo Expected;
|
|
Case.ExpectedBuilder(Expected);
|
|
SCOPED_TRACE(H->present(MarkupKind::Markdown));
|
|
EXPECT_EQ(H->Provider, Expected.Provider);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, ParseProviderInfo) {
|
|
HoverInfo HIFoo;
|
|
HIFoo.Name = "foo";
|
|
HIFoo.Provider = "\"foo.h\"";
|
|
|
|
HoverInfo HIFooBar;
|
|
HIFooBar.Name = "foo";
|
|
HIFooBar.Provider = "<bar.h>";
|
|
struct Case {
|
|
HoverInfo HI;
|
|
llvm::StringRef ExpectedMarkdown;
|
|
} Cases[] = {{HIFoo, "### `foo`\n\nprovided by `\"foo.h\"`"},
|
|
{HIFooBar, "### `foo`\n\nprovided by `<bar.h>`"}};
|
|
|
|
for (const auto &Case : Cases)
|
|
EXPECT_EQ(Case.HI.present(MarkupKind::Markdown), Case.ExpectedMarkdown);
|
|
}
|
|
|
|
TEST(Hover, UsedSymbols) {
|
|
struct {
|
|
const char *Code;
|
|
const std::function<void(HoverInfo &)> ExpectedBuilder;
|
|
} Cases[] = {{R"cpp(
|
|
#include ^"bar.h"
|
|
int fstBar = bar1();
|
|
int another= bar1(0);
|
|
int sndBar = bar2();
|
|
Bar bar;
|
|
int macroBar = BAR;
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.UsedSymbolNames = {"BAR", "Bar", "bar1", "bar2"};
|
|
}},
|
|
{R"cpp(
|
|
#in^clude <vector>
|
|
std::vector<int> vec;
|
|
)cpp",
|
|
[](HoverInfo &HI) { HI.UsedSymbolNames = {"vector"}; }}};
|
|
for (const auto &Case : Cases) {
|
|
Annotations Code{Case.Code};
|
|
SCOPED_TRACE(Code.code());
|
|
|
|
TestTU TU;
|
|
TU.Filename = "foo.cpp";
|
|
TU.Code = Code.code();
|
|
TU.AdditionalFiles["bar.h"] = guard(R"cpp(
|
|
#define BAR 5
|
|
int bar1();
|
|
int bar2();
|
|
int bar1(double);
|
|
class Bar {};
|
|
)cpp");
|
|
TU.AdditionalFiles["system/vector"] = guard(R"cpp(
|
|
namespace std {
|
|
template<typename>
|
|
class vector{};
|
|
}
|
|
)cpp");
|
|
TU.ExtraArgs.push_back("-isystem" + testPath("system"));
|
|
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, Code.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
HoverInfo Expected;
|
|
Case.ExpectedBuilder(Expected);
|
|
SCOPED_TRACE(H->present(MarkupKind::Markdown));
|
|
EXPECT_EQ(H->UsedSymbolNames, Expected.UsedSymbolNames);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, DocsFromIndex) {
|
|
Annotations T(R"cpp(
|
|
template <typename T> class X {};
|
|
void foo() {
|
|
auto t = X<int>();
|
|
X^<int> w;
|
|
(void)w;
|
|
})cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
Symbol IndexSym;
|
|
IndexSym.ID = getSymbolID(&findDecl(AST, "X"));
|
|
IndexSym.Documentation = "comment from index";
|
|
SymbolSlab::Builder Symbols;
|
|
Symbols.insert(IndexSym);
|
|
auto Index =
|
|
MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab());
|
|
|
|
for (const auto &P : T.points()) {
|
|
auto H = getHover(AST, P, format::getLLVMStyle(), Index.get());
|
|
ASSERT_TRUE(H);
|
|
EXPECT_EQ(H->Documentation, IndexSym.Documentation);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, DocsFromAST) {
|
|
Annotations T(R"cpp(
|
|
// doc
|
|
template <typename T> class X {};
|
|
// doc
|
|
template <typename T> void bar() {}
|
|
// doc
|
|
template <typename T> T baz;
|
|
void foo() {
|
|
au^to t = X<int>();
|
|
X^<int>();
|
|
b^ar<int>();
|
|
au^to T = ba^z<X<int>>;
|
|
ba^z<int> = 0;
|
|
})cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
for (const auto &P : T.points()) {
|
|
auto H = getHover(AST, P, format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
EXPECT_EQ(H->Documentation, "doc");
|
|
}
|
|
}
|
|
|
|
TEST(Hover, NoCrash) {
|
|
Annotations T(R"cpp(
|
|
/* error-ok */
|
|
template<typename T> T foo(T);
|
|
|
|
// Setter variable heuristic might fail if the callexpr is broken.
|
|
struct X { int Y; void [[^setY]](float) { Y = foo(undefined); } };)cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
for (const auto &P : T.points())
|
|
getHover(AST, P, format::getLLVMStyle(), nullptr);
|
|
}
|
|
|
|
TEST(Hover, NoCrashAPInt64) {
|
|
Annotations T(R"cpp(
|
|
constexpr unsigned long value = -1; // wrap around
|
|
void foo() { va^lue; }
|
|
)cpp");
|
|
auto AST = TestTU::withCode(T.code()).build();
|
|
getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
}
|
|
|
|
TEST(Hover, NoCrashInt128) {
|
|
Annotations T(R"cpp(
|
|
constexpr __int128_t value = -4;
|
|
void foo() { va^lue; }
|
|
)cpp");
|
|
auto TU = TestTU::withCode(T.code());
|
|
// Need a triple that support __int128_t.
|
|
TU.ExtraArgs.push_back("--target=x86_64-pc-linux-gnu");
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
EXPECT_EQ(H->Value, "-4 (0xfffffffc)");
|
|
}
|
|
|
|
TEST(Hover, DocsFromMostSpecial) {
|
|
Annotations T(R"cpp(
|
|
// doc1
|
|
template <typename T> class $doc1^X {};
|
|
// doc2
|
|
template <> class $doc2^X<int> {};
|
|
// doc3
|
|
template <typename T> class $doc3^X<T*> {};
|
|
void foo() {
|
|
X$doc1^<char>();
|
|
X$doc2^<int>();
|
|
X$doc3^<int*>();
|
|
})cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
for (const auto *Comment : {"doc1", "doc2", "doc3"}) {
|
|
for (const auto &P : T.points(Comment)) {
|
|
auto H = getHover(AST, P, format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(H);
|
|
EXPECT_EQ(H->Documentation, Comment);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(Hover, Present) {
|
|
struct {
|
|
const std::function<void(HoverInfo &)> Builder;
|
|
llvm::StringRef ExpectedRender;
|
|
} Cases[] = {
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Unknown;
|
|
HI.Name = "X";
|
|
},
|
|
R"(X)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::NamespaceAlias;
|
|
HI.Name = "foo";
|
|
},
|
|
R"(namespace-alias foo)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Class;
|
|
HI.Size = 80;
|
|
HI.TemplateParameters = {
|
|
{{"typename"}, std::string("T"), std::nullopt},
|
|
{{"typename"}, std::string("C"), std::string("bool")},
|
|
};
|
|
HI.Documentation = "documentation";
|
|
HI.Definition =
|
|
"template <typename T, typename C = bool> class Foo {}";
|
|
HI.Name = "foo";
|
|
HI.NamespaceScope.emplace();
|
|
},
|
|
R"(class foo
|
|
|
|
Size: 10 bytes
|
|
|
|
documentation
|
|
|
|
template <typename T, typename C = bool> class Foo {})",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Name = "foo";
|
|
HI.Type = {"type", "c_type"};
|
|
HI.ReturnType = {"ret_type", "can_ret_type"};
|
|
HI.Parameters.emplace();
|
|
HoverInfo::Param P;
|
|
HI.Parameters->push_back(P);
|
|
P.Type = {"type", "can_type"};
|
|
HI.Parameters->push_back(P);
|
|
P.Name = "foo";
|
|
HI.Parameters->push_back(P);
|
|
P.Default = "default";
|
|
HI.Parameters->push_back(P);
|
|
HI.NamespaceScope = "ns::";
|
|
HI.Definition = "ret_type foo(params) {}";
|
|
},
|
|
"function foo\n"
|
|
"\n"
|
|
"→ ret_type (aka can_ret_type)\n\n"
|
|
"Parameters:\n\n"
|
|
"- \n"
|
|
"- type (aka can_type)\n"
|
|
"- type foo (aka can_type)\n"
|
|
"- type foo = default (aka can_type)\n"
|
|
"\n"
|
|
"// In namespace ns\n"
|
|
"ret_type foo(params) {}",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.LocalScope = "test::Bar::";
|
|
HI.Value = "value";
|
|
HI.Name = "foo";
|
|
HI.Type = {"type", "can_type"};
|
|
HI.Definition = "def";
|
|
HI.Size = 32;
|
|
HI.Offset = 96;
|
|
HI.Padding = 32;
|
|
HI.Align = 32;
|
|
},
|
|
R"(field foo
|
|
|
|
Type: type (aka can_type)
|
|
|
|
Value = value
|
|
|
|
Offset: 12 bytes
|
|
|
|
Size: 4 bytes (+4 bytes padding), alignment 4 bytes
|
|
|
|
// In test::Bar
|
|
def)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.LocalScope = "test::Bar::";
|
|
HI.Value = "value";
|
|
HI.Name = "foo";
|
|
HI.Type = {"type", "can_type"};
|
|
HI.Definition = "def";
|
|
HI.Size = 25;
|
|
HI.Offset = 35;
|
|
HI.Padding = 4;
|
|
HI.Align = 64;
|
|
},
|
|
R"(field foo
|
|
|
|
Type: type (aka can_type)
|
|
|
|
Value = value
|
|
|
|
Offset: 4 bytes and 3 bits
|
|
|
|
Size: 25 bits (+4 bits padding), alignment 8 bytes
|
|
|
|
// In test::Bar
|
|
def)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Field;
|
|
HI.AccessSpecifier = "public";
|
|
HI.Name = "foo";
|
|
HI.LocalScope = "test::Bar::";
|
|
HI.Definition = "def";
|
|
},
|
|
R"(field foo
|
|
|
|
// In test::Bar
|
|
public: def)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "size_t method()";
|
|
HI.AccessSpecifier = "protected";
|
|
HI.Kind = index::SymbolKind::InstanceMethod;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "cls<int>::";
|
|
HI.Name = "method";
|
|
HI.Parameters.emplace();
|
|
HI.ReturnType = {"size_t", "unsigned long"};
|
|
HI.Type = {"size_t ()", "unsigned long ()"};
|
|
},
|
|
R"(instance-method method
|
|
|
|
→ size_t (aka unsigned long)
|
|
|
|
// In cls<int>
|
|
protected: size_t method())",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Definition = "cls(int a, int b = 5)";
|
|
HI.AccessSpecifier = "public";
|
|
HI.Kind = index::SymbolKind::Constructor;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "cls";
|
|
HI.Name = "cls";
|
|
HI.Parameters.emplace();
|
|
HI.Parameters->emplace_back();
|
|
HI.Parameters->back().Type = "int";
|
|
HI.Parameters->back().Name = "a";
|
|
HI.Parameters->emplace_back();
|
|
HI.Parameters->back().Type = "int";
|
|
HI.Parameters->back().Name = "b";
|
|
HI.Parameters->back().Default = "5";
|
|
},
|
|
R"(constructor cls
|
|
|
|
Parameters:
|
|
|
|
- int a
|
|
- int b = 5
|
|
|
|
// In cls
|
|
public: cls(int a, int b = 5))",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Union;
|
|
HI.AccessSpecifier = "private";
|
|
HI.Name = "foo";
|
|
HI.NamespaceScope = "ns1::";
|
|
HI.Definition = "union foo {}";
|
|
},
|
|
R"(union foo
|
|
|
|
// In namespace ns1
|
|
private: union foo {})",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Name = "foo";
|
|
HI.Definition = "int foo = 3";
|
|
HI.LocalScope = "test::Bar::";
|
|
HI.Value = "3";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_a";
|
|
HI.CalleeArgInfo->Type = "int";
|
|
HI.CalleeArgInfo->Default = "7";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, false};
|
|
},
|
|
R"(variable foo
|
|
|
|
Type: int
|
|
|
|
Value = 3
|
|
|
|
Passed as arg_a
|
|
|
|
// In test::Bar
|
|
int foo = 3)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Name = "foo";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Type = "int";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, false};
|
|
},
|
|
R"(variable foo
|
|
|
|
Passed by value)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Name = "foo";
|
|
HI.Definition = "int foo = 3";
|
|
HI.LocalScope = "test::Bar::";
|
|
HI.Value = "3";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_a";
|
|
HI.CalleeArgInfo->Type = "int";
|
|
HI.CalleeArgInfo->Default = "7";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Ref, false};
|
|
},
|
|
R"(variable foo
|
|
|
|
Type: int
|
|
|
|
Value = 3
|
|
|
|
Passed by reference as arg_a
|
|
|
|
// In test::Bar
|
|
int foo = 3)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Name = "foo";
|
|
HI.Definition = "int foo = 3";
|
|
HI.LocalScope = "test::Bar::";
|
|
HI.Value = "3";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_a";
|
|
HI.CalleeArgInfo->Type = {"alias_int", "int"};
|
|
HI.CalleeArgInfo->Default = "7";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::Value, true};
|
|
},
|
|
R"(variable foo
|
|
|
|
Type: int
|
|
|
|
Value = 3
|
|
|
|
Passed as arg_a (converted to alias_int)
|
|
|
|
// In test::Bar
|
|
int foo = 3)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Macro;
|
|
HI.Name = "PLUS_ONE";
|
|
HI.Definition = "#define PLUS_ONE(X) (X+1)\n\n"
|
|
"// Expands to\n"
|
|
"(1 + 1)";
|
|
},
|
|
R"(macro PLUS_ONE
|
|
|
|
#define PLUS_ONE(X) (X+1)
|
|
|
|
// Expands to
|
|
(1 + 1))",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Name = "foo";
|
|
HI.Definition = "int foo = 3";
|
|
HI.LocalScope = "test::Bar::";
|
|
HI.Value = "3";
|
|
HI.Type = "int";
|
|
HI.CalleeArgInfo.emplace();
|
|
HI.CalleeArgInfo->Name = "arg_a";
|
|
HI.CalleeArgInfo->Type = "int";
|
|
HI.CalleeArgInfo->Default = "7";
|
|
HI.CallPassType = HoverInfo::PassType{PassMode::ConstRef, true};
|
|
},
|
|
R"(variable foo
|
|
|
|
Type: int
|
|
|
|
Value = 3
|
|
|
|
Passed by const reference as arg_a (converted to int)
|
|
|
|
// In test::Bar
|
|
int foo = 3)",
|
|
},
|
|
{
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "stdio.h";
|
|
HI.Definition = "/usr/include/stdio.h";
|
|
},
|
|
R"(stdio.h
|
|
|
|
/usr/include/stdio.h)",
|
|
},
|
|
{[](HoverInfo &HI) {
|
|
HI.Name = "foo.h";
|
|
HI.UsedSymbolNames = {"Foo", "Bar", "Bar"};
|
|
},
|
|
R"(foo.h
|
|
|
|
provides Foo, Bar, Bar)"},
|
|
{[](HoverInfo &HI) {
|
|
HI.Name = "foo.h";
|
|
HI.UsedSymbolNames = {"Foo", "Bar", "Baz", "Foobar", "Qux", "Quux"};
|
|
},
|
|
R"(foo.h
|
|
|
|
provides Foo, Bar, Baz, Foobar, Qux and 1 more)"}};
|
|
|
|
for (const auto &C : Cases) {
|
|
HoverInfo HI;
|
|
C.Builder(HI);
|
|
Config Cfg;
|
|
Cfg.Hover.ShowAKA = true;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedRender);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, PresentDocumentation) {
|
|
struct {
|
|
const std::function<void(HoverInfo &)> Builder;
|
|
llvm::StringRef ExpectedRender;
|
|
} Cases[] = {
|
|
{[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Documentation = "@brief brief doc\n\n"
|
|
"longer doc";
|
|
HI.Definition = "void foo()";
|
|
HI.Name = "foo";
|
|
},
|
|
R"(### function `foo`
|
|
|
|
---
|
|
**@brief** brief doc
|
|
|
|
longer doc
|
|
|
|
---
|
|
```cpp
|
|
void foo()
|
|
```)"},
|
|
{[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Documentation = "@brief brief doc\n\n"
|
|
"longer doc";
|
|
HI.Definition = "int foo()";
|
|
HI.ReturnType = "int";
|
|
HI.Name = "foo";
|
|
},
|
|
R"(### function `foo`
|
|
|
|
---
|
|
→ `int`
|
|
|
|
**@brief** brief doc
|
|
|
|
longer doc
|
|
|
|
---
|
|
```cpp
|
|
int foo()
|
|
```)"},
|
|
{[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Documentation = "@brief brief doc\n\n"
|
|
"longer doc\n@param a this is a param\n@return it "
|
|
"returns something";
|
|
HI.Definition = "int foo(int a)";
|
|
HI.ReturnType = "int";
|
|
HI.Name = "foo";
|
|
HI.Parameters.emplace();
|
|
HI.Parameters->emplace_back();
|
|
HI.Parameters->back().Type = "int";
|
|
HI.Parameters->back().Name = "a";
|
|
},
|
|
R"(### function `foo`
|
|
|
|
---
|
|
→ `int`
|
|
|
|
Parameters:
|
|
|
|
- `int a` - this is a param
|
|
|
|
**@brief** brief doc
|
|
|
|
longer doc
|
|
|
|
**@return** it returns something
|
|
|
|
---
|
|
```cpp
|
|
int foo(int a)
|
|
```)"},
|
|
{[](HoverInfo &HI) {
|
|
HI.Kind = index::SymbolKind::Function;
|
|
HI.Documentation = "@brief brief doc\n\n"
|
|
"longer doc\n@param a this is a param\n@param b "
|
|
"does not exist\n@return it returns something";
|
|
HI.Definition = "int foo(int a)";
|
|
HI.ReturnType = "int";
|
|
HI.Name = "foo";
|
|
HI.Parameters.emplace();
|
|
HI.Parameters->emplace_back();
|
|
HI.Parameters->back().Type = "int";
|
|
HI.Parameters->back().Name = "a";
|
|
},
|
|
R"(### function `foo`
|
|
|
|
---
|
|
→ `int`
|
|
|
|
Parameters:
|
|
|
|
- `int a` - this is a param
|
|
|
|
**@brief** brief doc
|
|
|
|
longer doc
|
|
|
|
**@return** it returns something
|
|
|
|
---
|
|
```cpp
|
|
int foo(int a)
|
|
```)"},
|
|
};
|
|
|
|
for (const auto &C : Cases) {
|
|
HoverInfo HI;
|
|
C.Builder(HI);
|
|
Config Cfg;
|
|
Cfg.Hover.ShowAKA = true;
|
|
Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedRender);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, ParseDocumentation) {
|
|
struct Case {
|
|
llvm::StringRef Documentation;
|
|
llvm::StringRef ExpectedRenderEscapedMarkdown;
|
|
llvm::StringRef ExpectedRenderMarkdown;
|
|
llvm::StringRef ExpectedRenderPlainText;
|
|
} Cases[] = {{
|
|
" \n foo\nbar",
|
|
"foo\nbar",
|
|
"foo\nbar",
|
|
"foo bar",
|
|
},
|
|
{
|
|
"foo\nbar \n ",
|
|
"foo\nbar",
|
|
"foo\nbar",
|
|
"foo bar",
|
|
},
|
|
{
|
|
"foo \nbar",
|
|
"foo \nbar",
|
|
"foo \nbar",
|
|
"foo\nbar",
|
|
},
|
|
{
|
|
"foo \nbar",
|
|
"foo \nbar",
|
|
"foo \nbar",
|
|
"foo\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\nbar",
|
|
"foo\n\nbar",
|
|
"foo\n\nbar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n\tbar",
|
|
"foo\n\n\tbar",
|
|
"foo\n\n\tbar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n\nbar",
|
|
"foo\n\nbar",
|
|
"foo\n\nbar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n\n\tbar",
|
|
"foo\n\n\tbar",
|
|
"foo\n\n\tbar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo\n\n\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\n bar",
|
|
"foo\n\nbar",
|
|
},
|
|
{
|
|
"foo.\nbar",
|
|
"foo.\nbar",
|
|
"foo.\nbar",
|
|
"foo.\nbar",
|
|
},
|
|
{
|
|
"foo. \nbar",
|
|
"foo. \nbar",
|
|
"foo. \nbar",
|
|
"foo.\nbar",
|
|
},
|
|
{
|
|
"foo\n*bar",
|
|
"foo\n\\*bar",
|
|
"foo\n*bar",
|
|
"foo\n*bar",
|
|
},
|
|
{
|
|
"foo\nbar",
|
|
"foo\nbar",
|
|
"foo\nbar",
|
|
"foo bar",
|
|
},
|
|
{
|
|
"Tests primality of `p`.",
|
|
"Tests primality of `p`.",
|
|
"Tests primality of `p`.",
|
|
"Tests primality of `p`.",
|
|
},
|
|
{
|
|
"'`' should not occur in `Code`",
|
|
"'\\`' should not occur in `Code`",
|
|
"'`' should not occur in `Code`",
|
|
"'`' should not occur in `Code`",
|
|
},
|
|
{
|
|
"`not\nparsed`",
|
|
"\\`not\nparsed\\`",
|
|
"`not\nparsed`",
|
|
"`not parsed`",
|
|
}};
|
|
|
|
for (const auto &C : Cases) {
|
|
markup::Document Output;
|
|
parseDocumentation(C.Documentation, Output);
|
|
|
|
EXPECT_EQ(Output.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
|
|
EXPECT_EQ(Output.asMarkdown(), C.ExpectedRenderMarkdown);
|
|
EXPECT_EQ(Output.asPlainText(), C.ExpectedRenderPlainText);
|
|
}
|
|
}
|
|
|
|
// This is a separate test as headings don't create any differences in
|
|
// plaintext mode.
|
|
TEST(Hover, PresentHeadings) {
|
|
HoverInfo HI;
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Name = "foo";
|
|
|
|
EXPECT_EQ(HI.present(MarkupKind::Markdown), "### variable `foo`");
|
|
}
|
|
|
|
// This is a separate test as rulers behave differently in markdown vs
|
|
// plaintext.
|
|
TEST(Hover, PresentRulers) {
|
|
HoverInfo HI;
|
|
HI.Kind = index::SymbolKind::Variable;
|
|
HI.Name = "foo";
|
|
HI.Value = "val";
|
|
HI.Definition = "def";
|
|
|
|
llvm::StringRef ExpectedMarkdown = //
|
|
"### variable `foo`\n"
|
|
"\n"
|
|
"---\n"
|
|
"Value = `val`\n"
|
|
"\n"
|
|
"---\n"
|
|
"```cpp\n"
|
|
"def\n"
|
|
"```";
|
|
EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedMarkdown);
|
|
|
|
llvm::StringRef ExpectedPlaintext = R"pt(variable foo
|
|
|
|
Value = val
|
|
|
|
def)pt";
|
|
EXPECT_EQ(HI.present(MarkupKind::PlainText), ExpectedPlaintext);
|
|
}
|
|
|
|
TEST(Hover, SpaceshipTemplateNoCrash) {
|
|
Annotations T(R"cpp(
|
|
namespace std {
|
|
struct strong_ordering {
|
|
int n;
|
|
constexpr operator int() const { return n; }
|
|
static const strong_ordering equal, greater, less;
|
|
};
|
|
constexpr strong_ordering strong_ordering::equal = {0};
|
|
constexpr strong_ordering strong_ordering::greater = {1};
|
|
constexpr strong_ordering strong_ordering::less = {-1};
|
|
}
|
|
|
|
template <typename T>
|
|
struct S {
|
|
// Foo bar baz
|
|
friend auto operator<=>(S, S) = default;
|
|
};
|
|
static_assert(S<void>() =^= S<void>());
|
|
)cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-std=c++20");
|
|
auto AST = TU.build();
|
|
auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
EXPECT_EQ(HI->Documentation, "");
|
|
}
|
|
|
|
TEST(Hover, ForwardStructNoCrash) {
|
|
Annotations T(R"cpp(
|
|
struct Foo;
|
|
int bar;
|
|
auto baz = (Fo^o*)&bar;
|
|
)cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(HI);
|
|
EXPECT_EQ(*HI->Value, "&bar");
|
|
}
|
|
|
|
TEST(Hover, FunctionParameterDefaulValueNotEvaluatedOnInvalidDecls) {
|
|
struct {
|
|
const char *const Code;
|
|
const std::optional<std::string> HoverValue;
|
|
} Cases[] = {
|
|
{R"cpp(
|
|
// error-ok testing behavior on invalid decl
|
|
class Foo {};
|
|
void foo(Foo p^aram = nullptr);
|
|
)cpp",
|
|
std::nullopt},
|
|
{R"cpp(
|
|
class Foo {};
|
|
void foo(Foo *p^aram = nullptr);
|
|
)cpp",
|
|
"nullptr"},
|
|
};
|
|
|
|
for (const auto &C : Cases) {
|
|
Annotations T(C.Code);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(HI);
|
|
ASSERT_EQ(HI->Value, C.HoverValue);
|
|
}
|
|
}
|
|
|
|
TEST(Hover, DisableShowAKA) {
|
|
Annotations T(R"cpp(
|
|
using m_int = int;
|
|
m_int ^[[a]];
|
|
)cpp");
|
|
|
|
Config Cfg;
|
|
Cfg.Hover.ShowAKA = false;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.ExtraArgs.push_back("-std=c++17");
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
|
|
ASSERT_TRUE(H);
|
|
EXPECT_EQ(H->Type, HoverInfo::PrintedType("m_int"));
|
|
}
|
|
|
|
TEST(Hover, HideBigInitializers) {
|
|
Annotations T(R"cpp(
|
|
#define A(x) x, x, x, x
|
|
#define B(x) A(A(A(A(x))))
|
|
int a^rr[] = {B(0)};
|
|
)cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
|
|
ASSERT_TRUE(H);
|
|
EXPECT_EQ(H->Definition, "int arr[]");
|
|
}
|
|
|
|
#if defined(__aarch64__)
|
|
// FIXME: AARCH64 sanitizer buildbots are broken after 72142fbac4.
|
|
#define PREDEFINEMACROS_TEST(x) DISABLED_##x
|
|
#else
|
|
#define PREDEFINEMACROS_TEST(x) x
|
|
#endif
|
|
|
|
TEST(Hover, PREDEFINEMACROS_TEST(GlobalVarEnumeralCastNoCrash)) {
|
|
Annotations T(R"cpp(
|
|
using uintptr_t = __UINTPTR_TYPE__;
|
|
enum Test : uintptr_t {};
|
|
unsigned global_var;
|
|
void foo() {
|
|
Test v^al = static_cast<Test>(reinterpret_cast<uintptr_t>(&global_var));
|
|
}
|
|
)cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.PredefineMacros = true;
|
|
auto AST = TU.build();
|
|
auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(HI);
|
|
EXPECT_EQ(*HI->Value, "&global_var");
|
|
}
|
|
|
|
TEST(Hover, PREDEFINEMACROS_TEST(GlobalVarIntCastNoCrash)) {
|
|
Annotations T(R"cpp(
|
|
using uintptr_t = __UINTPTR_TYPE__;
|
|
unsigned global_var;
|
|
void foo() {
|
|
uintptr_t a^ddress = reinterpret_cast<uintptr_t>(&global_var);
|
|
}
|
|
)cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
TU.PredefineMacros = true;
|
|
auto AST = TU.build();
|
|
auto HI = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(HI);
|
|
EXPECT_EQ(*HI->Value, "&global_var");
|
|
}
|
|
|
|
TEST(Hover, Typedefs) {
|
|
Annotations T(R"cpp(
|
|
template <bool X, typename T, typename F>
|
|
struct cond { using type = T; };
|
|
template <typename T, typename F>
|
|
struct cond<false, T, F> { using type = F; };
|
|
|
|
template <bool X, typename T, typename F>
|
|
using type = typename cond<X, T, F>::type;
|
|
|
|
void foo() {
|
|
using f^oo = type<true, int, double>;
|
|
}
|
|
)cpp");
|
|
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
|
|
|
|
ASSERT_TRUE(H && H->Type);
|
|
EXPECT_EQ(H->Type->Type, "int");
|
|
EXPECT_EQ(H->Definition, "using foo = type<true, int, double>");
|
|
}
|
|
|
|
TEST(Hover, EvaluateMacros) {
|
|
llvm::StringRef PredefinedCXX = R"cpp(
|
|
#define X 42
|
|
#define SizeOf sizeof
|
|
#define AlignOf alignof
|
|
#define PLUS_TWO +2
|
|
#define TWO 2
|
|
|
|
using u64 = unsigned long long;
|
|
// calculate (a ** b) % p
|
|
constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
|
|
u64 ret = 1;
|
|
while (b) {
|
|
if (b & 1)
|
|
ret = (ret * a) % p;
|
|
a = (a * a) % p;
|
|
b >>= 1;
|
|
}
|
|
return ret;
|
|
}
|
|
#define last_n_digit(x, y, n) \
|
|
pow_with_mod(x, y, pow_with_mod(10, n, 2147483647))
|
|
#define declare_struct(X, name, value) \
|
|
struct X { \
|
|
constexpr auto name() { return value; } \
|
|
}
|
|
#define gnu_statement_expression(value) \
|
|
({ \
|
|
declare_struct(Widget, getter, value); \
|
|
Widget().getter(); \
|
|
})
|
|
#define define_lambda_begin(lambda, ...) \
|
|
[&](__VA_ARGS__) {
|
|
#define define_lambda_end() }
|
|
|
|
#define left_bracket [
|
|
#define right_bracket ]
|
|
#define dg_left_bracket <:
|
|
#define dg_right_bracket :>
|
|
#define array_decl(type, name, size) type name left_bracket size right_bracket
|
|
)cpp";
|
|
|
|
struct {
|
|
llvm::StringRef Code;
|
|
const std::function<void(std::optional<HoverInfo>, size_t /*Id*/)>
|
|
Validator;
|
|
} Cases[] = {
|
|
{
|
|
/*Code=*/R"cpp(
|
|
X^;
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_EQ(HI->Value, "42 (0x2a)");
|
|
EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int"));
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
Size^Of(int);
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_TRUE(HI->Value);
|
|
EXPECT_TRUE(HI->Type);
|
|
// Don't validate type or value of `sizeof` and `alignof` as we're
|
|
// getting different values or desugared types on different
|
|
// platforms. Same as below.
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
struct Y {
|
|
int y;
|
|
double z;
|
|
};
|
|
Alig^nOf(Y);
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_TRUE(HI->Value);
|
|
EXPECT_TRUE(HI->Type);
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
// 2**32 == 4294967296
|
|
last_n_di^git(2, 32, 6);
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_EQ(HI->Value, "967296 (0xec280)");
|
|
EXPECT_EQ(HI->Type, "u64");
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
gnu_statement_exp^ression(42);
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_EQ(HI->Value, "42 (0x2a)");
|
|
EXPECT_EQ(HI->Type, "int");
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
40 + PLU^S_TWO;
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_EQ(HI->Value, "2");
|
|
EXPECT_EQ(HI->Type, "int");
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
40 PLU^S_TWO;
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_FALSE(HI->Value) << HI->Value;
|
|
EXPECT_FALSE(HI->Type) << HI->Type;
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
40 + TW^O;
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t) {
|
|
EXPECT_EQ(HI->Value, "2");
|
|
EXPECT_EQ(HI->Type, "int");
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
arra^y_decl(int, vector, 10);
|
|
vector left_b^racket 3 right_b^racket;
|
|
vector dg_le^ft_bracket 3 dg_righ^t_bracket;
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t Id) {
|
|
switch (Id) {
|
|
case 0:
|
|
EXPECT_EQ(HI->Type, HoverInfo::PrintedType("int[10]"));
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
EXPECT_FALSE(HI->Type) << HI->Type;
|
|
EXPECT_FALSE(HI->Value) << HI->Value;
|
|
break;
|
|
default:
|
|
ASSERT_TRUE(false) << "Unhandled id: " << Id;
|
|
}
|
|
},
|
|
},
|
|
{
|
|
/*Code=*/R"cpp(
|
|
constexpr auto value = define_lamb^da_begin(lambda, int, char)
|
|
// Check if the expansion range is right.
|
|
return ^last_n_digit(10, 3, 3)^;
|
|
define_lam^bda_end();
|
|
)cpp",
|
|
/*Validator=*/
|
|
[](std::optional<HoverInfo> HI, size_t Id) {
|
|
switch (Id) {
|
|
case 0:
|
|
EXPECT_FALSE(HI->Value);
|
|
EXPECT_EQ(HI->Type, HoverInfo::PrintedType("const (lambda)"));
|
|
break;
|
|
case 1:
|
|
EXPECT_EQ(HI->Value, "0");
|
|
EXPECT_EQ(HI->Type, HoverInfo::PrintedType("u64"));
|
|
break;
|
|
case 2:
|
|
EXPECT_FALSE(HI);
|
|
break;
|
|
case 3:
|
|
EXPECT_FALSE(HI->Type) << HI->Type;
|
|
EXPECT_FALSE(HI->Value) << HI->Value;
|
|
break;
|
|
default:
|
|
ASSERT_TRUE(false) << "Unhandled id: " << Id;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
|
|
Config Cfg;
|
|
Cfg.Hover.ShowAKA = false;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
for (const auto &C : Cases) {
|
|
Annotations Code(
|
|
(PredefinedCXX + "void function() {\n" + C.Code + "}\n").str());
|
|
auto TU = TestTU::withCode(Code.code());
|
|
TU.ExtraArgs.push_back("-std=c++17");
|
|
auto AST = TU.build();
|
|
for (auto [Index, Position] : llvm::enumerate(Code.points())) {
|
|
C.Validator(getHover(AST, Position, format::getLLVMStyle(), nullptr),
|
|
Index);
|
|
}
|
|
}
|
|
|
|
Annotations C(R"c(
|
|
#define alignof _Alignof
|
|
void foo() {
|
|
al^ignof(struct { int x; char y[10]; });
|
|
}
|
|
)c");
|
|
|
|
auto TU = TestTU::withCode(C.code());
|
|
TU.Filename = "TestTU.c";
|
|
TU.ExtraArgs = {
|
|
"-std=c17",
|
|
};
|
|
auto AST = TU.build();
|
|
auto H = getHover(AST, C.point(), format::getLLVMStyle(), nullptr);
|
|
|
|
ASSERT_TRUE(H);
|
|
EXPECT_TRUE(H->Value);
|
|
EXPECT_TRUE(H->Type);
|
|
}
|
|
|
|
TEST(Hover, FunctionParameters) {
|
|
struct {
|
|
const char *const Code;
|
|
const std::function<void(HoverInfo &)> ExpectedBuilder;
|
|
std::string ExpectedRender;
|
|
} Cases[] = {
|
|
{R"cpp(/// Function doc
|
|
void foo(int [[^a]]);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int a";
|
|
HI.Documentation = "";
|
|
},
|
|
"### param `a`\n\n---\nType: `int`\n\n---\n```cpp\n// In foo\nint "
|
|
"a\n```"},
|
|
{R"cpp(/// Function doc
|
|
/// @param a this is doc for a
|
|
void foo(int [[^a]]);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int a";
|
|
HI.Documentation = "this is doc for a";
|
|
},
|
|
"### param `a`\n\n---\nType: `int`\n\nthis is doc for "
|
|
"a\n\n---\n```cpp\n// In foo\nint a\n```"},
|
|
{R"cpp(/// Function doc
|
|
/// @param b this is doc for b
|
|
void foo(int [[^a]], int b);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "a";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int a";
|
|
HI.Documentation = "";
|
|
},
|
|
"### param `a`\n\n---\nType: `int`\n\n---\n```cpp\n// In foo\nint "
|
|
"a\n```"},
|
|
{R"cpp(/// Function doc
|
|
/// @param b this is doc for \p b
|
|
void foo(int a, int [[^b]]);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "b";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int b";
|
|
HI.Documentation = "this is doc for \\p b";
|
|
},
|
|
"### param `b`\n\n---\nType: `int`\n\nthis is doc for "
|
|
"`b`\n\n---\n```cpp\n// In foo\nint b\n```"},
|
|
{R"cpp(/// Function doc
|
|
/// @param b this is doc for \p b
|
|
template <typename T>
|
|
void foo(T a, T [[^b]]);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "b";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "T";
|
|
HI.Definition = "T b";
|
|
HI.Documentation = "this is doc for \\p b";
|
|
},
|
|
"### param `b`\n\n---\nType: `T`\n\nthis is doc for "
|
|
"`b`\n\n---\n```cpp\n// In foo\nT b\n```"},
|
|
{R"cpp(/// Function doc
|
|
/// @param b this is <b>doc</b> <html-tag attribute/> <another-html-tag attribute="value">for</another-html-tag> \p b
|
|
void foo(int a, int [[^b]]);
|
|
)cpp",
|
|
[](HoverInfo &HI) {
|
|
HI.Name = "b";
|
|
HI.Kind = index::SymbolKind::Parameter;
|
|
HI.NamespaceScope = "";
|
|
HI.LocalScope = "foo::";
|
|
HI.Type = "int";
|
|
HI.Definition = "int b";
|
|
HI.Documentation =
|
|
"this is <b>doc</b> <html-tag attribute/> <another-html-tag "
|
|
"attribute=\"value\">for</another-html-tag> \\p b";
|
|
},
|
|
"### param `b`\n\n---\nType: `int`\n\nthis is \\<b>doc\\</b> "
|
|
"\\<html-tag attribute/> \\<another-html-tag "
|
|
"attribute=\"value\">for\\</another-html-tag> "
|
|
"`b`\n\n---\n```cpp\n// In foo\nint b\n```"},
|
|
};
|
|
|
|
// Create a tiny index, so tests above can verify documentation is fetched.
|
|
Symbol IndexSym = func("indexSymbol");
|
|
IndexSym.Documentation = "comment from index";
|
|
SymbolSlab::Builder Symbols;
|
|
Symbols.insert(IndexSym);
|
|
auto Index =
|
|
MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab());
|
|
|
|
for (const auto &Case : Cases) {
|
|
SCOPED_TRACE(Case.Code);
|
|
|
|
Annotations T(Case.Code);
|
|
TestTU TU = TestTU::withCode(T.code());
|
|
auto AST = TU.build();
|
|
Config Cfg;
|
|
Cfg.Hover.ShowAKA = true;
|
|
Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
auto H = getHover(AST, T.point(), format::getLLVMStyle(), Index.get());
|
|
ASSERT_TRUE(H);
|
|
HoverInfo Expected;
|
|
Expected.SymRange = T.range();
|
|
Case.ExpectedBuilder(Expected);
|
|
|
|
EXPECT_EQ(H->present(MarkupKind::Markdown), Case.ExpectedRender);
|
|
EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
|
|
EXPECT_EQ(H->LocalScope, Expected.LocalScope);
|
|
EXPECT_EQ(H->Name, Expected.Name);
|
|
EXPECT_EQ(H->Kind, Expected.Kind);
|
|
EXPECT_EQ(H->Documentation, Expected.Documentation);
|
|
EXPECT_EQ(H->Definition, Expected.Definition);
|
|
EXPECT_EQ(H->Type, Expected.Type);
|
|
EXPECT_EQ(H->ReturnType, Expected.ReturnType);
|
|
EXPECT_EQ(H->Parameters, Expected.Parameters);
|
|
EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
|
|
EXPECT_EQ(H->SymRange, Expected.SymRange);
|
|
EXPECT_EQ(H->Value, Expected.Value);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|