From a8bc8c9e9ba5bffebde00340786fe8542469c435 Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Thu, 23 Dec 2010 22:44:42 +0000 Subject: [PATCH] Implement parsing of function parameter packs and non-type template parameter packs (C++0x [dcl.fct]p13), including disambiguation between unnamed function parameter packs and varargs (C++0x [dcl.fct]p14) for cases like void f(T...) where T may or may not contain unexpanded parameter packs. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@122520 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/DiagnosticSemaKinds.td | 10 +++ include/clang/Sema/DeclSpec.h | 8 +++ include/clang/Sema/Sema.h | 21 ++++++ lib/Parse/ParseDecl.cpp | 17 ++++- lib/Parse/ParseTentative.cpp | 15 ++-- lib/Sema/SemaDecl.cpp | 41 ++++++----- lib/Sema/SemaTemplate.cpp | 6 ++ lib/Sema/SemaTemplateVariadic.cpp | 69 +++++++++++++++++++ lib/Sema/SemaType.cpp | 64 +++++++++++++++++ test/CXX/dcl.decl/dcl.meaning/dcl.fct/p13.cpp | 48 +++++++++++++ test/CXX/dcl.decl/dcl.meaning/dcl.fct/p14.cpp | 31 +++++++++ 11 files changed, 308 insertions(+), 22 deletions(-) create mode 100644 test/CXX/dcl.decl/dcl.meaning/dcl.fct/p13.cpp create mode 100644 test/CXX/dcl.decl/dcl.meaning/dcl.fct/p14.cpp diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 3dfa0c5b31..8e7a2ba818 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -1861,6 +1861,12 @@ def err_pack_expansion_length_conflict : Error< "pack expansion contains parameter packs %0 and %1 that have different " "lengths (%2 vs. %3)">; +def err_function_parameter_pack_without_parameter_packs : Error< + "type %0 of function parameter pack does not contain any unexpanded " + "parameter packs">; +def err_ellipsis_in_declarator_not_parameter : Error< + "only function and template parameters can be parameter packs">; + // Unsupported variadic templates features def err_pack_expansion_unsupported : Error< "clang does not yet support %select{non-type|template}0 pack expansions">; @@ -1868,6 +1874,10 @@ def err_pack_expansion_instantiation_unsupported : Error< "clang cannot yet instantiate pack expansions">; def err_pack_expansion_mismatch_unsupported : Error< "clang cannot yet instantiate pack expansions with mismatched pack levels">; +def err_function_parameter_pack_unsupported : Error< + "clang does not yet support function parameter packs">; +def err_non_type_parameter_pack_unsupported : Error< + "clang does not yet support non-type template parameter packs">; def err_unexpected_typedef : Error< "unexpected type name %0: expected expression">; diff --git a/include/clang/Sema/DeclSpec.h b/include/clang/Sema/DeclSpec.h index 484204b1b2..3a57657e60 100644 --- a/include/clang/Sema/DeclSpec.h +++ b/include/clang/Sema/DeclSpec.h @@ -1186,6 +1186,10 @@ private: /// Extension - true if the declaration is preceded by __extension__. bool Extension : 1; + /// \brief If provided, the source location of the ellipsis used to describe + /// this declarator as a parameter pack. + SourceLocation EllipsisLoc; + friend struct DeclaratorChunk; public: @@ -1427,6 +1431,10 @@ public: void setGroupingParens(bool flag) { GroupingParens = flag; } bool hasGroupingParens() const { return GroupingParens; } + + bool hasEllipsis() const { return EllipsisLoc.isValid(); } + SourceLocation getEllipsisLoc() const { return EllipsisLoc; } + void setEllipsisLoc(SourceLocation EL) { EllipsisLoc = EL; } }; /// FieldDeclarator - This little struct is used to capture information about diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 33b18e0514..2b01e16c50 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -3134,6 +3134,8 @@ public: const TemplateArgument *Args, unsigned NumArgs); +#pragma mark C++ Variadic Templates (C++0x [temp.variadic]) + /// \brief The context in which an unexpanded parameter pack is /// being diagnosed. /// @@ -3325,6 +3327,25 @@ public: bool &ShouldExpand, unsigned &NumExpansions); + /// \brief Determine whether the given declarator contains any unexpanded + /// parameter packs. + /// + /// This routine is used by the parser to disambiguate function declarators + /// with an ellipsis prior to the ')', e.g., + /// + /// \code + /// void f(T...); + /// \endcode + /// + /// To determine whether we have an (unnamed) function parameter pack or + /// a variadic function. + /// + /// \returns true if the declarator contains any unexpanded parameter packs, + /// false otherwise. + bool containsUnexpandedParameterPacks(Declarator &D); + +#pragma mark C++ Template Argument Deduction (C++ [temp.deduct]) + /// \brief Describes the result of template argument deduction. /// /// The TemplateDeductionResult enumeration describes the result of diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 53cccc0819..f627b2c9f4 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -2819,7 +2819,7 @@ void Parser::ParseDeclaratorInternal(Declarator &D, /// [C++] declarator-id /// /// declarator-id: [C++ 8] -/// id-expression +/// '...'[opt] id-expression /// '::'[opt] nested-name-specifier[opt] type-name /// /// id-expression: [C++ 5.1] @@ -2849,6 +2849,21 @@ void Parser::ParseDirectDeclarator(Declarator &D) { DeclScopeObj.EnterDeclaratorScope(); } + // C++0x [dcl.fct]p14: + // There is a syntactic ambiguity when an ellipsis occurs at the end + // of a parameter-declaration-clause without a preceding comma. In + // this case, the ellipsis is parsed as part of the + // abstract-declarator if the type of the parameter names a template + // parameter pack that has not been expanded; otherwise, it is parsed + // as part of the parameter-declaration-clause. + if (Tok.is(tok::ellipsis) && + !((D.getContext() == Declarator::PrototypeContext || + D.getContext() == Declarator::BlockLiteralContext) && + getCurScope()->getTemplateParamParent() && + NextToken().is(tok::r_paren) && + !Actions.containsUnexpandedParameterPacks(D))) + D.setEllipsisLoc(ConsumeToken()); + if (Tok.is(tok::identifier) || Tok.is(tok::kw_operator) || Tok.is(tok::annot_template_id) || Tok.is(tok::tilde)) { // We found something that indicates the start of an unqualified-id. diff --git a/lib/Parse/ParseTentative.cpp b/lib/Parse/ParseTentative.cpp index 81abdb884a..fc57b4086b 100644 --- a/lib/Parse/ParseTentative.cpp +++ b/lib/Parse/ParseTentative.cpp @@ -461,6 +461,7 @@ bool Parser::isCXX0XAttributeSpecifier (bool CheckClosing, /// abstract-declarator: /// ptr-operator abstract-declarator[opt] /// direct-abstract-declarator +/// ... /// /// direct-abstract-declarator: /// direct-abstract-declarator[opt] @@ -483,7 +484,7 @@ bool Parser::isCXX0XAttributeSpecifier (bool CheckClosing, /// 'volatile' /// /// declarator-id: -/// id-expression +/// '...'[opt] id-expression /// /// id-expression: /// unqualified-id @@ -522,7 +523,9 @@ Parser::TPResult Parser::TryParseDeclarator(bool mayBeAbstract, // direct-declarator: // direct-abstract-declarator: - + if (Tok.is(tok::ellipsis)) + ConsumeToken(); + if ((Tok.is(tok::identifier) || (Tok.is(tok::annot_cxxscope) && NextToken().is(tok::identifier))) && mayHaveIdentifier) { @@ -566,6 +569,10 @@ Parser::TPResult Parser::TryParseDeclarator(bool mayBeAbstract, while (1) { TPResult TPR(TPResult::Ambiguous()); + // abstract-declarator: ... + if (Tok.is(tok::ellipsis)) + ConsumeToken(); + if (Tok.is(tok::l_paren)) { // Check whether we have a function declarator or a possible ctor-style // initializer that follows the declarator. Note that ctor-style @@ -1165,8 +1172,8 @@ Parser::TPResult Parser::TryParseParameterDeclarationClause() { if (Tok.is(tok::equal)) { // '=' assignment-expression // Parse through assignment-expression. - tok::TokenKind StopToks[3] ={ tok::comma, tok::ellipsis, tok::r_paren }; - if (!SkipUntil(StopToks, 3, true/*StopAtSemi*/, true/*DontConsume*/)) + tok::TokenKind StopToks[2] ={ tok::comma, tok::r_paren }; + if (!SkipUntil(StopToks, 2, true/*StopAtSemi*/, true/*DontConsume*/)) return TPResult::Error(); } diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index 890399c428..d596649479 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -4959,20 +4959,34 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D) { DiagnoseFunctionSpecifiers(D); - // Check that there are no default arguments inside the type of this - // parameter (C++ only). - if (getLangOptions().CPlusPlus) - CheckExtraCXXDefaultArguments(D); - TagDecl *OwnedDecl = 0; TypeSourceInfo *TInfo = GetTypeForDeclarator(D, S, &OwnedDecl); QualType parmDeclType = TInfo->getType(); - if (getLangOptions().CPlusPlus && OwnedDecl && OwnedDecl->isDefinition()) { - // C++ [dcl.fct]p6: - // Types shall not be defined in return or parameter types. - Diag(OwnedDecl->getLocation(), diag::err_type_defined_in_param_type) - << Context.getTypeDeclType(OwnedDecl); + if (getLangOptions().CPlusPlus) { + // Check that there are no default arguments inside the type of this + // parameter. + CheckExtraCXXDefaultArguments(D); + + if (OwnedDecl && OwnedDecl->isDefinition()) { + // C++ [dcl.fct]p6: + // Types shall not be defined in return or parameter types. + Diag(OwnedDecl->getLocation(), diag::err_type_defined_in_param_type) + << Context.getTypeDeclType(OwnedDecl); + } + + // Parameter declarators cannot be qualified (C++ [dcl.meaning]p1). + if (D.getCXXScopeSpec().isSet()) { + Diag(D.getIdentifierLoc(), diag::err_qualified_param_declarator) + << D.getCXXScopeSpec().getRange(); + D.getCXXScopeSpec().clear(); + } + + // FIXME: Variadic templates. + if (D.hasEllipsis()) { + Diag(D.getEllipsisLoc(), diag::err_function_parameter_pack_unsupported); + D.setInvalidType(); + } } // Ensure we have a valid name @@ -5021,13 +5035,6 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D) { if (D.isInvalidType()) New->setInvalidDecl(); - // Parameter declarators cannot be qualified (C++ [dcl.meaning]p1). - if (D.getCXXScopeSpec().isSet()) { - Diag(D.getIdentifierLoc(), diag::err_qualified_param_declarator) - << D.getCXXScopeSpec().getRange(); - New->setInvalidDecl(); - } - // Add the parameter declaration into this scope. S->AddDecl(New); if (II) diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index 1f3e2ef9c8..7357e94490 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -632,6 +632,12 @@ Decl *Sema::ActOnNonTypeTemplateParameter(Scope *S, Declarator &D, Invalid = true; } + if (D.hasEllipsis()) { + // FIXME: Variadic templates. + Diag(D.getEllipsisLoc(), diag::err_non_type_parameter_pack_unsupported); + Invalid = true; + } + NonTypeTemplateParmDecl *Param = NonTypeTemplateParmDecl::Create(Context, Context.getTranslationUnitDecl(), D.getIdentifierLoc(), diff --git a/lib/Sema/SemaTemplateVariadic.cpp b/lib/Sema/SemaTemplateVariadic.cpp index bb9c01afad..d9c7e72a86 100644 --- a/lib/Sema/SemaTemplateVariadic.cpp +++ b/lib/Sema/SemaTemplateVariadic.cpp @@ -413,3 +413,72 @@ bool Sema::CheckParameterPacksForExpansion(SourceLocation EllipsisLoc, return false; } + +bool Sema::containsUnexpandedParameterPacks(Declarator &D) { + const DeclSpec &DS = D.getDeclSpec(); + switch (DS.getTypeSpecType()) { + case TST_typename: + case TST_typeofType: { + QualType T = DS.getRepAsType().get(); + if (!T.isNull() && T->containsUnexpandedParameterPack()) + return true; + break; + } + + case TST_typeofExpr: + case TST_decltype: + if (DS.getRepAsExpr() && + DS.getRepAsExpr()->containsUnexpandedParameterPack()) + return true; + break; + + case TST_unspecified: + case TST_void: + case TST_char: + case TST_wchar: + case TST_char16: + case TST_char32: + case TST_int: + case TST_float: + case TST_double: + case TST_bool: + case TST_decimal32: + case TST_decimal64: + case TST_decimal128: + case TST_enum: + case TST_union: + case TST_struct: + case TST_class: + case TST_auto: + case TST_error: + break; + } + + for (unsigned I = 0, N = D.getNumTypeObjects(); I != N; ++I) { + const DeclaratorChunk &Chunk = D.getTypeObject(I); + switch (Chunk.Kind) { + case DeclaratorChunk::Pointer: + case DeclaratorChunk::Reference: + case DeclaratorChunk::Paren: + // These declarator chunks cannot contain any parameter packs. + break; + + case DeclaratorChunk::Array: + case DeclaratorChunk::Function: + case DeclaratorChunk::BlockPointer: + // Syntactically, these kinds of declarator chunks all come after the + // declarator-id (conceptually), so the parser should not invoke this + // routine at this time. + llvm_unreachable("Could not have seen this kind of declarator chunk"); + break; + + case DeclaratorChunk::MemberPointer: + if (Chunk.Mem.Scope().getScopeRep() && + Chunk.Mem.Scope().getScopeRep()->containsUnexpandedParameterPack()) + return true; + break; + } + } + + return false; +} diff --git a/lib/Sema/SemaType.cpp b/lib/Sema/SemaType.cpp index 09b187b0d0..e693c8d4da 100644 --- a/lib/Sema/SemaType.cpp +++ b/lib/Sema/SemaType.cpp @@ -1465,6 +1465,64 @@ TypeSourceInfo *Sema::GetTypeForDeclarator(Declarator &D, Scope *S, T.addConst(); } + // If there was an ellipsis in the declarator, the declaration declares a + // parameter pack whose type may be a pack expansion type. + if (D.hasEllipsis() && !T.isNull()) { + // C++0x [dcl.fct]p13: + // A declarator-id or abstract-declarator containing an ellipsis shall + // only be used in a parameter-declaration. Such a parameter-declaration + // is a parameter pack (14.5.3). [...] + switch (D.getContext()) { + case Declarator::PrototypeContext: + // C++0x [dcl.fct]p13: + // [...] When it is part of a parameter-declaration-clause, the + // parameter pack is a function parameter pack (14.5.3). The type T + // of the declarator-id of the function parameter pack shall contain + // a template parameter pack; each template parameter pack in T is + // expanded by the function parameter pack. + // + // We represent function parameter packs as function parameters whose + // type is a pack expansion. + if (!T->containsUnexpandedParameterPack()) { + Diag(D.getEllipsisLoc(), + diag::err_function_parameter_pack_without_parameter_packs) + << T << D.getSourceRange(); + D.setEllipsisLoc(SourceLocation()); + } else { + T = Context.getPackExpansionType(T); + } + break; + + case Declarator::TemplateParamContext: + // C++0x [temp.param]p15: + // If a template-parameter is a [...] is a parameter-declaration that + // declares a parameter pack (8.3.5), then the template-parameter is a + // template parameter pack (14.5.3). + // + // Note: core issue 778 clarifies that, if there are any unexpanded + // parameter packs in the type of the non-type template parameter, then + // it expands those parameter packs. + if (T->containsUnexpandedParameterPack()) + T = Context.getPackExpansionType(T); + break; + + case Declarator::FileContext: + case Declarator::KNRTypeListContext: + case Declarator::TypeNameContext: + case Declarator::MemberContext: + case Declarator::BlockContext: + case Declarator::ForContext: + case Declarator::ConditionContext: + case Declarator::CXXCatchContext: + case Declarator::BlockLiteralContext: + // FIXME: We may want to allow parameter packs in block-literal contexts + // in the future. + Diag(D.getEllipsisLoc(), diag::err_ellipsis_in_declarator_not_parameter); + D.setEllipsisLoc(SourceLocation()); + break; + } + } + // Process any function attributes we might have delayed from the // declaration-specifiers. ProcessDelayedFnAttrs(*this, T, FnAttrsFromDeclSpec); @@ -1729,6 +1787,12 @@ Sema::GetTypeSourceInfoForDeclarator(Declarator &D, QualType T, TypeSourceInfo *TInfo = Context.CreateTypeSourceInfo(T); UnqualTypeLoc CurrTL = TInfo->getTypeLoc().getUnqualifiedLoc(); + // Handle parameter packs whose type is a pack expansion. + if (isa(T)) { + cast(CurrTL).setEllipsisLoc(D.getEllipsisLoc()); + CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc(); + } + for (unsigned i = 0, e = D.getNumTypeObjects(); i != e; ++i) { DeclaratorLocFiller(D.getTypeObject(i)).Visit(CurrTL); CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc(); diff --git a/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p13.cpp b/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p13.cpp new file mode 100644 index 0000000000..59a0e7538d --- /dev/null +++ b/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p13.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -std=c++0x -fsyntax-only -fexceptions -verify %s + +// When it is part of a parameter-declaration-clause, the parameter +// pack is a function parameter pack. +template +void f0(Types ...args); // FIXME: temporary expected-error{{clang does not yet support function parameter packs}} + +template +void f1(const Types &...args); // FIXME: temporary expected-error{{clang does not yet support function parameter packs}} + +// [ Note: Otherwise, the parameter-declaration is part of a +// template-parameter-list and the parameter pack is a template +// parameter pack; see 14.1. -- end note ] +template // FIXME: temporary expected-error{{clang does not yet support non-type template parameter packs}} +struct X0 { }; + +template +struct X1 { + template struct Inner; // FIXME: temporary expected-error{{clang does not yet support non-type template parameter packs}} +}; + +// A declarator-id or abstract-declarator containing an ellipsis shall +// only be used in a parameter-declaration. +int (...f2)(int); // expected-error{{only function and template parameters can be parameter packs}} + +void f3() { + int ...x; // expected-error{{only function and template parameters can be parameter packs}} + if (int ...y = 17) { } // expected-error{{only function and template parameters can be parameter packs}} + + for (int ...z = 0; z < 10; ++z) { } // expected-error{{only function and template parameters can be parameter packs}} + + try { + } catch (int ...e) { // expected-error{{only function and template parameters can be parameter packs}} + } +} + +template +struct X2 { + Types ...members; // expected-error{{only function and template parameters can be parameter packs}} \ + // expected-error{{data member type contains unexpanded parameter pack}} +}; + +// The type T of the declarator-id of the function parameter pack +// shall contain a template parameter pack; each template parameter +// pack in T is expanded by the function parameter pack. +template +void f4(T ...args); // expected-error{{type 'T' of function parameter pack does not contain any unexpanded parameter packs}} + diff --git a/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p14.cpp b/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p14.cpp new file mode 100644 index 0000000000..5f7de87808 --- /dev/null +++ b/test/CXX/dcl.decl/dcl.meaning/dcl.fct/p14.cpp @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -std=c++0x -fsyntax-only -verify %s + +template struct identity; +template struct tuple; + +template struct is_same { + static const bool value = false; +}; + +template struct is_same { + static const bool value = true; +}; + +// There is a syntactic ambiguity when an ellipsis occurs at the end +// of a parameter-declaration-clause without a preceding comma. In +// this case, the ellipsis is parsed as part of the +// abstract-declarator if the type of the parameter names a template +// parameter pack that has not been expanded; otherwise, it is parsed +// as part of the parameter-declaration-clause. + +template +struct X0 { + typedef identity function_pack_1; // expected-error{{clang does not yet support function parameter packs}} + typedef identity variadic_function_pack_1; // expected-error{{clang does not yet support function parameter packs}} + typedef identity variadic_1; + typedef tuple template_arg_expansion_1; +}; + + + +// FIXME: Once function parameter packs are implemented, we can test all of the disambiguation -- 2.40.0