https://github.com/efriedma-quic created https://github.com/llvm/llvm-project/pull/196427
DesignatedInitUpdateExpr exists to handle some obscure edge cases in C, where the usual InitListExpr canonicalization can't be performed. Previously, we didn't need constant evaluation for this, but C23 constexpr means we need to evaluate this before codegen. Implementation is straightforward: just need to evaluate the two subexpressions, in order, and skip any NoInitExprs. Fixes #193373. >From db10d77410fa485c9b47b7178cd9780961fb3c28 Mon Sep 17 00:00:00 2001 From: Eli Friedman <[email protected]> Date: Thu, 7 May 2026 14:44:28 -0700 Subject: [PATCH] [clang] Implement constexpr DesignatedInitUpdateExpr. DesignatedInitUpdateExpr exists to handle some obscure edge cases in C, where the usual InitListExpr canonicalization can't be performed. Previously, we didn't need constant evaluation for this, but C23 constexpr means we need to evaluate this before codegen. Implementation is straightforward: just need to evaluate the two subexperssions, in order, and skip any NoInitExprs. Fixes #193373. --- clang/lib/AST/ByteCode/Compiler.cpp | 21 ++++++++++++++++++++- clang/lib/AST/ByteCode/Compiler.h | 1 + clang/lib/AST/ExprConstant.cpp | 18 ++++++++++++++++++ clang/test/Sema/constexpr.c | 14 ++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 59510612d9617..d747dff081ef4 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -2193,6 +2193,13 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, R->getField(InitIndex)->isUnnamedBitField()) ++InitIndex; + // If this is a child of a DesignatedInitUpdateExpr, skip elements which + // aren't supposed to be modified. + if (isa<NoInitExpr>(Init)) { + ++InitIndex; + continue; + } + if (OptPrimType T = classify(Init)) { const Record::Field *FieldToInit = R->getField(InitIndex); if (!initPrimitiveField(FieldToInit, Init, *T)) @@ -2256,6 +2263,10 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, }; if (!EmbedS->doForEachDataElement(Eval, ElementIndex)) return false; + } else if (isa<NoInitExpr>(Init)) { + // If this is a child of a DesignatedInitUpdateExpr, skip elements which + // aren't supposed to be modified. + ++ElementIndex; } else { if (!this->visitArrayElemInit(ElementIndex, Init, InitT)) return false; @@ -2265,7 +2276,7 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, // Expand the filler expression. // FIXME: This should go away. - if (ArrayFiller) { + if (ArrayFiller && !isa<NoInitExpr>(ArrayFiller)) { for (; ElementIndex != NumElems; ++ElementIndex) { if (!this->visitArrayElemInit(ElementIndex, ArrayFiller, InitT)) return false; @@ -7633,6 +7644,14 @@ bool Compiler<Emitter>::VisitDeclRefExpr(const DeclRefExpr *E) { return this->visitDeclRef(D, E); } +template <class Emitter> +bool Compiler<Emitter>::VisitDesignatedInitUpdateExpr(const DesignatedInitUpdateExpr *E) { + assert(E->getType()->isRecordType()); + if (!this->visitInitializer(E->getBase())) + return false; + return this->visitInitializer(E->getUpdater()); +} + template <class Emitter> bool Compiler<Emitter>::emitCleanup() { for (VariableScope<Emitter> *C = VarScope; C; C = C->getParent()) { if (!C->destroyLocals()) diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 4a70db89dba74..45cda575b000e 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -232,6 +232,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool VisitCXXTypeidExpr(const CXXTypeidExpr *E); bool VisitObjCDictionaryLiteral(const ObjCDictionaryLiteral *E); bool VisitObjCArrayLiteral(const ObjCArrayLiteral *E); + bool VisitDesignatedInitUpdateExpr(const DesignatedInitUpdateExpr *E); // Statements. bool visitCompoundStmt(const CompoundStmt *S); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 4f45fa728c605..b26afb4449484 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -11065,6 +11065,7 @@ namespace { bool VisitCXXParenListInitExpr(const CXXParenListInitExpr *E); bool VisitCXXParenListOrInitListExpr(const Expr *ExprToVisit, ArrayRef<Expr *> Args); + bool VisitDesignatedInitUpdateExpr(const DesignatedInitUpdateExpr *E); }; } @@ -11325,6 +11326,11 @@ bool RecordExprEvaluator::VisitCXXParenListOrInitListExpr( ImplicitValueInitExpr VIE(HaveInit ? Info.Ctx.IntTy : Field->getType()); const Expr *Init = HaveInit ? Args[ElementNo++] : &VIE; + // If this is a child of a DesignatedInitUpdateExpr, skip elements which + // aren't supposed to be modified. + if (isa<NoInitExpr>(Init)) + continue; + if (Field->getType()->isIncompleteArrayType()) { if (auto *CAT = Info.Ctx.getAsConstantArrayType(Init->getType())) { if (!CAT->isZeroSize()) { @@ -11522,6 +11528,13 @@ bool RecordExprEvaluator::VisitLambdaExpr(const LambdaExpr *E) { return Success; } +bool RecordExprEvaluator::VisitDesignatedInitUpdateExpr( + const DesignatedInitUpdateExpr *E) { + if (!Visit(E->getBase())) + return false; + return Visit(E->getUpdater()); +} + static bool EvaluateRecord(const Expr *E, const LValue &This, APValue &Result, EvalInfo &Info) { assert(!E->isValueDependent()); @@ -15074,6 +15087,11 @@ bool ArrayExprEvaluator::VisitCXXParenListOrInitListExpr( if (Init->isValueDependent()) return EvaluateDependentExpr(Init, Info); + // If this is a child of a DesignatedInitUpdateExpr, skip elements which + // aren't supposed to be modified. + if (isa<NoInitExpr>(Init)) + return true; + if (!EvaluateInPlace(Result.getArrayInitializedElt(ArrayIndex), Info, Subobject, Init) || !HandleLValueArrayAdjustment(Info, Init, Subobject, diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c index 0b8de906e1838..79f9f877af12f 100644 --- a/clang/test/Sema/constexpr.c +++ b/clang/test/Sema/constexpr.c @@ -431,3 +431,17 @@ int gh173605(int x) { static int justincase = justincase; // expected-error {{initializer element is not a compile-time constant}} return x; } + + +struct designated_init_A { int a; int b; int c : 2; double d; int e[5]; }; +struct designated_init_B { struct designated_init_A a; int y; }; +constexpr struct designated_init_A designated_init_a = {1, 2}; +constexpr struct designated_init_B designated_init_b = + {designated_init_a, .a.a = 3, .a.c = 4, .a.d = 5.0, .a.e[1] = 6, .y = 7}; // expected-warning 4 {{initializer partially overrides prior initialization of this subobject}} // expected-note 4 {{previous initialization}} +static_assert(designated_init_b.a.a == 3); +static_assert(designated_init_b.a.b == 2); +static_assert(designated_init_b.a.c == 0); +static_assert(designated_init_b.a.d == 5.0); // expected-warning {{folding it to a constant is a GNU extension}} +static_assert(designated_init_b.a.e[0] == 0); // expected-warning {{folding it to a constant is a GNU extension}} +static_assert(designated_init_b.a.e[1] == 6); // expected-warning {{folding it to a constant is a GNU extension}} +static_assert(designated_init_b.y == 7); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
