Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Clang][C++26] Implement Pack Indexing (P2662R3). #72644

Merged
merged 12 commits into from
Jan 27, 2024

Conversation

cor3ntin
Copy link
Contributor

https://isocpp.org/files/papers/P2662R3.pdf

Because there is a slight chance the syntax might change slightly (see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2994r0.html), the feature is not exposed in other language modes.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:codegen clang:static analyzer debuginfo labels Nov 17, 2023
@llvmbot
Copy link
Member

llvmbot commented Nov 17, 2023

@llvm/pr-subscribers-clang
@llvm/pr-subscribers-clang-static-analyzer-1
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang-modules

Author: cor3ntin (cor3ntin)

Changes

https://isocpp.org/files/papers/P2662R3.pdf

Because there is a slight chance the syntax might change slightly (see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2994r0.html), the feature is not exposed in other language modes.


Patch is 101.21 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/72644.diff

69 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/include/clang-c/Index.h (+6-1)
  • (modified) clang/include/clang/AST/ASTContext.h (+8)
  • (modified) clang/include/clang/AST/ASTNodeTraverser.h (+6)
  • (modified) clang/include/clang/AST/ExprCXX.h (+106)
  • (modified) clang/include/clang/AST/RecursiveASTVisitor.h (+11)
  • (modified) clang/include/clang/AST/Type.h (+66)
  • (modified) clang/include/clang/AST/TypeLoc.h (+28)
  • (modified) clang/include/clang/AST/TypeProperties.td (+14)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7-1)
  • (modified) clang/include/clang/Basic/Specifiers.h (+1)
  • (modified) clang/include/clang/Basic/StmtNodes.td (+1)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+2)
  • (modified) clang/include/clang/Basic/TypeNodes.td (+1)
  • (modified) clang/include/clang/Parse/Parser.h (+9)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+23-2)
  • (modified) clang/include/clang/Sema/Sema.h (+25)
  • (modified) clang/include/clang/Serialization/ASTBitCodes.h (+1)
  • (modified) clang/include/clang/Serialization/TypeBitCodes.def (+2)
  • (modified) clang/lib/AST/ASTContext.cpp (+43)
  • (modified) clang/lib/AST/ASTImporter.cpp (+12)
  • (modified) clang/lib/AST/ASTStructuralEquivalence.cpp (+10)
  • (modified) clang/lib/AST/Expr.cpp (+8)
  • (modified) clang/lib/AST/ExprCXX.cpp (+37)
  • (modified) clang/lib/AST/ExprClassification.cpp (+3)
  • (modified) clang/lib/AST/ExprConstant.cpp (+7)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+9)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+6)
  • (modified) clang/lib/AST/StmtPrinter.cpp (+4)
  • (modified) clang/lib/AST/StmtProfile.cpp (+6)
  • (modified) clang/lib/AST/Type.cpp (+40)
  • (modified) clang/lib/AST/TypePrinter.cpp (+18)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+5)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+2)
  • (modified) clang/lib/CodeGen/CGExprAgg.cpp (+3)
  • (modified) clang/lib/CodeGen/CGExprComplex.cpp (+4)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+4)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+3)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+1)
  • (modified) clang/lib/Parse/ParseCXXInlineMethods.cpp (+14)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+5)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+101)
  • (modified) clang/lib/Parse/ParseExpr.cpp (+13)
  • (modified) clang/lib/Parse/ParseExprCXX.cpp (+67-1)
  • (modified) clang/lib/Parse/ParseTentative.cpp (+13)
  • (modified) clang/lib/Parse/Parser.cpp (+2-1)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+21)
  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+23)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+1)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+4)
  • (modified) clang/lib/Sema/SemaExceptionSpec.cpp (+1)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+3)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+24-8)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+5)
  • (modified) clang/lib/Sema/SemaTemplateDeduction.cpp (+16)
  • (modified) clang/lib/Sema/SemaTemplateVariadic.cpp (+64-2)
  • (modified) clang/lib/Sema/SemaType.cpp (+60)
  • (modified) clang/lib/Sema/TreeTransform.h (+200)
  • (modified) clang/lib/Serialization/ASTReader.cpp (+4)
  • (modified) clang/lib/Serialization/ASTReaderStmt.cpp (+22)
  • (modified) clang/lib/Serialization/ASTWriter.cpp (+5)
  • (modified) clang/lib/Serialization/ASTWriterStmt.cpp (+16)
  • (modified) clang/lib/StaticAnalyzer/Core/ExprEngine.cpp (+1)
  • (added) clang/test/PCH/pack_indexing.cpp (+16)
  • (added) clang/test/Parser/cxx2b-pack-indexing.cpp (+65)
  • (added) clang/test/SemaCXX/cxx2b-pack-indexing.cpp (+115)
  • (modified) clang/tools/libclang/CIndex.cpp (+8)
  • (modified) clang/tools/libclang/CXCursor.cpp (+4)
  • (modified) clang/www/cxx_status.html (+1-1)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ed1a978b5382d71..dab670409077678 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -182,6 +182,8 @@ C++2c Feature Support
   This is applied to both C++ standard attributes, and other attributes supported by Clang.
   This completes the implementation of `P2361R6 Unevaluated Strings <https://wg21.link/P2361R6>`_
 
+- Implemented `P2662R3 Pack Indexing <https://wg21.link/P2662R3>`_.
+
 
 Resolutions to C++ Defect Reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 64ab3378957c702..2c0b89a0d12b21e 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -1685,7 +1685,12 @@ enum CXCursorKind {
    */
   CXCursor_CXXParenListInitExpr = 155,
 
-  CXCursor_LastExpr = CXCursor_CXXParenListInitExpr,
+  /**
+   *  Represents a C++26 pack indexing expression
+   */
+  CXCursor_PackIndexingExpr = 156,
+
+  CXCursor_LastExpr = CXCursor_PackIndexingExpr,
 
   /* Statements */
   CXCursor_FirstStmt = 200,
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 3e46a5da3fc043f..9e1c44eb19b805c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -214,6 +214,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
       DependentTypeOfExprTypes;
   mutable llvm::ContextualFoldingSet<DependentDecltypeType, ASTContext &>
       DependentDecltypeTypes;
+
+  mutable llvm::FoldingSet<PackIndexingType> DependentPackIndexingTypes;
+
   mutable llvm::FoldingSet<TemplateTypeParmType> TemplateTypeParmTypes;
   mutable llvm::FoldingSet<ObjCTypeParamType> ObjCTypeParamTypes;
   mutable llvm::FoldingSet<SubstTemplateTypeParmType>
@@ -1713,6 +1716,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// C++11 decltype.
   QualType getDecltypeType(Expr *e, QualType UnderlyingType) const;
 
+  QualType getPackIndexingType(QualType Pattern, Expr *IndexExpr,
+                               bool FullyExpanded = false,
+                               ArrayRef<QualType> Expansions = {},
+                               int Index = -1) const;
+
   /// Unary type transforms
   QualType getUnaryTransformType(QualType BaseType, QualType UnderlyingType,
                                  UnaryTransformType::UTTKind UKind) const;
diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index cc8dab97f8b010f..950da2b8d2c4560 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -385,6 +385,12 @@ class ASTNodeTraverser
   void VisitDecltypeType(const DecltypeType *T) {
     Visit(T->getUnderlyingExpr());
   }
+
+  void VisitPackIndexingType(const PackIndexingType *T) {
+    Visit(T->getPattern());
+    Visit(T->getIndexExpr());
+  }
+
   void VisitUnaryTransformType(const UnaryTransformType *T) {
     Visit(T->getBaseType());
   }
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 24278016431837b..efd6558326099eb 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -4344,6 +4344,112 @@ class SizeOfPackExpr final
   }
 };
 
+class PackIndexingExpr final
+    : public Expr,
+      private llvm::TrailingObjects<PackIndexingExpr, Expr *> {
+  friend class ASTStmtReader;
+  friend class ASTStmtWriter;
+  friend TrailingObjects;
+
+  SourceLocation EllipsisLoc;
+
+  // The location of the closing bracket
+  SourceLocation RSquareLoc;
+
+  // The pack being indexed, followed by the index
+  Stmt *SubExprs[2];
+
+  // The evaluated index
+  std::optional<int64_t> Index;
+
+  size_t TransformedExpressions;
+
+  PackIndexingExpr(QualType Type, SourceLocation EllipsisLoc,
+                   SourceLocation RSquareLoc, Expr *PackIdExpr, Expr *IndexExpr,
+                   std::optional<int64_t> Index = std::nullopt,
+                   ArrayRef<Expr *> SubstitutedExprs = {})
+      : Expr(PackIndexingExprClass, Type, VK_LValue, OK_Ordinary),
+        EllipsisLoc(EllipsisLoc), RSquareLoc(RSquareLoc),
+        SubExprs{PackIdExpr, IndexExpr}, Index(Index),
+        TransformedExpressions(SubstitutedExprs.size()) {
+
+    auto *Exprs = getTrailingObjects<Expr *>();
+    std::uninitialized_copy(SubstitutedExprs.begin(), SubstitutedExprs.end(),
+                            Exprs);
+
+    ExprDependence D = IndexExpr->getDependence();
+    if (SubstitutedExprs.empty())
+      D |= (PackIdExpr->getDependence() |
+            ExprDependence::TypeValueInstantiation) &
+           ~ExprDependence::UnexpandedPack;
+    else if (!IndexExpr->isValueDependent()) {
+      assert(Index && *Index < int64_t(SubstitutedExprs.size()) &&
+             "pack index out of bound");
+      D |= SubstitutedExprs[*Index]->getDependence();
+      setValueKind(SubstitutedExprs[*Index]->getValueKind());
+    }
+    setDependence(D);
+  }
+
+  /// Create an empty expression.
+  PackIndexingExpr(EmptyShell Empty) : Expr(PackIndexingExprClass, Empty) {}
+
+  unsigned numTrailingObjects(OverloadToken<Expr *>) const {
+    return TransformedExpressions;
+  }
+
+public:
+  static PackIndexingExpr *Create(ASTContext &Context,
+                                  SourceLocation EllipsisLoc,
+                                  SourceLocation RSquareLoc, Expr *PackIdExpr,
+                                  Expr *IndexExpr,
+                                  std::optional<int64_t> Index = std::nullopt,
+                                  ArrayRef<Expr *> SubstitutedExprs = {});
+  static PackIndexingExpr *CreateDeserialized(ASTContext &Context,
+                                              unsigned NumTransformedExprs);
+
+  /// Determine the location of the 'sizeof' keyword.
+  SourceLocation getEllipsisLoc() const { return EllipsisLoc; }
+
+  /// Determine the location of the parameter pack.
+  SourceLocation getPackLoc() const { return SubExprs[0]->getBeginLoc(); }
+
+  /// Determine the location of the right parenthesis.
+  SourceLocation getRSquareLoc() const { return RSquareLoc; }
+
+  SourceLocation getBeginLoc() const LLVM_READONLY { return getPackLoc(); }
+  SourceLocation getEndLoc() const LLVM_READONLY { return RSquareLoc; }
+
+  Expr *getPackIdExpression() const { return cast<Expr>(SubExprs[0]); }
+
+  NamedDecl *getPackDecl() const;
+
+  Expr *getIndexExpr() const { return cast<Expr>(SubExprs[1]); }
+
+  Expr *getSelectedExpr() const {
+    assert(Index && !isInstantiationDependent() &&
+           "extracting the indexed expression of a dependant pack");
+    return getTrailingObjects<Expr *>()[*Index];
+  }
+
+  llvm::ArrayRef<Expr *> getExpressions() const {
+    if (TransformedExpressions == 0)
+      return {};
+    return {getTrailingObjects<Expr *>(), TransformedExpressions};
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == PackIndexingExprClass;
+  }
+
+  // Iterators
+  child_range children() { return child_range(SubExprs, SubExprs + 2); }
+
+  const_child_range children() const {
+    return const_child_range(SubExprs, SubExprs + 2);
+  }
+};
+
 /// Represents a reference to a non-type template parameter
 /// that has been substituted with a template argument.
 class SubstNonTypeTemplateParmExpr : public Expr {
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 53bc15e1b19f668..1c26f0512fa1342 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1063,6 +1063,11 @@ DEF_TRAVERSE_TYPE(TypeOfType, { TRY_TO(TraverseType(T->getUnmodifiedType())); })
 DEF_TRAVERSE_TYPE(DecltypeType,
                   { TRY_TO(TraverseStmt(T->getUnderlyingExpr())); })
 
+DEF_TRAVERSE_TYPE(PackIndexingType, {
+  TRY_TO(TraverseType(T->getPattern()));
+  TRY_TO(TraverseStmt(T->getIndexExpr()));
+})
+
 DEF_TRAVERSE_TYPE(UnaryTransformType, {
   TRY_TO(TraverseType(T->getBaseType()));
   TRY_TO(TraverseType(T->getUnderlyingType()));
@@ -1341,6 +1346,11 @@ DEF_TRAVERSE_TYPELOC(DecltypeType, {
   TRY_TO(TraverseStmt(TL.getTypePtr()->getUnderlyingExpr()));
 })
 
+DEF_TRAVERSE_TYPELOC(PackIndexingType, {
+  TRY_TO(TraverseType(TL.getPattern()));
+  TRY_TO(TraverseStmt(TL.getTypePtr()->getIndexExpr()));
+})
+
 DEF_TRAVERSE_TYPELOC(UnaryTransformType, {
   TRY_TO(TraverseTypeLoc(TL.getUnderlyingTInfo()->getTypeLoc()));
 })
@@ -2857,6 +2867,7 @@ DEF_TRAVERSE_STMT(CompoundAssignOperator, {})
 DEF_TRAVERSE_STMT(CXXNoexceptExpr, {})
 DEF_TRAVERSE_STMT(PackExpansionExpr, {})
 DEF_TRAVERSE_STMT(SizeOfPackExpr, {})
+DEF_TRAVERSE_STMT(PackIndexingExpr, {})
 DEF_TRAVERSE_STMT(SubstNonTypeTemplateParmPackExpr, {})
 DEF_TRAVERSE_STMT(SubstNonTypeTemplateParmExpr, {})
 DEF_TRAVERSE_STMT(FunctionParmPackExpr, {})
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 6c147eb8f640623..a710766d1cf92f9 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4884,6 +4884,72 @@ class DependentDecltypeType : public DecltypeType, public llvm::FoldingSetNode {
                       Expr *E);
 };
 
+class PackIndexingType final
+    : public Type,
+      public llvm::FoldingSetNode,
+      private llvm::TrailingObjects<PackIndexingType, QualType> {
+  friend TrailingObjects;
+
+  const ASTContext &Context;
+  QualType Pattern;
+  Expr *IndexExpr;
+
+  unsigned Size;
+  int Index = -1;
+
+protected:
+  friend class ASTContext; // ASTContext creates these.
+  PackIndexingType(const ASTContext &Context, QualType Canonical,
+                   QualType Pattern, Expr *IndexExpr,
+                   ArrayRef<QualType> Expansions = {}, int Index = -1);
+
+public:
+  Expr *getIndexExpr() const { return IndexExpr; }
+  QualType getPattern() const { return Pattern; }
+
+  bool isSugared() const { return hasSelectedType(); }
+
+  QualType desugar() const {
+    if (hasSelectedType())
+      return getSelectedType();
+    return QualType(this, 0);
+  }
+
+  QualType getSelectedType() const {
+    assert(hasSelectedType() && "Type is dependant");
+    return *(getExpansionsPtr() + Index);
+  }
+
+  bool hasSelectedType() const { return Index != -1 && !isDependentType(); }
+
+  ArrayRef<QualType> getExpansions() const {
+    return {getExpansionsPtr(), Size};
+  }
+
+  static bool classof(const Type *T) {
+    return T->getTypeClass() == PackIndexing;
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) {
+    if (hasSelectedType())
+      getSelectedType().Profile(ID);
+    else
+      Profile(ID, Context, getPattern(), getIndexExpr());
+  }
+  static void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
+                      QualType Pattern, Expr *E);
+
+private:
+  const QualType *getExpansionsPtr() const {
+    return getTrailingObjects<QualType>();
+  }
+
+  static TypeDependence computeDependence(QualType Pattern, Expr *IndexExpr,
+                                          ArrayRef<QualType> Expansions = {});
+
+  unsigned numTrailingObjects(OverloadToken<QualType>) const { return Size; }
+};
+
 /// A unary type transform, which is a type constructed from another.
 class UnaryTransformType : public Type {
 public:
diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h
index 471deb14aba51fc..38e40ece425fb8f 100644
--- a/clang/include/clang/AST/TypeLoc.h
+++ b/clang/include/clang/AST/TypeLoc.h
@@ -2055,6 +2055,34 @@ class DecltypeTypeLoc
   }
 };
 
+struct PackIndexingTypeLocInfo {
+  SourceLocation EllipsisLoc;
+};
+
+class PackIndexingTypeLoc
+    : public ConcreteTypeLoc<UnqualTypeLoc, PackIndexingTypeLoc,
+                             PackIndexingType, PackIndexingTypeLocInfo> {
+
+public:
+  Expr *getIndexExpr() const { return getTypePtr()->getIndexExpr(); }
+  QualType getPattern() const { return getTypePtr()->getPattern(); }
+
+  SourceLocation getEllipsisLoc() const { return getLocalData()->EllipsisLoc; }
+  void setEllipsisLoc(SourceLocation Loc) { getLocalData()->EllipsisLoc = Loc; }
+
+  void initializeLocal(ASTContext &Context, SourceLocation Loc) {
+    setEllipsisLoc(Loc);
+  }
+
+  TypeLoc getPatternLoc() const { return getInnerTypeLoc(); }
+
+  QualType getInnerType() const { return this->getTypePtr()->getPattern(); }
+
+  SourceRange getLocalSourceRange() const {
+    return SourceRange(getEllipsisLoc(), getEllipsisLoc());
+  }
+};
+
 struct UnaryTransformTypeLocInfo {
   // FIXME: While there's only one unary transform right now, future ones may
   // need different representations
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 682c869b0c58479..0ba172a4035fdb4 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -433,6 +433,20 @@ let Class = DecltypeType in {
   }]>;
 }
 
+let Class = PackIndexingType in {
+  def : Property<"pattern", QualType> {
+    let Read = [{ node->getPattern() }];
+  }
+  def : Property<"indexExpression", ExprRef> {
+    let Read = [{ node->getIndexExpr() }];
+  }
+
+  def : Creator<[{
+    return ctx.getPackIndexingType(pattern, indexExpression);
+  }]>;
+}
+
+
 let Class = UnaryTransformType in {
   def : Property<"baseType", QualType> {
     let Read = [{ node->getBaseType() }];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a9dde041bc22a6d..5f80debb9a454f6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5681,9 +5681,15 @@ def err_function_parameter_pack_without_parameter_packs : Error<
 def err_ellipsis_in_declarator_not_parameter : Error<
   "only function and template parameters can be parameter packs">;
 
-def err_sizeof_pack_no_pack_name : Error<
+def err_expected_name_of_pack : Error<
   "%0 does not refer to the name of a parameter pack">;
 
+def err_pack_index_out_of_bound : Error<
+  "%0 is not a valid index for pack %1 of size %2">;
+
+def err_pack_index_todo : Error<
+  "Pack index not implemented">;
+
 def err_fold_expression_packs_both_sides : Error<
   "binary fold expression has unexpanded parameter packs in both operands">;
 def err_fold_expression_empty : Error<
diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h
index 87f29c8ae10bd9a..bff4c1616c1d027 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -94,6 +94,7 @@ namespace clang {
     TST_auto_type,       // __auto_type extension
     TST_unknown_anytype, // __unknown_anytype extension
     TST_atomic,          // C11 _Atomic
+    TST_indexed_typename_pack,
 #define GENERIC_IMAGE_TYPE(ImgType, Id) TST_##ImgType##_t, // OpenCL image types
 #include "clang/Basic/OpenCLImageTypes.def"
     TST_error // erroneous type
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index cec301dfca2817b..9d03800840fcd0b 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -154,6 +154,7 @@ def UnresolvedMemberExpr : StmtNode<OverloadExpr>;
 def CXXNoexceptExpr : StmtNode<Expr>;
 def PackExpansionExpr : StmtNode<Expr>;
 def SizeOfPackExpr : StmtNode<Expr>;
+def PackIndexingExpr : StmtNode<Expr>;
 def SubstNonTypeTemplateParmExpr : StmtNode<Expr>;
 def SubstNonTypeTemplateParmPackExpr : StmtNode<Expr>;
 def FunctionParmPackExpr : StmtNode<Expr>;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 82a503d01068d53..6b5f8210fa6d0e3 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -836,6 +836,8 @@ ANNOTATION(primary_expr) // annotation for a primary expression, used when
                          // message send
 ANNOTATION(decltype)     // annotation for a decltype expression,
                          // e.g., "decltype(foo.bar())"
+ANNOTATION(indexed_pack_type) // annotation for an indexed pack of type,
+                              // e.g., "T...[expr]"
 
 // Annotation for #pragma unused(...)
 // For each argument inside the parentheses the pragma handler will produce
diff --git a/clang/include/clang/Basic/TypeNodes.td b/clang/include/clang/Basic/TypeNodes.td
index 649b071cebb9404..6419762417371b9 100644
--- a/clang/include/clang/Basic/TypeNodes.td
+++ b/clang/include/clang/Basic/TypeNodes.td
@@ -103,6 +103,7 @@ def InjectedClassNameType : TypeNode<Type>, AlwaysDependent, LeafType;
 def DependentNameType : TypeNode<Type>, AlwaysDependent;
 def DependentTemplateSpecializationType : TypeNode<Type>, AlwaysDependent;
 def PackExpansionType : TypeNode<Type>, AlwaysDependent;
+def PackIndexingType  : TypeNode<Type>, NeverCanonicalUnlessDependent;
 def ObjCTypeParamType : TypeNode<Type>, NeverCanonical;
 def ObjCObjectType : TypeNode<Type>;
 def ObjCInterfaceType : TypeNode<ObjCObjectType>, LeafType;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 30e0352c868637b..c7bbc3d6384306f 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1884,6 +1884,10 @@ class Parser : public CodeCompletionHandler {
   // C++ Expressions
   ExprResult tryParseCXXIdExpression(CXXScopeSpec &SS, bool isAddressOfOperand,
                                      Token &Replacement);
+
+  ExprResult tryParseCXXPackIndexingExpression(ExprResult PackIdExpression);
+  ExprResult ParseCXXPackIndexingExpression(ExprResult PackIdExpression);
+
   ExprResult ParseCXXIdExpression(bool isAddressOfOperand = false);
 
   bool areTokensAdjacent(const Token &A, const Token &B);
@@ -2430,6 +2434,11 @@ class Parser : public CodeCompletionHandler {
       DeclSpecContext DSC, LateParsedAttrList *LateAttrs,
       ImplicitTypenameContext AllowImplicitTypename);
 
+  SourceLocation ParseIndexedTypeNamePack(DeclSpec &DS);
+  void AnnotateExistingIndexedTypeNamePack(ParsedType T,
+                                           SourceLocation StartLoc,
+                                           SourceLocation EndLoc);
+
   bool DiagnoseMissingSemiAfterTagDefinition(
       DeclSpec &DS, AccessSpecifier AS, DeclSpecContext DSContext,
       LateParsedAttrList *LateAttrs = nullptr);
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 4561cca929c0d0b..4726cbbd1af50c9 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -309,6 +309,7 @@ class DeclSpec {
   static const TST TST_typeof_unqualExpr = clang::TST_typeof_unqualExpr;
   static const TST TST_decltype = clang::TST_decltype;
   static const TST TST_decltype_auto = clang::TST_decltype_auto;
+  static const TST TST_indexed_typename_pack = clang::TST_indexed_typename_pack;
 #define TRANSFORM_TYPE_TRAIT_DEF(_, Trait)                                     \
   static const TST TST_##Trait = clang::TST_##Trait;
 #include "clang/Basic/TransformTypeTraits.def"
@@ -389,6 +390,7 @@ class DeclSpec {
     Expr *ExprRep;
     TemplateIdAnnotation *TemplateIdRep;
   };
+  Expr *PackIndexingExpr = nullptr;
 
   /// ExplicitSpecifier - Store information about explicit spicifer.
   ExplicitSpecifier FS_explicit_specifier;
@@ -405,7 +407,7 @@ class DeclSpec {
 
   SourceLocation StorageClassSpecLoc, ThreadStorageClassSpecLoc;
   SourceRange TSWRange;
-  SourceLocation TSCLoc, TSSLoc, TSTLoc, AltiVecLoc, TSSatLoc;
+  SourceLocation TSCLoc, TSSLoc, TSTLoc, AltiVecLoc, TSSatLoc, EllipsisLoc;
   /// TSTNameLoc - If TypeSpecType is any of class, enum, struct, union,
   /// typename, then this is the location of the named type (if present);
   /// otherwise, it is the same as TSTLoc. Hence, the pair TSTLoc and
@@ -427,7 +429,8 @@ class DeclSpec {
 
   static bool isTypeRep(TST T) {
     return T == TST_atomic || T == TST_typename || T == TST_typeofType ||
-           T == TST_typeof_unqualType || isTransformTypeTrait(T);
+           T == TST_typeof_unqualType || isTransformTypeTrait(T) ||
+           T == TST_indexed_typename_pack;
   }
   static bool isExprRep(TST T) {
     return T == TST_typeofExpr || T == TST_typeof_unqualExpr ||
@@ -530,6 +533,13 @@ class DeclSpec {
     assert(isExprRep((TST) TypeSpecType) && "DeclSpec does not store an expr");
     return ExprRep;
   }
+
+  Expr *getPackIndexingExpr() const {
+    assert(TypeSpecType == TST_indexed_ty...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Nov 17, 2023

@llvm/pr-subscribers-debuginfo

Author: cor3ntin (cor3ntin)

Changes

https://isocpp.org/files/papers/P2662R3.pdf

Because there is a slight chance the syntax might change slightly (see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2994r0.html), the feature is not exposed in other language modes.


Patch is 101.21 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/72644.diff

69 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/include/clang-c/Index.h (+6-1)
  • (modified) clang/include/clang/AST/ASTContext.h (+8)
  • (modified) clang/include/clang/AST/ASTNodeTraverser.h (+6)
  • (modified) clang/include/clang/AST/ExprCXX.h (+106)
  • (modified) clang/include/clang/AST/RecursiveASTVisitor.h (+11)
  • (modified) clang/include/clang/AST/Type.h (+66)
  • (modified) clang/include/clang/AST/TypeLoc.h (+28)
  • (modified) clang/include/clang/AST/TypeProperties.td (+14)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7-1)
  • (modified) clang/include/clang/Basic/Specifiers.h (+1)
  • (modified) clang/include/clang/Basic/StmtNodes.td (+1)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+2)
  • (modified) clang/include/clang/Basic/TypeNodes.td (+1)
  • (modified) clang/include/clang/Parse/Parser.h (+9)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+23-2)
  • (modified) clang/include/clang/Sema/Sema.h (+25)
  • (modified) clang/include/clang/Serialization/ASTBitCodes.h (+1)
  • (modified) clang/include/clang/Serialization/TypeBitCodes.def (+2)
  • (modified) clang/lib/AST/ASTContext.cpp (+43)
  • (modified) clang/lib/AST/ASTImporter.cpp (+12)
  • (modified) clang/lib/AST/ASTStructuralEquivalence.cpp (+10)
  • (modified) clang/lib/AST/Expr.cpp (+8)
  • (modified) clang/lib/AST/ExprCXX.cpp (+37)
  • (modified) clang/lib/AST/ExprClassification.cpp (+3)
  • (modified) clang/lib/AST/ExprConstant.cpp (+7)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+9)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+6)
  • (modified) clang/lib/AST/StmtPrinter.cpp (+4)
  • (modified) clang/lib/AST/StmtProfile.cpp (+6)
  • (modified) clang/lib/AST/Type.cpp (+40)
  • (modified) clang/lib/AST/TypePrinter.cpp (+18)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+5)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+2)
  • (modified) clang/lib/CodeGen/CGExprAgg.cpp (+3)
  • (modified) clang/lib/CodeGen/CGExprComplex.cpp (+4)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+4)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+3)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+1)
  • (modified) clang/lib/Parse/ParseCXXInlineMethods.cpp (+14)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+5)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+101)
  • (modified) clang/lib/Parse/ParseExpr.cpp (+13)
  • (modified) clang/lib/Parse/ParseExprCXX.cpp (+67-1)
  • (modified) clang/lib/Parse/ParseTentative.cpp (+13)
  • (modified) clang/lib/Parse/Parser.cpp (+2-1)
  • (modified) clang/lib/Sema/DeclSpec.cpp (+21)
  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+23)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+1)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+4)
  • (modified) clang/lib/Sema/SemaExceptionSpec.cpp (+1)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+3)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+24-8)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+5)
  • (modified) clang/lib/Sema/SemaTemplateDeduction.cpp (+16)
  • (modified) clang/lib/Sema/SemaTemplateVariadic.cpp (+64-2)
  • (modified) clang/lib/Sema/SemaType.cpp (+60)
  • (modified) clang/lib/Sema/TreeTransform.h (+200)
  • (modified) clang/lib/Serialization/ASTReader.cpp (+4)
  • (modified) clang/lib/Serialization/ASTReaderStmt.cpp (+22)
  • (modified) clang/lib/Serialization/ASTWriter.cpp (+5)
  • (modified) clang/lib/Serialization/ASTWriterStmt.cpp (+16)
  • (modified) clang/lib/StaticAnalyzer/Core/ExprEngine.cpp (+1)
  • (added) clang/test/PCH/pack_indexing.cpp (+16)
  • (added) clang/test/Parser/cxx2b-pack-indexing.cpp (+65)
  • (added) clang/test/SemaCXX/cxx2b-pack-indexing.cpp (+115)
  • (modified) clang/tools/libclang/CIndex.cpp (+8)
  • (modified) clang/tools/libclang/CXCursor.cpp (+4)
  • (modified) clang/www/cxx_status.html (+1-1)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ed1a978b5382d71..dab670409077678 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -182,6 +182,8 @@ C++2c Feature Support
   This is applied to both C++ standard attributes, and other attributes supported by Clang.
   This completes the implementation of `P2361R6 Unevaluated Strings <https://wg21.link/P2361R6>`_
 
+- Implemented `P2662R3 Pack Indexing <https://wg21.link/P2662R3>`_.
+
 
 Resolutions to C++ Defect Reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 64ab3378957c702..2c0b89a0d12b21e 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -1685,7 +1685,12 @@ enum CXCursorKind {
    */
   CXCursor_CXXParenListInitExpr = 155,
 
-  CXCursor_LastExpr = CXCursor_CXXParenListInitExpr,
+  /**
+   *  Represents a C++26 pack indexing expression
+   */
+  CXCursor_PackIndexingExpr = 156,
+
+  CXCursor_LastExpr = CXCursor_PackIndexingExpr,
 
   /* Statements */
   CXCursor_FirstStmt = 200,
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 3e46a5da3fc043f..9e1c44eb19b805c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -214,6 +214,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
       DependentTypeOfExprTypes;
   mutable llvm::ContextualFoldingSet<DependentDecltypeType, ASTContext &>
       DependentDecltypeTypes;
+
+  mutable llvm::FoldingSet<PackIndexingType> DependentPackIndexingTypes;
+
   mutable llvm::FoldingSet<TemplateTypeParmType> TemplateTypeParmTypes;
   mutable llvm::FoldingSet<ObjCTypeParamType> ObjCTypeParamTypes;
   mutable llvm::FoldingSet<SubstTemplateTypeParmType>
@@ -1713,6 +1716,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// C++11 decltype.
   QualType getDecltypeType(Expr *e, QualType UnderlyingType) const;
 
+  QualType getPackIndexingType(QualType Pattern, Expr *IndexExpr,
+                               bool FullyExpanded = false,
+                               ArrayRef<QualType> Expansions = {},
+                               int Index = -1) const;
+
   /// Unary type transforms
   QualType getUnaryTransformType(QualType BaseType, QualType UnderlyingType,
                                  UnaryTransformType::UTTKind UKind) const;
diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index cc8dab97f8b010f..950da2b8d2c4560 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -385,6 +385,12 @@ class ASTNodeTraverser
   void VisitDecltypeType(const DecltypeType *T) {
     Visit(T->getUnderlyingExpr());
   }
+
+  void VisitPackIndexingType(const PackIndexingType *T) {
+    Visit(T->getPattern());
+    Visit(T->getIndexExpr());
+  }
+
   void VisitUnaryTransformType(const UnaryTransformType *T) {
     Visit(T->getBaseType());
   }
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 24278016431837b..efd6558326099eb 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -4344,6 +4344,112 @@ class SizeOfPackExpr final
   }
 };
 
+class PackIndexingExpr final
+    : public Expr,
+      private llvm::TrailingObjects<PackIndexingExpr, Expr *> {
+  friend class ASTStmtReader;
+  friend class ASTStmtWriter;
+  friend TrailingObjects;
+
+  SourceLocation EllipsisLoc;
+
+  // The location of the closing bracket
+  SourceLocation RSquareLoc;
+
+  // The pack being indexed, followed by the index
+  Stmt *SubExprs[2];
+
+  // The evaluated index
+  std::optional<int64_t> Index;
+
+  size_t TransformedExpressions;
+
+  PackIndexingExpr(QualType Type, SourceLocation EllipsisLoc,
+                   SourceLocation RSquareLoc, Expr *PackIdExpr, Expr *IndexExpr,
+                   std::optional<int64_t> Index = std::nullopt,
+                   ArrayRef<Expr *> SubstitutedExprs = {})
+      : Expr(PackIndexingExprClass, Type, VK_LValue, OK_Ordinary),
+        EllipsisLoc(EllipsisLoc), RSquareLoc(RSquareLoc),
+        SubExprs{PackIdExpr, IndexExpr}, Index(Index),
+        TransformedExpressions(SubstitutedExprs.size()) {
+
+    auto *Exprs = getTrailingObjects<Expr *>();
+    std::uninitialized_copy(SubstitutedExprs.begin(), SubstitutedExprs.end(),
+                            Exprs);
+
+    ExprDependence D = IndexExpr->getDependence();
+    if (SubstitutedExprs.empty())
+      D |= (PackIdExpr->getDependence() |
+            ExprDependence::TypeValueInstantiation) &
+           ~ExprDependence::UnexpandedPack;
+    else if (!IndexExpr->isValueDependent()) {
+      assert(Index && *Index < int64_t(SubstitutedExprs.size()) &&
+             "pack index out of bound");
+      D |= SubstitutedExprs[*Index]->getDependence();
+      setValueKind(SubstitutedExprs[*Index]->getValueKind());
+    }
+    setDependence(D);
+  }
+
+  /// Create an empty expression.
+  PackIndexingExpr(EmptyShell Empty) : Expr(PackIndexingExprClass, Empty) {}
+
+  unsigned numTrailingObjects(OverloadToken<Expr *>) const {
+    return TransformedExpressions;
+  }
+
+public:
+  static PackIndexingExpr *Create(ASTContext &Context,
+                                  SourceLocation EllipsisLoc,
+                                  SourceLocation RSquareLoc, Expr *PackIdExpr,
+                                  Expr *IndexExpr,
+                                  std::optional<int64_t> Index = std::nullopt,
+                                  ArrayRef<Expr *> SubstitutedExprs = {});
+  static PackIndexingExpr *CreateDeserialized(ASTContext &Context,
+                                              unsigned NumTransformedExprs);
+
+  /// Determine the location of the 'sizeof' keyword.
+  SourceLocation getEllipsisLoc() const { return EllipsisLoc; }
+
+  /// Determine the location of the parameter pack.
+  SourceLocation getPackLoc() const { return SubExprs[0]->getBeginLoc(); }
+
+  /// Determine the location of the right parenthesis.
+  SourceLocation getRSquareLoc() const { return RSquareLoc; }
+
+  SourceLocation getBeginLoc() const LLVM_READONLY { return getPackLoc(); }
+  SourceLocation getEndLoc() const LLVM_READONLY { return RSquareLoc; }
+
+  Expr *getPackIdExpression() const { return cast<Expr>(SubExprs[0]); }
+
+  NamedDecl *getPackDecl() const;
+
+  Expr *getIndexExpr() const { return cast<Expr>(SubExprs[1]); }
+
+  Expr *getSelectedExpr() const {
+    assert(Index && !isInstantiationDependent() &&
+           "extracting the indexed expression of a dependant pack");
+    return getTrailingObjects<Expr *>()[*Index];
+  }
+
+  llvm::ArrayRef<Expr *> getExpressions() const {
+    if (TransformedExpressions == 0)
+      return {};
+    return {getTrailingObjects<Expr *>(), TransformedExpressions};
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == PackIndexingExprClass;
+  }
+
+  // Iterators
+  child_range children() { return child_range(SubExprs, SubExprs + 2); }
+
+  const_child_range children() const {
+    return const_child_range(SubExprs, SubExprs + 2);
+  }
+};
+
 /// Represents a reference to a non-type template parameter
 /// that has been substituted with a template argument.
 class SubstNonTypeTemplateParmExpr : public Expr {
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 53bc15e1b19f668..1c26f0512fa1342 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1063,6 +1063,11 @@ DEF_TRAVERSE_TYPE(TypeOfType, { TRY_TO(TraverseType(T->getUnmodifiedType())); })
 DEF_TRAVERSE_TYPE(DecltypeType,
                   { TRY_TO(TraverseStmt(T->getUnderlyingExpr())); })
 
+DEF_TRAVERSE_TYPE(PackIndexingType, {
+  TRY_TO(TraverseType(T->getPattern()));
+  TRY_TO(TraverseStmt(T->getIndexExpr()));
+})
+
 DEF_TRAVERSE_TYPE(UnaryTransformType, {
   TRY_TO(TraverseType(T->getBaseType()));
   TRY_TO(TraverseType(T->getUnderlyingType()));
@@ -1341,6 +1346,11 @@ DEF_TRAVERSE_TYPELOC(DecltypeType, {
   TRY_TO(TraverseStmt(TL.getTypePtr()->getUnderlyingExpr()));
 })
 
+DEF_TRAVERSE_TYPELOC(PackIndexingType, {
+  TRY_TO(TraverseType(TL.getPattern()));
+  TRY_TO(TraverseStmt(TL.getTypePtr()->getIndexExpr()));
+})
+
 DEF_TRAVERSE_TYPELOC(UnaryTransformType, {
   TRY_TO(TraverseTypeLoc(TL.getUnderlyingTInfo()->getTypeLoc()));
 })
@@ -2857,6 +2867,7 @@ DEF_TRAVERSE_STMT(CompoundAssignOperator, {})
 DEF_TRAVERSE_STMT(CXXNoexceptExpr, {})
 DEF_TRAVERSE_STMT(PackExpansionExpr, {})
 DEF_TRAVERSE_STMT(SizeOfPackExpr, {})
+DEF_TRAVERSE_STMT(PackIndexingExpr, {})
 DEF_TRAVERSE_STMT(SubstNonTypeTemplateParmPackExpr, {})
 DEF_TRAVERSE_STMT(SubstNonTypeTemplateParmExpr, {})
 DEF_TRAVERSE_STMT(FunctionParmPackExpr, {})
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 6c147eb8f640623..a710766d1cf92f9 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4884,6 +4884,72 @@ class DependentDecltypeType : public DecltypeType, public llvm::FoldingSetNode {
                       Expr *E);
 };
 
+class PackIndexingType final
+    : public Type,
+      public llvm::FoldingSetNode,
+      private llvm::TrailingObjects<PackIndexingType, QualType> {
+  friend TrailingObjects;
+
+  const ASTContext &Context;
+  QualType Pattern;
+  Expr *IndexExpr;
+
+  unsigned Size;
+  int Index = -1;
+
+protected:
+  friend class ASTContext; // ASTContext creates these.
+  PackIndexingType(const ASTContext &Context, QualType Canonical,
+                   QualType Pattern, Expr *IndexExpr,
+                   ArrayRef<QualType> Expansions = {}, int Index = -1);
+
+public:
+  Expr *getIndexExpr() const { return IndexExpr; }
+  QualType getPattern() const { return Pattern; }
+
+  bool isSugared() const { return hasSelectedType(); }
+
+  QualType desugar() const {
+    if (hasSelectedType())
+      return getSelectedType();
+    return QualType(this, 0);
+  }
+
+  QualType getSelectedType() const {
+    assert(hasSelectedType() && "Type is dependant");
+    return *(getExpansionsPtr() + Index);
+  }
+
+  bool hasSelectedType() const { return Index != -1 && !isDependentType(); }
+
+  ArrayRef<QualType> getExpansions() const {
+    return {getExpansionsPtr(), Size};
+  }
+
+  static bool classof(const Type *T) {
+    return T->getTypeClass() == PackIndexing;
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) {
+    if (hasSelectedType())
+      getSelectedType().Profile(ID);
+    else
+      Profile(ID, Context, getPattern(), getIndexExpr());
+  }
+  static void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
+                      QualType Pattern, Expr *E);
+
+private:
+  const QualType *getExpansionsPtr() const {
+    return getTrailingObjects<QualType>();
+  }
+
+  static TypeDependence computeDependence(QualType Pattern, Expr *IndexExpr,
+                                          ArrayRef<QualType> Expansions = {});
+
+  unsigned numTrailingObjects(OverloadToken<QualType>) const { return Size; }
+};
+
 /// A unary type transform, which is a type constructed from another.
 class UnaryTransformType : public Type {
 public:
diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h
index 471deb14aba51fc..38e40ece425fb8f 100644
--- a/clang/include/clang/AST/TypeLoc.h
+++ b/clang/include/clang/AST/TypeLoc.h
@@ -2055,6 +2055,34 @@ class DecltypeTypeLoc
   }
 };
 
+struct PackIndexingTypeLocInfo {
+  SourceLocation EllipsisLoc;
+};
+
+class PackIndexingTypeLoc
+    : public ConcreteTypeLoc<UnqualTypeLoc, PackIndexingTypeLoc,
+                             PackIndexingType, PackIndexingTypeLocInfo> {
+
+public:
+  Expr *getIndexExpr() const { return getTypePtr()->getIndexExpr(); }
+  QualType getPattern() const { return getTypePtr()->getPattern(); }
+
+  SourceLocation getEllipsisLoc() const { return getLocalData()->EllipsisLoc; }
+  void setEllipsisLoc(SourceLocation Loc) { getLocalData()->EllipsisLoc = Loc; }
+
+  void initializeLocal(ASTContext &Context, SourceLocation Loc) {
+    setEllipsisLoc(Loc);
+  }
+
+  TypeLoc getPatternLoc() const { return getInnerTypeLoc(); }
+
+  QualType getInnerType() const { return this->getTypePtr()->getPattern(); }
+
+  SourceRange getLocalSourceRange() const {
+    return SourceRange(getEllipsisLoc(), getEllipsisLoc());
+  }
+};
+
 struct UnaryTransformTypeLocInfo {
   // FIXME: While there's only one unary transform right now, future ones may
   // need different representations
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 682c869b0c58479..0ba172a4035fdb4 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -433,6 +433,20 @@ let Class = DecltypeType in {
   }]>;
 }
 
+let Class = PackIndexingType in {
+  def : Property<"pattern", QualType> {
+    let Read = [{ node->getPattern() }];
+  }
+  def : Property<"indexExpression", ExprRef> {
+    let Read = [{ node->getIndexExpr() }];
+  }
+
+  def : Creator<[{
+    return ctx.getPackIndexingType(pattern, indexExpression);
+  }]>;
+}
+
+
 let Class = UnaryTransformType in {
   def : Property<"baseType", QualType> {
     let Read = [{ node->getBaseType() }];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a9dde041bc22a6d..5f80debb9a454f6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5681,9 +5681,15 @@ def err_function_parameter_pack_without_parameter_packs : Error<
 def err_ellipsis_in_declarator_not_parameter : Error<
   "only function and template parameters can be parameter packs">;
 
-def err_sizeof_pack_no_pack_name : Error<
+def err_expected_name_of_pack : Error<
   "%0 does not refer to the name of a parameter pack">;
 
+def err_pack_index_out_of_bound : Error<
+  "%0 is not a valid index for pack %1 of size %2">;
+
+def err_pack_index_todo : Error<
+  "Pack index not implemented">;
+
 def err_fold_expression_packs_both_sides : Error<
   "binary fold expression has unexpanded parameter packs in both operands">;
 def err_fold_expression_empty : Error<
diff --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h
index 87f29c8ae10bd9a..bff4c1616c1d027 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -94,6 +94,7 @@ namespace clang {
     TST_auto_type,       // __auto_type extension
     TST_unknown_anytype, // __unknown_anytype extension
     TST_atomic,          // C11 _Atomic
+    TST_indexed_typename_pack,
 #define GENERIC_IMAGE_TYPE(ImgType, Id) TST_##ImgType##_t, // OpenCL image types
 #include "clang/Basic/OpenCLImageTypes.def"
     TST_error // erroneous type
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index cec301dfca2817b..9d03800840fcd0b 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -154,6 +154,7 @@ def UnresolvedMemberExpr : StmtNode<OverloadExpr>;
 def CXXNoexceptExpr : StmtNode<Expr>;
 def PackExpansionExpr : StmtNode<Expr>;
 def SizeOfPackExpr : StmtNode<Expr>;
+def PackIndexingExpr : StmtNode<Expr>;
 def SubstNonTypeTemplateParmExpr : StmtNode<Expr>;
 def SubstNonTypeTemplateParmPackExpr : StmtNode<Expr>;
 def FunctionParmPackExpr : StmtNode<Expr>;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 82a503d01068d53..6b5f8210fa6d0e3 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -836,6 +836,8 @@ ANNOTATION(primary_expr) // annotation for a primary expression, used when
                          // message send
 ANNOTATION(decltype)     // annotation for a decltype expression,
                          // e.g., "decltype(foo.bar())"
+ANNOTATION(indexed_pack_type) // annotation for an indexed pack of type,
+                              // e.g., "T...[expr]"
 
 // Annotation for #pragma unused(...)
 // For each argument inside the parentheses the pragma handler will produce
diff --git a/clang/include/clang/Basic/TypeNodes.td b/clang/include/clang/Basic/TypeNodes.td
index 649b071cebb9404..6419762417371b9 100644
--- a/clang/include/clang/Basic/TypeNodes.td
+++ b/clang/include/clang/Basic/TypeNodes.td
@@ -103,6 +103,7 @@ def InjectedClassNameType : TypeNode<Type>, AlwaysDependent, LeafType;
 def DependentNameType : TypeNode<Type>, AlwaysDependent;
 def DependentTemplateSpecializationType : TypeNode<Type>, AlwaysDependent;
 def PackExpansionType : TypeNode<Type>, AlwaysDependent;
+def PackIndexingType  : TypeNode<Type>, NeverCanonicalUnlessDependent;
 def ObjCTypeParamType : TypeNode<Type>, NeverCanonical;
 def ObjCObjectType : TypeNode<Type>;
 def ObjCInterfaceType : TypeNode<ObjCObjectType>, LeafType;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 30e0352c868637b..c7bbc3d6384306f 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1884,6 +1884,10 @@ class Parser : public CodeCompletionHandler {
   // C++ Expressions
   ExprResult tryParseCXXIdExpression(CXXScopeSpec &SS, bool isAddressOfOperand,
                                      Token &Replacement);
+
+  ExprResult tryParseCXXPackIndexingExpression(ExprResult PackIdExpression);
+  ExprResult ParseCXXPackIndexingExpression(ExprResult PackIdExpression);
+
   ExprResult ParseCXXIdExpression(bool isAddressOfOperand = false);
 
   bool areTokensAdjacent(const Token &A, const Token &B);
@@ -2430,6 +2434,11 @@ class Parser : public CodeCompletionHandler {
       DeclSpecContext DSC, LateParsedAttrList *LateAttrs,
       ImplicitTypenameContext AllowImplicitTypename);
 
+  SourceLocation ParseIndexedTypeNamePack(DeclSpec &DS);
+  void AnnotateExistingIndexedTypeNamePack(ParsedType T,
+                                           SourceLocation StartLoc,
+                                           SourceLocation EndLoc);
+
   bool DiagnoseMissingSemiAfterTagDefinition(
       DeclSpec &DS, AccessSpecifier AS, DeclSpecContext DSContext,
       LateParsedAttrList *LateAttrs = nullptr);
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 4561cca929c0d0b..4726cbbd1af50c9 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -309,6 +309,7 @@ class DeclSpec {
   static const TST TST_typeof_unqualExpr = clang::TST_typeof_unqualExpr;
   static const TST TST_decltype = clang::TST_decltype;
   static const TST TST_decltype_auto = clang::TST_decltype_auto;
+  static const TST TST_indexed_typename_pack = clang::TST_indexed_typename_pack;
 #define TRANSFORM_TYPE_TRAIT_DEF(_, Trait)                                     \
   static const TST TST_##Trait = clang::TST_##Trait;
 #include "clang/Basic/TransformTypeTraits.def"
@@ -389,6 +390,7 @@ class DeclSpec {
     Expr *ExprRep;
     TemplateIdAnnotation *TemplateIdRep;
   };
+  Expr *PackIndexingExpr = nullptr;
 
   /// ExplicitSpecifier - Store information about explicit spicifer.
   ExplicitSpecifier FS_explicit_specifier;
@@ -405,7 +407,7 @@ class DeclSpec {
 
   SourceLocation StorageClassSpecLoc, ThreadStorageClassSpecLoc;
   SourceRange TSWRange;
-  SourceLocation TSCLoc, TSSLoc, TSTLoc, AltiVecLoc, TSSatLoc;
+  SourceLocation TSCLoc, TSSLoc, TSTLoc, AltiVecLoc, TSSatLoc, EllipsisLoc;
   /// TSTNameLoc - If TypeSpecType is any of class, enum, struct, union,
   /// typename, then this is the location of the named type (if present);
   /// otherwise, it is the same as TSTLoc. Hence, the pair TSTLoc and
@@ -427,7 +429,8 @@ class DeclSpec {
 
   static bool isTypeRep(TST T) {
     return T == TST_atomic || T == TST_typename || T == TST_typeofType ||
-           T == TST_typeof_unqualType || isTransformTypeTrait(T);
+           T == TST_typeof_unqualType || isTransformTypeTrait(T) ||
+           T == TST_indexed_typename_pack;
   }
   static bool isExprRep(TST T) {
     return T == TST_typeofExpr || T == TST_typeof_unqualExpr ||
@@ -530,6 +533,13 @@ class DeclSpec {
     assert(isExprRep((TST) TypeSpecType) && "DeclSpec does not store an expr");
     return ExprRep;
   }
+
+  Expr *getPackIndexingExpr() const {
+    assert(TypeSpecType == TST_indexed_ty...
[truncated]

Copy link

github-actions bot commented Nov 17, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A LOT of stuff here... probably going to need a few more cycles on this to completely grok it. I didn't spend much time on tests, but would love if we could come up with some 'common patterns' (particularly any that others have sent you as a 'look at this cool thing i can do with this!) to put in tests.

@@ -1685,7 +1685,12 @@ enum CXCursorKind {
*/
CXCursor_CXXParenListInitExpr = 155,

CXCursor_LastExpr = CXCursor_CXXParenListInitExpr,
/**
* Represents a C++26 pack indexing expression
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Represents a C++26 pack indexing expression
* Represents a C++26 pack indexing expression.

clang/include/clang/AST/ASTContext.h Show resolved Hide resolved
std::uninitialized_copy(SubstitutedExprs.begin(), SubstitutedExprs.end(),
Exprs);

ExprDependence D = IndexExpr->getDependence();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I THINK most of this dependence calculation should be moved into the 'calculateDependence' infrastructure, right? There is a file somewhere where all of these are implemented.

return *(getExpansionsPtr() + Index);
}

bool hasSelectedType() const { return Index != -1 && !isDependentType(); }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't JUST the second part of this be sufficient? I would think that as soon as we're not dependent, the index should be 'set', correct? Or am I misunderstanding?

"%0 does not refer to the name of a parameter pack">;

def err_pack_index_out_of_bound : Error<
"%0 is not a valid index for pack %1 of size %2">;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"%0 is not a valid index for pack %1 of size %2">;
"invalid index %0 for pack %1 of size %2">;

ArrayRef<Expr *> ExpandedExprs, bool EmptyPack) {

std::optional<int64_t> Index;
if (!IndexExpr->isValueDependent() && !IndexExpr->isTypeDependent()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this just isInstantiationDependent?

std::optional<int64_t> Index;
if (!IndexExpr->isValueDependent() && !IndexExpr->isTypeDependent()) {
llvm::APSInt Value(Context.getIntWidth(Context.getSizeType()));
// TODO: do we need a new enumerator instead of CCEK_ArrayBound?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice to have an answer to this :D I'd be concerned about this somehow deciding this is a VLA..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be a vla

Index = Value.getExtValue();
}

if (Index && (!ExpandedExprs.empty() || EmptyPack)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, hrmph... as 'hacky' as caching the index feels, i'm softening here. I guess we needed it all along, so letting it be cached is perhaps not the worst.

I could be greater convinced if we could figure out if we could union-ize the index and the index expression (that is, do we REALLY need the indexexpr once we're instantiated?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think once we have an index, it's always a ConstantExpr so presumably we could extract the value from that.
I suppose keeping the expression is best if people want to do funny things with matchers?

Index = Value.getExtValue();
}

if (FullyExpanded && Index) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, not sure what 'fully expanded' means here, can you help?

bool NotYetExpanded = Types.empty();
bool FullyExpanded = true;

if (Types.empty())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (Types.empty())
if (NotYetExpanded)

I think? Plus some comments here anyway, there are a couple of states crossing here.



std::optional<unsigned> getSelectedIndex() const {
if (isInstantiationDependent())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there good reason this isn't an assert?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's called from places that don't have another way to check whether an it would be valid.
Now that we don't store an index, we have to look at the expression to establish what the index is.

}

ArrayRef<Expr *> getExpressions() const {
if (TransformedExpressions == 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this test necessary? Worst case it just gives an empty collection, right?

clang/lib/AST/ExprCXX.cpp Show resolved Hide resolved
clang/lib/AST/Type.cpp Show resolved Hide resolved
Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No more comments, this looks good to me, and now that we are post release-branch, I think I'm happy to have this merged.

https://isocpp.org/files/papers/P2662R3.pdf

Because there is a slight chance the syntax might change slightly
(see https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2994r0.html),
the feature is not exposed in other language modes.
 - Add a computeDependence function
 - Remove the Index fron the type / expression:
we do keep it around in some of the functions to avoid haviung to recompute the index in multiple places
in short sucession.
 * improve error recovery for ill-formed pack indexing specifiers

This is important because we need to form a correct type for
an invalid pack indexing that we know is a type pack indexing so
that it is recognized as being part of a declaration.
@cor3ntin cor3ntin force-pushed the corentin/pack_indexing branch from 0503895 to b8e82eb Compare January 26, 2024 18:05
@cor3ntin
Copy link
Contributor Author

The libcxx test failure are unrelated.

@cor3ntin cor3ntin merged commit ad1a65f into llvm:main Jan 27, 2024
5 of 7 checks passed
@cor3ntin cor3ntin deleted the corentin/pack_indexing branch January 27, 2024 09:23
@cor3ntin
Copy link
Contributor Author

This triggers asan on bots, fix coming in the next couple hours.

@cor3ntin
Copy link
Contributor Author

Fixed in 143b510

@DamonFool
Copy link
Member

lldb may also need to be fixed?

llvm-project/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp:4074:11: error: enumeration value 'PackIndexing' not handled in switch [-Werror,-Wswitch]
  switch (qual_type->getTypeClass()) {
          ^~~~~~~~~~~~~~~~~~~~~~~~~
llvm-project/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp:4751:11: error: enumeration value 'PackIndexing' not handled in switch [-Werror,-Wswitch]
  switch (qual_type->getTypeClass()) {
          ^~~~~~~~~~~~~~~~~~~~~~~~~
llvm-project/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp:5080:11: error: enumeration value 'PackIndexing' not handled in switch [-Werror,-Wswitch]
  switch (qual_type->getTypeClass()) {
          ^~~~~~~~~~~~~~~~~~~~~~~~~
3 errors generated.

@cor3ntin
Copy link
Contributor Author

lldb may also need to be fixed?

llvm-project/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp:4074:11: error: enumeration value 'PackIndexing' not handled in switch [-Werror,-Wswitch]
  switch (qual_type->getTypeClass()) {
          ^~~~~~~~~~~~~~~~~~~~~~~~~
llvm-project/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp:4751:11: error: enumeration value 'PackIndexing' not handled in switch [-Werror,-Wswitch]
  switch (qual_type->getTypeClass()) {
          ^~~~~~~~~~~~~~~~~~~~~~~~~
llvm-project/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp:5080:11: error: enumeration value 'PackIndexing' not handled in switch [-Werror,-Wswitch]
  switch (qual_type->getTypeClass()) {
          ^~~~~~~~~~~~~~~~~~~~~~~~~
3 errors generated.

Yes. (I missed that, thanks). I'm not familiar with lldb codebase but the fix looks trivial.

@cor3ntin
Copy link
Contributor Author

#79695

cor3ntin added a commit that referenced this pull request Jan 27, 2024
We do not handle pack indexing types (added by yet #72644) but we add them to some switch
statement to ensure CI builds do not fail.
Copy link
Contributor

@bevin-hansson bevin-hansson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We hit some odd behavior in our downstream after this patch.

Comment on lines +4961 to +4965
QualType desugar() const {
if (hasSelectedType())
return getSelectedType();
return QualType(this, 0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? Calling getTypeInfo on a PackIndexingType where hasSelectedType is false will cause it to infinitely recurse as it tries to desugar itself over and over again. This seems to happen if the type is broken, like the not_pack examples in cxx2c-pack-indexing.cpp.

Maybe the issue lies elsewhere, but something is strange with the error recovery for these types. Perhaps because isDependentType isn't true for them when they are broken?

}

QualType getSelectedType() const {
assert(hasSelectedType() && "Type is dependant");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to dump/print a broken pack indexing expression/type breaks on this assertion. Try adding -ast-dump to cxx2c-pack-indexing.cpp and see what happens.

@bevin-hansson
Copy link
Contributor

Ping. Just wondering if anyone has seen the above. @cor3ntin ?

@cor3ntin
Copy link
Contributor Author

cor3ntin commented Feb 2, 2024

@bevin-hansson Thanks for letting me know, I'll look into it

cor3ntin added a commit to cor3ntin/llvm-project that referenced this pull request Feb 2, 2024
Fix a crash caused by incorrect assumptions
Reported here llvm#72644 (comment)
cor3ntin added a commit to cor3ntin/llvm-project that referenced this pull request Feb 2, 2024
Fix a crash caused by incorrect assumptions
Reported here llvm#72644 (comment)
cor3ntin added a commit that referenced this pull request Feb 2, 2024
Fix a crash caused by incorrect assumptions
Reported here
#72644 (comment)
agozillon pushed a commit to agozillon/llvm-project that referenced this pull request Feb 5, 2024
Fix a crash caused by incorrect assumptions
Reported here
llvm#72644 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ c++26 clang:codegen clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:static analyzer clang Clang issues not falling into any other category debuginfo
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants