[clang] [Clang] [C++26] Implement P1306R5 Expansion Statements (PR #165195)

via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 27 09:18:25 PDT 2025


================
@@ -0,0 +1,584 @@
+//===-- SemaExpand.cpp - Semantic Analysis for Expansion Statements--------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file implements semantic analysis for C++26 expansion statements,
+//  aka 'template for'.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/StmtCXX.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Sema/Lookup.h"
+#include "clang/Sema/Overload.h"
+#include "clang/Sema/Sema.h"
+
+#include <clang/Sema/EnterExpressionEvaluationContext.h>
+#include <clang/Sema/Template.h>
+
+using namespace clang;
+using namespace sema;
+
+namespace {
+struct IterableExpansionStmtData {
+  enum class State {
+    NotIterable,
+    Error,
+    Ok,
+  };
+
+  DeclStmt *RangeDecl{};
+  DeclStmt *BeginDecl{};
+  DeclStmt *EndDecl{};
+  Expr *Initializer{};
+  State TheState = State::NotIterable;
+
+  bool isIterable() const { return TheState == State::Ok; }
+  bool hasError() { return TheState == State::Error; }
+};
+} // namespace
+
+static bool CheckExpansionSize(Sema &S, uint64_t NumInstantiations,
+                               SourceLocation Loc) {
+  unsigned Max = S.LangOpts.MaxTemplateForExpansions;
+  if (Max != 0 && NumInstantiations > Max) {
+    S.Diag(Loc, diag::err_expansion_too_big) << NumInstantiations << Max;
+    S.Diag(Loc, diag::note_use_fexpansion_limit);
+    return true;
+  }
+
+  return false;
+}
+
+// Build a 'DeclRefExpr' designating the template parameter '__N'.
+static DeclRefExpr *BuildIndexDRE(Sema &S, ExpansionStmtDecl *ESD) {
+  return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(),
+                            S.Context.getPointerDiffType(), VK_PRValue,
+                            ESD->getBeginLoc());
+}
+
+static bool FinaliseExpansionVar(Sema &S, VarDecl *ExpansionVar,
+                                 ExprResult Initializer) {
+  if (Initializer.isInvalid()) {
+    S.ActOnInitializerError(ExpansionVar);
+    return true;
+  }
+
+  S.AddInitializerToDecl(ExpansionVar, Initializer.get(), /*DirectInit=*/false);
+  return ExpansionVar->isInvalidDecl();
+}
+
+static IterableExpansionStmtData
+TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
+                                         Expr *Index, SourceLocation ColonLoc,
+                                         bool VarIsConstexpr) {
+  IterableExpansionStmtData Data;
+
+  // C++26 [stmt.expand]p3: An expression is expansion-iterable if it does not
+  // have array type [...]
+  QualType Ty = ExpansionInitializer->getType().getNonReferenceType();
+  if (Ty->isArrayType())
+    return Data;
+
+  // Lookup member and ADL 'begin()'/'end()'. Only check if they exist; even if
+  // they're deleted, inaccessible, etc., this is still an iterating expansion
+  // statement, albeit an ill-formed one.
+  DeclarationNameInfo BeginName(&S.PP.getIdentifierTable().get("begin"),
+                                ColonLoc);
+  DeclarationNameInfo EndName(&S.PP.getIdentifierTable().get("end"), ColonLoc);
+
+  // Try member lookup first.
+  bool FoundBeginEnd = false;
+  if (auto *Record = Ty->getAsCXXRecordDecl()) {
+    LookupResult BeginLR(S, BeginName, Sema::LookupMemberName);
+    LookupResult EndLR(S, EndName, Sema::LookupMemberName);
+    FoundBeginEnd = S.LookupQualifiedName(BeginLR, Record) &&
+                    S.LookupQualifiedName(EndLR, Record);
+  }
+
+  // Try ADL.
+  if (!FoundBeginEnd) {
+    OverloadCandidateSet Candidates(ColonLoc, OverloadCandidateSet::CSK_Normal);
+
+    S.AddArgumentDependentLookupCandidates(
+        BeginName.getName(), ColonLoc, ExpansionInitializer,
+        /*ExplicitTemplateArgs=*/nullptr, Candidates);
+
+    if (Candidates.empty())
+      return Data;
+
+    Candidates.clear(OverloadCandidateSet::CSK_Normal);
+    S.AddArgumentDependentLookupCandidates(
+        EndName.getName(), ColonLoc, ExpansionInitializer,
+        /*ExplicitTemplateArgs=*/nullptr, Candidates);
+
+    if (Candidates.empty())
+      return Data;
+  }
+
+  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+  if (VarIsConstexpr)
+    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+  EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
+
+  // The declarations should be attached to the parent decl context.
+  Sema::ContextRAII CtxGuard(
+      S, S.CurContext->getEnclosingNonExpansionStatementContext(),
+      /*NewThis=*/false);
+
+  // Ok, we know that this is supposed to be an iterable expansion statement;
+  // delegate to the for-range code to build the range/begin/end variables.
+  //
+  // Any failure at this point is a hard error.
+  Data.TheState = IterableExpansionStmtData::State::Error;
+  Scope *Scope = S.getCurScope();
+  StmtResult Var = S.BuildCXXForRangeRangeVar(Scope, ExpansionInitializer,
+                                              /*ForExpansionStmt=*/true);
+  if (Var.isInvalid())
+    return Data;
+
+  auto *RangeVar = cast<DeclStmt>(Var.get());
+  Sema::ForRangeBeginEndInfo Info = S.BuildCXXForRangeBeginEndVars(
+      Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
+      /*CoawaitLoc=*/{},
+      /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, /*ForExpansionStmt=*/true);
+
+  if (!Info.isValid())
+    return Data;
+
+  StmtResult BeginStmt = S.ActOnDeclStmt(
+      S.ConvertDeclToDeclGroup(Info.BeginVar), ColonLoc, ColonLoc);
+  StmtResult EndStmt = S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(Info.EndVar),
+                                       ColonLoc, ColonLoc);
+  if (BeginStmt.isInvalid() || EndStmt.isInvalid())
+    return Data;
+
+  // Build '*(begin + i)'.
+  DeclRefExpr *Begin = S.BuildDeclRefExpr(
+      Info.BeginVar, Info.BeginVar->getType().getNonReferenceType(), VK_LValue,
+      ColonLoc);
+
+  ExprResult BeginPlusI =
+      S.ActOnBinOp(Scope, ColonLoc, tok::plus, Begin, Index);
+  if (BeginPlusI.isInvalid())
+    return Data;
+
+  ExprResult Deref =
+      S.ActOnUnaryOp(Scope, ColonLoc, tok::star, BeginPlusI.get());
+  if (Deref.isInvalid())
+    return Data;
+
+  Deref = S.MaybeCreateExprWithCleanups(Deref.get());
+  Data.BeginDecl = BeginStmt.getAs<DeclStmt>();
+  Data.EndDecl = EndStmt.getAs<DeclStmt>();
+  Data.RangeDecl = RangeVar;
+  Data.Initializer = Deref.get();
+  Data.TheState = IterableExpansionStmtData::State::Ok;
+  return Data;
+}
+
+static StmtResult BuildDestructuringExpansionStmtDecl(
+    Sema &S, Expr *ExpansionInitializer, SourceLocation ColonLoc,
+    bool VarIsConstexpr,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+  if (VarIsConstexpr)
+    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+  EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
+
+  // The declarations should be attached to the parent decl context.
+  Sema::ContextRAII CtxGuard(
+      S, S.CurContext->getEnclosingNonExpansionStatementContext(),
+      /*NewThis=*/false);
+
+  UnsignedOrNone Arity =
+      S.GetDecompositionElementCount(ExpansionInitializer->getType(), ColonLoc);
+
+  if (!Arity) {
+    S.Diag(ExpansionInitializer->getBeginLoc(),
+           diag::err_expansion_stmt_invalid_init)
+        << ExpansionInitializer->getType()
+        << ExpansionInitializer->getSourceRange();
+    return StmtError();
+  }
+
+  if (CheckExpansionSize(S, *Arity, ColonLoc))
+    return StmtError();
+
+  QualType AutoRRef = S.Context.getAutoRRefDeductType();
+  SmallVector<BindingDecl *> Bindings;
+  for (unsigned I = 0; I < *Arity; ++I)
+    Bindings.push_back(BindingDecl::Create(
+        S.Context, S.CurContext, ColonLoc,
+        S.getPreprocessor().getIdentifierInfo("__u" + std::to_string(I)),
+        AutoRRef));
+
+  TypeSourceInfo *TSI = S.Context.getTrivialTypeSourceInfo(AutoRRef);
+  auto *DD =
+      DecompositionDecl::Create(S.Context, S.CurContext, ColonLoc, ColonLoc,
+                                AutoRRef, TSI, SC_Auto, Bindings);
+
+  if (VarIsConstexpr)
+    DD->setConstexpr(true);
+
+  S.ApplyForRangeOrExpansionStatementLifetimeExtension(DD, LifetimeExtendTemps);
+  S.AddInitializerToDecl(DD, ExpansionInitializer, false);
+  return S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(DD), ColonLoc, ColonLoc);
+}
+
+ExpansionStmtDecl *Sema::ActOnExpansionStmtDecl(unsigned TemplateDepth,
+                                                SourceLocation TemplateKWLoc) {
+  // Create a template parameter '__N'. This will be used to denote the index
+  // of the element that we're instantiating. CWG 3044 requires this type to
+  // be 'ptrdiff_t' for iterating expansion statements, so use that in all
+  // cases.
+  IdentifierInfo *ParmName = &Context.Idents.get("__N");
+  QualType ParmTy = Context.getPointerDiffType();
+  TypeSourceInfo *ParmTI =
+      Context.getTrivialTypeSourceInfo(ParmTy, TemplateKWLoc);
+
+  auto *TParam = NonTypeTemplateParmDecl::Create(
+      Context, Context.getTranslationUnitDecl(), TemplateKWLoc, TemplateKWLoc,
+      TemplateDepth, /*Position=*/0, ParmName, ParmTy, /*ParameterPack=*/false,
+      ParmTI);
+
+  return BuildExpansionStmtDecl(CurContext, TemplateKWLoc, TParam);
+}
+
+ExpansionStmtDecl *Sema::BuildExpansionStmtDecl(DeclContext *Ctx,
+                                                SourceLocation TemplateKWLoc,
+                                                NonTypeTemplateParmDecl *NTTP) {
+  auto *TParamList = TemplateParameterList::Create(
+      Context, TemplateKWLoc, TemplateKWLoc, {NTTP}, TemplateKWLoc,
+      /*RequiresClause=*/nullptr);
+  auto *Result =
+      ExpansionStmtDecl::Create(Context, Ctx, TemplateKWLoc, TParamList);
+  Ctx->addDecl(Result);
+  return Result;
+}
+
+ExprResult Sema::ActOnCXXExpansionInitList(MultiExprArg SubExprs,
+                                           SourceLocation LBraceLoc,
+                                           SourceLocation RBraceLoc) {
+  return CXXExpansionInitListExpr::Create(Context, SubExprs, LBraceLoc,
+                                          RBraceLoc);
+}
+
+StmtResult Sema::ActOnCXXExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
+    Expr *ExpansionInitializer, SourceLocation ForLoc, SourceLocation LParenLoc,
+    SourceLocation ColonLoc, SourceLocation RParenLoc,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  if (!ExpansionInitializer || !ExpansionVarStmt)
+    return StmtError();
+
+  assert(CurContext->isExpansionStmt());
+  auto *DS = cast<DeclStmt>(ExpansionVarStmt);
+  if (!DS->isSingleDecl()) {
+    Diag(DS->getBeginLoc(), diag::err_type_defined_in_for_range);
+    return StmtError();
+  }
+
+  VarDecl *ExpansionVar = cast<VarDecl>(DS->getSingleDecl());
+  if (!ExpansionVar || ExpansionVar->isInvalidDecl() ||
+      ExpansionInitializer->containsErrors())
+    return StmtError();
+
+  // This is an enumerating expansion statement.
+  if (auto *ILE = dyn_cast<CXXExpansionInitListExpr>(ExpansionInitializer)) {
+    ExprResult Initializer =
+        BuildCXXExpansionInitListSelectExpr(ILE, BuildIndexDRE(*this, ESD));
+    if (FinaliseExpansionVar(*this, ExpansionVar, Initializer))
+      return StmtError();
+
+    // Note that lifetime extension only applies to destructurable expansion
+    // statements, so we just ignore 'LifetimeExtendedTemps' entirely for other
+    // types of expansion statements (this is CWG 3043).
+    return BuildCXXEnumeratingExpansionStmt(ESD, Init, DS, ForLoc, LParenLoc,
+                                            ColonLoc, RParenLoc);
+  }
+
+  if (ExpansionInitializer->hasPlaceholderType()) {
+    ExprResult R = CheckPlaceholderExpr(ExpansionInitializer);
+    if (R.isInvalid())
+      return StmtError();
+    ExpansionInitializer = R.get();
+  }
+
+  if (DiagnoseUnexpandedParameterPack(ExpansionInitializer))
+    return StmtError();
+
+  // Reject lambdas early.
+  if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+      RD && RD->isLambda()) {
+    Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+    return StmtError();
+  }
+
+  return BuildNonEnumeratingCXXExpansionStmt(
+      ESD, Init, DS, ExpansionInitializer, ForLoc, LParenLoc, ColonLoc,
+      RParenLoc, LifetimeExtendTemps);
+}
+
+StmtResult Sema::BuildCXXEnumeratingExpansionStmt(Decl *ESD, Stmt *Init,
+                                                  Stmt *ExpansionVar,
+                                                  SourceLocation ForLoc,
+                                                  SourceLocation LParenLoc,
+                                                  SourceLocation ColonLoc,
+                                                  SourceLocation RParenLoc) {
+  return new (Context) CXXEnumeratingExpansionStmt(
+      cast<ExpansionStmtDecl>(ESD), Init, cast<DeclStmt>(ExpansionVar), ForLoc,
+      LParenLoc, ColonLoc, RParenLoc);
+}
+
+StmtResult Sema::BuildNonEnumeratingCXXExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
+    Expr *ExpansionInitializer, SourceLocation ForLoc, SourceLocation LParenLoc,
+    SourceLocation ColonLoc, SourceLocation RParenLoc,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  VarDecl *ExpansionVar = cast<VarDecl>(ExpansionVarStmt->getSingleDecl());
+
+  if (ExpansionInitializer->isTypeDependent()) {
+    ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
+    return new (Context) CXXDependentExpansionStmt(
+        ESD, Init, ExpansionVarStmt, ExpansionInitializer, ForLoc, LParenLoc,
+        ColonLoc, RParenLoc);
+  }
+
+  // Otherwise, if it can be an iterating expansion statement, it is one.
+  DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
+  IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
+      *this, ExpansionInitializer, Index, ColonLoc,
+      ExpansionVar->isConstexpr());
+  if (Data.hasError()) {
+    ActOnInitializerError(ExpansionVar);
+    return StmtError();
+  }
+
+  if (Data.isIterable()) {
+    if (FinaliseExpansionVar(*this, ExpansionVar, Data.Initializer))
+      return StmtError();
+
+    return new (Context) CXXIteratingExpansionStmt(
+        ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
+        Data.EndDecl, ForLoc, LParenLoc, ColonLoc, RParenLoc);
+  }
+
+  // If not, try destructuring.
+  StmtResult DecompDeclStmt = BuildDestructuringExpansionStmtDecl(
+      *this, ExpansionInitializer, ColonLoc, ExpansionVar->isConstexpr(),
+      LifetimeExtendTemps);
+  if (DecompDeclStmt.isInvalid()) {
+    ActOnInitializerError(ExpansionVar);
+    return StmtError();
+  }
+
+  auto *DS = DecompDeclStmt.getAs<DeclStmt>();
+  auto *DD = cast<DecompositionDecl>(DS->getSingleDecl());
+  if (DD->isInvalidDecl())
+    return StmtError();
+
+  ExprResult Select = BuildCXXDestructuringExpansionSelectExpr(DD, Index);
+  if (Select.isInvalid()) {
+    ActOnInitializerError(ExpansionVar);
+    return StmtError();
+  }
+
+  if (FinaliseExpansionVar(*this, ExpansionVar, Select))
+    return StmtError();
+
+  return new (Context) CXXDestructuringExpansionStmt(
+      ESD, Init, ExpansionVarStmt, DS, ForLoc, LParenLoc, ColonLoc, RParenLoc);
+}
+
+StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
+  if (!Exp || !Body)
+    return StmtError();
+
+  auto *Expansion = cast<CXXExpansionStmt>(Exp);
+  assert(!Expansion->getDecl()->getInstantiations() &&
+         "should not rebuild expansion statement after instantiation");
+
+  Expansion->setBody(Body);
+  if (Expansion->hasDependentSize())
+    return Expansion;
+
+  // This can fail if this is an iterating expansion statement.
+  std::optional<uint64_t> NumInstantiations = ComputeExpansionSize(Expansion);
+  if (!NumInstantiations)
+    return StmtError();
+
+  if (CheckExpansionSize(*this, *NumInstantiations, Expansion->getColonLoc()))
+    return StmtError();
+
+  // Collect shared statements.
+  SmallVector<Stmt *, 1> Shared;
+  if (Expansion->getInit())
+    Shared.push_back(Expansion->getInit());
+
+  if (auto *Iter = dyn_cast<CXXIteratingExpansionStmt>(Expansion)) {
+    Shared.push_back(Iter->getRangeVarStmt());
+    Shared.push_back(Iter->getBeginVarStmt());
+    Shared.push_back(Iter->getEndVarStmt());
+  } else if (auto *Destructuring =
+                 dyn_cast<CXXDestructuringExpansionStmt>(Expansion)) {
+    Shared.push_back(Destructuring->getDecompositionDeclStmt());
+  }
+
+  // Return an empty statement if the range is empty.
+  if (*NumInstantiations == 0) {
+    Expansion->getDecl()->setInstantiations(
+        CXXExpansionInstantiationStmt::Create(
+            Context, Expansion->getBeginLoc(), Expansion->getEndLoc(),
+            /*Instantiations=*/{}, Shared,
+            isa<CXXDestructuringExpansionStmt>(Expansion)));
+    return Expansion;
+  }
+
+  // Create a compound statement binding loop and body.
+  Stmt *VarAndBody[] = {Expansion->getExpansionVarStmt(), Body};
+  Stmt *CombinedBody =
+      CompoundStmt::Create(Context, VarAndBody, FPOptionsOverride(),
+                           Body->getBeginLoc(), Body->getEndLoc());
+
+  // Expand the body for each instantiation.
+  SmallVector<Stmt *, 4> Instantiations;
+  ExpansionStmtDecl *ESD = Expansion->getDecl();
+  for (uint64_t I = 0; I < *NumInstantiations; ++I) {
+    // Now that we're expanding this, exit the context of the expansion stmt
+    // so that we no longer treat this as dependent.
+    ContextRAII CtxGuard(*this,
+                         CurContext->getEnclosingNonExpansionStatementContext(),
+                         /*NewThis=*/false);
+
+    TemplateArgument Arg{Context, llvm::APSInt::get(I),
+                         Context.getPointerDiffType()};
+    MultiLevelTemplateArgumentList MTArgList(ESD, Arg, true);
+    MTArgList.addOuterRetainedLevels(
+        Expansion->getDecl()->getIndexTemplateParm()->getDepth());
+
+    LocalInstantiationScope LIScope(*this, /*CombineWithOuterScope=*/true);
+    InstantiatingTemplate Inst(*this, Body->getBeginLoc(), Expansion, Arg,
+                               Body->getSourceRange());
+
+    StmtResult Instantiation = SubstStmt(CombinedBody, MTArgList);
+
+    if (Instantiation.isInvalid())
+      return StmtError();
+    Instantiations.push_back(Instantiation.get());
+  }
+
+  auto *InstantiationsStmt = CXXExpansionInstantiationStmt::Create(
+      Context, Expansion->getBeginLoc(), Expansion->getEndLoc(), Instantiations,
+      Shared, isa<CXXDestructuringExpansionStmt>(Expansion));
+
+  Expansion->getDecl()->setInstantiations(InstantiationsStmt);
+  return Expansion;
+}
+
+ExprResult
+Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
+                                          Expr *Idx) {
+  if (Range->containsPackExpansion() || Idx->isValueDependent())
+    return new (Context) CXXExpansionInitListSelectExpr(Context, Range, Idx);
+
+  // The index is a DRE to a template parameter; we should never
+  // fail to evaluate it.
+  Expr::EvalResult ER;
+  if (!Idx->EvaluateAsInt(ER, Context))
+    llvm_unreachable("Failed to evaluate expansion index");
+
+  uint64_t I = ER.Val.getInt().getZExtValue();
+  return Range->getExprs()[I];
+}
+
+ExprResult Sema::BuildCXXDestructuringExpansionSelectExpr(DecompositionDecl *DD,
+                                                          Expr *Idx) {
+  if (Idx->isValueDependent())
+    return new (Context) CXXDestructuringExpansionSelectExpr(Context, DD, Idx);
+
+  Expr::EvalResult ER;
+  if (!Idx->EvaluateAsInt(ER, Context))
+    llvm_unreachable("Failed to evaluate expansion index");
----------------
Sirraide wrote:

The `maybe_unused` part makes sense to me if we want to use `assert()`; my question was mainly why we’d want to use `assert()` instead of `llvm_unreachable()` here.

https://github.com/llvm/llvm-project/pull/165195


More information about the cfe-commits mailing list