Matheus Izvekov 91cdd35008
[clang] Improve nested name specifier AST representation (#147835)
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:

![image](https://github.com/user-attachments/assets/700dce98-2cab-4aa8-97d1-b038c0bee831)

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
2025-08-09 05:06:53 -03:00

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