[clang] Fix discarded non-ODR-uses in lambdas - #215603
Conversation
|
@llvm/pr-subscribers-clang Author: Tiago (tiagomacarios) ChangesFixes #127086. Summary
Commit structure
Each commit message links the relevant wording in https://eel.is/c++draft/. Validation
Patch is 26.48 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/215603.diff 8 Files Affected:
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index 4f4d38c961140..9c0b55dca2b9c 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -935,6 +935,11 @@ class LambdaScopeInfo final :
/// if the enclosing full-expression is instantiation dependent).
llvm::SmallPtrSet<Expr *, 8> NonODRUsedCapturingExprs;
+ /// Contains the subset of NonODRUsedCapturingExprs whose use is discarded.
+ /// These expressions remain non-odr-uses even if their full-expression is
+ /// instantiation-dependent.
+ llvm::SmallPtrSet<Expr *, 4> DiscardedValueCapturingExprs;
+
/// A map of explicit capture indices to their introducer source ranges.
llvm::DenseMap<unsigned, SourceRange> ExplicitCaptureRanges;
@@ -948,6 +953,7 @@ class LambdaScopeInfo final :
llvm::SmallVector<ShadowedOuterDecl, 4> ShadowingDecls;
SourceLocation PotentialThisCaptureLocation;
+ unsigned NumPotentialThisCaptures = 0;
/// Variables that are potentially ODR-used in CUDA/HIP.
llvm::SmallPtrSet<VarDecl *, 4> CUDAPotentialODRUsedVars;
@@ -997,11 +1003,10 @@ class LambdaScopeInfo final :
void addPotentialThisCapture(SourceLocation Loc) {
PotentialThisCaptureLocation = Loc;
+ ++NumPotentialThisCaptures;
}
- bool hasPotentialThisCapture() const {
- return PotentialThisCaptureLocation.isValid();
- }
+ bool hasPotentialThisCapture() const { return NumPotentialThisCaptures != 0; }
/// Mark a variable's reference in a lambda as non-odr using.
///
@@ -1042,11 +1047,14 @@ class LambdaScopeInfo final :
/// seemingly harmless change elsewhere in Sema could cause us to start or stop
/// building such a node. So we need a rule that anyone can implement and get
/// exactly the same result".
- void markVariableExprAsNonODRUsed(Expr *CapturingVarExpr) {
+ void markVariableExprAsNonODRUsed(Expr *CapturingVarExpr,
+ NonOdrUseReason NOUR) {
assert(isa<DeclRefExpr>(CapturingVarExpr) ||
isa<MemberExpr>(CapturingVarExpr) ||
isa<FunctionParmPackExpr>(CapturingVarExpr));
NonODRUsedCapturingExprs.insert(CapturingVarExpr);
+ if (NOUR == NOUR_Discarded)
+ DiscardedValueCapturingExprs.insert(CapturingVarExpr);
}
bool isVariableExprMarkedAsNonODRUsed(Expr *CapturingVarExpr) const {
assert(isa<DeclRefExpr>(CapturingVarExpr) ||
@@ -1054,24 +1062,43 @@ class LambdaScopeInfo final :
isa<FunctionParmPackExpr>(CapturingVarExpr));
return NonODRUsedCapturingExprs.count(CapturingVarExpr);
}
+ bool isVariableExprMarkedAsDiscarded(Expr *CapturingVarExpr) const {
+ assert(isa<DeclRefExpr>(CapturingVarExpr) ||
+ isa<MemberExpr>(CapturingVarExpr) ||
+ isa<FunctionParmPackExpr>(CapturingVarExpr));
+ return DiscardedValueCapturingExprs.count(CapturingVarExpr);
+ }
void removePotentialCapture(Expr *E) {
llvm::erase(PotentiallyCapturingExprs, E);
}
void clearPotentialCaptures() {
PotentiallyCapturingExprs.clear();
PotentialThisCaptureLocation = SourceLocation();
+ NumPotentialThisCaptures = 0;
+ }
+ void clearPotentialCaptures(unsigned NumVariableCaptures,
+ unsigned NumThisCaptures,
+ SourceLocation ThisCaptureLocation) {
+ PotentiallyCapturingExprs.resize(NumVariableCaptures);
+ NumPotentialThisCaptures = NumThisCaptures;
+ PotentialThisCaptureLocation = ThisCaptureLocation;
}
unsigned getNumPotentialVariableCaptures() const {
return PotentiallyCapturingExprs.size();
}
+ unsigned getNumPotentialThisCaptures() const {
+ return NumPotentialThisCaptures;
+ }
- bool hasPotentialCaptures() const {
- return getNumPotentialVariableCaptures() ||
- PotentialThisCaptureLocation.isValid();
+ bool hasPotentialCaptures(unsigned NumVariableCaptures = 0,
+ unsigned NumThisCaptures = 0) const {
+ return getNumPotentialVariableCaptures() != NumVariableCaptures ||
+ getNumPotentialThisCaptures() != NumThisCaptures;
}
- void visitPotentialCaptures(
- llvm::function_ref<void(ValueDecl *, Expr *)> Callback) const;
+ void
+ visitPotentialCaptures(llvm::function_ref<void(ValueDecl *, Expr *)> Callback,
+ unsigned FirstCapture = 0) const;
bool lambdaCaptureShouldBeConst() const;
};
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d68ed7c75b86f..74857e9ccb7ee 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -6770,6 +6770,11 @@ class Sema final : public SemaBase {
MaybeODRUseExprSet SavedMaybeODRUseExprs;
+ sema::LambdaScopeInfo *PotentialCaptureContext = nullptr;
+ unsigned NumPotentialVariableCaptures = 0;
+ unsigned NumPotentialThisCaptures = 0;
+ SourceLocation PotentialThisCaptureLocation;
+
/// The lambdas that are present within this context, if it
/// is indeed an unevaluated context.
SmallVector<LambdaExpr *, 2> Lambdas;
@@ -7069,6 +7074,7 @@ class Sema final : public SemaBase {
unsigned CapturingScopeIndex);
ExprResult CheckLValueToRValueConversionOperand(Expr *E);
+ ExprResult CheckDiscardedValueExpression(Expr *E);
void CleanupVarDeclMarking();
/// Try to capture the given variable.
diff --git a/clang/lib/Sema/ScopeInfo.cpp b/clang/lib/Sema/ScopeInfo.cpp
index d089836fa36dd..b689a0ae2cbaf 100644
--- a/clang/lib/Sema/ScopeInfo.cpp
+++ b/clang/lib/Sema/ScopeInfo.cpp
@@ -233,8 +233,9 @@ bool CapturingScopeInfo::isVLATypeCaptured(const VariableArrayType *VAT) const {
}
void LambdaScopeInfo::visitPotentialCaptures(
- llvm::function_ref<void(ValueDecl *, Expr *)> Callback) const {
- for (Expr *E : PotentiallyCapturingExprs) {
+ llvm::function_ref<void(ValueDecl *, Expr *)> Callback,
+ unsigned FirstCapture) const {
+ for (Expr *E : llvm::drop_begin(PotentiallyCapturingExprs, FirstCapture)) {
if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
Callback(cast<ValueDecl>(DRE->getFoundDecl()), E);
} else if (auto *ME = dyn_cast<MemberExpr>(E)) {
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 12681a1f0c73d..c478ffd4520e3 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17878,6 +17878,16 @@ Sema::PushExpressionEvaluationContext(
ExprEvalContexts.back().InImmediateEscalatingFunctionContext =
Prev.InImmediateEscalatingFunctionContext;
+ if (LambdaScopeInfo *LSI = getCurLambda(/*IgnoreCapturedRegions=*/true)) {
+ ExprEvalContexts.back().PotentialCaptureContext = LSI;
+ ExprEvalContexts.back().NumPotentialVariableCaptures =
+ LSI->getNumPotentialVariableCaptures();
+ ExprEvalContexts.back().NumPotentialThisCaptures =
+ LSI->getNumPotentialThisCaptures();
+ ExprEvalContexts.back().PotentialThisCaptureLocation =
+ LSI->PotentialThisCaptureLocation;
+ }
+
Cleanup.reset();
if (!MaybeODRUseExprs.empty())
std::swap(MaybeODRUseExprs, ExprEvalContexts.back().SavedMaybeODRUseExprs);
@@ -19902,9 +19912,26 @@ static ExprResult rebuildPotentialResultsAsNonOdrUsed(Sema &S, Expr *E,
// Mark that this expression does not constitute an odr-use.
auto MarkNotOdrUsed = [&] {
if (!MaybeCUDAODRUsed()) {
- S.MaybeODRUseExprs.remove(E);
- if (LambdaScopeInfo *LSI = S.getCurLambda())
- LSI->markVariableExprAsNonODRUsed(E);
+ LambdaScopeInfo *LSI = S.getCurLambda();
+ bool PreserveCaptureDefault = false;
+ if (NOUR == NOUR_Discarded && LSI &&
+ LSI->ImpCaptureStyle != CapturingScopeInfo::ImpCap_None &&
+ S.MaybeODRUseExprs.count(E)) {
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E))
+ PreserveCaptureDefault =
+ !cast<VarDecl>(DRE->getDecl())
+ ->isUsableInConstantExpressions(S.Context);
+ else if (auto *ME = dyn_cast<MemberExpr>(E))
+ PreserveCaptureDefault =
+ !cast<VarDecl>(ME->getMemberDecl())
+ ->isUsableInConstantExpressions(S.Context);
+ else
+ PreserveCaptureDefault = isa<FunctionParmPackExpr>(E);
+ }
+ if (!PreserveCaptureDefault)
+ S.MaybeODRUseExprs.remove(E);
+ if (LSI)
+ LSI->markVariableExprAsNonODRUsed(E, NOUR);
}
};
@@ -20041,6 +20068,44 @@ static ExprResult rebuildPotentialResultsAsNonOdrUsed(Sema &S, Expr *E,
CO->getCond(), LHS.get(), RHS.get());
}
+ // [Clang extension]
+ // -- If e is a GNU binary conditional expression, its false operand is a
+ // potential result. The common operand is also used as the condition,
+ // so it remains an odr-use.
+ case Expr::BinaryConditionalOperatorClass: {
+ auto *BCO = cast<BinaryConditionalOperator>(E);
+ ExprResult RHS = Rebuild(BCO->getFalseExpr());
+ if (!RHS.isUsable())
+ return RHS;
+ return new (S.Context) BinaryConditionalOperator(
+ BCO->getCommon(), BCO->getOpaqueValue(), BCO->getCond(),
+ BCO->getTrueExpr(), RHS.get(), BCO->getQuestionLoc(),
+ BCO->getColonLoc(), BCO->getType(), BCO->getValueKind(),
+ BCO->getObjectKind());
+ }
+
+ // [Clang extension]
+ // -- If e is a comma fold-expression, its rightmost operand is a
+ // potential result.
+ case Expr::CXXFoldExprClass: {
+ auto *FE = cast<CXXFoldExpr>(E);
+ if (FE->getOperator() != BO_Comma)
+ break;
+
+ Expr *LHS = FE->getLHS();
+ Expr *RHS = FE->getRHS();
+ ExprResult Sub = Rebuild(RHS ? RHS : LHS);
+ if (!Sub.isUsable())
+ return Sub;
+ if (RHS)
+ RHS = Sub.get();
+ else
+ LHS = Sub.get();
+ return S.BuildCXXFoldExpr(
+ FE->getCallee(), FE->getLParenLoc(), LHS, FE->getOperator(),
+ FE->getEllipsisLoc(), RHS, FE->getRParenLoc(), FE->getNumExpansions());
+ }
+
// [Clang extension]
// -- If e has the form __extension__ e1...
case Expr::UnaryOperatorClass: {
@@ -20098,7 +20163,7 @@ static ExprResult rebuildPotentialResultsAsNonOdrUsed(Sema &S, Expr *E,
if (LHS.isInvalid())
return ExprError();
- ExprResult RHS = Rebuild(CE->getLHS());
+ ExprResult RHS = Rebuild(CE->getRHS());
if (RHS.isInvalid())
return ExprError();
@@ -20178,6 +20243,14 @@ ExprResult Sema::CheckLValueToRValueConversionOperand(Expr *E) {
return Result.get() ? Result : E;
}
+ExprResult Sema::CheckDiscardedValueExpression(Expr *E) {
+ ExprResult Result =
+ rebuildPotentialResultsAsNonOdrUsed(*this, E, NOUR_Discarded);
+ if (Result.isInvalid())
+ return ExprError();
+ return Result.get() ? Result : E;
+}
+
ExprResult Sema::ActOnConstantExpression(ExprResult Res) {
if (!Res.isUsable())
return Res;
@@ -20214,35 +20287,43 @@ void Sema::CleanupVarDeclMarking() {
"MarkVarDeclODRUsed failed to cleanup MaybeODRUseExprs?");
}
-static void DoMarkPotentialCapture(Sema &SemaRef, SourceLocation Loc,
- ValueDecl *Var, Expr *E) {
+static LambdaScopeInfo *getLambdaForPotentialCapture(Sema &SemaRef,
+ ValueDecl *Var) {
VarDecl *VD = Var->getPotentiallyDecomposedVarDecl();
if (!VD)
- return;
+ return nullptr;
const bool RefersToEnclosingScope =
(SemaRef.CurContext != VD->getDeclContext() &&
VD->getDeclContext()->isFunctionOrMethod() && VD->hasLocalStorage());
- if (RefersToEnclosingScope) {
- LambdaScopeInfo *const LSI =
- SemaRef.getCurLambda(/*IgnoreNonLambdaCapturingScope=*/true);
- if (LSI && (!LSI->CallOperator ||
- !LSI->CallOperator->Encloses(Var->getDeclContext()))) {
- // If a variable could potentially be odr-used, defer marking it so
- // until we finish analyzing the full expression for any
- // lvalue-to-rvalue
- // or discarded value conversions that would obviate odr-use.
- // Add it to the list of potential captures that will be analyzed
- // later (ActOnFinishFullExpr) for eventual capture and odr-use marking
- // unless the variable is a reference that was initialized by a constant
- // expression (this will never need to be captured or odr-used).
- //
- // FIXME: We can simplify this a lot after implementing P0588R1.
- assert(E && "Capture variable should be used in an expression.");
- if (!Var->getType()->isReferenceType() ||
- !VD->isUsableInConstantExpressions(SemaRef.Context))
- LSI->addPotentialCapture(E->IgnoreParens());
- }
+ if (!RefersToEnclosingScope)
+ return nullptr;
+
+ LambdaScopeInfo *LSI =
+ SemaRef.getCurLambda(/*IgnoreNonLambdaCapturingScope=*/true);
+ if (LSI && (!LSI->CallOperator ||
+ !LSI->CallOperator->Encloses(Var->getDeclContext())))
+ return LSI;
+ return nullptr;
+}
+
+static void DoMarkPotentialCapture(Sema &SemaRef, SourceLocation Loc,
+ ValueDecl *Var, Expr *E) {
+ if (LambdaScopeInfo *LSI = getLambdaForPotentialCapture(SemaRef, Var)) {
+ // If a variable could potentially be odr-used, defer marking it so
+ // until we finish analyzing the full expression for any lvalue-to-rvalue
+ // or discarded value conversions that would obviate odr-use.
+ // Add it to the list of potential captures that will be analyzed
+ // later (ActOnFinishFullExpr) for eventual capture and odr-use marking
+ // unless the variable is a reference that was initialized by a constant
+ // expression (this will never need to be captured or odr-used).
+ //
+ // FIXME: We can simplify this a lot after implementing P0588R1.
+ assert(E && "Capture variable should be used in an expression.");
+ VarDecl *VD = Var->getPotentiallyDecomposedVarDecl();
+ if (!Var->getType()->isReferenceType() ||
+ !VD->isUsableInConstantExpressions(SemaRef.Context))
+ LSI->addPotentialCapture(E->IgnoreParens());
}
}
@@ -20378,12 +20459,14 @@ static void DoMarkVarDeclReferenced(
// conversion is applied
// -- x is a variable of non-reference type, and e is an element of the set
// of potential results of a discarded-value expression to which the
- // lvalue-to-rvalue conversion is not applied [FIXME]
+ // lvalue-to-rvalue conversion is not applied
//
- // We check the first part of the second bullet here, and
- // Sema::CheckLValueToRValueConversionOperand deals with the second part.
- // FIXME: To get the third bullet right, we need to delay this even for
- // variables that are not usable in constant expressions.
+ // Delay marking variables usable in constant expressions until the
+ // enclosing full-expression determines whether an lvalue-to-rvalue
+ // conversion is applied. Also delay non-reference variables that are
+ // potential lambda captures so a discarded-value expression can obviate
+ // their capture.
+ // FIXME: Implement the third bullet for non-capturing contexts too.
// If we already know this isn't an odr-use, there's nothing more to do.
if (DeclRefExpr *DRE = dyn_cast_or_null<DeclRefExpr>(E))
@@ -20407,14 +20490,19 @@ static void DoMarkVarDeclReferenced(
// behavior.
break;
- case OdrUseContext::Used:
+ case OdrUseContext::Used: {
// If we might later find that this expression isn't actually an odr-use,
// delay the marking.
- if (E && Var->isUsableInConstantExpressions(SemaRef.Context))
+ LambdaScopeInfo *PotentialCaptureLSI =
+ getLambdaForPotentialCapture(SemaRef, Var);
+ if (E && (Var->isUsableInConstantExpressions(SemaRef.Context) ||
+ (!Var->getType()->isReferenceType() && PotentialCaptureLSI &&
+ PotentialCaptureLSI->AfterParameterList)))
SemaRef.MaybeODRUseExprs.insert(E);
else
MarkVarDeclODRUsed(Var, Loc, SemaRef);
break;
+ }
case OdrUseContext::Dependent:
// If this is a dependent context, we don't need to mark variables as
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 91967a7a9ff97..2b9c1aea8df3a 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7459,6 +7459,11 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
return E;
E = Res.get();
} else {
+ ExprResult Res = CheckDiscardedValueExpression(E);
+ if (Res.isInvalid())
+ return E;
+ E = Res.get();
+
// Per C++2a [expr.ass]p5, a volatile assignment is not deprecated if
// it occurs as a discarded-value expression.
CheckUnusedVolatileAssignment(E);
@@ -7566,7 +7571,9 @@ static inline bool VariableCanNeverBeAConstantExpression(VarDecl *Var,
/// need to be captured.
static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
- Expr *const FE, LambdaScopeInfo *const CurrentLSI, Sema &S) {
+ Expr *const FE, LambdaScopeInfo *const CurrentLSI, Sema &S,
+ unsigned FirstVariableCapture, unsigned FirstThisCapture,
+ SourceLocation SavedThisCaptureLocation) {
assert(!S.isUnevaluatedContext());
assert(S.CurContext->isDependentContext());
@@ -7584,7 +7591,7 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
// All the potentially captureable variables in the current nested
// lambda (within a generic outer lambda), must be captured by an
// outer lambda that is enclosed within a non-dependent context.
- CurrentLSI->visitPotentialCaptures([&](ValueDecl *Var, Expr *VarExpr) {
+ auto CheckCapture = [&](ValueDecl *Var, Expr *VarExpr) {
// If the variable is clearly identified as non-odr-used and the full
// expression is not instantiation dependent, only then do we not
// need to check enclosing lambda's for speculative captures.
@@ -7596,13 +7603,31 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
// (void) +x + a;
// };
// }
- if (CurrentLSI->isVariableExprMarkedAsNonODRUsed(VarExpr) &&
- !IsFullExprInstantiationDependent)
- return;
+ VarDecl *UnderlyingVar = Var->getPotentiallyDecomposedVarDecl();
+ if (!UnderlyingVar)
+ return;
- VarDecl *UnderlyingVar = Var->getPotentiallyDecomposedVarDecl();
- if (!UnderlyingVar)
+ if (CurrentLSI->isVariableExprMarkedAsNonODRUsed(VarExpr) &&
+ (!IsFullExprInstantiationDependent ||
+ CurrentLSI->isVariableExprMarkedAsDiscarded(VarExpr))) {
+ // Preserve Clang's existing implicit-capture behavior for lambdas with
+ // a capture-default. The discarded-use exception suppresses a capture
+ // diagnostic for [], but does not make [=] or [&] closures empty.
+ if (!CurrentLSI->isVariableExprMarkedAsDiscarded(VarExpr))
+ return;
+ if (CurrentLSI->ImpCaptureStyle == CapturingScopeInfo::ImpCap_None)
+ return;
+ if (UnderlyingVar->isUsableInConstantExpressions(S.Context))
+ return;
+
+ QualType CaptureType, DeclRefType;
+ S.tryCaptureVariable(Var, VarExpr->getExprLoc(),
+ TryCaptureKind::Implicit,
+ /*EllipsisLoc=*/SourceLocation(),
+ /*BuildAndDiagnose=*/true, CaptureType, DeclRefType,
+ nullptr);
return;
+ }
// If we have a capture-capable lambda for the variable, go ahead and
// capture the variable in that lambda (and all its enclosing lambdas).
@@ -7633,10 +7658,11 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
DeclRefType, nullptr);
}
}
- });
+ };
+ CurrentLSI->visitPotentialCaptures(CheckCapture, FirstVariableCapture);
// Check if 'this' needs to be captured.
- if (CurrentLSI->hasPotentialThisCapture()) {
+ if (CurrentLSI->getNumPotentialThisCaptures() != FirstThisCapture) {
// If we have a capture-capable lambda for 'this', go ahead and capture
// 'this' in that lambda (and all its enclosing lambdas).
if (const UnsignedOrNone Index =
@@ -7650,7 +7676,8 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
}
// Reset all the potential captures at the end of each full-expression.
- CurrentLSI->clearPotentialCaptures();
+ CurrentLSI->clearPotentialCaptures(FirstVariableCapture, ...
[truncated]
|
GNU choose expressions are an extension modeled with potential-result rebuilding analogous to conditional expressions. The potential results of a conditional come from its second and third operands, while its first operand is evaluated as the condition: https://eel.is/c++draft/basic.def.odr#3.7 https://eel.is/c++draft/expr.cond#1 Rebuild the right operand from the right operand instead of transforming the left operand twice. Add a CodeGen regression with an explicit Itanium target triple so the selected branch and its side effects are stable across hosts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3177956-d545-4f54-92ee-af0a5a8e5046
A discarded-value expression is defined by [expr.context]/2. The potential-result rules cover id-expressions, conditional operands, and the right operand of comma expressions, and [basic.def.odr]/5.2.1 makes a non-reference variable non-ODR-used when such a potential result is discarded without an lvalue-to-rvalue conversion: https://eel.is/c++draft/expr.context#2 https://eel.is/c++draft/basic.def.odr#3.1 https://eel.is/c++draft/basic.def.odr#3.7 https://eel.is/c++draft/basic.def.odr#3.8 https://eel.is/c++draft/basic.def.odr#5.2.1 Process those potential results with NOUR_Discarded, retain that reason through dependent expressions, and delay ODR-use marking for constexpr and non-constant non-reference potential lambda captures. The lambda rules describe potentially referenced entities, reaching scope, and implicit capture under a capture-default: https://eel.is/c++draft/expr.prim.lambda.capture#7 https://eel.is/c++draft/expr.prim.lambda.capture#10 https://eel.is/c++draft/expr.prim.lambda.capture#11 https://eel.is/c++draft/expr.prim.lambda.capture#12 Accordingly, [] permits a discarded non-ODR-use, while [=] and [&] retain Clang's established implicit capture for non-constant entities. Constexpr non-ODR-used entities continue to omit capture. For [=], the tests verify the copy-capture layout; reference-capture storage is unspecified, so no [&] layout property is asserted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3177956-d545-4f54-92ee-af0a5a8e5046
A full-expression includes the conversions and initialization required by its surrounding construct, and its evaluation can include subexpressions that are not lexically part of it: https://eel.is/c++draft/intro.execution#5.2 Template-argument checking can therefore introduce nested full-expression boundaries while determining a constant template argument: https://eel.is/c++draft/temp.arg.nontype#2 Snapshot the potential variable captures and the location and count of repeated potential this captures in each ExpressionEvaluationContextRecord. When a nested context finishes, process only the suffix introduced by that context, then restore the outer prefix, location, and count. This keeps the local entities potentially referenced under the lambda rules associated with the correct full-expression: https://eel.is/c++draft/expr.prim.lambda.capture#7 Add ordinary template-call, user-defined-literal, dependent-initializer, and dependent-conditional regressions for these nested boundaries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3177956-d545-4f54-92ee-af0a5a8e5046
Fold-expression syntax and expansion rules preserve which operand is syntactically rightmost: https://eel.is/c++draft/expr.prim.fold#1 https://eel.is/c++draft/expr.prim.fold#4 Because a comma expression evaluates left then right and has the right operand's result, and that right operand supplies its potential results, comma CXXFoldExpr rebuilding follows the syntactic rightmost operand: https://eel.is/c++draft/expr.comma#1 https://eel.is/c++draft/basic.def.odr#3.8 GNU a ?: b is an extension. Model its potential results analogously to a conditional, but rebuild only b: a is both the common result operand and the expression evaluated as the condition, so its use cannot be discarded: https://eel.is/c++draft/basic.def.odr#3.7 https://eel.is/c++draft/expr.cond#1 Add lambda regressions for comma folds and GNU binary conditional operators. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c3177956-d545-4f54-92ee-af0a5a8e5046
93cea00 to
4940911
Compare
You can test this locally with the following command:git-clang-format --diff origin/main HEAD --extensions cpp,h -- clang/test/CodeGenCXX/choose-expr-discarded.cpp clang/test/SemaCXX/lambda-expressions-gh127086.cpp clang/include/clang/Sema/ScopeInfo.h clang/include/clang/Sema/Sema.h clang/lib/Sema/ScopeInfo.cpp clang/lib/Sema/SemaExpr.cpp clang/lib/Sema/SemaExprCXX.cpp clang/test/CXX/basic/basic.def.odr/p2.cpp --diff_from_common_commit
View the diff from clang-format here.diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index cec6d6b57..d6af8a7fc 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20486,9 +20486,9 @@ static ExprResult rebuildPotentialResultsAsNonOdrUsed(Sema &S, Expr *E,
RHS = Sub.get();
else
LHS = Sub.get();
- return S.BuildCXXFoldExpr(
- FE->getCallee(), FE->getLParenLoc(), LHS, FE->getOperator(),
- FE->getEllipsisLoc(), RHS, FE->getRParenLoc(), FE->getNumExpansions());
+ return S.BuildCXXFoldExpr(FE->getCallee(), FE->getLParenLoc(), LHS,
+ FE->getOperator(), FE->getEllipsisLoc(), RHS,
+ FE->getRParenLoc(), FE->getNumExpansions());
}
// [Clang extension]
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 2cb43e88e..a7bfc394e 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7787,13 +7787,13 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
// (void) +x + a;
// };
// }
- VarDecl *UnderlyingVar = Var->getPotentiallyDecomposedVarDecl();
- if (!UnderlyingVar)
- return;
+ VarDecl *UnderlyingVar = Var->getPotentiallyDecomposedVarDecl();
+ if (!UnderlyingVar)
+ return;
- if (CurrentLSI->isVariableExprMarkedAsNonODRUsed(VarExpr) &&
- (!IsFullExprInstantiationDependent ||
- CurrentLSI->isVariableExprMarkedAsDiscarded(VarExpr))) {
+ if (CurrentLSI->isVariableExprMarkedAsNonODRUsed(VarExpr) &&
+ (!IsFullExprInstantiationDependent ||
+ CurrentLSI->isVariableExprMarkedAsDiscarded(VarExpr))) {
// Preserve Clang's existing implicit-capture behavior for lambdas with
// a capture-default. The discarded-use exception suppresses a capture
// diagnostic for [], but does not make [=] or [&] closures empty.
@@ -7805,8 +7805,7 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
return;
QualType CaptureType, DeclRefType;
- S.tryCaptureVariable(Var, VarExpr->getExprLoc(),
- TryCaptureKind::Implicit,
+ S.tryCaptureVariable(Var, VarExpr->getExprLoc(), TryCaptureKind::Implicit,
/*EllipsisLoc=*/SourceLocation(),
/*BuildAndDiagnose=*/true, CaptureType, DeclRefType,
nullptr);
|
@tiagomacarios, please confirm whether you have read https://llvm.org/docs/AIToolPolicy.html. |
Fixes #127086.
Summary
[=]and[&], including dependent lambdas, while keeping discarded constexpr objects non-capturing.__builtin_choose_expr, and handle comma folds plus GNU binary conditional potential results.Commit structure
Each commit message links the relevant wording in https://eel.is/c++draft/.
Validation
clang-clbinaries (2,768 build steps).main; one patch has adjusted surrounding assertion-guard context from upstream changes, with the fix logic unchanged.