From 15a430a368ba80ace64891fb35715f78c2673403 Mon Sep 17 00:00:00 2001 From: David Blaikie Date: Sun, 4 Dec 2011 05:04:18 +0000 Subject: [PATCH] Support decltype in nested-name-specifiers. llvm-svn: 145785 --- clang/include/clang/AST/NestedNameSpecifier.h | 5 +- .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/include/clang/Basic/TokenKinds.def | 2 + clang/include/clang/Parse/Parser.h | 5 +- clang/include/clang/Sema/Sema.h | 4 + clang/lib/Parse/ParseDecl.cpp | 31 +++--- clang/lib/Parse/ParseDeclCXX.cpp | 102 +++++++++++++----- clang/lib/Parse/ParseExpr.cpp | 1 + clang/lib/Parse/ParseExprCXX.cpp | 16 +++ clang/lib/Parse/ParseTentative.cpp | 8 +- clang/lib/Parse/Parser.cpp | 8 +- clang/lib/Sema/SemaCXXScopeSpec.cpp | 23 ++++ .../expr.prim/expr.prim.general/p4-0x.cpp | 3 +- .../expr.prim/expr.prim.general/p8-0x.cpp | 82 ++++++++++++++ 14 files changed, 240 insertions(+), 52 deletions(-) create mode 100644 clang/test/CXX/expr/expr.prim/expr.prim.general/p8-0x.cpp diff --git a/clang/include/clang/AST/NestedNameSpecifier.h b/clang/include/clang/AST/NestedNameSpecifier.h index e831113863e7..bda2d31fd5a4 100644 --- a/clang/include/clang/AST/NestedNameSpecifier.h +++ b/clang/include/clang/AST/NestedNameSpecifier.h @@ -36,8 +36,9 @@ class LangOptions; /// namespaces. For example, "foo::" in "foo::x" is a nested name /// specifier. Nested name specifiers are made up of a sequence of /// specifiers, each of which can be a namespace, type, identifier -/// (for dependent names), or the global specifier ('::', must be the -/// first specifier). +/// (for dependent names), decltype specifier, or the global specifier ('::'). +/// The last two specifiers can only appear at the start of a +/// nested-namespace-specifier. class NestedNameSpecifier : public llvm::FoldingSetNode { /// \brief Enumeration describing diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 424309129131..fa2fabcdeb88 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3993,6 +3993,8 @@ def err_typecheck_deleted_function : Error< "conversion function from %0 to %1 invokes a deleted function">; def err_expected_class_or_namespace : Error<"expected a class or namespace">; +def err_expected_class : Error<"%0 is not a class%select{ or namespace|, " + "namespace, or scoped enumeration}1">; def err_missing_qualified_for_redecl : Error< "must qualify the name %0 to declare %q1 in this scope">; def err_invalid_declarator_scope : Error< diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 99ccc9ac4e0b..e9b772cddedf 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -560,6 +560,8 @@ ANNOTATION(template_id) // annotation for a C++ template-id that names a // function template specialization (not a type), // e.g., "std::swap" ANNOTATION(primary_expr) // annotation for a primary expression +ANNOTATION(decltype) // annotation for a decltype expression, + // e.g., "decltype(foo.bar())" // Annotation for #pragma unused(...) // For each argument inside the parentheses the pragma handler will produce diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 2c8f7ef98689..5dfb99ec0762 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -1901,7 +1901,10 @@ private: void ParseTypeofSpecifier(DeclSpec &DS); - void ParseDecltypeSpecifier(DeclSpec &DS); + SourceLocation ParseDecltypeSpecifier(DeclSpec &DS); + void AnnotateExistingDecltypeSpecifier(const DeclSpec &DS, + SourceLocation StartLoc, + SourceLocation EndLoc); void ParseUnderlyingTypeSpecifier(DeclSpec &DS); void ParseAtomicSpecifier(DeclSpec &DS); diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index ab644e2c65b8..adb00dfcb3a1 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3326,6 +3326,10 @@ public: bool EnteringContext, CXXScopeSpec &SS); + bool ActOnCXXNestedNameSpecifierDecltype(CXXScopeSpec &SS, + const DeclSpec &DS, + SourceLocation ColonColonLoc); + bool IsInvalidUnlessNestedName(Scope *S, CXXScopeSpec &SS, IdentifierInfo &Identifier, SourceLocation IdentifierLoc, diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 3bddd3de13d0..0c5a4879aedb 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -1889,6 +1889,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, goto DoneWithDeclSpec; // typedef-name + case tok::kw_decltype: case tok::identifier: { // In C++, check to see if this is a scope specifier like foo::bar::, if // so handle it as such. This is important for ctor parsing. @@ -2248,7 +2249,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, ParseTypeofSpecifier(DS); continue; - case tok::kw_decltype: + case tok::annot_decltype: ParseDecltypeSpecifier(DS); continue; @@ -2370,6 +2371,7 @@ bool Parser::ParseOptionalTypeSpecifier(DeclSpec &DS, bool& isInvalid, if (TryAltiVecToken(DS, Loc, PrevSpec, DiagID, isInvalid)) break; // Fall through. + case tok::kw_decltype: case tok::kw_typename: // typename foo::bar // Annotate typenames and C++ scope specifiers. If we get one, just // recurse to handle whatever we get. @@ -2532,7 +2534,7 @@ bool Parser::ParseOptionalTypeSpecifier(DeclSpec &DS, bool& isInvalid, return true; // C++0x decltype support. - case tok::kw_decltype: + case tok::annot_decltype: ParseDecltypeSpecifier(DS); return true; @@ -3346,6 +3348,7 @@ bool Parser::isDeclarationSpecifier(bool DisambiguatingWithExpression) { if (TryAltiVecVectorToken()) return true; // Fall through. + case tok::kw_decltype: // decltype(T())::type case tok::kw_typename: // typename T::type // Annotate typenames and C++ scope specifiers. If we get one, just // recurse to handle whatever we get. @@ -3441,7 +3444,7 @@ bool Parser::isDeclarationSpecifier(bool DisambiguatingWithExpression) { return true; // C++0x decltype. - case tok::kw_decltype: + case tok::annot_decltype: return true; // C1x _Atomic() @@ -3968,6 +3971,10 @@ void Parser::ParseDirectDeclarator(Declarator &D) { while (1) { if (Tok.is(tok::l_paren)) { + // Enter function-declaration scope, limiting any declarators to the + // function prototype scope, including parameter declarators. + ParseScope PrototypeScope(this, + Scope::FunctionPrototypeScope|Scope::DeclScope); // The paren may be part of a C++ direct initializer, eg. "int x(1);". // In such a case, check if we actually have a function declarator; if it // is not, the declarator has been fully parsed. @@ -3982,13 +3989,14 @@ void Parser::ParseDirectDeclarator(Declarator &D) { BalancedDelimiterTracker T(*this, tok::l_paren); T.consumeOpen(); ParseFunctionDeclarator(D, attrs, T); + PrototypeScope.Exit(); } else if (Tok.is(tok::l_square)) { ParseBracketDeclarator(D); } else { break; } } -} +} /// ParseParenDeclarator - We parsed the declarator D up to a paren. This is /// only called before the identifier, so these are most likely just grouping @@ -4084,7 +4092,12 @@ void Parser::ParseParenDeclarator(Declarator &D) { // ParseFunctionDeclarator to handle of argument list. D.SetIdentifier(0, Tok.getLocation()); + // Enter function-declaration scope, limiting any declarators to the + // function prototype scope, including parameter declarators. + ParseScope PrototypeScope(this, + Scope::FunctionPrototypeScope|Scope::DeclScope); ParseFunctionDeclarator(D, attrs, T, RequiresArg); + PrototypeScope.Exit(); } /// ParseFunctionDeclarator - We are after the identifier and have parsed the @@ -4109,6 +4122,8 @@ void Parser::ParseFunctionDeclarator(Declarator &D, ParsedAttributes &attrs, BalancedDelimiterTracker &Tracker, bool RequiresArg) { + assert(getCurScope()->isFunctionPrototypeScope() && + "Should call from a Function scope"); // lparen is already consumed! assert(D.isPastIdentifier() && "Should not call before identifier!"); @@ -4142,11 +4157,6 @@ void Parser::ParseFunctionDeclarator(Declarator &D, Tracker.consumeClose(); EndLoc = Tracker.getCloseLocation(); } else { - // Enter function-declaration scope, limiting any declarators to the - // function prototype scope, including parameter declarators. - ParseScope PrototypeScope(this, - Scope::FunctionPrototypeScope|Scope::DeclScope); - if (Tok.isNot(tok::r_paren)) ParseParameterDeclarationClause(D, attrs, ParamInfo, EllipsisLoc); else if (RequiresArg) @@ -4197,9 +4207,6 @@ void Parser::ParseFunctionDeclarator(Declarator &D, EndLoc = Range.getEnd(); } } - - // Leave prototype scope. - PrototypeScope.Exit(); } // Remember that we parsed a function type, and remember the attributes. diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 04c94a053d6b..f2bdb9f7bd55 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -632,39 +632,85 @@ Decl *Parser::ParseStaticAssertDeclaration(SourceLocation &DeclEnd){ /// /// 'decltype' ( expression ) /// -void Parser::ParseDecltypeSpecifier(DeclSpec &DS) { - assert(Tok.is(tok::kw_decltype) && "Not a decltype specifier"); +SourceLocation Parser::ParseDecltypeSpecifier(DeclSpec &DS) { + assert((Tok.is(tok::kw_decltype) || Tok.is(tok::annot_decltype)) + && "Not a decltype specifier"); + - SourceLocation StartLoc = ConsumeToken(); - BalancedDelimiterTracker T(*this, tok::l_paren); - if (T.expectAndConsume(diag::err_expected_lparen_after, - "decltype", tok::r_paren)) { - return; + ExprResult Result; + SourceLocation StartLoc = Tok.getLocation(); + SourceLocation EndLoc; + + if (Tok.is(tok::annot_decltype)) { + Result = getExprAnnotation(Tok); + EndLoc = Tok.getAnnotationEndLoc(); + ConsumeToken(); + if (Result.isInvalid()) { + DS.SetTypeSpecError(); + return EndLoc; + } + } else { + ConsumeToken(); + + BalancedDelimiterTracker T(*this, tok::l_paren); + if (T.expectAndConsume(diag::err_expected_lparen_after, + "decltype", tok::r_paren)) { + DS.SetTypeSpecError(); + return T.getOpenLocation() == Tok.getLocation() ? + StartLoc : T.getOpenLocation(); + } + + // Parse the expression + + // C++0x [dcl.type.simple]p4: + // The operand of the decltype specifier is an unevaluated operand. + EnterExpressionEvaluationContext Unevaluated(Actions, + Sema::Unevaluated); + Result = ParseExpression(); + if (Result.isInvalid()) { + SkipUntil(tok::r_paren, true, true); + DS.SetTypeSpecError(); + return Tok.is(tok::eof) ? Tok.getLocation() : ConsumeParen(); + } + + // Match the ')' + T.consumeClose(); + if (T.getCloseLocation().isInvalid()) { + DS.SetTypeSpecError(); + // FIXME: this should return the location of the last token + // that was consumed (by "consumeClose()") + return T.getCloseLocation(); + } + + EndLoc = T.getCloseLocation(); } - // Parse the expression - - // C++0x [dcl.type.simple]p4: - // The operand of the decltype specifier is an unevaluated operand. - EnterExpressionEvaluationContext Unevaluated(Actions, - Sema::Unevaluated); - ExprResult Result = ParseExpression(); - if (Result.isInvalid()) { - SkipUntil(tok::r_paren); - return; - } - - // Match the ')' - T.consumeClose(); - if (T.getCloseLocation().isInvalid()) - return; - const char *PrevSpec = 0; unsigned DiagID; // Check for duplicate type specifiers (e.g. "int decltype(a)"). if (DS.SetTypeSpecType(DeclSpec::TST_decltype, StartLoc, PrevSpec, - DiagID, Result.release())) + DiagID, Result.release())) { Diag(StartLoc, DiagID) << PrevSpec; + DS.SetTypeSpecError(); + } + return EndLoc; +} + +void Parser::AnnotateExistingDecltypeSpecifier(const DeclSpec& DS, + SourceLocation StartLoc, + SourceLocation EndLoc) { + // make sure we have a token we can turn into an annotation token + if (PP.isBacktrackEnabled()) + PP.RevertCachedTokens(1); + else + PP.EnterToken(Tok); + + Tok.setKind(tok::annot_decltype); + setExprAnnotation(Tok, DS.getTypeSpecType() == TST_decltype ? + DS.getRepAsExpr() : ExprResult()); + Tok.setAnnotationEndLoc(EndLoc); + Tok.setLocation(StartLoc); + PP.AnnotateCachedTokens(Tok); } void Parser::ParseUnderlyingTypeSpecifier(DeclSpec &DS) { @@ -727,14 +773,16 @@ Parser::TypeResult Parser::ParseBaseTypeSpecifier(SourceLocation &BaseLoc, BaseLoc = Tok.getLocation(); // Parse decltype-specifier - if (Tok.is(tok::kw_decltype)) { + // tok == kw_decltype is just error recovery, it can only happen when SS + // isn't empty + if (Tok.is(tok::kw_decltype) || Tok.is(tok::annot_decltype)) { if (SS.isNotEmpty()) Diag(SS.getBeginLoc(), diag::err_unexpected_scope_on_base_decltype) << FixItHint::CreateRemoval(SS.getRange()); // Fake up a Declarator to use with ActOnTypeName. DeclSpec DS(AttrFactory); - ParseDecltypeSpecifier(DS); + ParseDecltypeSpecifier(DS); EndLocation = DS.getSourceRange().getEnd(); Declarator DeclaratorInfo(DS, Declarator::TypeNameContext); diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 75fc9be5aa43..dcbee4f9e6ac 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -664,6 +664,7 @@ ExprResult Parser::ParseCastExpression(bool isUnaryExpression, ConsumeToken(); break; + case tok::kw_decltype: case tok::identifier: { // primary-expression: identifier // unqualified-id: identifier // constant: enumeration-constant diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index d9983d416769..79d2ec21ab09 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -168,6 +168,22 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS, *MayBePseudoDestructor = false; } + if (Tok.is(tok::kw_decltype) || Tok.is(tok::annot_decltype)) { + DeclSpec DS(AttrFactory); + SourceLocation DeclLoc = Tok.getLocation(); + SourceLocation EndLoc = ParseDecltypeSpecifier(DS); + if (Tok.isNot(tok::coloncolon)) { + AnnotateExistingDecltypeSpecifier(DS, DeclLoc, EndLoc); + return false; + } + + SourceLocation CCLoc = ConsumeToken(); + if (Actions.ActOnCXXNestedNameSpecifierDecltype(SS, DS, CCLoc)) + SS.SetInvalid(SourceRange(DeclLoc, CCLoc)); + + HasScopeSpecifier = true; + } + while (true) { if (HasScopeSpecifier) { // C++ [basic.lookup.classref]p5: diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp index e682cb421895..7c280ff50be9 100644 --- a/clang/lib/Parse/ParseTentative.cpp +++ b/clang/lib/Parse/ParseTentative.cpp @@ -706,7 +706,6 @@ Parser::isExpressionOrTypeSpecifierSimple(tok::TokenKind Kind) { case tok::kw_wchar_t: case tok::kw_char16_t: case tok::kw_char32_t: - case tok::kw_decltype: case tok::kw___underlying_type: case tok::kw_thread_local: case tok::kw__Decimal32: @@ -848,13 +847,14 @@ Parser::TPResult Parser::isCXXDeclarationSpecifier() { if (Next.is(tok::kw_new) || // ::new Next.is(tok::kw_delete)) // ::delete return TPResult::False(); - + } + // Fall through. + case tok::kw_decltype: // Annotate typenames and C++ scope specifiers. If we get one, just // recurse to handle whatever we get. if (TryAnnotateTypeOrScopeToken()) return TPResult::Error(); return isCXXDeclarationSpecifier(); - } // decl-specifier: // storage-class-specifier @@ -1030,7 +1030,7 @@ Parser::TPResult Parser::isCXXDeclarationSpecifier() { } // C++0x decltype support. - case tok::kw_decltype: + case tok::annot_decltype: return TPResult::True(); // C++0x type traits support diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index e74c46e2fda1..112d7a2d12c3 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -1202,8 +1202,8 @@ TemplateIdAnnotation *Parser::takeTemplateIdAnnotation(const Token &tok) { /// as the current tokens, so only call it in contexts where these are invalid. bool Parser::TryAnnotateTypeOrScopeToken(bool EnteringContext, bool NeedType) { assert((Tok.is(tok::identifier) || Tok.is(tok::coloncolon) - || Tok.is(tok::kw_typename) || Tok.is(tok::annot_cxxscope)) && - "Cannot be a type or scope token!"); + || Tok.is(tok::kw_typename) || Tok.is(tok::annot_cxxscope) + || Tok.is(tok::kw_decltype)) && "Cannot be a type or scope token!"); if (Tok.is(tok::kw_typename)) { // Parse a C++ typename-specifier, e.g., "typename T::type". @@ -1382,8 +1382,8 @@ bool Parser::TryAnnotateCXXScopeToken(bool EnteringContext) { assert(getLang().CPlusPlus && "Call sites of this function should be guarded by checking for C++"); assert((Tok.is(tok::identifier) || Tok.is(tok::coloncolon) || - (Tok.is(tok::annot_template_id) && NextToken().is(tok::coloncolon)))&& - "Cannot be a type or scope token!"); + (Tok.is(tok::annot_template_id) && NextToken().is(tok::coloncolon)) || + Tok.is(tok::kw_decltype)) && "Cannot be a type or scope token!"); CXXScopeSpec SS; if (ParseOptionalCXXScopeSpecifier(SS, ParsedType(), EnteringContext)) diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp index dfd59bfe4fd5..93f83256b018 100644 --- a/clang/lib/Sema/SemaCXXScopeSpec.cpp +++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp @@ -676,6 +676,29 @@ bool Sema::ActOnCXXNestedNameSpecifier(Scope *S, /*ScopeLookupResult=*/0, false); } +bool Sema::ActOnCXXNestedNameSpecifierDecltype(CXXScopeSpec &SS, + const DeclSpec &DS, + SourceLocation ColonColonLoc) { + if (SS.isInvalid() || DS.getTypeSpecType() == DeclSpec::TST_error) + return true; + + assert(DS.getTypeSpecType() == DeclSpec::TST_decltype); + + QualType T = BuildDecltypeType(DS.getRepAsExpr(), DS.getTypeSpecTypeLoc()); + if (!T->isDependentType() && !T->getAs()) { + Diag(DS.getTypeSpecTypeLoc(), diag::err_expected_class) + << T << getLangOptions().CPlusPlus; + return true; + } + + TypeLocBuilder TLB; + DecltypeTypeLoc DecltypeTL = TLB.push(T); + DecltypeTL.setNameLoc(DS.getTypeSpecTypeLoc()); + SS.Extend(Context, SourceLocation(), TLB.getTypeLocInContext(Context, T), + ColonColonLoc); + return false; +} + /// IsInvalidUnlessNestedName - This method is used for error recovery /// purposes to determine whether the specified identifier is only valid as /// a nested name specifier, for example a namespace name. It is diff --git a/clang/test/CXX/expr/expr.prim/expr.prim.general/p4-0x.cpp b/clang/test/CXX/expr/expr.prim/expr.prim.general/p4-0x.cpp index 143ba897ae95..b976e1664e1f 100644 --- a/clang/test/CXX/expr/expr.prim/expr.prim.general/p4-0x.cpp +++ b/clang/test/CXX/expr/expr.prim/expr.prim.general/p4-0x.cpp @@ -2,8 +2,7 @@ struct S { S *p = this; // ok - decltype(this) q; // expected-error {{invalid use of 'this' outside of a nonstatic member function}} \ - expected-error {{C++ requires a type specifier for all declarations}} + decltype(this) q; // expected-error {{invalid use of 'this' outside of a nonstatic member function}} int arr[sizeof(this)]; // expected-error {{invalid use of 'this' outside of a nonstatic member function}} int sz = sizeof(this); // ok diff --git a/clang/test/CXX/expr/expr.prim/expr.prim.general/p8-0x.cpp b/clang/test/CXX/expr/expr.prim/expr.prim.general/p8-0x.cpp new file mode 100644 index 000000000000..80ae67a97c03 --- /dev/null +++ b/clang/test/CXX/expr/expr.prim/expr.prim.general/p8-0x.cpp @@ -0,0 +1,82 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s + +struct global { +}; + +namespace PR10127 { + struct outer { + struct middle { + struct inner { + int func(); + int i; + }; + struct inner2 { + }; + struct inner3 { + }; + int mfunc(); + }; + typedef int td_int; + }; + + struct str { + operator decltype(outer::middle::inner()) (); + operator decltype(outer::middle())::inner2 (); + operator decltype(outer())::middle::inner3 (); + str(int (decltype(outer::middle::inner())::*n)(), + int (decltype(outer::middle())::inner::*o)(), + int (decltype(outer())::middle::inner::*p)()); + }; + + int decltype(outer::middle())::inner::func() { + return 0; + } + + decltype(outer::middle::inner()) a; + void scope() { + a.decltype(outer::middle())::mfunc(); // expected-error{{'PR10127::outer::middle::mfunc' is not a member of class 'decltype(outer::middle::inner())'}} + a.decltype(outer::middle::inner())::func(); + a.decltype(outer::middle())::inner::func(); + a.decltype(outer())::middle::inner::func(); + + a.decltype(outer())::middle::inner::~inner(); + + decltype(outer())::middle::inner().func(); + } + decltype(outer::middle())::inner b; + decltype(outer())::middle::inner c; + decltype(outer())::fail d; // expected-error{{no type named 'fail' in 'PR10127::outer'}} + decltype(outer())::fail::inner e; // expected-error{{no member named 'fail' in 'PR10127::outer'}} + decltype()::fail f; // expected-error{{expected expression}} + decltype()::middle::fail g; // expected-error{{expected expression}} + + decltype(int()) h; + decltype(int())::PR10127::outer i; // expected-error{{'decltype(int())' (aka 'int') is not a class, namespace, or scoped enumeration}} + decltype(int())::global j; // expected-error{{'decltype(int())' (aka 'int') is not a class, namespace, or scoped enumeration}} + + outer::middle k = decltype(outer())::middle(); + outer::middle::inner l = decltype(outer())::middle::inner(); + + template + struct templ { + typename decltype(T())::middle::inner x; // expected-error{{type 'decltype(int())' (aka 'int') cannot be used prior to '::' because it has no members}} + }; + + template class templ; // expected-note{{in instantiation of template class 'PR10127::templ' requested here}} + template class templ; + + enum class foo { + bar, + baz + }; + + foo m = decltype(foo::bar)::baz; + + enum E { + }; + struct bar { + enum E : decltype(outer())::td_int(4); + enum F : decltype(outer())::td_int; + enum G : decltype; // expected-error{{expected '(' after 'decltype'}} + }; +}