From: Douglas Gregor Date: Wed, 13 Jan 2010 17:31:36 +0000 (+0000) Subject: Reimplement constructor declarator parsing to cope with template-ids X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0efc2c1716be4f1c5f1343cad3b047e74861f030;p=clang Reimplement constructor declarator parsing to cope with template-ids that name constructors, the endless joys of out-of-line constructor definitions, and various other corner cases that the previous hack never imagined. Fixes PR5688 and tightens up semantic analysis for constructor names. Additionally, fixed a problem where we wouldn't properly enter the declarator scope of a parenthesized declarator. We were entering the scope, then leaving it when we saw the ")"; now, we re-enter the declarator scope before parsing the parameter list. Note that we are forced to perform some tentative parsing within a class (call it C) to tell the difference between C(int); // constructor and C (f)(int); // member function which is rather unfortunate. And, although it isn't necessary for correctness, we use the same tentative-parsing mechanism for out-of-line constructors to improve diagnostics in icky cases like: C::C C::f(int); // error: C::C refers to the constructor name, but // we complain nicely and recover by treating it as // a type. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@93322 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Basic/DiagnosticParseKinds.td b/include/clang/Basic/DiagnosticParseKinds.td index 51b6bffe00..c6d0605252 100644 --- a/include/clang/Basic/DiagnosticParseKinds.td +++ b/include/clang/Basic/DiagnosticParseKinds.td @@ -291,6 +291,16 @@ def err_explicit_instantiation_with_definition : Error< "definition is meant to be an explicit specialization, add '<>' after the " "'template' keyword">; +// Constructor template diagnostics. +def err_out_of_line_constructor_template_id : Error< + "out-of-line constructor for %0 cannot have template arguments">; +def err_out_of_line_template_id_names_constructor : Error< + "qualified reference to %0 is a constructor name rather than a " + "template name wherever a constructor can be declared">; +def err_out_of_line_type_names_constructor : Error< + "qualified reference to %0 is a constructor name rather than a " + "type wherever a constructor can be declared">; + def err_expected_qualified_after_typename : Error< "expected a qualified name after 'typename'">; def err_typename_refers_to_non_type_template : Error< diff --git a/include/clang/Parse/DeclSpec.h b/include/clang/Parse/DeclSpec.h index 7c99e3e582..06d0e4745a 100644 --- a/include/clang/Parse/DeclSpec.h +++ b/include/clang/Parse/DeclSpec.h @@ -493,6 +493,8 @@ public: IK_LiteralOperatorId, /// \brief A constructor name. IK_ConstructorName, + /// \brief A constructor named via a template-id. + IK_ConstructorTemplateId, /// \brief A destructor name. IK_DestructorName, /// \brief A template-id, e.g., f. @@ -534,8 +536,9 @@ public: /// class-name. ActionBase::TypeTy *DestructorName; - /// \brief When Kind == IK_TemplateId, the template-id annotation that - /// contains the template name and template arguments. + /// \brief When Kind == IK_TemplateId or IK_ConstructorTemplateId, + /// the template-id annotation that contains the template name and + /// template arguments. TemplateIdAnnotation *TemplateId; }; @@ -648,6 +651,14 @@ public: ConstructorName = ClassType; } + /// \brief Specify that this unqualified-id was parsed as a + /// template-id that names a constructor. + /// + /// \param TemplateId the template-id annotation that describes the parsed + /// template-id. This UnqualifiedId instance will take ownership of the + /// \p TemplateId and will free it on destruction. + void setConstructorTemplateId(TemplateIdAnnotation *TemplateId); + /// \brief Specify that this unqualified-id was parsed as a destructor name. /// /// \param TildeLoc the location of the '~' that introduces the destructor diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index 8d651361f3..0fc9413c30 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -1037,7 +1037,8 @@ private: /// would be best implemented in the parser. enum DeclSpecContext { DSC_normal, // normal context - DSC_class // class context, enables 'friend' + DSC_class, // class context, enables 'friend' + DSC_top_level // top-level/namespace declaration context }; DeclGroupPtrTy ParseDeclaration(unsigned Context, SourceLocation &DeclEnd, @@ -1056,6 +1057,7 @@ private: bool ParseImplicitInt(DeclSpec &DS, CXXScopeSpec *SS, const ParsedTemplateInfo &TemplateInfo, AccessSpecifier AS); + DeclSpecContext getDeclSpecContextFromDeclaratorContext(unsigned Context); void ParseDeclarationSpecifiers(DeclSpec &DS, const ParsedTemplateInfo &TemplateInfo = ParsedTemplateInfo(), AccessSpecifier AS = AS_none, @@ -1109,6 +1111,11 @@ private: return isDeclarationSpecifier(); } + /// \brief Starting with a scope specifier, identifier, or + /// template-id that refers to the current class, determine whether + /// this is a constructor declarator. + bool isConstructorDeclarator(); + /// \brief Specifies the context in which type-id/expression /// disambiguation will occur. enum TentativeCXXTypeIdContext { diff --git a/lib/Parse/DeclSpec.cpp b/lib/Parse/DeclSpec.cpp index 4cd8fe887b..f52d8b9856 100644 --- a/lib/Parse/DeclSpec.cpp +++ b/lib/Parse/DeclSpec.cpp @@ -36,6 +36,14 @@ void UnqualifiedId::setTemplateId(TemplateIdAnnotation *TemplateId) { EndLocation = TemplateId->RAngleLoc; } +void UnqualifiedId::setConstructorTemplateId(TemplateIdAnnotation *TemplateId) { + assert(TemplateId && "NULL template-id annotation?"); + Kind = IK_ConstructorTemplateId; + this->TemplateId = TemplateId; + StartLocation = TemplateId->TemplateNameLoc; + EndLocation = TemplateId->RAngleLoc; +} + /// DeclaratorChunk::getFunction - Return a DeclaratorChunk for a function. /// "TheDeclarator" is the declarator that this will be added to. DeclaratorChunk DeclaratorChunk::getFunction(bool hasProto, bool isVariadic, diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index bc6dda8ed7..1e8e33ebd8 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -356,7 +356,8 @@ Parser::DeclGroupPtrTy Parser::ParseSimpleDeclaration(unsigned Context, ParsingDeclSpec DS(*this); if (Attr) DS.AddAttributes(Attr); - ParseDeclarationSpecifiers(DS); + ParseDeclarationSpecifiers(DS, ParsedTemplateInfo(), AS_none, + getDeclSpecContextFromDeclaratorContext(Context)); // C99 6.7.2.3p6: Handle "struct-or-union identifier;", "enum { X };" // declaration-specifiers init-declarator-list[opt] ';' @@ -786,6 +787,20 @@ bool Parser::ParseImplicitInt(DeclSpec &DS, CXXScopeSpec *SS, return false; } +/// \brief Determine the declaration specifier context from the declarator +/// context. +/// +/// \param Context the declarator context, which is one of the +/// Declarator::TheContext enumerator values. +Parser::DeclSpecContext +Parser::getDeclSpecContextFromDeclaratorContext(unsigned Context) { + if (Context == Declarator::MemberContext) + return DSC_class; + if (Context == Declarator::FileContext) + return DSC_top_level; + return DSC_normal; +} + /// ParseDeclarationSpecifiers /// declaration-specifiers: [C99 6.7] /// storage-class-specifier declaration-specifiers[opt] @@ -861,6 +876,47 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, static_cast(Next.getAnnotationValue()) ->Kind == TNK_Type_template) { // We have a qualified template-id, e.g., N::A + + // C++ [class.qual]p2: + // In a lookup in which the constructor is an acceptable lookup + // result and the nested-name-specifier nominates a class C: + // + // - if the name specified after the + // nested-name-specifier, when looked up in C, is the + // injected-class-name of C (Clause 9), or + // + // - if the name specified after the nested-name-specifier + // is the same as the identifier or the + // simple-template-id's template-name in the last + // component of the nested-name-specifier, + // + // the name is instead considered to name the constructor of + // class C. + // + // Thus, if the template-name is actually the constructor + // name, then the code is ill-formed; this interpretation is + // reinforced by the NAD status of core issue 635. + TemplateIdAnnotation *TemplateId + = static_cast(Next.getAnnotationValue()); + if (DSContext == DSC_top_level && TemplateId->Name && + Actions.isCurrentClassName(*TemplateId->Name, CurScope, &SS)) { + if (isConstructorDeclarator()) { + // The user meant this to be an out-of-line constructor + // definition, but template arguments are not allowed + // there. Just allow this as a constructor; we'll + // complain about it later. + goto DoneWithDeclSpec; + } + + // The user meant this to name a type, but it actually names + // a constructor with some extraneous template + // arguments. Complain, then parse it as a type as the user + // intended. + Diag(TemplateId->TemplateNameLoc, + diag::err_out_of_line_template_id_names_constructor) + << TemplateId->Name; + } + DS.getTypeSpecScope() = SS; ConsumeToken(); // The C++ scope. assert(Tok.is(tok::annot_template_id) && @@ -885,13 +941,23 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, if (Next.isNot(tok::identifier)) goto DoneWithDeclSpec; - // If the next token is the name of the class type that the C++ scope - // denotes, followed by a '(', then this is a constructor declaration. - // We're done with the decl-specifiers. - if (Actions.isCurrentClassName(*Next.getIdentifierInfo(), - CurScope, &SS) && - GetLookAheadToken(2).is(tok::l_paren)) - goto DoneWithDeclSpec; + // If we're in a context where the identifier could be a class name, + // check whether this is a constructor declaration. + if (DSContext == DSC_top_level && + Actions.isCurrentClassName(*Next.getIdentifierInfo(), CurScope, + &SS)) { + if (isConstructorDeclarator()) + goto DoneWithDeclSpec; + + // As noted in C++ [class.qual]p2 (cited above), when the name + // of the class is qualified in a context where it could name + // a constructor, its a constructor name. However, we've + // looked at the declarator, and the user probably meant this + // to be a type. Complain that it isn't supposed to be treated + // as a type, then proceed to parse it as a type. + Diag(Next.getLocation(), diag::err_out_of_line_type_names_constructor) + << Next.getIdentifierInfo(); + } TypeTy *TypeRep = Actions.getTypeName(*Next.getIdentifierInfo(), Next.getLocation(), CurScope, &SS); @@ -972,16 +1038,11 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, goto DoneWithDeclSpec; } - // C++: If the identifier is actually the name of the class type - // being defined and the next token is a '(', then this is a - // constructor declaration. We're done with the decl-specifiers - // and will treat this token as an identifier. - if (getLang().CPlusPlus && - (CurScope->isClassScope() || - (CurScope->isTemplateParamScope() && - CurScope->getParent()->isClassScope())) && + // If we're in a context where the identifier could be a class name, + // check whether this is a constructor declaration. + if (getLang().CPlusPlus && DSContext == DSC_class && Actions.isCurrentClassName(*Tok.getIdentifierInfo(), CurScope) && - NextToken().getKind() == tok::l_paren) + isConstructorDeclarator()) goto DoneWithDeclSpec; isInvalid = DS.SetTypeSpecType(DeclSpec::TST_typename, Loc, PrevSpec, @@ -1024,6 +1085,14 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, goto DoneWithDeclSpec; } + // If we're in a context where the template-id could be a + // constructor name or specialization, check whether this is a + // constructor declaration. + if (getLang().CPlusPlus && DSContext == DSC_class && + Actions.isCurrentClassName(*TemplateId->Name, CurScope) && + isConstructorDeclarator()) + goto DoneWithDeclSpec; + // Turn the template-id annotation token into a type annotation // token, then try again to parse it as a type-specifier. AnnotateTemplateIdTokenAsType(); @@ -2089,6 +2158,48 @@ bool Parser::isDeclarationSpecifier() { } } +bool Parser::isConstructorDeclarator() { + TentativeParsingAction TPA(*this); + + // Parse the C++ scope specifier. + CXXScopeSpec SS; + ParseOptionalCXXScopeSpecifier(SS, 0, true); + + // Parse the constructor name. + if (Tok.is(tok::identifier) || Tok.is(tok::annot_template_id)) { + // We already know that we have a constructor name; just consume + // the token. + ConsumeToken(); + } else { + TPA.Revert(); + return false; + } + + // Current class name must be followed by a left parentheses. + if (Tok.isNot(tok::l_paren)) { + TPA.Revert(); + return false; + } + ConsumeParen(); + + // A right parentheses or ellipsis signals that we have a constructor. + if (Tok.is(tok::r_paren) || Tok.is(tok::ellipsis)) { + TPA.Revert(); + return true; + } + + // If we need to, enter the specified scope. + DeclaratorScopeObj DeclScopeObj(*this, SS); + if (SS.isSet() && Actions.ShouldEnterDeclaratorScope(CurScope, SS)) + DeclScopeObj.EnterDeclaratorScope(); + + // Check whether the next token(s) are part of a declaration + // specifier, in which case we have the start of a parameter and, + // therefore, we know that this is a constructor. + bool IsConstructor = isDeclarationSpecifier(); + TPA.Revert(); + return IsConstructor; +} /// ParseTypeQualifierListOpt /// type-qualifier-list: [C99 6.7.5] @@ -2373,10 +2484,16 @@ void Parser::ParseDirectDeclarator(Declarator &D) { Tok.is(tok::annot_template_id) || Tok.is(tok::tilde)) { // We found something that indicates the start of an unqualified-id. // Parse that unqualified-id. + bool AllowConstructorName + = ((D.getCXXScopeSpec().isSet() && + D.getContext() == Declarator::FileContext) || + (!D.getCXXScopeSpec().isSet() && + D.getContext() == Declarator::MemberContext)) && + !D.getDeclSpec().hasTypeSpecifier(); if (ParseUnqualifiedId(D.getCXXScopeSpec(), /*EnteringContext=*/true, /*AllowDestructorName=*/true, - /*AllowConstructorName=*/!D.getDeclSpec().hasTypeSpecifier(), + AllowConstructorName, /*ObjectType=*/0, D.getName())) { D.SetIdentifier(0, Tok.getLocation()); @@ -2403,6 +2520,16 @@ void Parser::ParseDirectDeclarator(Declarator &D) { // direct-declarator: '(' attributes declarator ')' // Example: 'char (*X)' or 'int (*XX)(void)' ParseParenDeclarator(D); + + // If the declarator was parenthesized, we entered the declarator + // scope when parsing the parenthesized declarator, then exited + // the scope already. Re-enter the scope, if we need to. + if (D.getCXXScopeSpec().isSet()) { + if (Actions.ShouldEnterDeclaratorScope(CurScope, D.getCXXScopeSpec())) + // Change the declaration context for name lookup, until this function + // is exited (and the declarator has been parsed). + DeclScopeObj.EnterDeclaratorScope(); + } } else if (D.mayOmitIdentifier()) { // This could be something simple like "int" (in which case the declarator // portion is empty), if an abstract-declarator is allowed. diff --git a/lib/Parse/ParseExprCXX.cpp b/lib/Parse/ParseExprCXX.cpp index dc6f7cf458..ca50ef4000 100644 --- a/lib/Parse/ParseExprCXX.cpp +++ b/lib/Parse/ParseExprCXX.cpp @@ -1182,12 +1182,41 @@ bool Parser::ParseUnqualifiedId(CXXScopeSpec &SS, bool EnteringContext, // unqualified-id: // template-id (already parsed and annotated) if (Tok.is(tok::annot_template_id)) { - // FIXME: Could this be a constructor name??? - + TemplateIdAnnotation *TemplateId + = static_cast(Tok.getAnnotationValue()); + + // If the template-name names the current class, then this is a constructor + if (AllowConstructorName && TemplateId->Name && + Actions.isCurrentClassName(*TemplateId->Name, CurScope, &SS)) { + if (SS.isSet()) { + // C++ [class.qual]p2 specifies that a qualified template-name + // is taken as the constructor name where a constructor can be + // declared. Thus, the template arguments are extraneous, so + // complain about them and remove them entirely. + Diag(TemplateId->TemplateNameLoc, + diag::err_out_of_line_constructor_template_id) + << TemplateId->Name + << CodeModificationHint::CreateRemoval( + SourceRange(TemplateId->LAngleLoc, TemplateId->RAngleLoc)); + Result.setConstructorName(Actions.getTypeName(*TemplateId->Name, + TemplateId->TemplateNameLoc, + CurScope, + &SS, false), + TemplateId->TemplateNameLoc, + TemplateId->RAngleLoc); + TemplateId->Destroy(); + ConsumeToken(); + return false; + } + + Result.setConstructorTemplateId(TemplateId); + ConsumeToken(); + return false; + } + // We have already parsed a template-id; consume the annotation token as // our unqualified-id. - Result.setTemplateId( - static_cast(Tok.getAnnotationValue())); + Result.setTemplateId(TemplateId); ConsumeToken(); return false; } diff --git a/lib/Parse/ParseTemplate.cpp b/lib/Parse/ParseTemplate.cpp index 8b8af99ec6..797c1dfe3e 100644 --- a/lib/Parse/ParseTemplate.cpp +++ b/lib/Parse/ParseTemplate.cpp @@ -196,7 +196,8 @@ Parser::ParseSingleDeclarationAfterTemplate( if (getLang().CPlusPlus0x && isCXX0XAttributeSpecifier()) DS.AddAttributes(ParseCXX0XAttributes().AttrList); - ParseDeclarationSpecifiers(DS, TemplateInfo, AS); + ParseDeclarationSpecifiers(DS, TemplateInfo, AS, + getDeclSpecContextFromDeclaratorContext(Context)); if (Tok.is(tok::semi)) { DeclEnd = ConsumeToken(); diff --git a/lib/Parse/Parser.cpp b/lib/Parse/Parser.cpp index bf0c7b286a..2a94339c79 100644 --- a/lib/Parse/Parser.cpp +++ b/lib/Parse/Parser.cpp @@ -541,7 +541,7 @@ Parser::ParseDeclarationOrFunctionDefinition(ParsingDeclSpec &DS, if (Attr) DS.AddAttributes(Attr); - ParseDeclarationSpecifiers(DS, ParsedTemplateInfo(), AS); + ParseDeclarationSpecifiers(DS, ParsedTemplateInfo(), AS, DSC_top_level); // C99 6.7.2.3p6: Handle "struct-or-union identifier;", "enum { X };" // declaration-specifiers init-declarator-list[opt] ';' diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index e53f141c96..948418b42a 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -1972,6 +1972,30 @@ DeclarationName Sema::GetNameFromUnqualifiedId(const UnqualifiedId &Name) { Context.getCanonicalType(Ty)); } + case UnqualifiedId::IK_ConstructorTemplateId: { + // In well-formed code, we can only have a constructor + // template-id that refers to the current context, so go there + // to find the actual type being constructed. + CXXRecordDecl *CurClass = dyn_cast(CurContext); + if (!CurClass || CurClass->getIdentifier() != Name.TemplateId->Name) + return DeclarationName(); + + // Determine the type of the class being constructed. + QualType CurClassType; + if (ClassTemplateDecl *ClassTemplate + = CurClass->getDescribedClassTemplate()) + CurClassType = ClassTemplate->getInjectedClassNameType(Context); + else + CurClassType = Context.getTypeDeclType(CurClass); + + // FIXME: Check two things: that the template-id names the same type as + // CurClassType, and that the template-id does not occur when the name + // was qualified. + + return Context.DeclarationNames.getCXXConstructorName( + Context.getCanonicalType(CurClassType)); + } + case UnqualifiedId::IK_DestructorName: { QualType Ty = GetTypeFromParser(Name.DestructorName); if (Ty.isNull()) diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index edeb7c13e1..a81a04e45e 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -3002,6 +3002,7 @@ Sema::DeclPtrTy Sema::ActOnUsingDeclaration(Scope *S, break; case UnqualifiedId::IK_ConstructorName: + case UnqualifiedId::IK_ConstructorTemplateId: // C++0x inherited constructors. if (getLangOptions().CPlusPlus0x) break; diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index 2bddf9ecd6..7c0460dbed 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -897,6 +897,7 @@ QualType Sema::GetTypeForDeclarator(Declarator &D, Scope *S, break; case UnqualifiedId::IK_ConstructorName: + case UnqualifiedId::IK_ConstructorTemplateId: case UnqualifiedId::IK_DestructorName: // Constructors and destructors don't have return types. Use // "void" instead. diff --git a/test/CXX/basic/basic.lookup/basic.lookup.qual/class.qual/p2.cpp b/test/CXX/basic/basic.lookup/basic.lookup.qual/class.qual/p2.cpp new file mode 100644 index 0000000000..7ecedd5a6a --- /dev/null +++ b/test/CXX/basic/basic.lookup/basic.lookup.qual/class.qual/p2.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +struct X0 { + X0 f1(); + X0 f2(); +}; + +template +struct X1 { + X1(int); + (X1)(float); + X1 f2(); + X1 f2(int); + X1 f2(float); +}; + +// Error recovery: out-of-line constructors whose names have template arguments. +template X1::X1(int) { } // expected-error{{out-of-line constructor for 'X1' cannot have template arguments}} +template (X1::X1)(float) { } // expected-error{{out-of-line constructor for 'X1' cannot have template arguments}} + +// Error recovery: out-of-line constructor names intended to be types +X0::X0 X0::f1() { return X0(); } // expected-error{{qualified reference to 'X0' is a constructor name rather than a type wherever a constructor can be declared}} + +struct X0::X0 X0::f2() { return X0(); } + +template X1::X1 X1::f2() { } // expected-error{{qualified reference to 'X1' is a constructor name rather than a template name wherever a constructor can be declared}} +template X1::X1 (X1::f2)(int) { } // expected-error{{qualified reference to 'X1' is a constructor name rather than a template name wherever a constructor can be declared}} +template struct X1::X1 (X1::f2)(float) { } diff --git a/test/CXX/special/class.ctor/p1.cpp b/test/CXX/special/class.ctor/p1.cpp new file mode 100644 index 0000000000..9500a7d234 --- /dev/null +++ b/test/CXX/special/class.ctor/p1.cpp @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +struct X0 { + struct type { }; + + X0(); + X0(int); + (X0)(float); + X0 (f0)(int); + X0 (f0)(type); + + X0 f1(); + X0 f1(double); +}; + +X0::X0() { } +(X0::X0)(int) { } + +X0 (X0::f0)(int) { return X0(); } + +template +struct X1 { + struct type { }; + + X1(); + X1(int); + (X1)(float); + X1(float, float); + (X1)(double); + X1 (f0)(int); + X1 (f0)(type); + X1 (f1)(int); + X1 (f1)(type); + + template X1(U); + X1 f2(); + X1 f2(int); +}; + +template X1::X1() { } +template (X1::X1)(double) { } +template X1 X1::f1(int) { return 0; } +template X1 (X1::f1)(type) { return 0; } diff --git a/test/SemaTemplate/injected-class-name.cpp b/test/SemaTemplate/injected-class-name.cpp index 1a65aeb3d6..482eae14ba 100644 --- a/test/SemaTemplate/injected-class-name.cpp +++ b/test/SemaTemplate/injected-class-name.cpp @@ -11,11 +11,7 @@ struct X { typedef X *ptr; }; -// FIXME: EDG rejects this in their strict-conformance mode, but I -// don't see any wording making this ill-formed. Actually, -// [temp.local]p2 might make it ill-formed. Are we "in the scope of -// the class template specialization?" -X::X xi = x; +X::X xi = x; // expected-error{{qualified reference to 'X' is a constructor name rather than a template name wherever a constructor can be declared}} // [temp.local]p1: diff --git a/test/SemaTemplate/instantiate-member-class.cpp b/test/SemaTemplate/instantiate-member-class.cpp index 8a19d74cdd..742abcc569 100644 --- a/test/SemaTemplate/instantiate-member-class.cpp +++ b/test/SemaTemplate/instantiate-member-class.cpp @@ -14,8 +14,8 @@ public: X::C *c1; X::C *c2; -X::X *xi; -X::X *xf; +X::X *xi; // expected-error{{qualified reference to 'X' is a constructor name rather than a type wherever a constructor can be declared}} +X::X *xf; // expected-error{{qualified reference to 'X' is a constructor name rather than a type wherever a constructor can be declared}} void test_naming() { c1 = c2; // expected-error{{incompatible type assigning 'X::C *', expected 'X::C *'}}