Files
clice/tests/unit/AST/Selection.cpp
2025-09-07 23:27:39 +08:00

766 lines
27 KiB
C++

#include "Test/Tester.h"
#include "AST/Selection.h"
#include "clang/Lex/Lexer.h"
namespace clice {
std::ostringstream& operator<< (std::ostringstream& os, const LocalSourceRange& range) {
os << range.begin << " - " << range.end;
return os;
}
} // namespace clice
namespace clice::testing {
namespace {
using namespace clang;
static unsigned getTokenLengthAtLoc(SourceLocation Loc,
const SourceManager& SM,
const LangOptions& LangOpts) {
clang::Token TheTok;
if(clang::Lexer::getRawToken(Loc, TheTok, SM, LangOpts))
return 0;
// FIXME: Here we check whether the token at the location is a greatergreater
// (>>) token and consider it as a single greater (>). This is to get it
// working for templates but it isn't correct for the right shift operator. We
// can avoid this by using half open char ranges in getFileRange() but getting
// token ending is not well supported in macroIDs.
if(TheTok.is(tok::greatergreater))
return 1;
return TheTok.getLength();
}
// Returns location of the starting of the token at a given EndLoc
static SourceLocation getLocForTokenBegin(SourceLocation EndLoc,
const SourceManager& SM,
const LangOptions& LangOpts) {
return EndLoc.getLocWithOffset(-(signed)getTokenLengthAtLoc(EndLoc, SM, LangOpts));
}
// Returns location of the last character of the token at a given loc
static SourceLocation getLocForTokenEnd(SourceLocation BeginLoc,
const SourceManager& SM,
const LangOptions& LangOpts) {
unsigned Len = getTokenLengthAtLoc(BeginLoc, SM, LangOpts);
return BeginLoc.getLocWithOffset(Len ? Len - 1 : 0);
}
// Converts a char source range to a token range.
static SourceRange toTokenRange(CharSourceRange Range,
const SourceManager& SM,
const LangOptions& LangOpts) {
if(!Range.isTokenRange())
Range.setEnd(getLocForTokenBegin(Range.getEnd(), SM, LangOpts));
return Range.getAsRange();
}
// Returns the union of two token ranges.
// To find the maximum of the Ends of the ranges, we compare the location of the
// last character of the token.
static SourceRange unionTokenRange(SourceRange R1,
SourceRange R2,
const SourceManager& SM,
const LangOptions& LangOpts) {
SourceLocation Begin =
SM.isBeforeInTranslationUnit(R1.getBegin(), R2.getBegin()) ? R1.getBegin() : R2.getBegin();
SourceLocation End = SM.isBeforeInTranslationUnit(getLocForTokenEnd(R1.getEnd(), SM, LangOpts),
getLocForTokenEnd(R2.getEnd(), SM, LangOpts))
? R2.getEnd()
: R1.getEnd();
return SourceRange(Begin, End);
}
bool isValidFileRange(const SourceManager& Mgr, SourceRange R) {
if(!R.getBegin().isValid() || !R.getEnd().isValid())
return false;
FileID BeginFID;
size_t BeginOffset = 0;
std::tie(BeginFID, BeginOffset) = Mgr.getDecomposedLoc(R.getBegin());
FileID EndFID;
size_t EndOffset = 0;
std::tie(EndFID, EndOffset) = Mgr.getDecomposedLoc(R.getEnd());
return BeginFID.isValid() && BeginFID == EndFID && BeginOffset <= EndOffset;
}
SourceLocation includeHashLoc(FileID IncludedFile, const SourceManager& SM) {
assert(SM.getLocForEndOfFile(IncludedFile).isFileID());
FileID IncludingFile;
unsigned Offset;
std::tie(IncludingFile, Offset) = SM.getDecomposedExpansionLoc(SM.getIncludeLoc(IncludedFile));
bool Invalid = false;
llvm::StringRef Buf = SM.getBufferData(IncludingFile, &Invalid);
if(Invalid)
return SourceLocation();
// Now buf is "...\n#include <foo>\n..."
// and Offset points here: ^
// Rewind to the preceding # on the line.
assert(Offset < Buf.size());
for(;; --Offset) {
if(Buf[Offset] == '#')
return SM.getComposedLoc(IncludingFile, Offset);
if(Buf[Offset] == '\n' || Offset == 0) // no hash, what's going on?
return SourceLocation();
}
}
// Given a range whose endpoints may be in different expansions or files,
// tries to find a range within a common file by following up the expansion and
// include location in each.
static SourceRange rangeInCommonFile(SourceRange R,
const SourceManager& SM,
const LangOptions& LangOpts) {
// Fast path for most common cases.
if(SM.isWrittenInSameFile(R.getBegin(), R.getEnd()))
return R;
// Record the stack of expansion locations for the beginning, keyed by FileID.
llvm::DenseMap<FileID, SourceLocation> BeginExpansions;
for(SourceLocation Begin = R.getBegin(); Begin.isValid();
Begin = Begin.isFileID() ? includeHashLoc(SM.getFileID(Begin), SM)
: SM.getImmediateExpansionRange(Begin).getBegin()) {
BeginExpansions[SM.getFileID(Begin)] = Begin;
}
// Move up the stack of expansion locations for the end until we find the
// location in BeginExpansions with that has the same file id.
for(SourceLocation End = R.getEnd(); End.isValid();
End = End.isFileID()
? includeHashLoc(SM.getFileID(End), SM)
: toTokenRange(SM.getImmediateExpansionRange(End), SM, LangOpts).getEnd()) {
auto It = BeginExpansions.find(SM.getFileID(End));
if(It != BeginExpansions.end()) {
if(SM.getFileOffset(It->second) > SM.getFileOffset(End))
return SourceLocation();
return {It->second, End};
}
}
return SourceRange();
}
// Find an expansion range (not necessarily immediate) the ends of which are in
// the same file id.
static SourceRange getExpansionTokenRangeInSameFile(SourceLocation Loc,
const SourceManager& SM,
const LangOptions& LangOpts) {
return rangeInCommonFile(toTokenRange(SM.getImmediateExpansionRange(Loc), SM, LangOpts),
SM,
LangOpts);
}
// Returns the file range for a given Location as a Token Range
// This is quite similar to getFileLoc in SourceManager as both use
// getImmediateExpansionRange and getImmediateSpellingLoc (for macro IDs).
// However:
// - We want to maintain the full range information as we move from one file to
// the next. getFileLoc only uses the BeginLoc of getImmediateExpansionRange.
// - We want to split '>>' tokens as the lexer parses the '>>' in nested
// template instantiations as a '>>' instead of two '>'s.
// There is also getExpansionRange but it simply calls
// getImmediateExpansionRange on the begin and ends separately which is wrong.
static SourceRange getTokenFileRange(SourceLocation Loc,
const SourceManager& SM,
const LangOptions& LangOpts) {
SourceRange FileRange = Loc;
while(!FileRange.getBegin().isFileID()) {
if(SM.isMacroArgExpansion(FileRange.getBegin())) {
FileRange = unionTokenRange(SM.getImmediateSpellingLoc(FileRange.getBegin()),
SM.getImmediateSpellingLoc(FileRange.getEnd()),
SM,
LangOpts);
assert(SM.isWrittenInSameFile(FileRange.getBegin(), FileRange.getEnd()));
} else {
SourceRange ExpansionRangeForBegin =
getExpansionTokenRangeInSameFile(FileRange.getBegin(), SM, LangOpts);
SourceRange ExpansionRangeForEnd =
getExpansionTokenRangeInSameFile(FileRange.getEnd(), SM, LangOpts);
if(ExpansionRangeForBegin.isInvalid() || ExpansionRangeForEnd.isInvalid())
return SourceRange();
assert(SM.isWrittenInSameFile(ExpansionRangeForBegin.getBegin(),
ExpansionRangeForEnd.getBegin()) &&
"Both Expansion ranges should be in same file.");
FileRange = unionTokenRange(ExpansionRangeForBegin, ExpansionRangeForEnd, SM, LangOpts);
}
}
return FileRange;
}
std::optional<SourceRange> toHalfOpenFileRange(const SourceManager& SM,
const LangOptions& LangOpts,
SourceRange R) {
SourceRange R1 = getTokenFileRange(R.getBegin(), SM, LangOpts);
if(!isValidFileRange(SM, R1))
return std::nullopt;
SourceRange R2 = getTokenFileRange(R.getEnd(), SM, LangOpts);
if(!isValidFileRange(SM, R2))
return std::nullopt;
SourceRange Result = rangeInCommonFile(unionTokenRange(R1, R2, SM, LangOpts), SM, LangOpts);
unsigned TokLen = getTokenLengthAtLoc(Result.getEnd(), SM, LangOpts);
// Convert from closed token range to half-open (char) range
Result.setEnd(Result.getEnd().getLocWithOffset(TokLen));
if(!isValidFileRange(SM, Result))
return std::nullopt;
return Result;
}
} // namespace
suite<"SelectionTree"> selection = [] {
auto select_right = [](llvm::StringRef code, auto&& callback) {
Tester tester;
tester.add_main("main.cpp", code);
expect(that % tester.compile());
/// expect(that % tester.unit->diagnostics().empty());
auto points = tester.nameless_points();
expect(that % points.size() >= 1);
LocalSourceRange selected_range;
selected_range.begin = points[0];
selected_range.end = points.size() == 2 ? points[1] : points[0];
auto tree = SelectionTree::create_right(*tester.unit, selected_range);
callback(tester, tree);
};
auto expect_select = [&](llvm::StringRef code, const char* kind) {
select_right(code, [&](Tester& tester, SelectionTree& tree) {
auto node = tree.common_ancestor();
if(!kind) {
expect(that % !node);
} else {
expect(that % node);
auto range2 = toHalfOpenFileRange(tester.unit->context().getSourceManager(),
tester.unit->lang_options(),
node->source_range());
LocalSourceRange range = {
tester.unit->file_offset(range2->getBegin()),
tester.unit->file_offset(range2->getEnd()),
};
/// llvm::outs() << tree << "\n";
/// tree.print(llvm::outs(), *node, 2);
expect(that % node->kind() == llvm::StringRef(kind));
expect(that % range == tester.range());
}
});
};
test("Expressions") = [&] {
expect_select(R"(
struct AAA { struct BBB { static int ccc(); };};
int x = @[AAA::BBB::c$c$c]();
)",
"DeclRefExpr");
expect_select(R"(
struct AAA { struct BBB { static int ccc(); };};
int x = @[AAA::BBB::ccc($)];
)",
"CallExpr");
expect_select(R"(
struct S {
int foo() const;
int bar() { return @[f$oo](); }
};
)",
"MemberExpr");
expect_select(R"(void foo() { @[$foo](); })", "DeclRefExpr");
expect_select(R"(void foo() { @[f$oo](); })", "DeclRefExpr");
expect_select(R"(void foo() { @[fo$o](); })", "DeclRefExpr");
expect_select(R"(void foo() { @[foo$] (); })", "DeclRefExpr");
expect_select(R"(void foo() { @[foo$()]; })", "CallExpr");
expect_select(R"(void foo() { @[foo$()]; /*comment*/$})", "CallExpr");
expect_select(R"(const int x = 1, y = 2; int array[ @[$x] ][10][y];)", "DeclRefExpr");
expect_select(R"(const int x = 1, y = 2; int array[x][10][ @[$y] ];)", "DeclRefExpr");
expect_select(R"(void func(int x) { int v_array[ @[$x] ][10]; })", "DeclRefExpr");
expect_select(R"(
int a;
decltype(@[$a] + a) b;
)",
"DeclRefExpr");
expect_select(R"(
void func() { @[__$func__]; }
)",
"PredefinedExpr");
};
test("Literals") = [&] {
expect_select(R"(
auto lambda = [](const char*){ return 0; };
int x = lambda(@["y$"]);
)",
"StringLiteral");
expect_select(R"(int x = @[42]$;)", "IntegerLiteral");
expect_select(R"(const int x = 1, y = 2; int array[x][ @[$10] ][y];)", "IntegerLiteral");
expect_select(R"(
struct Foo{};
Foo operator""_ud(unsigned long long);
Foo x = @[$12_ud];
)",
"UserDefinedLiteral");
};
test("ControlFlow") = [&] {
expect_select(R"(
void foo() { @[if (1$11) { return; } else {$ }]} }
)",
"IfStmt");
expect_select(R"(int bar; void foo() @[{ foo (); }]$)", "CompoundStmt");
/// FIXME:
/// expect_select(R"(
/// /*error-ok*/
/// void func() @[{^])",
/// "CompoundStmt");
expect_select(R"(
struct Str {
const char *begin();
const char *end();
};
Str makeStr(const char*);
void loop() {
for (const char C : @[mak$eStr("foo"$)])
;
}
)",
"CallExpr");
};
test("Declarations") = [&] {
/// FIXME: how to handle this?
/// expect_select(R"(
/// #define TARGET void foo()
/// @[TAR$GET{ return; }]
/// )",
/// "FunctionDecl");
expect_select(R"(@[$void foo$()];)", "FunctionDecl");
expect_select(R"(@[void $foo()];)", "FunctionDecl");
expect_select(R"(
struct S { S(const char*); };
@[S s $= "foo"];
)",
"VarDecl");
expect_select(R"(
struct S { S(const char*); };
@[S $s = "foo"];
)",
"VarDecl");
expect_select(R"(
@[void (*$S)(int) = nullptr];
)",
"VarDecl");
expect_select(R"(@[int $a], b;)", "VarDecl");
expect_select(R"(@[int a, $b];)", "VarDecl");
expect_select(R"(@[struct {int x;} $y];)", "VarDecl");
expect_select(R"(struct foo { @[int has$h<:32:>]; };)", "FieldDecl");
expect_select(R"(struct {@[int $x];} y;)", "FieldDecl");
expect_select(R"(
void test(int bar) {
auto l = [ $@[foo = bar] ] { };
})",
"VarDecl");
};
test("Types") = [&] {
expect_select(R"(
struct AAA { struct BBB { static int ccc(); };};
int x = AAA::@[B$B$B]::ccc();
)",
"RecordTypeLoc");
expect_select(R"(
struct AAA { struct BBB { static int ccc(); };};
int x = AAA::@[B$BB$]::ccc();
)",
"RecordTypeLoc");
expect_select(R"(
struct Foo {};
struct Bar : private @[Fo$o] {};
)",
"RecordTypeLoc");
expect_select(R"(
struct Foo {};
struct Bar : @[Fo$o] {};
)",
"RecordTypeLoc");
expect_select(R"(@[$void] (*S)(int) = nullptr;)", "BuiltinTypeLoc");
/// expect_select(R"(@[void (*S)$(int)] = nullptr;)", "FunctionProtoTypeLoc");
expect_select(R"(@[void ($*S)(int)] = nullptr;)", "PointerTypeLoc");
/// expect_select(R"(@[void $(*S)(int)] = nullptr;)", "ParenTypeLoc");
expect_select(R"(@[$void] foo();)", "BuiltinTypeLoc");
expect_select(R"(@[void foo$()];)", "FunctionProtoTypeLoc");
expect_select(R"(const int x = 1, y = 2; @[i$nt] array[x][10][y];)", "BuiltinTypeLoc");
expect_select(R"(int (*getFunc(@[do$uble]))(int);)", "BuiltinTypeLoc");
expect_select(R"(class X{}; @[int X::$*]y[10];)", "MemberPointerTypeLoc");
expect_select(R"(const @[a$uto] x = 42;)", "AutoTypeLoc");
/// expect_select(R"(@[decltype$(1)] b;)", "DecltypeTypeLoc");
expect_select(R"(@[de$cltype(a$uto)] a = 1;)", "AutoTypeLoc");
expect_select(R"(
typedef int Foo;
enum Bar : @[Fo$o] {};
)",
"TypedefTypeLoc");
expect_select(R"(
typedef int Foo;
enum Bar : @[Fo$o];
)",
"TypedefTypeLoc");
};
test("CXXFeatures") = [&] {
expect_select(R"(
template <typename T>
int x = @[T::$U::]ccc();
)",
"NestedNameSpecifierLoc");
expect_select(R"(
struct Foo {};
struct Bar : @[v$ir$tual private Foo] {};
)",
"CXXBaseSpecifier");
expect_select(R"(
struct X { X(int); };
class Y {
X x;
Y() : @[$x(4)] {}
};
)",
"CXXCtorInitializer");
expect_select(R"(@[st$ruct {int x;}] y;)", "CXXRecordDecl");
expect_select(R"(struct foo { @[op$erator int()]; };)", "CXXConversionDecl");
expect_select(R"(struct foo { @[$~foo()]; };)", "CXXDestructorDecl");
expect_select(R"(struct foo { @[~$foo()]; };)", "CXXDestructorDecl");
expect_select(R"(struct foo { @[fo$o(){}] };)", "CXXConstructorDecl");
expect_select(R"(
struct S1 { void f(); };
struct S2 { S1 * operator->(); };
void test(S2 s2) {
s2@[-$>]f();
}
)",
"DeclRefExpr"); // Test for overloaded operator->
};
test("UsingEnum") = [&] {
expect_select(R"(
namespace ns { enum class A {}; };
using enum ns::@[$A];
)",
"EnumTypeLoc");
expect_select(R"(
namespace ns { enum class A {}; using B = A; };
using enum ns::@[$B];
)",
"TypedefTypeLoc");
expect_select(R"(
namespace ns { enum class A {}; };
using enum @[$ns::]A;
)",
"NestedNameSpecifierLoc");
expect_select(R"(
namespace ns { enum class A {}; };
@[using $enum ns::A];
)",
"UsingEnumDecl");
expect_select(R"(
namespace ns { enum class A {}; };
@[$using enum ns::A];
)",
"UsingEnumDecl");
};
test("Templates") = [&] {
expect_select(R"(template<typename ...T> void foo(@[T*$...]x);)", "PackExpansionTypeLoc");
expect_select(R"(template<typename ...T> void foo(@[$T]*...x);)",
"TemplateTypeParmTypeLoc");
expect_select(R"(template <typename T> void foo() { @[$T] t; })",
"TemplateTypeParmTypeLoc");
expect_select(R"(
template <class T> struct Foo {};
template <@[template<class> class /*cursor here*/$U]>
struct Foo<U<int>*> {};
)",
"TemplateTemplateParmDecl");
expect_select(R"(template <class T> struct foo { ~foo<@[$T]>(){} };)",
"TemplateTypeParmTypeLoc");
expect_select(R"(
template <typename> class Vector {};
template <template <typename> class Container> class A {};
A<@[V$ector]> a;
)",
"TemplateArgumentLoc");
};
test("Concepts") = [&] {
expect_select(R"(
template <class> concept C = true;
auto x = @[$C<int>];
)",
"ConceptReference");
expect_select(R"(
template <class> concept C = true;
@[$C] auto x = 0;
)",
"ConceptReference");
expect_select(R"(
template <class> concept C = true;
void foo(@[$C] auto x) {}
)",
"ConceptReference");
expect_select(R"(
template <class> concept C = true;
template <@[$C] x> int i = 0;
)",
"ConceptReference");
expect_select(R"(
namespace ns { template <class> concept C = true; }
auto x = @[ns::$C<int>];
)",
"ConceptReference");
expect_select(R"(
template <typename T, typename K>
concept D = true;
template <typename T> void g(D<@[$T]> auto abc) {}
)",
"TemplateTypeParmTypeLoc");
};
test("Attributes") = [&] {
expect_select(R"(
void f(int * __attribute__((@[no$nnull])) );
)",
"NonNullAttr");
expect_select(R"(
// Digraph syntax for attributes to avoid accidental annotations.
class [[gsl::Owner( @[in$t] )]] X{};
)",
"BuiltinTypeLoc");
};
test("Macros") = [&] {
expect_select(R"(
int x(int);
#define M(foo) x(foo)
int a = 42;
int b = M(@[$a]);
)",
"DeclRefExpr");
expect_select(R"(
void foo();
#define CALL_FUNCTION(X) X()
void bar() { CALL_FUNCTION(@[f$o$o]); }
)",
"DeclRefExpr");
expect_select(R"(
void foo();
#define CALL_FUNCTION(X) X()
void bar() { @[CALL_FUNC$TION(fo$o)]; }
)",
"CallExpr");
expect_select(R"(
void foo();
#define CALL_FUNCTION(X) X()
void bar() { @[C$ALL_FUNC$TION(foo)]; }
)",
"CallExpr");
};
test("NullOrInvalid") = [&] {
expect_select(R"(
void foo();
#$define CALL_FUNCTION(X) X($)
void bar() { CALL_FUNCTION(foo); }
)",
nullptr);
expect_select(R"(
void foo();
#define CALL_FUNCTION(X) X()
void bar() { CALL_FUNCTION(foo$)$; }
)",
nullptr);
expect_select(R"(
namespace ns {
#if 0
void fo$o() {}
#endif
}
)",
nullptr);
expect_select(R"(co$nst auto x = 42;)", nullptr);
expect_select(R"($)", nullptr);
expect_select(R"(int x = 42;$)", nullptr);
expect_select(R"($int x; int y;$)", nullptr);
expect_select(R"(void foo() { @[foo$] (); })",
"DeclRefExpr"); // Technically valid, but tricky
};
test("InjectedClassName") = [&] {
llvm::StringRef code = "struct $X { int x; };";
select_right(code, [](Tester& tester, SelectionTree& tree) {
auto ancestor = tree.common_ancestor();
expect(that % ancestor->kind() == llvm::StringRef("CXXRecordDecl"));
auto* D = dyn_cast<clang::CXXRecordDecl>(ancestor->get<clang::Decl>());
expect(that % !D->isInjectedClassName());
});
};
test("Metrics") = [&] {
llvm::StringRef code = R"cpp(
// error-ok: testing behavior on recovery expression
int foo();
int foo(int, int);
int x = fo^o(42);
)cpp";
/// FIXME:
};
test("Selected") = [&] {
/// FIXME:
};
test("PathologicalPreprocessor") = [&] {
/// FIXME:
};
test("IncludedFile") = [&] {
/// FIXME:
Tester tester;
llvm::StringRef code = R"(
#[expand.inc]
while (0)
#[main.cpp]
void test() {
#include "exp$and.inc"
break;
}
)";
tester.add_files("main.cpp", code);
expect(that % tester.compile());
auto point = tester.nameless_points("main.cpp")[0];
auto tree = SelectionTree::create_right(*tester.unit, {point, point});
expect(that % tester.unit->diagnostics().empty());
expect(that % tree.common_ancestor() == nullptr);
};
test("Implicit") = [&] {
llvm::StringRef code = R"cpp(
struct S { S(const char*); };
int f(S);
int x = f("$");
)cpp";
select_right(code, [](Tester& tester, SelectionTree& tree) {
auto ancestor = tree.common_ancestor();
expect(that % ancestor->kind() == llvm::StringRef("StringLiteral"));
expect(that % ancestor->parent->kind() == llvm::StringRef("ImplicitCastExpr"));
expect(that % ancestor->parent->parent->kind() == llvm::StringRef("CXXConstructExpr"));
auto implicit = ancestor->parent->parent->parent;
expect(that % implicit->kind() == llvm::StringRef("ImplicitCastExpr"));
expect(that % implicit->parent->kind() == llvm::StringRef("CallExpr"));
expect(that % ancestor == &implicit->ignore_implicit());
expect(that % &ancestor->outer_implicit() == implicit);
});
};
test("DeclContextIsLexical") = [&] {
llvm::StringRef code = R"cpp(
namespace a {
void f$oo();
}
void a::foo() { }
)cpp";
select_right(code, [](Tester& tester, SelectionTree& tree) {
auto ancestor = tree.common_ancestor();
expect(that % !ancestor->decl_context().isTranslationUnit());
});
code = R"cpp(
namespace a {
void foo();
}
void a::f$oo() { }
)cpp";
select_right(code, [](Tester& tester, SelectionTree& tree) {
auto ancestor = tree.common_ancestor();
expect(that % ancestor->decl_context().isTranslationUnit());
});
};
test("DeclContextLambda") = [&] {
llvm::StringRef code = R"cpp(
void foo();
auto lambda = [] {
return $foo();
};
)cpp";
select_right(code, [](Tester& tester, SelectionTree& tree) {
auto ancestor = tree.common_ancestor();
expect(that % ancestor->decl_context().isFunctionOrMethod());
});
};
test("UsingConcepts") = [&] {
llvm::StringRef code = R"cpp(
namespace ns {
template <typename T>
concept Foo = true;
}
using ns::Foo;
template <Fo$o... T, Fo$o auto U>
auto Func(Fo$o auto V) -> Fo$o decltype(auto) {
Fo$o auto W = V;
return W;
}
)cpp";
Tester tester;
tester.add_main("main.cpp", code);
expect(that % tester.compile());
auto points = tester.nameless_points();
for(auto point: tester.nameless_points()) {
auto tree = SelectionTree::create_right(*tester.unit, {point, point});
auto* C = tree.common_ancestor()->get<clang::ConceptReference>();
expect(that % C && C->getFoundDecl() &&
C->getFoundDecl()->getKind() == clang::Decl::UsingShadow);
}
};
};
} // namespace clice::testing