From 8abb5fb68f81b0e42d824bf080b1cef9a61559d6 Mon Sep 17 00:00:00 2001 From: Eduardo Caldas Date: Tue, 4 Aug 2020 17:33:36 +0000 Subject: [PATCH] [SyntaxTree] Use simplified grammar rule for `NestedNameSpecifier` grammar nodes This is our grammar rule for nested-name-specifiers: globalbal-specifier: /*empty*/ simple-template-specifier: template_opt simple-template-id name-specifier: global-specifier decltype-specifier identifier simple-template-specifier nested-name-specifier: list(name-specifier, ::, non-empty, terminated) It is a relaxed version of C++ [expr.prim.id] and quite simpler to map to our API. TODO: refine name specifiers, `simple-template-name-specifier` and decltype-name-specifier` are token soup for now. --- clang/include/clang/Tooling/Syntax/Nodes.h | 70 ++++++- clang/lib/Tooling/Syntax/BuildTree.cpp | 143 ++++++++++--- clang/lib/Tooling/Syntax/Nodes.cpp | 28 ++- clang/unittests/Tooling/Syntax/TreeTest.cpp | 314 ++++++++++++++++++---------- 4 files changed, 411 insertions(+), 144 deletions(-) diff --git a/clang/include/clang/Tooling/Syntax/Nodes.h b/clang/include/clang/Tooling/Syntax/Nodes.h index a5972a39..2273cb7 100644 --- a/clang/include/clang/Tooling/Syntax/Nodes.h +++ b/clang/include/clang/Tooling/Syntax/Nodes.h @@ -95,9 +95,13 @@ enum class NodeKind : uint16_t { TrailingReturnType, ParametersAndQualifiers, MemberPointer, + UnqualifiedId, + // Nested Name Specifiers. NestedNameSpecifier, - NameSpecifier, - UnqualifiedId + GlobalNameSpecifier, + DecltypeNameSpecifier, + IdentifierNameSpecifier, + SimpleTemplateNameSpecifier }; /// For debugging purposes. raw_ostream &operator<<(raw_ostream &OS, NodeKind K); @@ -138,6 +142,7 @@ enum class NodeRole : uint8_t { /// Tokens or Keywords ArrowToken, ExternKeyword, + TemplateKeyword, /// An inner statement for those that have only a single child of kind /// statement, e.g. loop body for while, for, etc; inner statement for case, /// default, etc. @@ -167,6 +172,7 @@ enum class NodeRole : uint8_t { IdExpression_id, IdExpression_qualifier, NestedNameSpecifier_specifier, + NestedNameSpecifier_delimiter, ParenExpression_subExpression }; /// For debugging purposes. @@ -195,12 +201,60 @@ public: }; /// A sequence of these specifiers make a `nested-name-specifier`. -/// e.g. the `std::` or `vector::` in `std::vector::size`. -class NameSpecifier final : public Tree { +/// e.g. the `std` or `vector` in `std::vector::size`. +class NameSpecifier : public Tree { public: - NameSpecifier() : Tree(NodeKind::NameSpecifier) {} + NameSpecifier(NodeKind K) : Tree(K) {} static bool classof(const Node *N) { - return N->kind() == NodeKind::NameSpecifier; + return N->kind() == NodeKind::GlobalNameSpecifier || + N->kind() == NodeKind::DecltypeNameSpecifier || + N->kind() == NodeKind::IdentifierNameSpecifier || + N->kind() == NodeKind::SimpleTemplateNameSpecifier; + } +}; + +/// The global namespace name specifier, this specifier doesn't correspond to a +/// token instead an absence of tokens before a `::` characterizes it, in +/// `::std::vector` it would be characterized by the absence of a token +/// before the first `::` +class GlobalNameSpecifier final : public NameSpecifier { +public: + GlobalNameSpecifier() : NameSpecifier(NodeKind::GlobalNameSpecifier) {} + static bool classof(const Node *N) { + return N->kind() == NodeKind::GlobalNameSpecifier; + } +}; + +/// A name specifier holding a decltype, of the form: `decltype ( expression ) ` +/// e.g. the `decltype(s)` in `decltype(s)::size`. +class DecltypeNameSpecifier final : public NameSpecifier { +public: + DecltypeNameSpecifier() : NameSpecifier(NodeKind::DecltypeNameSpecifier) {} + static bool classof(const Node *N) { + return N->kind() == NodeKind::DecltypeNameSpecifier; + } +}; + +/// A identifier name specifier, of the form `identifier` +/// e.g. the `std` in `std::vector::size`. +class IdentifierNameSpecifier final : public NameSpecifier { +public: + IdentifierNameSpecifier() + : NameSpecifier(NodeKind::IdentifierNameSpecifier) {} + static bool classof(const Node *N) { + return N->kind() == NodeKind::IdentifierNameSpecifier; + } +}; + +/// A name specifier with a simple-template-id, of the form `template_opt +/// identifier < template-args >` e.g. the `vector` in +/// `std::vector::size`. +class SimpleTemplateNameSpecifier final : public NameSpecifier { +public: + SimpleTemplateNameSpecifier() + : NameSpecifier(NodeKind::SimpleTemplateNameSpecifier) {} + static bool classof(const Node *N) { + return N->kind() == NodeKind::SimpleTemplateNameSpecifier; } }; @@ -213,6 +267,7 @@ public: return N->kind() <= NodeKind::NestedNameSpecifier; } std::vector specifiers(); + std::vector delimiters(); }; /// Models an `unqualified-id`. C++ [expr.prim.id.unqual] @@ -239,8 +294,7 @@ public: return N->kind() == NodeKind::IdExpression; } NestedNameSpecifier *qualifier(); - // TODO after expose `id-expression` from `DependentScopeDeclRefExpr`: - // Add accessor for `template_opt`. + Leaf *templateKeyword(); UnqualifiedId *unqualifiedId(); }; diff --git a/clang/lib/Tooling/Syntax/BuildTree.cpp b/clang/lib/Tooling/Syntax/BuildTree.cpp index 29b1a9d..21ac6b8 100644 --- a/clang/lib/Tooling/Syntax/BuildTree.cpp +++ b/clang/lib/Tooling/Syntax/BuildTree.cpp @@ -286,6 +286,11 @@ public: foldNode(Range, New, nullptr); } + void foldNode(ArrayRef Range, syntax::Tree *New, + NestedNameSpecifierLoc L) { + // FIXME: add mapping for NestedNameSpecifierLoc + foldNode(Range, New, nullptr); + } /// Notifies that we should not consume trailing semicolon when computing /// token range of \p D. void noticeDeclWithoutSemicolon(Decl *D); @@ -690,21 +695,6 @@ public: return true; } - syntax::NestedNameSpecifier * - BuildNestedNameSpecifier(NestedNameSpecifierLoc QualifierLoc) { - if (!QualifierLoc) - return nullptr; - for (auto it = QualifierLoc; it; it = it.getPrefix()) { - auto *NS = new (allocator()) syntax::NameSpecifier; - Builder.foldNode(Builder.getRange(it.getLocalSourceRange()), NS, nullptr); - Builder.markChild(NS, syntax::NodeRole::NestedNameSpecifier_specifier); - } - auto *NNS = new (allocator()) syntax::NestedNameSpecifier; - Builder.foldNode(Builder.getRange(QualifierLoc.getSourceRange()), NNS, - nullptr); - return NNS; - } - bool TraverseUserDefinedLiteral(UserDefinedLiteral *S) { // The semantic AST node `UserDefinedLiteral` (UDL) may have one child node // referencing the location of the UDL suffix (`_w` in `1.2_w`). The @@ -754,23 +744,118 @@ public: return true; } + syntax::NameSpecifier *BuildNameSpecifier(const NestedNameSpecifier &NNS) { + switch (NNS.getKind()) { + case NestedNameSpecifier::Global: + return new (allocator()) syntax::GlobalNameSpecifier; + case NestedNameSpecifier::Namespace: + case NestedNameSpecifier::NamespaceAlias: + case NestedNameSpecifier::Identifier: + return new (allocator()) syntax::IdentifierNameSpecifier; + case NestedNameSpecifier::TypeSpecWithTemplate: + return new (allocator()) syntax::SimpleTemplateNameSpecifier; + case NestedNameSpecifier::TypeSpec: { + const auto *NNSType = NNS.getAsType(); + assert(NNSType); + if (isa(NNSType)) + return new (allocator()) syntax::DecltypeNameSpecifier; + if (isa( + NNSType)) + return new (allocator()) syntax::SimpleTemplateNameSpecifier; + return new (allocator()) syntax::IdentifierNameSpecifier; + } + case NestedNameSpecifier::Super: + // FIXME: Support Microsoft's __super + llvm::report_fatal_error("We don't yet support the __super specifier", + true); + } + } + + // FIXME: Fix `NestedNameSpecifierLoc::getLocalSourceRange` for the + // `DependentTemplateSpecializationType` case. + /// Given a nested-name-specifier return the range for the last name specifier + /// + /// e.g. `std::T::template X::` => `template X::` + SourceRange getLocalSourceRange(const NestedNameSpecifierLoc &NNSLoc) { + auto SR = NNSLoc.getLocalSourceRange(); + + // The method `NestedNameSpecifierLoc::getLocalSourceRange` *should* return + // the desired `SourceRange`, but there is a corner + // case. For a `DependentTemplateSpecializationType` this method returns its + // qualifiers as well, in other words in the example above this method + // returns `T::template X::` instead of only `template X::` + if (auto TL = NNSLoc.getTypeLoc()) { + if (auto DependentTL = + TL.getAs()) { + // The 'template' keyword is always present in dependent template + // specializations. Except in the case of incorrect code + // TODO: Treat the case of incorrect code. + SR.setBegin(DependentTL.getTemplateKeywordLoc()); + } + } + + return SR; + } + + syntax::NestedNameSpecifier * + BuildNestedNameSpecifier(const NestedNameSpecifierLoc &QualifierLoc) { + if (!QualifierLoc) + return nullptr; + for (auto it = QualifierLoc; it; it = it.getPrefix()) { + assert(it.hasQualifier()); + auto *NS = BuildNameSpecifier(*it.getNestedNameSpecifier()); + assert(NS); + if (!isa(NS)) + Builder.foldNode(Builder.getRange(getLocalSourceRange(it)).drop_back(), + NS, it); + Builder.markChild(NS, syntax::NodeRole::NestedNameSpecifier_specifier); + Builder.markChildToken(it.getEndLoc(), + syntax::NodeRole::NestedNameSpecifier_delimiter); + } + auto *NNS = new (allocator()) syntax::NestedNameSpecifier; + Builder.foldNode(Builder.getRange(QualifierLoc.getSourceRange()), NNS, + QualifierLoc); + return NNS; + } + bool WalkUpFromDeclRefExpr(DeclRefExpr *S) { - if (auto *NNS = BuildNestedNameSpecifier(S->getQualifierLoc())) - Builder.markChild(NNS, syntax::NodeRole::IdExpression_qualifier); + auto *Qualifier = BuildNestedNameSpecifier(S->getQualifierLoc()); + if (Qualifier) + Builder.markChild(Qualifier, syntax::NodeRole::IdExpression_qualifier); + + auto TemplateKeywordLoc = S->getTemplateKeywordLoc(); + if (TemplateKeywordLoc.isValid()) + Builder.markChildToken(TemplateKeywordLoc, + syntax::NodeRole::TemplateKeyword); auto *unqualifiedId = new (allocator()) syntax::UnqualifiedId; - // Get `UnqualifiedId` from `DeclRefExpr`. - // FIXME: Extract this logic so that it can be used by `MemberExpr`, - // and other semantic constructs, now it is tied to `DeclRefExpr`. - if (!S->hasExplicitTemplateArgs()) { - Builder.foldNode(Builder.getRange(S->getNameInfo().getSourceRange()), - unqualifiedId, nullptr); - } else { - auto templateIdSourceRange = - SourceRange(S->getNameInfo().getBeginLoc(), S->getRAngleLoc()); - Builder.foldNode(Builder.getRange(templateIdSourceRange), unqualifiedId, - nullptr); - } + + Builder.foldNode(Builder.getRange(S->getLocation(), S->getEndLoc()), + unqualifiedId, nullptr); + + Builder.markChild(unqualifiedId, syntax::NodeRole::IdExpression_id); + + Builder.foldNode(Builder.getExprRange(S), + new (allocator()) syntax::IdExpression, S); + return true; + } + + // Same logic as DeclRefExpr. + bool WalkUpFromDependentScopeDeclRefExpr(DependentScopeDeclRefExpr *S) { + auto *Qualifier = BuildNestedNameSpecifier(S->getQualifierLoc()); + if (Qualifier) + Builder.markChild(Qualifier, syntax::NodeRole::IdExpression_qualifier); + + auto TemplateKeywordLoc = S->getTemplateKeywordLoc(); + if (TemplateKeywordLoc.isValid()) + Builder.markChildToken(TemplateKeywordLoc, + syntax::NodeRole::TemplateKeyword); + + auto *unqualifiedId = new (allocator()) syntax::UnqualifiedId; + + Builder.foldNode(Builder.getRange(S->getLocation(), S->getEndLoc()), + unqualifiedId, nullptr); + Builder.markChild(unqualifiedId, syntax::NodeRole::IdExpression_id); Builder.foldNode(Builder.getExprRange(S), diff --git a/clang/lib/Tooling/Syntax/Nodes.cpp b/clang/lib/Tooling/Syntax/Nodes.cpp index 47af2c4..b5a4c50b 100644 --- a/clang/lib/Tooling/Syntax/Nodes.cpp +++ b/clang/lib/Tooling/Syntax/Nodes.cpp @@ -116,8 +116,14 @@ raw_ostream &syntax::operator<<(raw_ostream &OS, NodeKind K) { return OS << "ParametersAndQualifiers"; case NodeKind::MemberPointer: return OS << "MemberPointer"; - case NodeKind::NameSpecifier: - return OS << "NameSpecifier"; + case NodeKind::GlobalNameSpecifier: + return OS << "GlobalNameSpecifier"; + case NodeKind::DecltypeNameSpecifier: + return OS << "DecltypeNameSpecifier"; + case NodeKind::IdentifierNameSpecifier: + return OS << "IdentifierNameSpecifier"; + case NodeKind::SimpleTemplateNameSpecifier: + return OS << "SimpleTemplateNameSpecifier"; case NodeKind::NestedNameSpecifier: return OS << "NestedNameSpecifier"; } @@ -142,6 +148,8 @@ raw_ostream &syntax::operator<<(raw_ostream &OS, NodeRole R) { return OS << "ArrowToken"; case syntax::NodeRole::ExternKeyword: return OS << "ExternKeyword"; + case syntax::NodeRole::TemplateKeyword: + return OS << "TemplateKeyword"; case syntax::NodeRole::BodyStatement: return OS << "BodyStatement"; case syntax::NodeRole::CaseStatement_value: @@ -190,12 +198,23 @@ raw_ostream &syntax::operator<<(raw_ostream &OS, NodeRole R) { return OS << "IdExpression_qualifier"; case syntax::NodeRole::NestedNameSpecifier_specifier: return OS << "NestedNameSpecifier_specifier"; + case syntax::NodeRole::NestedNameSpecifier_delimiter: + return OS << "NestedNameSpecifier_delimiter"; case syntax::NodeRole::ParenExpression_subExpression: return OS << "ParenExpression_subExpression"; } llvm_unreachable("invalid role"); } +std::vector syntax::NestedNameSpecifier::delimiters() { + std::vector Children; + for (auto *C = firstChild(); C; C = C->nextSibling()) { + assert(C->role() == syntax::NodeRole::NestedNameSpecifier_delimiter); + Children.push_back(llvm::cast(C)); + } + return Children; +} + std::vector syntax::NestedNameSpecifier::specifiers() { std::vector Children; for (auto *C = firstChild(); C; C = C->nextSibling()) { @@ -210,6 +229,11 @@ syntax::NestedNameSpecifier *syntax::IdExpression::qualifier() { findChild(syntax::NodeRole::IdExpression_qualifier)); } +syntax::Leaf *syntax::IdExpression::templateKeyword() { + return llvm::cast_or_null( + findChild(syntax::NodeRole::TemplateKeyword)); +} + syntax::UnqualifiedId *syntax::IdExpression::unqualifiedId() { return cast_or_null( findChild(syntax::NodeRole::IdExpression_id)); diff --git a/clang/unittests/Tooling/Syntax/TreeTest.cpp b/clang/unittests/Tooling/Syntax/TreeTest.cpp index f818959..fa5da1f 100644 --- a/clang/unittests/Tooling/Syntax/TreeTest.cpp +++ b/clang/unittests/Tooling/Syntax/TreeTest.cpp @@ -873,24 +873,47 @@ TEST_P(SyntaxTreeTest, QualifiedId) { } EXPECT_TRUE(treeDumpEqual( R"cpp( -namespace a { +namespace n { struct S { template - static T f(){} + struct ST { + static void f(); + }; }; } +template +struct ST { + struct S { + template + static U f(); + }; +}; void test() { - :: // global-namespace-specifier - a:: // namespace-specifier - S:: // type-name-specifier + :: // global-namespace-specifier + n:: // namespace-specifier + S:: // type-name-specifier + template ST:: // type-template-instantiation-specifier + f(); + + n:: // namespace-specifier + S:: // type-name-specifier + ST:: // type-template-instantiation-specifier + f(); + + ST:: // type-name-specifier + S:: // type-name-specifier f(); + + ST:: // type-name-specifier + S:: // type-name-specifier + template f(); } )cpp", R"txt( *: TranslationUnit |-NamespaceDefinition | |-namespace -| |-a +| |-n | |-{ | |-SimpleDeclaration | | |-struct @@ -904,19 +927,58 @@ void test() { | | | | `-T | | | |-> | | | `-SimpleDeclaration -| | | |-static -| | | |-T -| | | |-SimpleDeclarator -| | | | |-f -| | | | `-ParametersAndQualifiers -| | | | |-( -| | | | `-) -| | | `-CompoundStatement -| | | |-{ -| | | `-} +| | | |-struct +| | | |-ST +| | | |-{ +| | | |-SimpleDeclaration +| | | | |-static +| | | | |-void +| | | | |-SimpleDeclarator +| | | | | |-f +| | | | | `-ParametersAndQualifiers +| | | | | |-( +| | | | | `-) +| | | | `-; +| | | |-} +| | | `-; | | |-} | | `-; | `-} +|-TemplateDeclaration +| |-template +| |-< +| |-UnknownDeclaration +| | |-typename +| | `-T +| |-> +| `-SimpleDeclaration +| |-struct +| |-ST +| |-{ +| |-SimpleDeclaration +| | |-struct +| | |-S +| | |-{ +| | |-TemplateDeclaration +| | | |-template +| | | |-< +| | | |-UnknownDeclaration +| | | | |-typename +| | | | `-U +| | | |-> +| | | `-SimpleDeclaration +| | | |-static +| | | |-U +| | | |-SimpleDeclarator +| | | | |-f +| | | | `-ParametersAndQualifiers +| | | | |-( +| | | | `-) +| | | `-; +| | |-} +| | `-; +| |-} +| `-; `-SimpleDeclaration |-void |-SimpleDeclarator @@ -930,14 +992,81 @@ void test() { | |-UnknownExpression | | |-IdExpression | | | |-NestedNameSpecifier - | | | | |-NameSpecifier - | | | | | `-:: - | | | | |-NameSpecifier - | | | | | |-a - | | | | | `-:: - | | | | `-NameSpecifier - | | | | |-S - | | | | `-:: + | | | | |-:: + | | | | |-IdentifierNameSpecifier + | | | | | `-n + | | | | |-:: + | | | | |-IdentifierNameSpecifier + | | | | | `-S + | | | | |-:: + | | | | |-SimpleTemplateNameSpecifier + | | | | | |-template + | | | | | |-ST + | | | | | |-< + | | | | | |-int + | | | | | `-> + | | | | `-:: + | | | `-UnqualifiedId + | | | `-f + | | |-( + | | `-) + | `-; + |-ExpressionStatement + | |-UnknownExpression + | | |-IdExpression + | | | |-NestedNameSpecifier + | | | | |-IdentifierNameSpecifier + | | | | | `-n + | | | | |-:: + | | | | |-IdentifierNameSpecifier + | | | | | `-S + | | | | |-:: + | | | | |-SimpleTemplateNameSpecifier + | | | | | |-ST + | | | | | |-< + | | | | | |-int + | | | | | `-> + | | | | `-:: + | | | `-UnqualifiedId + | | | `-f + | | |-( + | | `-) + | `-; + |-ExpressionStatement + | |-UnknownExpression + | | |-IdExpression + | | | |-NestedNameSpecifier + | | | | |-SimpleTemplateNameSpecifier + | | | | | |-ST + | | | | | |-< + | | | | | |-int + | | | | | `-> + | | | | |-:: + | | | | |-IdentifierNameSpecifier + | | | | | `-S + | | | | `-:: + | | | `-UnqualifiedId + | | | |-f + | | | |-< + | | | |-int + | | | `-> + | | |-( + | | `-) + | `-; + |-ExpressionStatement + | |-UnknownExpression + | | |-IdExpression + | | | |-NestedNameSpecifier + | | | | |-SimpleTemplateNameSpecifier + | | | | | |-ST + | | | | | |-< + | | | | | |-int + | | | | | `-> + | | | | |-:: + | | | | |-IdentifierNameSpecifier + | | | | | `-S + | | | | `-:: + | | | |-template | | | `-UnqualifiedId | | | |-f | | | |-< @@ -950,7 +1079,7 @@ void test() { )txt")); } -TEST_P(SyntaxTreeTest, QualifiedIdWithTemplateKeyword) { +TEST_P(SyntaxTreeTest, QualifiedIdWithDependentType) { if (!GetParam().isCXX()) { return; } @@ -961,63 +1090,17 @@ TEST_P(SyntaxTreeTest, QualifiedIdWithTemplateKeyword) { } EXPECT_TRUE(treeDumpEqual( R"cpp( -struct X { - template static void f(); - template - struct Y { - static void f(); - }; -}; -template void test() { - // TODO: Expose `id-expression` from `DependentScopeDeclRefExpr` - T::template f<0>(); // nested-name-specifier template unqualified-id - T::template Y<0>::f(); // nested-name-specifier template :: unqualified-id +template +void test() { + T::template U::f(); + + T::U::f(); + + T::template f<0>(); } )cpp", R"txt( *: TranslationUnit -|-SimpleDeclaration -| |-struct -| |-X -| |-{ -| |-TemplateDeclaration -| | |-template -| | |-< -| | |-SimpleDeclaration -| | | `-int -| | |-> -| | `-SimpleDeclaration -| | |-static -| | |-void -| | |-SimpleDeclarator -| | | |-f -| | | `-ParametersAndQualifiers -| | | |-( -| | | `-) -| | `-; -| |-TemplateDeclaration -| | |-template -| | |-< -| | |-SimpleDeclaration -| | | `-int -| | |-> -| | `-SimpleDeclaration -| | |-struct -| | |-Y -| | |-{ -| | |-SimpleDeclaration -| | | |-static -| | | |-void -| | | |-SimpleDeclarator -| | | | |-f -| | | | `-ParametersAndQualifiers -| | | | |-( -| | | | `-) -| | | `-; -| | |-} -| | `-; -| |-} -| `-; `-TemplateDeclaration |-template |-< @@ -1036,31 +1119,52 @@ template void test() { |-{ |-ExpressionStatement | |-UnknownExpression - | | |-UnknownExpression - | | | |-T - | | | |-:: - | | | |-template - | | | |-f - | | | |-< - | | | |-IntegerLiteralExpression - | | | | `-0 - | | | `-> + | | |-IdExpression + | | | |-NestedNameSpecifier + | | | | |-IdentifierNameSpecifier + | | | | | `-T + | | | | |-:: + | | | | |-SimpleTemplateNameSpecifier + | | | | | |-template + | | | | | |-U + | | | | | |-< + | | | | | |-int + | | | | | `-> + | | | | `-:: + | | | `-UnqualifiedId + | | | `-f + | | |-( + | | `-) + | `-; + |-ExpressionStatement + | |-UnknownExpression + | | |-IdExpression + | | | |-NestedNameSpecifier + | | | | |-IdentifierNameSpecifier + | | | | | `-T + | | | | |-:: + | | | | |-IdentifierNameSpecifier + | | | | | `-U + | | | | `-:: + | | | `-UnqualifiedId + | | | `-f | | |-( | | `-) | `-; |-ExpressionStatement | |-UnknownExpression - | | |-UnknownExpression - | | | |-T - | | | |-:: + | | |-IdExpression + | | | |-NestedNameSpecifier + | | | | |-IdentifierNameSpecifier + | | | | | `-T + | | | | `-:: | | | |-template - | | | |-Y - | | | |-< - | | | |-IntegerLiteralExpression - | | | | `-0 - | | | |-> - | | | |-:: - | | | `-f + | | | `-UnqualifiedId + | | | |-f + | | | |-< + | | | |-IntegerLiteralExpression + | | | | `-0 + | | | `-> | | |-( | | `-) | `-; @@ -1118,14 +1222,14 @@ void test(S s) { | |-UnknownExpression | | |-IdExpression | | | |-NestedNameSpecifier - | | | | `-NameSpecifier - | | | | |-decltype - | | | | |-( - | | | | |-IdExpression - | | | | | `-UnqualifiedId - | | | | | `-s - | | | | |-) - | | | | `-:: + | | | | |-DecltypeNameSpecifier + | | | | | |-decltype + | | | | | |-( + | | | | | |-IdExpression + | | | | | | `-UnqualifiedId + | | | | | | `-s + | | | | | `-) + | | | | `-:: | | | `-UnqualifiedId | | | `-f | | |-( -- 2.7.4