[clang] [Clang] Implement P0963R3 "Structured binding declaration as a condition" (PR #130228)
Younan Zhang via cfe-commits
cfe-commits at lists.llvm.org
Sun Mar 9 22:57:24 PDT 2025
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 <zyn7109 at 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 at -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 <zyn7109 at 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 <zyn7109 at 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 at -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 at -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 at -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 <zyn7109 at 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
More information about the cfe-commits
mailing list