diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -3658,6 +3658,9 @@ /// FIXME: could also be a template-id DeclarationNameInfo MemberNameInfo; + /// Whether the member is a member pack. + bool isMemberPack; + // CXXDependentScopeMemberExpr is followed by several trailing objects, // some of which optional. They are in order: // @@ -3693,14 +3696,12 @@ return hasFirstQualifierFoundInScope(); } - CXXDependentScopeMemberExpr(const ASTContext &Ctx, Expr *Base, - QualType BaseType, bool IsArrow, - SourceLocation OperatorLoc, - NestedNameSpecifierLoc QualifierLoc, - SourceLocation TemplateKWLoc, - NamedDecl *FirstQualifierFoundInScope, - DeclarationNameInfo MemberNameInfo, - const TemplateArgumentListInfo *TemplateArgs); + CXXDependentScopeMemberExpr( + const ASTContext &Ctx, Expr *Base, QualType BaseType, bool IsArrow, + SourceLocation OperatorLoc, NestedNameSpecifierLoc QualifierLoc, + SourceLocation TemplateKWLoc, NamedDecl *FirstQualifierFoundInScope, + DeclarationNameInfo MemberNameInfo, + const TemplateArgumentListInfo *TemplateArgs, bool isMemberPack); CXXDependentScopeMemberExpr(EmptyShell Empty, bool HasTemplateKWAndArgsInfo, bool HasFirstQualifierFoundInScope); @@ -3711,7 +3712,8 @@ SourceLocation OperatorLoc, NestedNameSpecifierLoc QualifierLoc, SourceLocation TemplateKWLoc, NamedDecl *FirstQualifierFoundInScope, DeclarationNameInfo MemberNameInfo, - const TemplateArgumentListInfo *TemplateArgs); + const TemplateArgumentListInfo *TemplateArgs, + bool isMemberPack = false); static CXXDependentScopeMemberExpr * CreateEmpty(const ASTContext &Ctx, bool HasTemplateKWAndArgsInfo, @@ -3735,6 +3737,8 @@ QualType getBaseType() const { return BaseType; } + bool isMemberPackAccess() const { return isMemberPack; } + /// Determine whether this member expression used the '->' /// operator; otherwise, it used the '.' operator. bool isArrow() const { return CXXDependentScopeMemberExprBits.IsArrow; } diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp --- a/clang/lib/AST/ComputeDependence.cpp +++ b/clang/lib/AST/ComputeDependence.cpp @@ -826,6 +826,8 @@ D |= getDependenceInExpr(E->getMemberNameInfo()); for (const auto &A : E->template_arguments()) D |= toExprDependence(A.getArgument().getDependence()); + if (E->isMemberPackAccess()) + D |= ExprDependence::UnexpandedPack; return D; } diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -1443,11 +1443,11 @@ SourceLocation OperatorLoc, NestedNameSpecifierLoc QualifierLoc, SourceLocation TemplateKWLoc, NamedDecl *FirstQualifierFoundInScope, DeclarationNameInfo MemberNameInfo, - const TemplateArgumentListInfo *TemplateArgs) + const TemplateArgumentListInfo *TemplateArgs, bool isMemberPack) : Expr(CXXDependentScopeMemberExprClass, Ctx.DependentTy, VK_LValue, OK_Ordinary), Base(Base), BaseType(BaseType), QualifierLoc(QualifierLoc), - MemberNameInfo(MemberNameInfo) { + MemberNameInfo(MemberNameInfo), isMemberPack(isMemberPack) { CXXDependentScopeMemberExprBits.IsArrow = IsArrow; CXXDependentScopeMemberExprBits.HasTemplateKWAndArgsInfo = (TemplateArgs != nullptr) || TemplateKWLoc.isValid(); @@ -1478,6 +1478,7 @@ HasTemplateKWAndArgsInfo; CXXDependentScopeMemberExprBits.HasFirstQualifierFoundInScope = HasFirstQualifierFoundInScope; + isMemberPack = false; } CXXDependentScopeMemberExpr *CXXDependentScopeMemberExpr::Create( @@ -1485,7 +1486,8 @@ SourceLocation OperatorLoc, NestedNameSpecifierLoc QualifierLoc, SourceLocation TemplateKWLoc, NamedDecl *FirstQualifierFoundInScope, DeclarationNameInfo MemberNameInfo, - const TemplateArgumentListInfo *TemplateArgs) { + const TemplateArgumentListInfo *TemplateArgs, + bool isMemberPack) { bool HasTemplateKWAndArgsInfo = (TemplateArgs != nullptr) || TemplateKWLoc.isValid(); unsigned NumTemplateArgs = TemplateArgs ? TemplateArgs->size() : 0; @@ -1498,7 +1500,7 @@ void *Mem = Ctx.Allocate(Size, alignof(CXXDependentScopeMemberExpr)); return new (Mem) CXXDependentScopeMemberExpr( Ctx, Base, BaseType, IsArrow, OperatorLoc, QualifierLoc, TemplateKWLoc, - FirstQualifierFoundInScope, MemberNameInfo, TemplateArgs); + FirstQualifierFoundInScope, MemberNameInfo, TemplateArgs, isMemberPack); } CXXDependentScopeMemberExpr *CXXDependentScopeMemberExpr::CreateEmpty( diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp --- a/clang/lib/Sema/SemaExprMember.cpp +++ b/clang/lib/Sema/SemaExprMember.cpp @@ -514,12 +514,26 @@ return Arg.getArgument().isDependent(); }))); - // Get the type being accessed in BaseType. If this is an arrow, the BaseExpr + bool isMemberPack = false; + // Check for member packs by looking up base's template decl. + if (const auto *ET = dyn_cast(BaseType)) { + if (const auto *TST = + dyn_cast(ET->getNamedType())) { + const auto *TD = TST->getTemplateName().getAsTemplateDecl(); + const auto Fields = cast(TD)->getTemplatedDecl()->fields(); + auto It = llvm::find_if(Fields, [NameInfo](const FieldDecl *Field) { + return Field->getDeclName() == NameInfo.getName(); + }); + if (It != Fields.end() && isa(*It->getType())) + isMemberPack = true; + } + } + // Get the type being accessed in BaseType. If this is an arrow, the BaseExpr // must have pointer type, and the accessed type is the pointee. return CXXDependentScopeMemberExpr::Create( Context, BaseExpr, BaseType, IsArrow, OpLoc, SS.getWithLocInContext(Context), TemplateKWLoc, FirstQualifierInScope, - NameInfo, TemplateArgs); + NameInfo, TemplateArgs, isMemberPack); } /// We know that the given qualified member reference points only to @@ -995,10 +1009,16 @@ << isa(FD); if (R.empty()) { + // The member is expanded from member pack if its name has `@` in it. In this + // case a failed name lookup is not an error, but represents it's at the end + // of the expanded members. Return an empty expression to inform its + // caller. + auto MemberNameStr = MemberNameInfo.getName().getAsString(); + if (MemberNameStr.find('@') != std::string::npos) + return ExprEmpty(); // Rederive where we looked up. - DeclContext *DC = (SS.isSet() - ? computeDeclContext(SS, false) - : BaseType->castAs()->getDecl()); + DeclContext *DC = (SS.isSet() ? computeDeclContext(SS, false) + : BaseType->castAs()->getDecl()); if (ExtraArgs) { ExprResult RetryExpr; diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -3135,6 +3135,99 @@ } } +/// Instantiate the member pack of a class. +void InstantiateMemberPack(Sema &S, CXXRecordDecl *Instantiation, + const MultiLevelTemplateArgumentList &TemplateArgs, + FieldDecl *Field, SmallVector &Fields, + TemplateDeclInstantiator &Instantiator) { + QualType PatternType = + Field->getType()->castAs()->getPattern(); + std::optional NumArgumentsInExpansion = + S.getNumArgumentsInExpansion(Field->getType(), TemplateArgs); + assert(NumArgumentsInExpansion && + "should not see unknown template argument here"); + for (unsigned Arg = 0; Arg < *NumArgumentsInExpansion; ++Arg) { + // Generate a new field from PackExpansion field. + if (Decl *NewMember = Instantiator.Visit(Field)) { + FieldDecl *PackedField = cast(NewMember); + Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(S, Arg); + QualType T = + S.SubstType(PatternType, TemplateArgs, PackedField->getLocation(), + PackedField->getDeclName()); + PackedField->setType(T); + // Rename the expanded fields to avoid ambiguous names in + // name lookup. The renamed fields need to be invisible to users so an + // `@` is added to the name. + std::string NewFieldName = + PackedField->getName().str() + "@" + std::to_string(Arg); + PackedField->setDeclName(&(S.Context).Idents.get(NewFieldName)); + Fields.push_back(PackedField); + if (NewMember->isInvalidDecl()) { + // When `NewMember` has type of `PackExpansionType`, it escapes + // validation checks in `Visit`. Handling of such cases + // will be implemented in a future commit. + // Currently this branch should never be reached. + assert(false && "not implemented"); + Instantiation->setInvalidDecl(); + } + } else { + // FIXME: This is the same situation of InstantiateMember, when handling + // non-pack members. + continue; + } + } +} + +/// Instantiate the non-pack members of a class. +/// +/// \returns true if need to bail out the member instantiation ,loop, false otherwise. +bool InstantiateMember(Sema &S, SourceLocation &PointOfInstantiation, + CXXRecordDecl *Instantiation, Decl *Member, + TemplateSpecializationKind TSK, + SmallVector &Fields, + TemplateDeclInstantiator &Instantiator, + bool &MightHaveConstexprVirtualFunctions) { + Decl *NewMember = Instantiator.Visit(Member); + if (NewMember) { + if (FieldDecl *Field = dyn_cast(NewMember)) { + Fields.push_back(Field); + } else if (EnumDecl *Enum = dyn_cast(NewMember)) { + // C++11 [temp.inst]p1: The implicit instantiation of a class template + // specialization causes the implicit instantiation of the definitions + // of unscoped member enumerations. + // Record a point of instantiation for this implicit instantiation. + if (TSK == TSK_ImplicitInstantiation && !Enum->isScoped() && + Enum->isCompleteDefinition()) { + MemberSpecializationInfo *MSInfo = Enum->getMemberSpecializationInfo(); + assert(MSInfo && "no spec info for member enum specialization"); + MSInfo->setTemplateSpecializationKind(TSK_ImplicitInstantiation); + MSInfo->setPointOfInstantiation(PointOfInstantiation); + } + } else if (StaticAssertDecl *SA = dyn_cast(NewMember)) { + if (SA->isFailed()) { + // A static_assert failed. Bail out; instantiating this + // class is probably not meaningful. + Instantiation->setInvalidDecl(); + return true; + } + } else if (CXXMethodDecl *MD = dyn_cast(NewMember)) { + if (MD->isConstexpr() && !MD->getFriendObjectKind() && + (MD->isVirtualAsWritten() || Instantiation->getNumBases())) + MightHaveConstexprVirtualFunctions = true; + } + + if (NewMember->isInvalidDecl()) + Instantiation->setInvalidDecl(); + } else { + // FIXME: Eventually, a NULL return will mean that one of the + // instantiations was a semantic disaster, and we'll want to mark the + // declaration invalid. + // For now, we expect to skip some members that we can't yet handle. + // Same situation occurs when handling member packs, in InstantiateMember. + } + return false; +} + /// Instantiate the definition of a class from a given pattern. /// /// \param PointOfInstantiation The point of instantiation within the @@ -3267,42 +3360,16 @@ continue; } - Decl *NewMember = Instantiator.Visit(Member); - if (NewMember) { - if (FieldDecl *Field = dyn_cast(NewMember)) { - Fields.push_back(Field); - } else if (EnumDecl *Enum = dyn_cast(NewMember)) { - // C++11 [temp.inst]p1: The implicit instantiation of a class template - // specialization causes the implicit instantiation of the definitions - // of unscoped member enumerations. - // Record a point of instantiation for this implicit instantiation. - if (TSK == TSK_ImplicitInstantiation && !Enum->isScoped() && - Enum->isCompleteDefinition()) { - MemberSpecializationInfo *MSInfo =Enum->getMemberSpecializationInfo(); - assert(MSInfo && "no spec info for member enum specialization"); - MSInfo->setTemplateSpecializationKind(TSK_ImplicitInstantiation); - MSInfo->setPointOfInstantiation(PointOfInstantiation); - } - } else if (StaticAssertDecl *SA = dyn_cast(NewMember)) { - if (SA->isFailed()) { - // A static_assert failed. Bail out; instantiating this - // class is probably not meaningful. - Instantiation->setInvalidDecl(); - break; - } - } else if (CXXMethodDecl *MD = dyn_cast(NewMember)) { - if (MD->isConstexpr() && !MD->getFriendObjectKind() && - (MD->isVirtualAsWritten() || Instantiation->getNumBases())) - MightHaveConstexprVirtualFunctions = true; - } - - if (NewMember->isInvalidDecl()) - Instantiation->setInvalidDecl(); + // Instantiate data member packs. + if (FieldDecl *Field = dyn_cast(Member); + Field && isa(Field->getType().getTypePtr())) { + InstantiateMemberPack(*this, Instantiation, TemplateArgs, Field, Fields, Instantiator); } else { - // FIXME: Eventually, a NULL return will mean that one of the - // instantiations was a semantic disaster, and we'll want to mark the - // declaration invalid. - // For now, we expect to skip some members that we can't yet handle. + // Instantiate normal members. + if (InstantiateMember(*this, PointOfInstantiation, Instantiation, Member, + TSK, Fields, Instantiator, + MightHaveConstexprVirtualFunctions)) + break; } } diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -5927,6 +5927,9 @@ /*ExpectPackInType=*/false); } break; + case DeclaratorContext::Member: + // Expand for data member packs. + // https://discourse.llvm.org/t/adding-support-for-data-member-packs/71333 case DeclaratorContext::TemplateParam: // C++0x [temp.param]p15: // If a template-parameter is a [...] is a parameter-declaration that @@ -5954,7 +5957,6 @@ case DeclaratorContext::CXXNew: case DeclaratorContext::AliasDecl: case DeclaratorContext::AliasTemplate: - case DeclaratorContext::Member: case DeclaratorContext::Block: case DeclaratorContext::ForInit: case DeclaratorContext::SelectionInit: diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -4168,6 +4168,58 @@ if (PackExpansionExpr *Expansion = dyn_cast(Inputs[I])) { Expr *Pattern = Expansion->getPattern(); + if (CXXDependentScopeMemberExpr *MemberExpr = + dyn_cast(Pattern)) { + if (ArgChanged) + *ArgChanged = true; + assert(MemberExpr->isMemberPackAccess() && + "trying to expand non-pack member access"); + std::string UnExpanedNameStr = + MemberExpr->getMemberNameInfo().getName().getAsString(); + + ExprResult Base = getDerived().TransformExpr(MemberExpr->getBase()); + if (Base.isInvalid()) + return true; + QualType BaseType = ((Expr *)Base.get())->getType(); + if (MemberExpr->isArrow()) { + assert(BaseType->isPointerType()); + BaseType = BaseType->castAs()->getPointeeType(); + } + + unsigned Arg = 0; + while (true) { + // Transform unexpanded field name and create a new member expression. + DeclarationName ExpandedName = &SemaRef.Context.Idents.get( + UnExpanedNameStr + "@" + std::to_string(Arg)); + // Construct name info with new name and keep other members the same. + DeclarationNameInfo ExpandedNameInfo = DeclarationNameInfo( + ExpandedName, MemberExpr->getMemberNameInfo().getLoc(), + MemberExpr->getMemberNameInfo().getInfo()); + TemplateArgumentListInfo TemplateArgs = TemplateArgumentListInfo(); + MemberExpr->copyTemplateArgumentsInto(TemplateArgs); + auto *ExpandedMemberExpr = CXXDependentScopeMemberExpr::Create( + SemaRef.Context, MemberExpr->getBase(), MemberExpr->getBaseType(), + MemberExpr->isArrow(), MemberExpr->getOperatorLoc(), + MemberExpr->getQualifierLoc(), + MemberExpr->getTemplateKeywordLoc(), + MemberExpr->getFirstQualifierFoundInScope(), ExpandedNameInfo, + &TemplateArgs, MemberExpr->isMemberPackAccess()); + + Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(getSema(), Arg); + ExprResult Out = getDerived().TransformExpr(ExpandedMemberExpr); + if (Out.isInvalid()) + return true; + // An empty expression is returned when name lookup fails in accessing + // member packs. This means the last field in member pack has been + // processd and time to exit the loop. + if (Out.isUnset()) + break; + Outputs.push_back(Out.get()); + Arg++; + } + continue; + } + SmallVector Unexpanded; getSema().collectUnexpandedParameterPacks(Pattern, Unexpanded); assert(!Unexpanded.empty() && "Pack expansion without parameter packs?"); diff --git a/clang/test/CodeGenCXX/data_member_packs.cpp b/clang/test/CodeGenCXX/data_member_packs.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/data_member_packs.cpp @@ -0,0 +1,79 @@ +// RUN: %clang_cc1 --std=c++20 %s -emit-llvm -o - -triple x86_64-linux | FileCheck %s --check-prefixes=CHECK + +// Tests declaration data member packs. +template struct S1 { + Ts... ts; +}; + +template struct S2 { + T t[2]; + Ts... ts; +}; + +template struct S3 { + T t; + Ts... ts; +}; + +// CHECK: %struct.S1 = type { i32 } +S1 s1; +// CHECK-NEXT: %struct.S1.0 = type { i32, float, double } +S1 s2; +// Test template args as the last arg. +// CHECK-NEXT: %struct.S2 = type { [2 x i32], float, double } +S2 s3; +// Test nested template args. +// CHECK-NEXT: %struct.S1.1 = type { i32, float, %struct.S1.2 } +// CHECK-NEXT: %struct.S1.2 = type { double, double } +S1> s4; +// Test empty template arg. +// CHECK-NEXT: %struct.S1.3 = type { i8 } +S1<> s5; +// Test duplicate types in template args. +// CHECK-NEXT: %struct.S1.4 = type { i32, i32 } +S1 s6; + +S1 s7; + +template auto sum(Ts... ts) { + return (ts + ...); +} + +auto take_empty() { + return 0; +} + +template auto sum_pack(S1 s) { + return sum(s.ts...); +} +// Test template arg + expansion. +template auto sum_pack2(S1 s) { + return sum(s.ts...); +} +// Test empty expansion. +template auto take_empty(S3 s) { + return take_empty(s.ts...); +} +// Test nested template args and multiple expansions. +template struct S4 { + template auto sum_pack(S1 s) { + return sum(s.ts...); + } +}; + +int main() { + // Check calling take_empty() + // CHECK: %call = call noundef i32 @_Z10take_emptyv() + take_empty(S3{}); + // Check instantiation of sum(int, float, double) + // CHECK: double @_Z3sumIJifdEEDaDpT_(i32 noundef %ts, float noundef %ts1, double noundef %ts3) + sum_pack(s2); + // Check instantiation of sum(int, int) + // CHECK: i32 @_Z3sumIJiiEEDaDpT_(i32 noundef %ts, i32 noundef %ts1) + sum_pack2(s6); + // Check instantiation of sum(int, long, float, double) + // CHECK: double @_Z3sumIJilfdEEDaDpT_(i32 noundef %ts, i64 noundef %ts1, float noundef %ts3, double noundef %ts5) + S4{}.sum_pack(s7); + return 0; +} +