Skip to content

Commit

Permalink
[Clang][P1061] Add stuctured binding packs (#121417)
Browse files Browse the repository at this point in the history
This is an implementation of P1061 Structure Bindings Introduce a Pack
without the ability to use packs outside of templates. There is a couple
of ways the AST could have been sliced so let me know what you think.
The only part of this change that I am unsure of is the
serialization/deserialization stuff. I followed the implementation of
other Exprs, but I do not really know how it is tested. Thank you for
your time considering this.

---------

Co-authored-by: Yanzuo Liu <[email protected]>
  • Loading branch information
ricejasonf and zwuis authored Jan 29, 2025
1 parent 608012a commit abc8812
Show file tree
Hide file tree
Showing 43 changed files with 883 additions and 91 deletions.
8 changes: 4 additions & 4 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,10 @@ class ValueDecl : public NamedDecl {
return const_cast<ValueDecl *>(this)->getPotentiallyDecomposedVarDecl();
}

/// Determine whether this value is actually a function parameter pack,
/// init-capture pack, or structured binding pack
bool isParameterPack() const;

// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K >= firstValue && K <= lastValue; }
Expand Down Expand Up @@ -1527,10 +1531,6 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
NonParmVarDeclBits.IsInitCapture = IC;
}

/// Determine whether this variable is actually a function parameter pack or
/// init-capture pack.
bool isParameterPack() const;

/// Whether this local extern variable declaration's previous declaration
/// was declared in the same block scope. Only correct in C++.
bool isPreviousDeclInSameBlockScope() const {
Expand Down
58 changes: 48 additions & 10 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -4175,31 +4175,32 @@ class BindingDecl : public ValueDecl {
/// binding).
Expr *Binding = nullptr;

BindingDecl(DeclContext *DC, SourceLocation IdLoc, IdentifierInfo *Id)
: ValueDecl(Decl::Binding, DC, IdLoc, Id, QualType()) {}
BindingDecl(DeclContext *DC, SourceLocation IdLoc, IdentifierInfo *Id,
QualType T)
: ValueDecl(Decl::Binding, DC, IdLoc, Id, T) {}

void anchor() override;

public:
friend class ASTDeclReader;

static BindingDecl *Create(ASTContext &C, DeclContext *DC,
SourceLocation IdLoc, IdentifierInfo *Id);
SourceLocation IdLoc, IdentifierInfo *Id,
QualType T);
static BindingDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID);

/// Get the expression to which this declaration is bound. This may be null
/// in two different cases: while parsing the initializer for the
/// decomposition declaration, and when the initializer is type-dependent.
Expr *getBinding() const { return Binding; }

// Get the array of Exprs when the binding represents a pack.
llvm::ArrayRef<Expr *> getBindingPackExprs() const;

/// Get the decomposition declaration that this binding represents a
/// decomposition of.
ValueDecl *getDecomposedDecl() const { return Decomp; }

/// Get the variable (if any) that holds the value of evaluating the binding.
/// Only present for user-defined bindings for tuple-like types.
VarDecl *getHoldingVar() const;

/// Set the binding for this BindingDecl, along with its declared type (which
/// should be a possibly-cv-qualified form of the type of the binding, or a
/// reference to such a type).
Expand All @@ -4211,6 +4212,10 @@ class BindingDecl : public ValueDecl {
/// Set the decomposed variable for this BindingDecl.
void setDecomposedDecl(ValueDecl *Decomposed) { Decomp = Decomposed; }

/// Get the variable (if any) that holds the value of evaluating the binding.
/// Only present for user-defined bindings for tuple-like types.
VarDecl *getHoldingVar() const;

static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == Decl::Binding; }
};
Expand Down Expand Up @@ -4238,8 +4243,16 @@ class DecompositionDecl final
NumBindings(Bindings.size()) {
std::uninitialized_copy(Bindings.begin(), Bindings.end(),
getTrailingObjects<BindingDecl *>());
for (auto *B : Bindings)
for (auto *B : Bindings) {
B->setDecomposedDecl(this);
if (B->isParameterPack() && B->getBinding()) {
for (Expr *E : B->getBindingPackExprs()) {
auto *DRE = cast<DeclRefExpr>(E);
auto *NestedB = cast<BindingDecl>(DRE->getDecl());
NestedB->setDecomposedDecl(this);
}
}
}
}

void anchor() override;
Expand All @@ -4257,8 +4270,33 @@ class DecompositionDecl final
static DecompositionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
unsigned NumBindings);

ArrayRef<BindingDecl *> bindings() const {
return llvm::ArrayRef(getTrailingObjects<BindingDecl *>(), NumBindings);
// Provide the range of bindings which may have a nested pack.
llvm::ArrayRef<BindingDecl *> bindings() const {
return {getTrailingObjects<BindingDecl *>(), NumBindings};
}

// Provide a flattened range to visit each binding.
auto flat_bindings() const {
llvm::ArrayRef<BindingDecl *> Bindings = bindings();
llvm::ArrayRef<Expr *> PackExprs;

// Split the bindings into subranges split by the pack.
auto S1 = Bindings.take_until(
[](BindingDecl *BD) { return BD->isParameterPack(); });

Bindings = Bindings.drop_front(S1.size());
if (!Bindings.empty()) {
PackExprs = Bindings.front()->getBindingPackExprs();
Bindings = Bindings.drop_front();
}

auto S2 = llvm::map_range(PackExprs, [](Expr *E) {
auto *DRE = cast<DeclRefExpr>(E);
return cast<BindingDecl>(DRE->getDecl());
});

return llvm::concat<BindingDecl *>(std::move(S1), std::move(S2),
std::move(Bindings));
}

void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override;
Expand Down
53 changes: 53 additions & 0 deletions clang/include/clang/AST/ExprCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -5319,6 +5319,59 @@ class BuiltinBitCastExpr final
}
};

// Represents an unexpanded pack where the list of expressions are
// known. These are used when structured bindings introduce a pack.
class ResolvedUnexpandedPackExpr final
: public Expr,
private llvm::TrailingObjects<ResolvedUnexpandedPackExpr, Expr *> {
friend class ASTStmtReader;
friend class ASTStmtWriter;
friend TrailingObjects;

SourceLocation BeginLoc;
unsigned NumExprs;

ResolvedUnexpandedPackExpr(SourceLocation BL, QualType QT, unsigned NumExprs);

public:
static ResolvedUnexpandedPackExpr *CreateDeserialized(ASTContext &C,
unsigned NumExprs);
static ResolvedUnexpandedPackExpr *
Create(ASTContext &C, SourceLocation BeginLoc, QualType T, unsigned NumExprs);
static ResolvedUnexpandedPackExpr *Create(ASTContext &C,
SourceLocation BeginLoc, QualType T,
llvm::ArrayRef<Expr *> Exprs);

unsigned getNumExprs() const { return NumExprs; }

llvm::MutableArrayRef<Expr *> getExprs() {
return {getTrailingObjects<Expr *>(), NumExprs};
}

llvm::ArrayRef<Expr *> getExprs() const {
return {getTrailingObjects<Expr *>(), NumExprs};
}

Expr *getExpansion(unsigned Idx) { return getExprs()[Idx]; }
Expr *getExpansion(unsigned Idx) const { return getExprs()[Idx]; }

// Iterators
child_range children() {
return child_range((Stmt **)getTrailingObjects<Expr *>(),
(Stmt **)getTrailingObjects<Expr *>() + getNumExprs());
}

SourceLocation getBeginLoc() const LLVM_READONLY { return BeginLoc; }
SourceLocation getEndLoc() const LLVM_READONLY { return BeginLoc; }

// Returns the resolved pack of a decl or nullptr
static ResolvedUnexpandedPackExpr *getFromDecl(Decl *);

static bool classof(const Stmt *T) {
return T->getStmtClass() == ResolvedUnexpandedPackExprClass;
}
};

} // namespace clang

#endif // LLVM_CLANG_AST_EXPRCXX_H
1 change: 1 addition & 0 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -2950,6 +2950,7 @@ DEF_TRAVERSE_STMT(FunctionParmPackExpr, {})
DEF_TRAVERSE_STMT(CXXFoldExpr, {})
DEF_TRAVERSE_STMT(AtomicExpr, {})
DEF_TRAVERSE_STMT(CXXParenListInitExpr, {})
DEF_TRAVERSE_STMT(ResolvedUnexpandedPackExpr, {})

DEF_TRAVERSE_STMT(MaterializeTemporaryExpr, {
if (S->getLifetimeExtendedTemporaryDecl()) {
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,16 @@ def err_lambda_capture_misplaced_ellipsis : Error<
"the name of the capture">;
def err_lambda_capture_multiple_ellipses : Error<
"multiple ellipses in pack capture">;
def err_binding_multiple_ellipses : Error<
"multiple packs in structured binding declaration">;
def note_previous_ellipsis : Note<
"previous binding pack specified here">;
def ext_cxx_binding_pack : ExtWarn<
"structured binding packs are a C++2c extension ">,
InGroup<CXX26>;
def warn_cxx23_compat_binding_pack : Warning<
"structured binding packs are incompatible with C++ standards before C++2c">,
InGroup<CXXPre26Compat>, DefaultIgnore;
def err_capture_default_first : Error<
"capture default must be first">;
def ext_decl_attrs_on_lambda : ExtWarn<
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -5945,6 +5945,9 @@ def warn_cxx23_pack_indexing : Warning<
"pack indexing is incompatible with C++ standards before C++2c">,
DefaultIgnore, InGroup<CXXPre26Compat>;

def err_pack_outside_template : Error<
"pack declaration outside of template">;

def err_fold_expression_packs_both_sides : Error<
"binary fold expression has unexpanded parameter packs in both operands">;
def err_fold_expression_empty : Error<
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/StmtNodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def MaterializeTemporaryExpr : StmtNode<Expr>;
def LambdaExpr : StmtNode<Expr>;
def CXXFoldExpr : StmtNode<Expr>;
def CXXParenListInitExpr: StmtNode<Expr>;
def ResolvedUnexpandedPackExpr : StmtNode<Expr>;

// C++ Coroutines expressions
def CoroutineSuspendExpr : StmtNode<Expr, 1>;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/DeclSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -1795,6 +1795,7 @@ class DecompositionDeclarator {
IdentifierInfo *Name;
SourceLocation NameLoc;
std::optional<ParsedAttributes> Attrs;
SourceLocation EllipsisLoc;
};

private:
Expand Down
4 changes: 3 additions & 1 deletion clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ void threadSafetyCleanup(BeforeSet *Cache);

// FIXME: No way to easily map from TemplateTypeParmTypes to
// TemplateTypeParmDecls, so we have this horrible PointerUnion.
typedef std::pair<llvm::PointerUnion<const TemplateTypeParmType *, NamedDecl *>,
typedef std::pair<llvm::PointerUnion<const TemplateTypeParmType *, NamedDecl *,
ResolvedUnexpandedPackExpr *>,
SourceLocation>
UnexpandedParameterPack;

Expand Down Expand Up @@ -6021,6 +6022,7 @@ class Sema final : public SemaBase {
RecordDecl *ClassDecl,
const IdentifierInfo *Name);

unsigned GetDecompositionElementCount(QualType DecompType);
void CheckCompleteDecompositionDeclaration(DecompositionDecl *DD);

/// Stack containing information needed when in C++2a an 'auto' is encountered
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,7 @@ enum StmtCode {
EXPR_PACK_EXPANSION, // PackExpansionExpr
EXPR_PACK_INDEXING, // PackIndexingExpr
EXPR_SIZEOF_PACK, // SizeOfPackExpr
EXPR_RESOLVED_UNEXPANDED_PACK, // ResolvedUnexpandedPackExpr
EXPR_SUBST_NON_TYPE_TEMPLATE_PARM, // SubstNonTypeTemplateParmExpr
EXPR_SUBST_NON_TYPE_TEMPLATE_PARM_PACK, // SubstNonTypeTemplateParmPackExpr
EXPR_FUNCTION_PARM_PACK, // FunctionParmPackExpr
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12841,11 +12841,12 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) {

// Likewise, variables with tuple-like bindings are required if their
// bindings have side-effects.
if (const auto *DD = dyn_cast<DecompositionDecl>(VD))
for (const auto *BD : DD->bindings())
if (const auto *DD = dyn_cast<DecompositionDecl>(VD)) {
for (const auto *BD : DD->flat_bindings())
if (const auto *BindingVD = BD->getHoldingVar())
if (DeclMustBeEmitted(BindingVD))
return true;
}

return false;
}
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2553,7 +2553,7 @@ ExpectedDecl ASTNodeImporter::VisitBindingDecl(BindingDecl *D) {

BindingDecl *ToD;
if (GetImportedOrCreateDecl(ToD, D, Importer.getToContext(), DC, Loc,
Name.getAsIdentifierInfo()))
Name.getAsIdentifierInfo(), D->getType()))
return ToD;

Error Err = Error::success();
Expand Down
11 changes: 7 additions & 4 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2659,10 +2659,6 @@ bool VarDecl::checkForConstantInitialization(
return Eval->HasConstantInitialization;
}

bool VarDecl::isParameterPack() const {
return isa<PackExpansionType>(getType());
}

template<typename DeclT>
static DeclT *getDefinitionOrSelf(DeclT *D) {
assert(D);
Expand Down Expand Up @@ -5421,6 +5417,13 @@ bool ValueDecl::isInitCapture() const {
return false;
}

bool ValueDecl::isParameterPack() const {
if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(this))
return NTTP->isParameterPack();

return isa_and_nonnull<PackExpansionType>(getType().getTypePtrOrNull());
}

void ImplicitParamDecl::anchor() {}

ImplicitParamDecl *ImplicitParamDecl::Create(ASTContext &C, DeclContext *DC,
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/DeclBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ bool Decl::isTemplateParameterPack() const {
}

bool Decl::isParameterPack() const {
if (const auto *Var = dyn_cast<VarDecl>(this))
if (const auto *Var = dyn_cast<ValueDecl>(this))
return Var->isParameterPack();

return isTemplateParameterPack();
Expand Down
16 changes: 12 additions & 4 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3462,19 +3462,21 @@ VarDecl *ValueDecl::getPotentiallyDecomposedVarDecl() {
if (auto *Var = llvm::dyn_cast<VarDecl>(this))
return Var;
if (auto *BD = llvm::dyn_cast<BindingDecl>(this))
return llvm::dyn_cast<VarDecl>(BD->getDecomposedDecl());
return llvm::dyn_cast_if_present<VarDecl>(BD->getDecomposedDecl());
return nullptr;
}

void BindingDecl::anchor() {}

BindingDecl *BindingDecl::Create(ASTContext &C, DeclContext *DC,
SourceLocation IdLoc, IdentifierInfo *Id) {
return new (C, DC) BindingDecl(DC, IdLoc, Id);
SourceLocation IdLoc, IdentifierInfo *Id,
QualType T) {
return new (C, DC) BindingDecl(DC, IdLoc, Id, T);
}

BindingDecl *BindingDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
return new (C, ID) BindingDecl(nullptr, SourceLocation(), nullptr);
return new (C, ID)
BindingDecl(nullptr, SourceLocation(), nullptr, QualType());
}

VarDecl *BindingDecl::getHoldingVar() const {
Expand All @@ -3490,6 +3492,12 @@ VarDecl *BindingDecl::getHoldingVar() const {
return VD;
}

llvm::ArrayRef<Expr *> BindingDecl::getBindingPackExprs() const {
assert(Binding && "expecting a pack expr");
auto *RP = cast<ResolvedUnexpandedPackExpr>(Binding);
return RP->getExprs();
}

void DecompositionDecl::anchor() {}

DecompositionDecl *DecompositionDecl::Create(ASTContext &C, DeclContext *DC,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3659,6 +3659,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
case PackIndexingExprClass:
case HLSLOutArgExprClass:
case OpenACCAsteriskSizeExprClass:
case ResolvedUnexpandedPackExprClass:
// These never have a side-effect.
return false;

Expand Down
Loading

0 comments on commit abc8812

Please sign in to comment.