Implement demangling support for C++20 lambda expression extensions.

This implements demangling support for the mangling extensions specified
in https://github.com/itanium-cxx-abi/cxx-abi/pull/85, much of which is
implemented in Clang r359967 and r371004.

Specifically, this provides demangling for:

 * <template-param-decl> in <lambda-sig>
 * <template-param> with non-zero level
 * lambda-expression literals (not emitted by Clang yet)
 * nullptr literals
 * string literals

(The final two seem unrelated, but handling them was necessary in order
to disambiguate between lambda expressions and the other forms of
literal for which we have a type but no value.)

When demangling a <lambda-sig>, we form template parameters with no
corresponding argument, so we cannot substitute in the argument in the
demangling. Instead we invent synthetic names for the template
parameters (eg, '[]<typename $T>($T *x)').

llvm-svn: 371273
This commit is contained in:
Richard Smith 2019-09-06 23:53:21 +00:00
parent 28328c3771
commit 4bbe85c5d4
3 changed files with 429 additions and 31 deletions

View File

@ -171,6 +171,16 @@ struct DumpVisitor {
return printStr("SpecialSubKind::iostream");
}
}
void print(TemplateParamKind TPK) {
switch (TPK) {
case TemplateParamKind::Type:
return printStr("TemplateParamKind::Type");
case TemplateParamKind::NonType:
return printStr("TemplateParamKind::NonType");
case TemplateParamKind::Template:
return printStr("TemplateParamKind::Template");
}
}
void newLine() {
printStr("\n");

View File

@ -57,6 +57,11 @@
X(LocalName) \
X(VectorType) \
X(PixelVectorType) \
X(SyntheticTemplateParamName) \
X(TypeTemplateParamDecl) \
X(NonTypeTemplateParamDecl) \
X(TemplateTemplateParamDecl) \
X(TemplateParamPackDecl) \
X(ParameterPack) \
X(TemplateArgumentPack) \
X(ParameterPackExpansion) \
@ -91,6 +96,8 @@
X(ThrowExpr) \
X(UUIDOfExpr) \
X(BoolExpr) \
X(StringLiteral) \
X(LambdaExpr) \
X(IntegerCastExpr) \
X(IntegerLiteral) \
X(FloatLiteral) \
@ -303,7 +310,7 @@ inline Qualifiers operator|=(Qualifiers &Q1, Qualifiers Q2) {
return Q1 = static_cast<Qualifiers>(Q1 | Q2);
}
class QualType : public Node {
class QualType final : public Node {
protected:
const Qualifiers Quals;
const Node *Child;
@ -964,6 +971,127 @@ public:
}
};
enum class TemplateParamKind { Type, NonType, Template };
/// An invented name for a template parameter for which we don't have a
/// corresponding template argument.
///
/// This node is created when parsing the <lambda-sig> for a lambda with
/// explicit template arguments, which might be referenced in the parameter
/// types appearing later in the <lambda-sig>.
class SyntheticTemplateParamName final : public Node {
TemplateParamKind Kind;
unsigned Index;
public:
SyntheticTemplateParamName(TemplateParamKind Kind_, unsigned Index_)
: Node(KSyntheticTemplateParamName), Kind(Kind_), Index(Index_) {}
template<typename Fn> void match(Fn F) const { F(Kind, Index); }
void printLeft(OutputStream &S) const override {
switch (Kind) {
case TemplateParamKind::Type:
S += "$T";
break;
case TemplateParamKind::NonType:
S += "$N";
break;
case TemplateParamKind::Template:
S += "$TT";
break;
}
if (Index > 0)
S << Index - 1;
}
};
/// A template type parameter declaration, 'typename T'.
class TypeTemplateParamDecl final : public Node {
Node *Name;
public:
TypeTemplateParamDecl(Node *Name_)
: Node(KTypeTemplateParamDecl, Cache::Yes), Name(Name_) {}
template<typename Fn> void match(Fn F) const { F(Name); }
void printLeft(OutputStream &S) const override {
S += "typename ";
}
void printRight(OutputStream &S) const override {
Name->print(S);
}
};
/// A non-type template parameter declaration, 'int N'.
class NonTypeTemplateParamDecl final : public Node {
Node *Name;
Node *Type;
public:
NonTypeTemplateParamDecl(Node *Name_, Node *Type_)
: Node(KNonTypeTemplateParamDecl, Cache::Yes), Name(Name_), Type(Type_) {}
template<typename Fn> void match(Fn F) const { F(Name, Type); }
void printLeft(OutputStream &S) const override {
Type->printLeft(S);
if (!Type->hasRHSComponent(S))
S += " ";
}
void printRight(OutputStream &S) const override {
Name->print(S);
Type->printRight(S);
}
};
/// A template template parameter declaration,
/// 'template<typename T> typename N'.
class TemplateTemplateParamDecl final : public Node {
Node *Name;
NodeArray Params;
public:
TemplateTemplateParamDecl(Node *Name_, NodeArray Params_)
: Node(KTemplateTemplateParamDecl, Cache::Yes), Name(Name_),
Params(Params_) {}
template<typename Fn> void match(Fn F) const { F(Name, Params); }
void printLeft(OutputStream &S) const override {
S += "template<";
Params.printWithComma(S);
S += "> typename ";
}
void printRight(OutputStream &S) const override {
Name->print(S);
}
};
/// A template parameter pack declaration, 'typename ...T'.
class TemplateParamPackDecl final : public Node {
Node *Param;
public:
TemplateParamPackDecl(Node *Param_)
: Node(KTemplateParamPackDecl, Cache::Yes), Param(Param_) {}
template<typename Fn> void match(Fn F) const { F(Param); }
void printLeft(OutputStream &S) const override {
Param->printLeft(S);
S += "...";
}
void printRight(OutputStream &S) const override {
Param->printRight(S);
}
};
/// An unexpanded parameter pack (either in the expression or type context). If
/// this AST is correct, this node will have a ParameterPackExpansion node above
/// it.
@ -1410,21 +1538,36 @@ public:
};
class ClosureTypeName : public Node {
NodeArray TemplateParams;
NodeArray Params;
StringView Count;
public:
ClosureTypeName(NodeArray Params_, StringView Count_)
: Node(KClosureTypeName), Params(Params_), Count(Count_) {}
ClosureTypeName(NodeArray TemplateParams_, NodeArray Params_,
StringView Count_)
: Node(KClosureTypeName), TemplateParams(TemplateParams_),
Params(Params_), Count(Count_) {}
template<typename Fn> void match(Fn F) const { F(Params, Count); }
template<typename Fn> void match(Fn F) const {
F(TemplateParams, Params, Count);
}
void printDeclarator(OutputStream &S) const {
if (!TemplateParams.empty()) {
S += "<";
TemplateParams.printWithComma(S);
S += ">";
}
S += "(";
Params.printWithComma(S);
S += ")";
}
void printLeft(OutputStream &S) const override {
S += "\'lambda";
S += Count;
S += "\'(";
Params.printWithComma(S);
S += ")";
S += "\'";
printDeclarator(S);
}
};
@ -1902,6 +2045,38 @@ public:
}
};
class StringLiteral : public Node {
const Node *Type;
public:
StringLiteral(const Node *Type_) : Node(KStringLiteral), Type(Type_) {}
template<typename Fn> void match(Fn F) const { F(Type); }
void printLeft(OutputStream &S) const override {
S += "\"<";
Type->print(S);
S += ">\"";
}
};
class LambdaExpr : public Node {
const Node *Type;
void printLambdaDeclarator(OutputStream &S) const;
public:
LambdaExpr(const Node *Type_) : Node(KLambdaExpr), Type(Type_) {}
template<typename Fn> void match(Fn F) const { F(Type); }
void printLeft(OutputStream &S) const override {
S += "[]";
printLambdaDeclarator(S);
S += "{...}";
}
};
class IntegerCastExpr : public Node {
// ty(integer)
const Node *Ty;
@ -2034,6 +2209,39 @@ FOR_EACH_NODE_KIND(SPECIALIZATION)
#undef FOR_EACH_NODE_KIND
inline void LambdaExpr::printLambdaDeclarator(OutputStream &S) const {
struct LambdaDeclaratorPrinter {
OutputStream &S;
void operator()(const ClosureTypeName *LambdaType) {
LambdaType->printDeclarator(S);
}
// Walk through any qualifiers to find the lambda-expression.
void operator()(const SpecialName *Name) {
Name->match([&](StringView, const Node *Name) { Name->visit(*this); });
}
void operator()(const NestedName *Name) {
Name->match([&](const Node *, const Node *Name) { Name->visit(*this); });
}
void operator()(const LocalName *Name) {
Name->match([&](const Node *, const Node *Name) { Name->visit(*this); });
}
void operator()(const QualifiedName *Name) {
Name->match([&](const Node *, const Node *Name) { Name->visit(*this); });
}
void operator()(const GlobalQualifiedName *Name) {
Name->match([&](const Node *Child) { Child->visit(*this); });
}
void operator()(const StdQualifiedName *Name) {
Name->match([&](const Node *Child) { Child->visit(*this); });
}
void operator()(const Node *) {
// If we can't find the lambda type, just print '[]{...}'.
}
};
return Type->visit(LambdaDeclaratorPrinter{S});
}
template <class T, size_t N>
class PODSmallVector {
static_assert(std::is_pod<T>::value,
@ -2167,10 +2375,39 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
// table.
PODSmallVector<Node *, 32> Subs;
using TemplateParamList = PODSmallVector<Node *, 8>;
class ScopedTemplateParamList {
AbstractManglingParser *Parser;
size_t OldNumTemplateParamLists;
TemplateParamList Params;
public:
ScopedTemplateParamList(AbstractManglingParser *Parser)
: Parser(Parser),
OldNumTemplateParamLists(Parser->TemplateParams.size()) {
Parser->TemplateParams.push_back(&Params);
}
~ScopedTemplateParamList() {
assert(Parser->TemplateParams.size() >= OldNumTemplateParamLists);
Parser->TemplateParams.dropBack(OldNumTemplateParamLists);
}
void push_back(Node *Param) {
Params.push_back(Param);
}
};
// Template parameter table. Like the above, but referenced like "T42_".
// This has a smaller size compared to Subs and Names because it can be
// stored on the stack.
PODSmallVector<Node *, 8> TemplateParams;
TemplateParamList OuterTemplateParams;
// Lists of template parameters indexed by template parameter depth,
// referenced like "TL2_4_". If nonempty, element 0 is always
// OuterTemplateParams; inner elements are always template parameter lists of
// lambda expressions. For a generic lambda with no explicit template
// parameter list, the corresponding parameter list pointer will be null.
PODSmallVector<TemplateParamList *, 4> TemplateParams;
// Set of unresolved forward <template-param> references. These can occur in a
// conversion operator's type, and are resolved in the enclosing <encoding>.
@ -2178,7 +2415,9 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
bool TryToParseTemplateArgs = true;
bool PermitForwardTemplateReferences = false;
bool ParsingLambdaParams = false;
size_t ParsingLambdaParamsAtLevel = (size_t)-1;
unsigned NumSyntheticTemplateParameters[3] = {};
Alloc ASTAllocator;
@ -2193,9 +2432,10 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
Names.clear();
Subs.clear();
TemplateParams.clear();
ParsingLambdaParams = false;
ParsingLambdaParamsAtLevel = (size_t)-1;
TryToParseTemplateArgs = true;
PermitForwardTemplateReferences = false;
NumSyntheticTemplateParameters = {};
ASTAllocator.reset();
}
@ -2253,6 +2493,7 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
bool parseSeqId(size_t *Out);
Node *parseSubstitution();
Node *parseTemplateParam();
Node *parseTemplateParamDecl();
Node *parseTemplateArgs(bool TagTemplates = false);
Node *parseTemplateArg();
@ -2301,9 +2542,10 @@ template <typename Derived, typename Alloc> struct AbstractManglingParser {
size_t E = ForwardTemplateRefs.size();
for (; I < E; ++I) {
size_t Idx = ForwardTemplateRefs[I]->Index;
if (Idx >= TemplateParams.size())
if (TemplateParams.empty() || !TemplateParams[0] ||
Idx >= TemplateParams[0]->size())
return true;
ForwardTemplateRefs[I]->Ref = TemplateParams[Idx];
ForwardTemplateRefs[I]->Ref = (*TemplateParams[0])[Idx];
}
ForwardTemplateRefs.dropBack(State.ForwardTemplateRefsBegin);
return false;
@ -2470,7 +2712,12 @@ AbstractManglingParser<Derived, Alloc>::parseUnqualifiedName(NameState *State) {
// <lambda-sig> ::= <parameter type>+ # Parameter types or "v" if the lambda has no parameters
template <typename Derived, typename Alloc>
Node *
AbstractManglingParser<Derived, Alloc>::parseUnnamedTypeName(NameState *) {
AbstractManglingParser<Derived, Alloc>::parseUnnamedTypeName(NameState *State) {
// <template-params> refer to the innermost <template-args>. Clear out any
// outer args that we may have inserted into TemplateParams.
if (State != nullptr)
TemplateParams.clear();
if (consumeIf("Ut")) {
StringView Count = parseNumber();
if (!consumeIf('_'))
@ -2478,22 +2725,60 @@ AbstractManglingParser<Derived, Alloc>::parseUnnamedTypeName(NameState *) {
return make<UnnamedTypeName>(Count);
}
if (consumeIf("Ul")) {
NodeArray Params;
SwapAndRestore<bool> SwapParams(ParsingLambdaParams, true);
SwapAndRestore<size_t> SwapParams(ParsingLambdaParamsAtLevel,
TemplateParams.size());
ScopedTemplateParamList LambdaTemplateParams(this);
size_t ParamsBegin = Names.size();
while (look() == 'T' &&
StringView("yptn").find(look(1)) != StringView::npos) {
Node *T = parseTemplateParamDecl();
if (!T)
return nullptr;
LambdaTemplateParams.push_back(T);
Names.push_back(T);
}
NodeArray TempParams = popTrailingNodeArray(ParamsBegin);
// FIXME: If TempParams is empty and none of the function parameters
// includes 'auto', we should remove LambdaTemplateParams from the
// TemplateParams list. Unfortunately, we don't find out whether there are
// any 'auto' parameters until too late in an example such as:
//
// template<typename T> void f(
// decltype([](decltype([]<typename T>(T v) {}),
// auto) {})) {}
// template<typename T> void f(
// decltype([](decltype([]<typename T>(T w) {}),
// int) {})) {}
//
// Here, the type of v is at level 2 but the type of w is at level 1. We
// don't find this out until we encounter the type of the next parameter.
//
// However, compilers can't actually cope with the former example in
// practice, and it's likely to be made ill-formed in future, so we don't
// need to support it here.
//
// If we encounter an 'auto' in the function parameter types, we will
// recreate a template parameter scope for it, but any intervening lambdas
// will be parsed in the 'wrong' template parameter depth.
if (TempParams.empty())
TemplateParams.pop_back();
if (!consumeIf("vE")) {
size_t ParamsBegin = Names.size();
do {
Node *P = getDerived().parseType();
if (P == nullptr)
return nullptr;
Names.push_back(P);
} while (!consumeIf('E'));
Params = popTrailingNodeArray(ParamsBegin);
}
NodeArray Params = popTrailingNodeArray(ParamsBegin);
StringView Count = parseNumber();
if (!consumeIf('_'))
return nullptr;
return make<ClosureTypeName>(Params, Count);
return make<ClosureTypeName>(TempParams, Params, Count);
}
if (consumeIf("Ub")) {
(void)parseNumber();
@ -3949,6 +4234,7 @@ Node *AbstractManglingParser<Derived, Alloc>::parseConversionExpr() {
// ::= L <type> <value float> E # floating literal
// ::= L <string type> E # string literal
// ::= L <nullptr type> E # nullptr literal (i.e., "LDnE")
// ::= L <lambda type> E # lambda expression
// FIXME: ::= L <type> <real-part float> _ <imag-part float> E # complex floating point literal (C 2000)
// ::= L <mangled-name> E # external name
template <typename Derived, typename Alloc>
@ -4020,6 +4306,19 @@ Node *AbstractManglingParser<Derived, Alloc>::parseExprPrimary() {
return R;
}
return nullptr;
case 'A': {
Node *T = getDerived().parseType();
if (T == nullptr)
return nullptr;
// FIXME: We need to include the string contents in the mangling.
if (consumeIf('E'))
return make<StringLiteral>(T);
return nullptr;
}
case 'D':
if (consumeIf("DnE"))
return make<NameType>("nullptr");
return nullptr;
case 'T':
// Invalid mangled name per
// http://sourcerytools.com/pipermail/cxx-abi-dev/2011-August/002422.html
@ -4036,7 +4335,7 @@ Node *AbstractManglingParser<Derived, Alloc>::parseExprPrimary() {
return make<IntegerCastExpr>(T, N);
}
if (consumeIf('E'))
return T;
return make<LambdaExpr>(T);
return nullptr;
}
}
@ -5062,11 +5361,22 @@ Node *AbstractManglingParser<Derived, Alloc>::parseSubstitution() {
// <template-param> ::= T_ # first template parameter
// ::= T <parameter-2 non-negative number> _
// ::= TL <level-1> __
// ::= TL <level-1> _ <parameter-2 non-negative number> _
template <typename Derived, typename Alloc>
Node *AbstractManglingParser<Derived, Alloc>::parseTemplateParam() {
if (!consumeIf('T'))
return nullptr;
size_t Level = 0;
if (consumeIf('L')) {
if (parsePositiveInteger(&Level))
return nullptr;
++Level;
if (!consumeIf('_'))
return nullptr;
}
size_t Index = 0;
if (!consumeIf('_')) {
if (parsePositiveInteger(&Index))
@ -5076,15 +5386,11 @@ Node *AbstractManglingParser<Derived, Alloc>::parseTemplateParam() {
return nullptr;
}
// Itanium ABI 5.1.8: In a generic lambda, uses of auto in the parameter list
// are mangled as the corresponding artificial template type parameter.
if (ParsingLambdaParams)
return make<NameType>("auto");
// If we're in a context where this <template-param> refers to a
// <template-arg> further ahead in the mangled name (currently just conversion
// operator types), then we should only look it up in the right context.
if (PermitForwardTemplateReferences) {
// This can only happen at the outermost level.
if (PermitForwardTemplateReferences && Level == 0) {
Node *ForwardRef = make<ForwardTemplateReference>(Index);
if (!ForwardRef)
return nullptr;
@ -5094,9 +5400,78 @@ Node *AbstractManglingParser<Derived, Alloc>::parseTemplateParam() {
return ForwardRef;
}
if (Index >= TemplateParams.size())
if (Level >= TemplateParams.size() || !TemplateParams[Level] ||
Index >= TemplateParams[Level]->size()) {
// Itanium ABI 5.1.8: In a generic lambda, uses of auto in the parameter
// list are mangled as the corresponding artificial template type parameter.
if (ParsingLambdaParamsAtLevel == Level && Level <= TemplateParams.size()) {
// This will be popped by the ScopedTemplateParamList in
// parseUnnamedTypeName.
if (Level == TemplateParams.size())
TemplateParams.push_back(nullptr);
return make<NameType>("auto");
}
return nullptr;
return TemplateParams[Index];
}
return (*TemplateParams[Level])[Index];
}
// <template-param-decl> ::= Ty # type parameter
// ::= Tn <type> # non-type parameter
// ::= Tt <template-param-decl>* E # template parameter
// ::= Tp <template-param-decl> # parameter pack
template <typename Derived, typename Alloc>
Node *AbstractManglingParser<Derived, Alloc>::parseTemplateParamDecl() {
auto InventTemplateParamName = [&](TemplateParamKind Kind) {
unsigned Index = NumSyntheticTemplateParameters[(int)Kind]++;
Node *N = make<SyntheticTemplateParamName>(Kind, Index);
if (N) TemplateParams.back()->push_back(N);
return N;
};
if (consumeIf("Ty")) {
Node *Name = InventTemplateParamName(TemplateParamKind::Type);
if (!Name)
return nullptr;
return make<TypeTemplateParamDecl>(Name);
}
if (consumeIf("Tn")) {
Node *Name = InventTemplateParamName(TemplateParamKind::NonType);
if (!Name)
return nullptr;
Node *Type = parseType();
if (!Type)
return nullptr;
return make<NonTypeTemplateParamDecl>(Name, Type);
}
if (consumeIf("Tt")) {
Node *Name = InventTemplateParamName(TemplateParamKind::Template);
if (!Name)
return nullptr;
size_t ParamsBegin = Names.size();
ScopedTemplateParamList TemplateTemplateParamParams(this);
while (!consumeIf("E")) {
Node *P = parseTemplateParamDecl();
if (!P)
return nullptr;
Names.push_back(P);
}
NodeArray Params = popTrailingNodeArray(ParamsBegin);
return make<TemplateTemplateParamDecl>(Name, Params);
}
if (consumeIf("Tp")) {
Node *P = parseTemplateParamDecl();
if (!P)
return nullptr;
return make<TemplateParamPackDecl>(P);
}
return nullptr;
}
// <template-arg> ::= <type> # type or template
@ -5153,8 +5528,11 @@ AbstractManglingParser<Derived, Alloc>::parseTemplateArgs(bool TagTemplates) {
// <template-params> refer to the innermost <template-args>. Clear out any
// outer args that we may have inserted into TemplateParams.
if (TagTemplates)
if (TagTemplates) {
TemplateParams.clear();
TemplateParams.push_back(&OuterTemplateParams);
OuterTemplateParams.clear();
}
size_t ArgsBegin = Names.size();
while (!consumeIf('E')) {
@ -5172,7 +5550,7 @@ AbstractManglingParser<Derived, Alloc>::parseTemplateArgs(bool TagTemplates) {
if (!TableEntry)
return nullptr;
}
TemplateParams.push_back(TableEntry);
TemplateParams.back()->push_back(TableEntry);
} else {
Node *Arg = getDerived().parseTemplateArg();
if (Arg == nullptr)

View File

@ -29582,8 +29582,8 @@ const char* cases[][2] =
{"_ZSteqIcEN9__gnu_cxx11__enable_ifIXsr9__is_charIT_EE7__valueEbE6__typeERKSbIS2_St11char_traitsIS2_ESaIS2_EESA_", "__gnu_cxx::__enable_if<__is_char<char>::__value, bool>::__type std::operator==<char>(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)"},
{"_ZZ10+[Foo bar]E3Baz", "+[Foo bar]::Baz"},
{"_ZN9__gnu_cxx17__normal_iteratorIPKSt4pairISsbESt6vectorIS2_SaIS2_EEEC5ERKS4_", "__gnu_cxx::__normal_iterator<std::pair<std::string, bool> const*, std::vector<std::pair<std::string, bool>, std::allocator<std::pair<std::string, bool> > > >::__normal_iterator(std::pair<std::string, bool> const* const&)"},
{"_Z1fIiEDTeqfp_LDnEEPT_", "decltype((fp) == (std::nullptr_t)) f<int>(int*)"},
{"_Z1fIiEDTeqfp1_LDnEEicPT_", "decltype((fp1) == (std::nullptr_t)) f<int>(int, char, int*)"},
{"_Z1fIiEDTeqfp_LDnEEPT_", "decltype((fp) == (nullptr)) f<int>(int*)"},
{"_Z1fIiEDTeqfp1_LDnEEicPT_", "decltype((fp1) == (nullptr)) f<int>(int, char, int*)"},
{"_ZZN1S1fEiiEd0_NKUlvE_clEv", "S::f(int, int)::'lambda'()::operator()() const"},
{"_Z3fooPM2ABi", "foo(int AB::**)"},
{"_Z1rM1GFivEMS_KFivES_M1HFivES1_4whatIKS_E5what2IS8_ES3_", "r(int (G::*)(), int (G::*)() const, G, int (H::*)(), int (G::*)(), what<G const>, what2<G const>, int (G::*)() const)"},
@ -29669,6 +29669,10 @@ const char* cases[][2] =
{"_ZN6test481fINS_1SEEEvPTuNT_1uE", "void test48::f<test48::S>(union test48::S::u*)"},
{"_ZN6test451fINS_1SEEEvPTeNT_1eE", "void test45::f<test45::S>(enum test45::S::e*)"},
// String literals
// FIXME: We need to encode the string contents in order to avoid symbol collisions.
{"_Z1fIcEvDTcv3StrIT_ELA6_KcEE", "void f<char>(decltype((Str<char>)(\"<char const [6]>\")))"},
// Initializer list expressions
{"_ZN5test43tf2INS_1XEEEvDTnw_T_piilLi1EEEE", "void test4::tf2<test4::X>(decltype(new test4::X({1})))"},
{"_ZN5test73fA1IiEEDTcmtlNS_1AELi1ELi2EEcvT__EES2_", "decltype((test7::A{1, 2}) , ((int)())) test7::fA1<int>(int)"},
@ -29775,6 +29779,12 @@ const char* cases[][2] =
// C++2a char8_t:
{"_ZTSPDu", "typeinfo name for char8_t*"},
// C++2a lambda-expressions:
{"_ZNK1xMUlTyT_E_clIiEEDaS_", "auto x::'lambda'<typename $T>($T)::operator()<int>(x) const"},
{"_ZNK1xMUlTnPA3_ivE_clILS0_0EEEDav", "auto x::'lambda'<int (*$N) [3]>()::operator()<(int [3])0>() const"},
{"_ZNK1xMUlTyTtTyTnT_TpTnPA3_TL0__ETpTyvE_clIi1XJfEEEDav", "auto x::'lambda'<typename $T, template<typename $T0, $T $N, $T0 (*...$N0) [3]> typename $TT, typename ...$T1>()::operator()<int, X, float>() const"},
{"_ZN1AIiE1fIfEEvDTLZ1AIiEEUlTyTtTyTnTL1__ETL0_1_T_TL0__E_EE", "void A<int>::f<float>(decltype([]<typename $T, template<typename $T0, $T0 $N> typename $TT>($TT, int, $T){...}))"},
};
const unsigned N = sizeof(cases) / sizeof(cases[0]);