https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/130228
>From 295b8173b6913d9014c5786eb4af0112384afa65 Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Fri, 7 Mar 2025 11:38:11 +0800 Subject: [PATCH 1/4] [Clang] Implement P0963R3 "Structured binding declaration as a condition" --- clang/include/clang/AST/DeclCXX.h | 23 +++++++++++------- .../clang/Basic/DiagnosticSemaKinds.td | 8 +++++-- clang/lib/AST/ASTImporter.cpp | 7 +++--- clang/lib/AST/DeclCXX.cpp | 21 +++++++++------- clang/lib/AST/ExprConstant.cpp | 24 +++++++++++++++---- clang/lib/CodeGen/CGDecl.cpp | 13 ++++++---- clang/lib/CodeGen/CGStmt.cpp | 17 ++++++++++--- clang/lib/CodeGen/CodeGenFunction.cpp | 7 +++++- clang/lib/CodeGen/CodeGenFunction.h | 5 +++- clang/lib/Sema/SemaDecl.cpp | 6 ++--- clang/lib/Sema/SemaDeclCXX.cpp | 17 +++++++------ .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 8 ++++--- clang/lib/Serialization/ASTReaderDecl.cpp | 9 ++++--- clang/lib/Serialization/ASTWriterDecl.cpp | 1 + clang/test/AST/ByteCode/if.cpp | 2 +- clang/test/Parser/cxx1z-decomposition.cpp | 12 +++++----- clang/test/Parser/decomposed-condition.cpp | 24 +++++++++---------- clang/test/SemaCXX/decomposed-condition.cpp | 4 ++-- 18 files changed, 136 insertions(+), 72 deletions(-) diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index dbd02ef7f8011..414a5aea32566 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -4224,15 +4224,18 @@ class DecompositionDecl final : public VarDecl, private llvm::TrailingObjects<DecompositionDecl, BindingDecl *> { /// The number of BindingDecl*s following this object. - unsigned NumBindings; + unsigned NumBindings : 31; + + LLVM_PREFERRED_TYPE(bool) + unsigned IsDecisionVariable : 1; DecompositionDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, SourceLocation LSquareLoc, QualType T, TypeSourceInfo *TInfo, StorageClass SC, - ArrayRef<BindingDecl *> Bindings) + ArrayRef<BindingDecl *> Bindings, bool IsDecisionVariable) : VarDecl(Decomposition, C, DC, StartLoc, LSquareLoc, nullptr, T, TInfo, SC), - NumBindings(Bindings.size()) { + NumBindings(Bindings.size()), IsDecisionVariable(IsDecisionVariable) { std::uninitialized_copy(Bindings.begin(), Bindings.end(), getTrailingObjects<BindingDecl *>()); for (auto *B : Bindings) { @@ -4253,12 +4256,14 @@ class DecompositionDecl final static DecompositionDecl *Create(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, - SourceLocation LSquareLoc, - QualType T, TypeSourceInfo *TInfo, - StorageClass S, - ArrayRef<BindingDecl *> Bindings); + SourceLocation LSquareLoc, QualType T, + TypeSourceInfo *TInfo, StorageClass S, + ArrayRef<BindingDecl *> Bindings, + bool IsDecisionVariable); + static DecompositionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID, - unsigned NumBindings); + unsigned NumBindings, + bool IsDecisionVariable); // Provide the range of bindings which may have a nested pack. llvm::ArrayRef<BindingDecl *> bindings() const { @@ -4285,6 +4290,8 @@ class DecompositionDecl final std::move(Bindings)); } + bool isDecisionVariable() const { return IsDecisionVariable; } + void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override; static bool classof(const Decl *D) { return classofKind(D->getKind()); } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 0b121c04cd3c0..fbc91f2eb8dd6 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -529,8 +529,12 @@ def warn_cxx14_compat_decomp_decl : Warning< def ext_decomp_decl : ExtWarn< "decomposition declarations are a C++17 extension">, InGroup<CXX17>; def ext_decomp_decl_cond : ExtWarn< - "ISO C++17 does not permit structured binding declaration in a condition">, - InGroup<DiagGroup<"binding-in-condition">>; + "structured binding declaration in a condition is a C++2c extenstion">, + InGroup<CXX26>; +def warn_cxx26_decomp_decl_cond : Warning< + "structured binding declaration in a condition is incompatible with " + "C++ standards before C++2c">, + InGroup<CXXPre26Compat>, DefaultIgnore; def err_decomp_decl_spec : Error< "decomposition declaration cannot be declared " "%plural{1:'%1'|:with '%1' specifiers}0">; diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 82180486f3c7c..95ca085c5b746 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -4614,9 +4614,10 @@ ExpectedDecl ASTNodeImporter::VisitVarDecl(VarDecl *D) { ImportArrayChecked(FromDecomp->bindings(), Bindings.begin())) return std::move(Err); DecompositionDecl *ToDecomp; - if (GetImportedOrCreateDecl( - ToDecomp, FromDecomp, Importer.getToContext(), DC, ToInnerLocStart, - Loc, ToType, ToTypeSourceInfo, D->getStorageClass(), Bindings)) + if (GetImportedOrCreateDecl(ToDecomp, FromDecomp, Importer.getToContext(), + DC, ToInnerLocStart, Loc, ToType, + ToTypeSourceInfo, D->getStorageClass(), + Bindings, FromDecomp->isDecisionVariable())) return ToDecomp; ToVar = ToDecomp; } else { diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 7eff776882629..fdad1150c8b1a 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -3520,21 +3520,24 @@ DecompositionDecl *DecompositionDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation LSquareLoc, QualType T, TypeSourceInfo *TInfo, StorageClass SC, - ArrayRef<BindingDecl *> Bindings) { + ArrayRef<BindingDecl *> Bindings, + bool IsDecisionVariable) { size_t Extra = additionalSizeToAlloc<BindingDecl *>(Bindings.size()); - return new (C, DC, Extra) - DecompositionDecl(C, DC, StartLoc, LSquareLoc, T, TInfo, SC, Bindings); + return new (C, DC, Extra) DecompositionDecl( + C, DC, StartLoc, LSquareLoc, T, TInfo, SC, Bindings, IsDecisionVariable); } -DecompositionDecl *DecompositionDecl::CreateDeserialized(ASTContext &C, - GlobalDeclID ID, - unsigned NumBindings) { +DecompositionDecl * +DecompositionDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID, + unsigned NumBindings, + bool IsDecisionVariable) { size_t Extra = additionalSizeToAlloc<BindingDecl *>(NumBindings); - auto *Result = new (C, ID, Extra) - DecompositionDecl(C, nullptr, SourceLocation(), SourceLocation(), - QualType(), nullptr, StorageClass(), {}); + auto *Result = new (C, ID, Extra) DecompositionDecl( + C, nullptr, SourceLocation(), SourceLocation(), QualType(), nullptr, + StorageClass(), {}, IsDecisionVariable); // Set up and clean out the bindings array. Result->NumBindings = NumBindings; + Result->IsDecisionVariable = IsDecisionVariable; auto *Trail = Result->getTrailingObjects<BindingDecl *>(); for (unsigned I = 0; I != NumBindings; ++I) new (Trail + I) BindingDecl*(nullptr); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 7244120d1be51..564a47a839336 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5218,16 +5218,28 @@ static bool EvaluateVarDecl(EvalInfo &Info, const VarDecl *VD) { return true; } +static bool EvaluateDecompositionDeclInit(EvalInfo &Info, + const DecompositionDecl *DD); + static bool EvaluateDecl(EvalInfo &Info, const Decl *D) { bool OK = true; if (const VarDecl *VD = dyn_cast<VarDecl>(D)) OK &= EvaluateVarDecl(Info, VD); - if (const DecompositionDecl *DD = dyn_cast<DecompositionDecl>(D)) - for (auto *BD : DD->flat_bindings()) - if (auto *VD = BD->getHoldingVar()) - OK &= EvaluateDecl(Info, VD); + if (const DecompositionDecl *DD = dyn_cast<DecompositionDecl>(D); + DD && !DD->isDecisionVariable()) + OK &= EvaluateDecompositionDeclInit(Info, DD); + + return OK; +} + +static bool EvaluateDecompositionDeclInit(EvalInfo &Info, + const DecompositionDecl *DD) { + bool OK = true; + for (auto *BD : DD->flat_bindings()) + if (auto *VD = BD->getHoldingVar()) + OK &= EvaluateDecl(Info, VD); return OK; } @@ -5251,6 +5263,10 @@ static bool EvaluateCond(EvalInfo &Info, const VarDecl *CondDecl, return false; if (!EvaluateAsBooleanCondition(Cond, Result, Info)) return false; + if (auto *DD = dyn_cast_if_present<DecompositionDecl>(CondDecl); + DD && DD->isDecisionVariable() && + !EvaluateDecompositionDeclInit(Info, DD)) + return false; return Scope.destroy(); } diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 9cd5885aaae51..44d787a85f691 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -164,10 +164,9 @@ void CodeGenFunction::EmitDecl(const Decl &D) { assert(VD.isLocalVarDecl() && "Should not see file-scope variables inside a function!"); EmitVarDecl(VD); - if (auto *DD = dyn_cast<DecompositionDecl>(&VD)) - for (auto *B : DD->flat_bindings()) - if (auto *HD = B->getHoldingVar()) - EmitVarDecl(*HD); + if (auto *DD = dyn_cast<DecompositionDecl>(&VD); + DD && !DD->isDecisionVariable()) + EmitDecompositionVarInit(*DD); return; } @@ -2057,6 +2056,12 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) { /*IsAutoInit=*/false); } +void CodeGenFunction::EmitDecompositionVarInit(const DecompositionDecl &DD) { + for (auto *B : DD.flat_bindings()) + if (auto *HD = B->getHoldingVar()) + EmitVarDecl(*HD); +} + /// Emit an expression as an initializer for an object (variable, field, etc.) /// at the given location. The expression is not necessarily the normal /// initializer for the object, and the address is not necessarily diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index abe799af32c6e..1d71f22dc2d47 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -913,6 +913,11 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) { if (CondConstant) incrementProfileCounter(&S); if (Executed) { + if (auto *DD = dyn_cast_if_present<DecompositionDecl>( + S.getConditionVariable())) { + assert(DD->isDecisionVariable()); + EmitDecompositionVarInit(*DD); + } RunCleanupsScope ExecutedScope(*this); EmitStmt(Executed); } @@ -952,10 +957,16 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) { // there is a 'return' within the body, but this is particularly beneficial // when one if-stmt is nested within another if-stmt so that all of the MC/DC // updates are kept linear and consistent. - if (!CGM.getCodeGenOpts().MCDCCoverage) - EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH); - else { + if (!CGM.getCodeGenOpts().MCDCCoverage) { + EmitBranchOnBoolExpr(S.getCond(), ThenBlock, ElseBlock, ThenCount, LH, + nullptr, S.getConditionVariable()); + } else { llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond()); + if (auto *DD = + dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable())) { + assert(DD->isDecisionVariable()); + EmitDecompositionVarInit(*DD); + } Builder.CreateCondBr(BoolCondVal, ThenBlock, ElseBlock); } diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 08165e0b28406..c17a7d308bca8 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -1846,7 +1846,8 @@ void CodeGenFunction::EmitBranchToCounterBlock( /// LHS and RHS nodes. void CodeGenFunction::EmitBranchOnBoolExpr( const Expr *Cond, llvm::BasicBlock *TrueBlock, llvm::BasicBlock *FalseBlock, - uint64_t TrueCount, Stmt::Likelihood LH, const Expr *ConditionalOp) { + uint64_t TrueCount, Stmt::Likelihood LH, const Expr *ConditionalOp, + const VarDecl *ConditionalDecl) { Cond = Cond->IgnoreParens(); if (const BinaryOperator *CondBOp = dyn_cast<BinaryOperator>(Cond)) { @@ -2047,6 +2048,10 @@ void CodeGenFunction::EmitBranchOnBoolExpr( CondV = EvaluateExprAsBool(Cond); } + if (auto *DD = dyn_cast_if_present<DecompositionDecl>(ConditionalDecl); + DD && DD->isDecisionVariable()) + EmitDecompositionVarInit(*DD); + // If not at the top of the logical operator nest, update MCDC temp with the // boolean result of the evaluated condition. if (!MCDCLogOpStack.empty()) { diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 018fc66b72a1e..3586594429da8 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3474,6 +3474,8 @@ class CodeGenFunction : public CodeGenTypeCache { void emitAutoVarTypeCleanup(const AutoVarEmission &emission, QualType::DestructionKind dtorKind); + void EmitDecompositionVarInit(const DecompositionDecl &var); + /// Emits the alloca and debug information for the size expressions for each /// dimension of an array. It registers the association of its (1-dimensional) /// QualTypes and size expression's debug node, so that CGDebugInfo can @@ -5180,7 +5182,8 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitBranchOnBoolExpr(const Expr *Cond, llvm::BasicBlock *TrueBlock, llvm::BasicBlock *FalseBlock, uint64_t TrueCount, Stmt::Likelihood LH = Stmt::LH_None, - const Expr *ConditionalOp = nullptr); + const Expr *ConditionalOp = nullptr, + const VarDecl *ConditionalDecl = nullptr); /// Given an assignment `*LHS = RHS`, emit a test that checks if \p RHS is /// nonnull, if \p LHS is marked _Nonnull. diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 36de02d91e151..28215332f5ca3 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -7768,9 +7768,9 @@ NamedDecl *Sema::ActOnVariableDeclarator( NewVD = cast<VarDecl>(Res.get()); AddToScope = false; } else if (D.isDecompositionDeclarator()) { - NewVD = DecompositionDecl::Create(Context, DC, D.getBeginLoc(), - D.getIdentifierLoc(), R, TInfo, SC, - Bindings); + NewVD = DecompositionDecl::Create( + Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), R, TInfo, SC, + Bindings, D.getContext() == DeclaratorContext::Condition); } else NewVD = VarDecl::Create(Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), II, R, TInfo, SC); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index a3a028b9485d6..0e6017b75cfd8 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -740,13 +740,16 @@ Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D, return nullptr; } - Diag(Decomp.getLSquareLoc(), - !getLangOpts().CPlusPlus17 - ? diag::ext_decomp_decl - : D.getContext() == DeclaratorContext::Condition - ? diag::ext_decomp_decl_cond - : diag::warn_cxx14_compat_decomp_decl) - << Decomp.getSourceRange(); + unsigned DiagID; + if (!getLangOpts().CPlusPlus17) + DiagID = diag::ext_decomp_decl; + else if (D.getContext() == DeclaratorContext::Condition) { + DiagID = getLangOpts().CPlusPlus26 ? diag::warn_cxx26_decomp_decl_cond + : diag::ext_decomp_decl_cond; + } else + DiagID = diag::warn_cxx14_compat_decomp_decl; + + Diag(Decomp.getLSquareLoc(), DiagID) << Decomp.getSourceRange(); // The semantic context is always just the current context. DeclContext *const DC = CurContext; diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index dd894df851488..c87ce2ae15ae9 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -14,6 +14,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/ASTMutationListener.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DependentDiagnostic.h" #include "clang/AST/Expr.h" @@ -1473,9 +1474,10 @@ Decl *TemplateDeclInstantiator::VisitVarDecl(VarDecl *D, // Build the instantiated declaration. VarDecl *Var; if (Bindings) - Var = DecompositionDecl::Create(SemaRef.Context, DC, D->getInnerLocStart(), - D->getLocation(), DI->getType(), DI, - D->getStorageClass(), *Bindings); + Var = DecompositionDecl::Create( + SemaRef.Context, DC, D->getInnerLocStart(), D->getLocation(), + DI->getType(), DI, D->getStorageClass(), *Bindings, + cast<DecompositionDecl>(D)->isDecisionVariable()); else Var = VarDecl::Create(SemaRef.Context, DC, D->getInnerLocStart(), D->getLocation(), D->getIdentifier(), DI->getType(), diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp index 2a580c44b94e5..113ed96ea2021 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -4115,9 +4115,12 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) { case DECL_PARM_VAR: D = ParmVarDecl::CreateDeserialized(Context, ID); break; - case DECL_DECOMPOSITION: - D = DecompositionDecl::CreateDeserialized(Context, ID, Record.readInt()); - break; + case DECL_DECOMPOSITION: { + unsigned NumBindings = Record.readInt(); + bool IsDecisionVariable = Record.readBool(); + D = DecompositionDecl::CreateDeserialized(Context, ID, NumBindings, + IsDecisionVariable); + } break; case DECL_BINDING: D = BindingDecl::CreateDeserialized(Context, ID); break; diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp index a1810003f5425..c11866319f2b0 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -1327,6 +1327,7 @@ void ASTDeclWriter::VisitParmVarDecl(ParmVarDecl *D) { void ASTDeclWriter::VisitDecompositionDecl(DecompositionDecl *D) { // Record the number of bindings first to simplify deserialization. Record.push_back(D->bindings().size()); + Record.push_back(D->isDecisionVariable()); VisitVarDecl(D); for (auto *B : D->bindings()) diff --git a/clang/test/AST/ByteCode/if.cpp b/clang/test/AST/ByteCode/if.cpp index c48b2b8d378c8..909e08a22a283 100644 --- a/clang/test/AST/ByteCode/if.cpp +++ b/clang/test/AST/ByteCode/if.cpp @@ -54,7 +54,7 @@ namespace InitDecl { constexpr char g(char const (&x)[2]) { return 'x'; if (auto [a, b] = x) // both-error {{an array type is not allowed here}} \ - // both-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + // both-warning {{structured binding declaration in a condition is a C++2c extenstion}} ; } static_assert(g("x") == 'x'); diff --git a/clang/test/Parser/cxx1z-decomposition.cpp b/clang/test/Parser/cxx1z-decomposition.cpp index acf3f99069185..96e25e8d9593b 100644 --- a/clang/test/Parser/cxx1z-decomposition.cpp +++ b/clang/test/Parser/cxx1z-decomposition.cpp @@ -37,12 +37,12 @@ namespace OtherDecl { void g() { // A condition is allowed as a Clang extension. // See commentary in test/Parser/decomposed-condition.cpp - for (; auto [a, b, c] = S(); ) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} - if (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} - if (int n; auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} - switch (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('S' invalid)}} - switch (int n; auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('S' invalid)}} - while (auto [a, b, c] = S()) {} // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} + for (; auto [a, b, c] = S(); ) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} + if (auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} + if (int n; auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} + switch (auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{statement requires expression of integer type ('S' invalid)}} + switch (int n; auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{statement requires expression of integer type ('S' invalid)}} + while (auto [a, b, c] = S()) {} // pre2c-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'S' is not contextually convertible to 'bool'}} // An exception-declaration is not a simple-declaration. try {} diff --git a/clang/test/Parser/decomposed-condition.cpp b/clang/test/Parser/decomposed-condition.cpp index 4c635c4735db8..37a24f8f95093 100644 --- a/clang/test/Parser/decomposed-condition.cpp +++ b/clang/test/Parser/decomposed-condition.cpp @@ -33,33 +33,33 @@ Na g(); namespace CondInIf { int h() { - if (auto [ok, d] = f()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + if (auto [ok, d] = f()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} ; - if (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}} + if (auto [ok, d] = g()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}} ; - if (auto [value] = Get()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + if (auto [value] = Get()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} return value; } } // namespace CondInIf namespace CondInWhile { int h() { - while (auto [ok, d] = f()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + while (auto [ok, d] = f()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} ; - while (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}} + while (auto [ok, d] = g()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}} ; - while (auto [value] = Get()) // expected-warning{{ISO C++17 does not permit structured binding declaration in a condition}} + while (auto [value] = Get()) // expected-warning{{structured binding declaration in a condition is a C++2c extenstion}} return value; } } // namespace CondInWhile namespace CondInFor { int h() { - for (; auto [ok, d] = f();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + for (; auto [ok, d] = f();) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} ; - for (; auto [ok, d] = g();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}} + for (; auto [ok, d] = g();) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{value of type 'Na' is not contextually convertible to 'bool'}} ; - for (; auto [value] = Get();) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + for (; auto [value] = Get();) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} return value; } } // namespace CondInFor @@ -74,11 +74,11 @@ struct IntegerLike { namespace CondInSwitch { int h(IntegerLike x) { - switch (auto [ok, d] = x) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + switch (auto [ok, d] = x) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} ; - switch (auto [ok, d] = g()) // expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} expected-error {{statement requires expression of integer type ('Na' invalid)}} + switch (auto [ok, d] = g()) // expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} expected-error {{statement requires expression of integer type ('Na' invalid)}} ; - switch (auto [value] = Get()) {// expected-warning {{ISO C++17 does not permit structured binding declaration in a condition}} + switch (auto [value] = Get()) {// expected-warning {{structured binding declaration in a condition is a C++2c extenstion}} // expected-warning@-1{{switch condition has boolean value}} case 1: return value; diff --git a/clang/test/SemaCXX/decomposed-condition.cpp b/clang/test/SemaCXX/decomposed-condition.cpp index e55bbee3134ca..32ab3a34f567b 100644 --- a/clang/test/SemaCXX/decomposed-condition.cpp +++ b/clang/test/SemaCXX/decomposed-condition.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -std=c++1z -Wno-binding-in-condition -verify %s -// RUN: %clang_cc1 -std=c++1z -Wno-binding-in-condition -verify %s -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s +// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s -fexperimental-new-constant-interpreter struct X { bool flag; >From 5cd91f8adf02d633e7503b144a2d2b264d50dcaf Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Sun, 9 Mar 2025 23:29:02 +0800 Subject: [PATCH 2/4] Add a simple CG test, release note, comments for decision variable [dcl.struct.bind] --- clang/docs/ReleaseNotes.rst | 2 + clang/include/clang/AST/DeclCXX.h | 4 ++ clang/lib/CodeGen/CGStmt.cpp | 21 ++++++ clang/lib/Sema/SemaDecl.cpp | 3 +- clang/test/CodeGen/p0963r3.cpp | 108 ++++++++++++++++++++++++++++++ clang/www/cxx_status.html | 2 +- 6 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 clang/test/CodeGen/p0963r3.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 37ea963bf337d..bea43ebcedb2f 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -76,6 +76,8 @@ C++2c Feature Support - Implemented `P1061R10 Structured Bindings can introduce a Pack <https://wg21.link/P1061R10>`_. +- Implemented `P0963R3 Structured binding declaration as a condition <https://wg21.link/P0963R3>`_. + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 414a5aea32566..d82d9900945a1 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -4290,6 +4290,10 @@ class DecompositionDecl final std::move(Bindings)); } + /// The decision variable of a condition that is a structured binding + /// declaration is specified in [dcl.struct.bind]p4: + /// If a structured binding declaration appears as a condition, the decision + /// variable of the condition is e. bool isDecisionVariable() const { return IsDecisionVariable; } void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override; diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 1d71f22dc2d47..0a49eadbf33d1 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -1110,6 +1110,11 @@ void CodeGenFunction::EmitWhileStmt(const WhileStmt &S, // execution of the loop body. llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond()); + if (auto *DD = + dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable()); + DD && DD->isDecisionVariable()) + EmitDecompositionVarInit(*DD); + // while(1) is common, avoid extra exit blocks. Be sure // to correctly handle break/continue though. llvm::ConstantInt *C = dyn_cast<llvm::ConstantInt>(BoolCondVal); @@ -1343,6 +1348,12 @@ void CodeGenFunction::EmitForStmt(const ForStmt &S, // C99 6.8.5p2/p4: The first substatement is executed if the expression // compares unequal to 0. The condition must be a scalar type. llvm::Value *BoolCondVal = EvaluateExprAsBool(S.getCond()); + + if (auto *DD = + dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable()); + DD && DD->isDecisionVariable()) + EmitDecompositionVarInit(*DD); + llvm::MDNode *Weights = createProfileWeightsForLoop(S.getCond(), getProfileCount(S.getBody())); if (!Weights && CGM.getCodeGenOpts().OptimizationLevel) @@ -2240,6 +2251,11 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) { if (S.getConditionVariable()) EmitDecl(*S.getConditionVariable()); + if (auto *DD = + dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable()); + DD && DD->isDecisionVariable()) + EmitDecompositionVarInit(*DD); + // At this point, we are no longer "within" a switch instance, so // we can temporarily enforce this to ensure that any embedded case // statements are not emitted. @@ -2271,6 +2287,11 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) { EmitDecl(*S.getConditionVariable()); llvm::Value *CondV = EmitScalarExpr(S.getCond()); + if (auto *DD = + dyn_cast_if_present<DecompositionDecl>(S.getConditionVariable()); + DD && DD->isDecisionVariable()) + EmitDecompositionVarInit(*DD); + // Create basic block to hold stuff that comes after switch // statement. We also need to create a default block now so that // explicit case ranges tests can have a place to jump to on diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 28215332f5ca3..675a928551fcb 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -7770,7 +7770,8 @@ NamedDecl *Sema::ActOnVariableDeclarator( } else if (D.isDecompositionDeclarator()) { NewVD = DecompositionDecl::Create( Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), R, TInfo, SC, - Bindings, D.getContext() == DeclaratorContext::Condition); + Bindings, /*IsDecisionVariable=*/D.getContext() == + DeclaratorContext::Condition); } else NewVD = VarDecl::Create(Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), II, R, TInfo, SC); diff --git a/clang/test/CodeGen/p0963r3.cpp b/clang/test/CodeGen/p0963r3.cpp new file mode 100644 index 0000000000000..64f97feb28ae1 --- /dev/null +++ b/clang/test/CodeGen/p0963r3.cpp @@ -0,0 +1,108 @@ +// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm %s -o - | FileCheck %s +// expected-no-diagnostics + +namespace std { + +template <typename T> struct tuple_size; + +template <int, typename> struct tuple_element; + +} // namespace std + +namespace Case1 { + +struct S { + int a, b; + bool called_operator_bool = false; + + operator bool() { + called_operator_bool = true; + return a != b; + } + + template <int I> int get() { + if (!called_operator_bool) + return a + b; + return I == 0 ? a : b; + } +}; + +} // namespace Case1 + +template <> struct std::tuple_size<Case1::S> { + static const int value = 2; +}; + +template <int I> struct std::tuple_element<I, Case1::S> { + using type = int; +}; + +namespace Case1 { + +void foo() { + if (S s(1, 2); auto [a, b] = s) { + __builtin_assume(a == 1); + __builtin_assume(b == 2); + } +// CHECK: %[[call:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: br i1 %[[call]], label {{.*}}, label {{.*}} + + if (auto [a, b] = S(1, 2)) { + __builtin_assume(a == 1); + __builtin_assume(b == 2); + } +// CHECK: %[[call2:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: br i1 %[[call2]], label {{.*}}, label {{.*}} + + if (S s(3, 4); auto& [a, b] = s) { + __builtin_assume(a == 3); + __builtin_assume(b == 4); + } +// CHECK: %[[call3:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: br i1 %[[call3]], label {{.*}}, label {{.*}} + + while (auto [i, j] = S(5, 6)) + break; + +// CHECK: while.cond{{.*}}: +// CHECK: %[[call4:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: br i1 %[[call4]], label {{.*}}, label {{.*}} + + S s(7, 8); + while (auto& [i, j] = s) + break; + +// CHECK: while.cond{{.*}}: +// CHECK: %[[call5:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: br i1 %[[call5]], label {{.*}}, label {{.*}} + + for (int k = 0; auto [i, j] = S(24, 42); ++k) + break; + +// CHECK: for.cond{{.*}}: +// CHECK: %[[call6:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: br i1 %[[call6]], label {{.*}}, label {{.*}} + + for (S s(114, 514); auto& [i, j] = s; ++i) + break; + +// CHECK: for.cond{{.*}}: +// CHECK: %[[call7:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv +// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: br i1 %[[call7]], label {{.*}}, label {{.*}} +} + +} // namespace Case1 diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 8570592101cc5..3f652c4291ddd 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -223,7 +223,7 @@ <h2 id="cxx26">C++2c implementation status</h2> <tr> <td>Structured binding declaration as a condition</td> <td><a href="https://wg21.link/P0963R3">P0963R3</a></td> - <td class="none" align="center">No</td> + <td class="unreleased" align="center">Clang 21</td> </tr> <!--Poland, Fall 2024--> <tr> >From ffe3922efc996e017b9425f22fe7953f352e12b6 Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Sun, 9 Mar 2025 23:38:06 +0800 Subject: [PATCH 3/4] Test -Wpre-c++26-compat --- clang/test/SemaCXX/decomposed-condition.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/clang/test/SemaCXX/decomposed-condition.cpp b/clang/test/SemaCXX/decomposed-condition.cpp index 32ab3a34f567b..14a23af3f5658 100644 --- a/clang/test/SemaCXX/decomposed-condition.cpp +++ b/clang/test/SemaCXX/decomposed-condition.cpp @@ -1,5 +1,7 @@ -// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s -// RUN: %clang_cc1 -std=c++1z -Wno-c++26-extensions -verify %s -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++17 -Wno-c++26-extensions -verify %s +// RUN: %clang_cc1 -std=c++17 -Wno-c++26-extensions -verify %s -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++2c -Wpre-c++26-compat -verify=cxx26,expected %s +// RUN: %clang_cc1 -std=c++2c -Wpre-c++26-compat -verify=cxx26,expected %s -fexperimental-new-constant-interpreter struct X { bool flag; @@ -14,7 +16,7 @@ struct X { namespace CondInIf { constexpr int f(X x) { - if (auto [ok, d] = x) + if (auto [ok, d] = x) // cxx26-warning {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}} return d + int(ok); else return d * int(ok); @@ -26,12 +28,13 @@ static_assert(f({true, 2}) == 3); static_assert(f({false, 2}) == 0); constexpr char g(char const (&x)[2]) { - if (auto &[a, b] = x) + if (auto &[a, b] = x) // cxx26-warning {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}} return a; else return b; - if (auto [a, b] = x) // expected-error {{an array type is not allowed here}} + if (auto [a, b] = x) // expected-error {{an array type is not allowed here}} \ + // cxx26-warning {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}} ; } @@ -41,6 +44,7 @@ static_assert(g("x") == 'x'); namespace CondInSwitch { constexpr int f(int n) { switch (X s = {true, n}; auto [ok, d] = s) { + // cxx26-warning@-1 {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}} s = {}; case 0: return int(ok); @@ -65,6 +69,7 @@ namespace CondInWhile { constexpr int f(int n) { int m = 1; while (auto [ok, d] = X{n > 1, n}) { + // cxx26-warning@-1 {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}} m *= d; --n; } @@ -81,6 +86,7 @@ namespace CondInFor { constexpr int f(int n) { int a = 1, b = 1; for (X x = {true, n}; auto &[ok, d] = x; --d) { + // cxx26-warning@-1 {{structured binding declaration in a condition is incompatible with C++ standards before C++2c}} if (d < 2) ok = false; else { >From 2ae72b4710e036a3b19ac06b0feee96d13a68092 Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Mon, 10 Mar 2025 13:56:32 +0800 Subject: [PATCH 4/4] Add more tests; handle switch (..) case .. in constant evaluator --- clang/lib/AST/ExprConstant.cpp | 6 ++ clang/test/CodeGen/p0963r3.cpp | 145 ++++++++++++++++++++++++++++----- 2 files changed, 130 insertions(+), 21 deletions(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 564a47a839336..898f8a2fce769 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5351,6 +5351,12 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info, if (!EvaluateInteger(SS->getCond(), Value, Info)) return ESR_Failed; + if (auto *DD = + dyn_cast_if_present<DecompositionDecl>(SS->getConditionVariable()); + DD && DD->isDecisionVariable() && + !EvaluateDecompositionDeclInit(Info, DD)) + return ESR_Failed; + if (!CondScope.destroy()) return ESR_Failed; } diff --git a/clang/test/CodeGen/p0963r3.cpp b/clang/test/CodeGen/p0963r3.cpp index 64f97feb28ae1..b356e78adfa83 100644 --- a/clang/test/CodeGen/p0963r3.cpp +++ b/clang/test/CodeGen/p0963r3.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -std=c++2c -verify -emit-llvm -triple=x86_64-pc-linux-gnu %s -o - | FileCheck %s // expected-no-diagnostics namespace std { @@ -13,16 +13,26 @@ namespace Case1 { struct S { int a, b; - bool called_operator_bool = false; + bool flag = false; - operator bool() { - called_operator_bool = true; + constexpr explicit operator bool() { + flag = true; return a != b; } - template <int I> int get() { - if (!called_operator_bool) - return a + b; + constexpr operator int() { + flag = true; + return a * b; + } + + constexpr bool operator==(S rhs) const { + return a == rhs.a && b == rhs.b; + } + + template <int I> + constexpr int& get() { + if (!flag) + return a = a + b; return I == 0 ? a : b; } }; @@ -45,8 +55,8 @@ void foo() { __builtin_assume(b == 2); } // CHECK: %[[call:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv // CHECK: br i1 %[[call]], label {{.*}}, label {{.*}} if (auto [a, b] = S(1, 2)) { @@ -54,8 +64,8 @@ void foo() { __builtin_assume(b == 2); } // CHECK: %[[call2:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv // CHECK: br i1 %[[call2]], label {{.*}}, label {{.*}} if (S s(3, 4); auto& [a, b] = s) { @@ -63,8 +73,8 @@ void foo() { __builtin_assume(b == 4); } // CHECK: %[[call3:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv // CHECK: br i1 %[[call3]], label {{.*}}, label {{.*}} while (auto [i, j] = S(5, 6)) @@ -72,8 +82,8 @@ void foo() { // CHECK: while.cond{{.*}}: // CHECK: %[[call4:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv // CHECK: br i1 %[[call4]], label {{.*}}, label {{.*}} S s(7, 8); @@ -82,8 +92,8 @@ void foo() { // CHECK: while.cond{{.*}}: // CHECK: %[[call5:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv // CHECK: br i1 %[[call5]], label {{.*}}, label {{.*}} for (int k = 0; auto [i, j] = S(24, 42); ++k) @@ -91,8 +101,8 @@ void foo() { // CHECK: for.cond{{.*}}: // CHECK: %[[call6:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv // CHECK: br i1 %[[call6]], label {{.*}}, label {{.*}} for (S s(114, 514); auto& [i, j] = s; ++i) @@ -100,9 +110,102 @@ void foo() { // CHECK: for.cond{{.*}}: // CHECK: %[[call7:.+]] = call {{.*}} i1 @_ZN5Case11ScvbEv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi0EEEiv -// CHECK: %{{.*}} = call {{.*}} i32 @_ZN5Case11S3getILi1EEEiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv // CHECK: br i1 %[[call7]], label {{.*}}, label {{.*}} + + switch (S s(10, 11); auto& [i, j] = s) { + case 10 * 11: + __builtin_assume(i == 10); + __builtin_assume(j == 11); + break; + default: + break; + } + +// CHECK: %[[call8:.+]] = call {{.*}} i32 @_ZN5Case11ScviEv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi0EEERiv +// CHECK: %{{.*}} = call {{.*}} ptr @_ZN5Case11S3getILi1EEERiv +// CHECK: switch i32 %[[call8]], label {{.*}} + } +constexpr int bar(auto) { + constexpr auto value = [] { + if (S s(1, 2); auto [i, j] = s) + return S(i, j); + return S(0, 0); + }(); + static_assert(value == S(1, 2)); + + // FIXME: The diagnostic message adds a trailing comma "static assertion failed due to requirement 'value == Case1::S((0, 1, ))'" + // static_assert(value == S(0, 1)); + + constexpr auto value2 = [] { + if (auto [a, b] = S(1, 2)) + return S(a, b); + return S(0, 0); + }(); + static_assert(value2 == S(1, 2)); + + constexpr auto value3 = [] { + if (auto&& [a, b] = S(3, 4)) + return S(a, b); + return S(0, 0); + }(); + static_assert(value3 == S(3, 4)); + + constexpr auto value4 = [] { + S s(7, 8); + int cnt = 0; + while (auto& [i, j] = s) { + s.flag = false; + ++i, ++j; + if (++cnt == 10) + break; + } + return s; + }(); + static_assert(value4 == S(17, 18)); + + constexpr auto value5 = [] { + S s(3, 4); + for (int cnt = 0; auto& [x, y] = s; s.flag = false, ++cnt) { + if (cnt == 3) + break; + ++x, ++y; + } + return s; + }(); + static_assert(value5 == S(6, 7)); + + constexpr auto value6 = [] { + switch (auto [x, y] = S(3, 4)) { + case 3 * 4: + return S(x, y); + default: + return S(y, x); + } + }(); + static_assert(value6 == S(3, 4)); + + return 42; +} + +constexpr int value = bar(1); + +#if 0 + +// FIXME: This causes clang to ICE, though this is not a regression. +constexpr int ice(auto) { + if constexpr (S s(1, 2); auto [i, j] = s) { + static_assert(i == 1); + } + return 42; +} + +constexpr int value2 = ice(1); + +#endif + } // namespace Case1 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits