https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/222332
>From 0c01007843e53535272631ef344142c5136c960e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Tue, 8 Sep 2026 07:57:56 +0200 Subject: [PATCH] asdf --- clang/lib/AST/ByteCode/Compiler.cpp | 42 +++++++++++++- clang/lib/AST/ByteCode/Interp.cpp | 74 +++++++++++++++++++++--- clang/lib/AST/ByteCode/Interp.h | 29 +++------- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 45 +++++++++----- clang/lib/AST/ByteCode/InterpHelpers.h | 7 ++- clang/lib/AST/ByteCode/Opcodes.td | 3 + clang/lib/AST/ByteCode/Pointer.h | 1 + clang/lib/AST/ExprConstShared.h | 4 ++ clang/lib/AST/ExprConstant.cpp | 6 +- clang/test/AST/ByteCode/cxx14.cpp | 20 +++++++ clang/test/AST/ByteCode/cxx26.cpp | 56 ++++++++++++++++++ clang/test/AST/ByteCode/cxx2a.cpp | 17 ++++++ 12 files changed, 253 insertions(+), 51 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 473494c694a98..f63b25a94293c 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -6142,6 +6142,15 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E, return true; } +static bool isTrivialMemoryOperation(const CXXMethodDecl *MD) { + if (!MD || !MD->isDefaulted()) + return false; + if (!MD->isCopyAssignmentOperator() && !MD->isMoveAssignmentOperator()) + return false; + return MD->getParent()->isUnion() || + (MD->isTrivial() && isReadByLvalueToRvalueConversion(MD->getParent())); +} + template <class Emitter> bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) { if (E->containsErrors()) @@ -6174,6 +6183,35 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) { } LocalScope<Emitter> CallScope(this, ScopeKind::Call); + ArrayRef<const Expr *> Args(E->getArgs(), E->getNumArgs()); + bool ActivateLHS = false; + + // Emit a special op for trivial copy/move operators. + if (isTrivialMemoryOperation(dyn_cast_if_present<CXXMethodDecl>(FuncDecl))) { + const Function *Func = getFunction(FuncDecl); + if (!Func) + return false; + + if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(E); + OCE && OCE->isAssignmentOp()) { + const CXXRecordDecl *LHSRecord = Args[0]->getType()->getAsCXXRecordDecl(); + ActivateLHS = LHSRecord && LHSRecord->hasTrivialDefaultConstructor(); + } + if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(E)) + if (!this->visit(MCE->getImplicitObjectArgument())) + return false; + + if (!this->visitCallArgs(Args, FuncDecl, /*ActivateLHS=*/ActivateLHS, + isa<CXXOperatorCallExpr>(E))) + return false; + + if (!this->emitTrivialCopy(ActivateLHS, Func, E)) + return false; + + if (!DiscardResult) + return CallScope.destroyLocals(); + return this->emitPopPtr(E) && CallScope.destroyLocals(); + } QualType ReturnType = E->getCallReturnType(Ctx.getASTContext()); OptPrimType T = classify(ReturnType); @@ -6202,11 +6240,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) { } } - ArrayRef<const Expr *> Args(E->getArgs(), E->getNumArgs()); const Expr *ReversedArgs[2]; - bool IsAssignmentOperatorCall = false; - bool ActivateLHS = false; if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(E); OCE && OCE->isAssignmentOp()) { // Just like with regular assignments, we need to special-case assignment @@ -6220,6 +6255,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) { ReversedArgs[1] = Args[0]; Args = ReversedArgs; } + // Calling a static operator will still // pass the instance, but we don't need it. // Discard it here. diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 160cc745c89b6..1996c68cf03dc 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1044,7 +1044,7 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { } bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - bool WillBeActivated) { + AccessKinds AK, bool WillBeActivated) { if (Ptr.isZero()) return false; @@ -1055,25 +1055,25 @@ bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr, return false; if (!Ptr.block()->isAccessible()) { - if (!CheckLive(S, OpPC, Ptr, AK_Assign)) + if (!CheckLive(S, OpPC, Ptr, AK)) return false; if (!CheckExtern(S, OpPC, Ptr)) return false; - return CheckDummy(S, OpPC, Ptr.block(), AK_Assign); + return CheckDummy(S, OpPC, Ptr.block(), AK); } - if (!WillBeActivated && !CheckLifetime(S, OpPC, Ptr, AK_Assign)) + if (!WillBeActivated && !CheckLifetime(S, OpPC, Ptr, AK)) return false; - if (!CheckRange(S, OpPC, Ptr, AK_Assign)) + if (!CheckRange(S, OpPC, Ptr, AK)) return false; - if (!CheckActive(S, OpPC, Ptr, AK_Assign, WillBeActivated)) + if (!CheckActive(S, OpPC, Ptr, AK, WillBeActivated)) return false; if (!CheckGlobal(S, OpPC, Ptr)) return false; if (!CheckConst(S, OpPC, Ptr)) return false; - if (!CheckVolatile(S, OpPC, Ptr, AK_Assign)) + if (!CheckVolatile(S, OpPC, Ptr, AK)) return false; - if (!CheckMutable(S, OpPC, Ptr, AK_Assign)) + if (!CheckMutable(S, OpPC, Ptr, AK)) return false; if (isConstexprUnknown(Ptr)) return false; @@ -3566,6 +3566,64 @@ bool virtBaseHelper(InterpState &S, const CXXRecordDecl *Decl, return true; } +bool Memcpy(InterpState &S, CodePtr OpPC) { + const Pointer &Src = S.Stk.pop<Pointer>(); + Pointer &Dest = S.Stk.peek<Pointer>(); + + if (Src.isDummy() || !Src.isBlockPointer()) + return false; + if (!Dest.isBlockPointer()) + return false; + + if ((Src.getRecord() && Src.getRecord()->isUnion() && + !Src.getRecord()->isAnonymousUnion()) || + Src.inUnion()) { + if (!CheckLoad(S, OpPC, Src)) + return false; + } + + return DoMemcpy(S, OpPC, Src, Dest); +} + +bool TrivialCopy(InterpState &S, CodePtr OpPC, bool Activate, + const Function *Func) { + const Pointer &Src = S.Stk.pop<Pointer>(); + Pointer &Dest = S.Stk.peek<Pointer>(); + + if (Src.isDummy() || Src.isConstexprUnknown() || !Src.isBlockPointer()) + return false; + if (!Dest.isBlockPointer() || Dest.isDummy() || Dest.isConstexprUnknown()) + return false; + + if (!CheckStore(S, OpPC, Dest, AK_MemberCall, + /*WillBeActivated=*/Activate)) + return false; + + if (S.checkingPotentialConstantExpression()) + return false; + + // NOTE: This is a fake function frame that doesn't do anything except show up + // in the "in call to" diagnostics. Since the copies we replace with this + // opcode are always defaulted/trivial, they don't add much there either + // though. Once we default to the bytecode interpreter, we shoud consider just + // removing it. + auto Memory = std::make_unique<char[]>(InterpFrame::allocSize(Func)); + auto *NewFrame = + new (Memory.get()) InterpFrame(S, Func, S.PC, /*VarArgSize=*/0); + InterpFrame *FrameBefore = S.Current; + S.Current = NewFrame; + + if (!CheckLoad(S, OpPC, Src, AK_Read)) { + S.Current = FrameBefore; + return false; + } + + bool Result = DoMemcpy(S, OpPC, Src, Dest, Activate, /*Diagnose=*/true); + S.Current = FrameBefore; + + return Result; +} + // FIXME: Would be nice to generate this instead of hardcoding it here. [[maybe_unused]] static constexpr bool OpReturns(Opcode Op) { return Op == OP_RetVoid || Op == OP_RetValue || Op == OP_NoRet || diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index a20a3f91dd267..0cf9e1a9a948d 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -82,9 +82,6 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const Pointer &Ptr); bool diagnoseUninitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); -bool diagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern, - const Block *B, Lifetime LT = Lifetime::Started, - AccessKinds AK = AK_Read); /// Checks a direct load of a primitive value from a global or local variable. bool CheckGlobalLoad(InterpState &S, CodePtr OpPC, const Block *B); @@ -92,7 +89,7 @@ bool CheckLocalLoad(InterpState &S, CodePtr OpPC, const Block *B); /// Checks if a value can be stored in a block. bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr, - bool WillBeActivated = false); + AccessKinds AK = AK_Assign, bool WillBeActivated = false); /// Checks if a value can be initialized. bool CheckInit(InterpState &S, CodePtr OpPC, const Pointer &Ptr); @@ -2346,7 +2343,7 @@ bool StoreActivate(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.peek<Pointer>(); - if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true)) + if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true)) return false; if (Ptr.canBeInitialized()) { Ptr.initialize(); @@ -2361,7 +2358,7 @@ bool StoreActivatePop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true)) + if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true)) return false; if (Ptr.canBeInitialized()) { Ptr.initialize(); @@ -2407,7 +2404,7 @@ bool StoreBitFieldActivate(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.peek<Pointer>(); - if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true)) + if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true)) return false; if (Ptr.canBeInitialized()) { Ptr.initialize(); @@ -2425,7 +2422,7 @@ bool StoreBitFieldActivatePop(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (!CheckStore(S, OpPC, Ptr, /*WillBeActivated=*/true)) + if (!CheckStore(S, OpPC, Ptr, AK_Assign, /*WillBeActivated=*/true)) return false; if (Ptr.canBeInitialized()) { Ptr.initialize(); @@ -2536,18 +2533,6 @@ bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t Idx) { return true; } -inline bool Memcpy(InterpState &S, CodePtr OpPC) { - const Pointer &Src = S.Stk.pop<Pointer>(); - Pointer &Dest = S.Stk.peek<Pointer>(); - - if (!Src.getRecord() || !Src.getRecord()->isAnonymousUnion()) { - if (!CheckLoad(S, OpPC, Src)) - return false; - } - - return DoMemcpy(S, OpPC, Src, Dest); -} - inline bool ToMemberPtr(InterpState &S) { const auto &Member = S.Stk.pop<MemberPointer>(); const auto &Base = S.Stk.pop<Pointer>(); @@ -2566,6 +2551,10 @@ inline bool CastMemberPtrPtr(InterpState &S, CodePtr OpPC) { return Invalid(S, OpPC); } +bool Memcpy(InterpState &S, CodePtr OpPC); +bool TrivialCopy(InterpState &S, CodePtr OpPC, bool Activate, + const Function *Func); + //===----------------------------------------------------------------------===// // AddOffset, SubOffset //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 42ee33541b41b..b8e9abed839c6 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -6877,26 +6877,34 @@ static void zeroAll(PtrView Dest) { } static bool copyComposite(InterpState &S, CodePtr OpPC, PtrView Src, - PtrView Dest, bool Activate); + PtrView Dest, bool Activate, bool Diagnose); static bool copyRecord(InterpState &S, CodePtr OpPC, PtrView Src, PtrView Dest, - bool Activate = false) { + bool Activate = false, bool Diagnose = true) { [[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc(); const Descriptor *DestDesc = Dest.getFieldDesc(); auto copyField = [&](const Record::Field &F, bool Activate) -> bool { PtrView DestField = Dest.atField(F.Offset); + PtrView SrcField = Src.atField(F.Offset); + if (OptPrimType FT = F.T) { - TYPE_SWITCH(*FT, { - DestField.deref<T>() = Src.atField(F.Offset).deref<T>(); - if (Src.atField(F.Offset).isInitialized()) - DestField.initialize(); - if (Activate) - DestField.activate(); - }); + if (!SrcField.isInitialized()) { + if (Diagnose) + return diagnoseUninitialized(S, OpPC, false, SrcField.block(), + SrcField.getLifetime(), AK_Read); + // Just skip. + return true; + } + + TYPE_SWITCH(*FT, DestField.deref<T>() = SrcField.deref<T>();); + if (DestField.canBeInitialized()) + DestField.initialize(); + if (Activate) + DestField.activate(); return true; } - // Composite field. - return copyComposite(S, OpPC, Src.atField(F.Offset), DestField, Activate); + + return copyComposite(S, OpPC, SrcField, DestField, Activate, Diagnose); }; assert(SrcDesc->isRecord()); @@ -6925,16 +6933,20 @@ static bool copyRecord(InterpState &S, CodePtr OpPC, PtrView Src, PtrView Dest, for (const Record::Base &B : R->bases()) { PtrView DestBase = Dest.atField(B.Offset); - if (!copyRecord(S, OpPC, Src.atField(B.Offset), DestBase, Activate)) + if (!copyRecord(S, OpPC, Src.atField(B.Offset), DestBase, Activate, + Diagnose)) return false; } Dest.initialize(); + if (Activate) + Dest.activate(); return true; } static bool copyComposite(InterpState &S, CodePtr OpPC, PtrView Src, - PtrView Dest, bool Activate = false) { + PtrView Dest, bool Activate = false, + bool Diagnose = false) { assert(Src.isLive() && Dest.isLive()); [[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc(); @@ -6982,18 +6994,19 @@ static bool copyComposite(InterpState &S, CodePtr OpPC, PtrView Src, if (DestDesc->isRecord()) { if (!SrcDesc->isRecord()) return false; - return copyRecord(S, OpPC, Src, Dest, Activate); + return copyRecord(S, OpPC, Src, Dest, Activate, Diagnose); } return Invalid(S, OpPC); } -bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest) { +bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest, + bool Activate, bool Diagnose) { if (!Src.isBlockPointer() || Src.getFieldDesc()->isPrimitive()) return false; if (!Dest.isBlockPointer() || Dest.getFieldDesc()->isPrimitive()) return false; - return copyComposite(S, OpPC, Src.view(), Dest.view()); + return copyComposite(S, OpPC, Src.view(), Dest.view(), Activate, Diagnose); } } // namespace interp diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h index f183efb5b19d1..68c5266733e9f 100644 --- a/clang/lib/AST/ByteCode/InterpHelpers.h +++ b/clang/lib/AST/ByteCode/InterpHelpers.h @@ -85,12 +85,17 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, const Expr *NewExpr); /// Copy the contents of Src into Dest. -bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest); +bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest, + bool Activate = true, bool Diagnose = false); UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx, unsigned Kind, Pointer &Ptr, const Expr *E, bool IsDynamic = false); +bool diagnoseUninitialized(InterpState &S, CodePtr OpPC, bool Extern, + const Block *B, Lifetime LT = Lifetime::Started, + AccessKinds AK = AK_Read); + template <typename T> bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) { const Expr *E = S.Current->getExpr(OpPC); diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 1831dc161f0a5..e603394cbab46 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -958,6 +958,9 @@ def CheckNonNullArg : Opcode { } def Memcpy : Opcode; +def TrivialCopy : Opcode { + let Args = [ArgBool, ArgFunction]; +} def ToMemberPtr : SuccessOpcode; def CastMemberPtrPtr : Opcode; diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h index 5b43df9db49c4..9e919e75a8d8a 100644 --- a/clang/lib/AST/ByteCode/Pointer.h +++ b/clang/lib/AST/ByteCode/Pointer.h @@ -53,6 +53,7 @@ struct PtrView { bool inUnion() const { return getInlineDesc()->InUnion; }; bool inArray() const { return getFieldDesc()->IsArray; } bool inPrimitiveArray() const { return getFieldDesc()->isPrimitiveArray(); } + bool canBeInitialized() const { return Pointee && Base > 0; } const Block *block() const { return Pointee; } unsigned getEvalID() { return Pointee->getEvalID(); } diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index 0eee03dce57ab..ca3481270605e 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -30,6 +30,7 @@ class ASTContext; class CharUnits; class Expr; class CallExpr; +class CXXRecordDecl; } // namespace clang using namespace clang; /// Values returned by __builtin_classify_type, chosen to match the values @@ -112,4 +113,7 @@ EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B, const Expr *ignorePointerCastsAndParens(const Expr *E); +bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD); +bool isReadByLvalueToRvalueConversion(QualType T); + #endif diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 1286c77ad1c69..9242491832841 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3697,12 +3697,12 @@ static void expandVector(APValue &Vec, unsigned NumElements) { /// is trivial. Note that this is never true for a union type with fields /// (because the copy always "reads" the active member) and always true for /// a non-class type. -static bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD); -static bool isReadByLvalueToRvalueConversion(QualType T) { +bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD); +bool isReadByLvalueToRvalueConversion(QualType T) { CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl(); return !RD || isReadByLvalueToRvalueConversion(RD); } -static bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD) { +bool isReadByLvalueToRvalueConversion(const CXXRecordDecl *RD) { // FIXME: A trivial copy of a union copies the object representation, even if // the union is empty. if (RD->isUnion()) diff --git a/clang/test/AST/ByteCode/cxx14.cpp b/clang/test/AST/ByteCode/cxx14.cpp index 97d2c0ac4f711..7dcf385bab084 100644 --- a/clang/test/AST/ByteCode/cxx14.cpp +++ b/clang/test/AST/ByteCode/cxx14.cpp @@ -47,3 +47,23 @@ namespace InitListModify { constexpr Aggregate aggr2 = {}; static_assert(aggr2.x == 1 && aggr2.y == 1, ""); } + +namespace UnionTrivialCopy { + struct A { + constexpr A() {} + struct B { + union U { + constexpr U() : y(4) {} + int x; + int y; + } u; + } b; + }; + constexpr int testA() { + A a, b; + a.b.u.y = 5; + b = a; + return b.b.u.y; + } + static_assert(testA() == 5, ""); +} diff --git a/clang/test/AST/ByteCode/cxx26.cpp b/clang/test/AST/ByteCode/cxx26.cpp index 9dc6270b9d551..68da8bae0e946 100644 --- a/clang/test/AST/ByteCode/cxx26.cpp +++ b/clang/test/AST/ByteCode/cxx26.cpp @@ -103,3 +103,59 @@ namespace UnknownSizeArrayString { // ref-note {{initializer of 'foo' is unknown}} \ // both-error {{static assertion failed}} } + +namespace TrivialAssignment { + struct array { + int _M_elems[4]; + }; + + struct item { + array words; + unsigned priority; + }; + + constexpr void __insertion_sort(item * __first, + item *__last) { + *__first = *__first; + } + + consteval unsigned sorted_first() { + item items[]{{{}, 1}}; + __insertion_sort(items, items + 1); + + return items[0].priority; + } + static_assert(sorted_first()); + + constexpr void __insertion_sort2(item * __first, + item *__last) { + *__first = *__last; // both-note {{read of dereferenced one-past-the-end pointer}} \ + // both-note {{in call to}} + } + + consteval unsigned sorted_first2() { + item items[]{{{}, 1}}; + __insertion_sort2(items, items + 1); // both-note {{in call}} + + return items[0].priority; + } + static_assert(sorted_first2()); // both-error {{not an integral constant expression}} \ + // both-note {{in call}} + + + + constexpr void __insertion_sort3(item * __first, + item *__last) { + *__last = *__first; // both-note {{member call on dereferenced one-past-the-end pointer}} + } + + consteval unsigned sorted_first3() { + item items[]{{{}, 1}}; + __insertion_sort3(items, items + 1); // both-note {{in call to '__insertion_sort3(&items[0], &items[1])'}} + + return items[0].priority; + } + static_assert(sorted_first3()); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} + +} diff --git a/clang/test/AST/ByteCode/cxx2a.cpp b/clang/test/AST/ByteCode/cxx2a.cpp index e2cf74aa4ec57..9fc434708b8bf 100644 --- a/clang/test/AST/ByteCode/cxx2a.cpp +++ b/clang/test/AST/ByteCode/cxx2a.cpp @@ -314,3 +314,20 @@ namespace UninitializedAndLifetime { static_assert((use_after_destroy(), true)); // both-error {{not an integral constant expression}} \ // both-note {{in call}} } + +namespace UnionNoActivate { + struct A { int n; }; + struct B { A a; }; + constexpr A a = (A() = B().a); + + union C { + int n; + A a; + }; + constexpr bool g() { // both-error {{never produces a constant expression}} + C c = {.n = 1}; + c.a.operator=(B{2}.a); // both-note 2{{member call on member 'a' of union with active member 'n' is not allowed in a constant expression}} + return c.a.n == 2; + } + static_assert(g()); // both-error {{constant expression}} both-note {{in call}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
