diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -695,6 +695,8 @@ } }; +class VarDecl; + /// Represent the declaration of a variable (in which case it is /// an lvalue) a function (in which case it is a function designator) or /// an enum constant. @@ -721,6 +723,13 @@ /// can be captured. bool isInitCapture() const; + // If this is a VarDecl, or a BindindDecl with an + // associated decomposed VarDecl, return that VarDecl. + VarDecl *getPotentiallyDecomposedVarDecl(); + const VarDecl *getPotentiallyDecomposedVarDecl() const { + return const_cast(this)->getPotentiallyDecomposedVarDecl(); + } + // Implement isa/cast/dyncast/etc. static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { return K >= firstValue && K <= lastValue; } diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h --- a/clang/include/clang/Sema/ScopeInfo.h +++ b/clang/include/clang/Sema/ScopeInfo.h @@ -1028,7 +1028,7 @@ } void visitPotentialCaptures( - llvm::function_ref Callback) const; + llvm::function_ref Callback) const; }; FunctionScopeInfo::WeakObjectProfileTy::WeakObjectProfileTy() diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5409,7 +5409,7 @@ void MarkDeclRefReferenced(DeclRefExpr *E, const Expr *Base = nullptr); void MarkMemberReferenced(MemberExpr *E); void MarkFunctionParmPackReferenced(FunctionParmPackExpr *E); - void MarkCaptureUsedInEnclosingContext(VarDecl *Capture, SourceLocation Loc, + void MarkCaptureUsedInEnclosingContext(ValueDecl *Capture, SourceLocation Loc, unsigned CapturingScopeIndex); ExprResult CheckLValueToRValueConversionOperand(Expr *E); diff --git a/clang/include/clang/Sema/SemaLambda.h b/clang/include/clang/Sema/SemaLambda.h --- a/clang/include/clang/Sema/SemaLambda.h +++ b/clang/include/clang/Sema/SemaLambda.h @@ -32,7 +32,7 @@ Optional getStackIndexOfNearestEnclosingCaptureCapableLambda( ArrayRef FunctionScopes, - VarDecl *VarToCapture, Sema &S); + ValueDecl *VarToCapture, Sema &S); } // clang diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -3249,6 +3249,16 @@ nullptr, SourceLocation(), false); } +VarDecl *ValueDecl::getPotentiallyDecomposedVarDecl() { + assert((isa(this) || isa(this)) && + "expected a VarDecl or a BindingDecl"); + if (auto *Var = llvm::dyn_cast(this)) + return Var; + if (auto *BD = llvm::dyn_cast(this)) + return llvm::dyn_cast(BD->getDecomposedDecl()); + return nullptr; +} + void BindingDecl::anchor() {} BindingDecl *BindingDecl::Create(ASTContext &C, DeclContext *DC, diff --git a/clang/lib/Sema/ScopeInfo.cpp b/clang/lib/Sema/ScopeInfo.cpp --- a/clang/lib/Sema/ScopeInfo.cpp +++ b/clang/lib/Sema/ScopeInfo.cpp @@ -231,14 +231,14 @@ } void LambdaScopeInfo::visitPotentialCaptures( - llvm::function_ref Callback) const { + llvm::function_ref Callback) const { for (Expr *E : PotentiallyCapturingExprs) { if (auto *DRE = dyn_cast(E)) { - Callback(cast(DRE->getFoundDecl()), E); + Callback(cast(DRE->getFoundDecl()), E); } else if (auto *ME = dyn_cast(E)) { - Callback(cast(ME->getMemberDecl()), E); + Callback(cast(ME->getMemberDecl()), E); } else if (auto *FP = dyn_cast(E)) { - for (VarDecl *VD : *FP) + for (ValueDecl *VD : *FP) Callback(VD, E); } else { llvm_unreachable("unexpected expression in potential captures list"); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -88,7 +88,11 @@ /// determine whether this declaration can be used in the default /// argument expression. bool CheckDefaultArgumentVisitor::VisitDeclRefExpr(const DeclRefExpr *DRE) { - const NamedDecl *Decl = DRE->getDecl(); + const ValueDecl *Decl = dyn_cast(DRE->getDecl()); + + if (!isa(Decl)) + return false; + if (const auto *Param = dyn_cast(Decl)) { // C++ [dcl.fct.default]p9: // [...] parameters of a function shall not be used in default @@ -102,30 +106,23 @@ return S.Diag(DRE->getBeginLoc(), diag::err_param_default_argument_references_param) << Param->getDeclName() << DefaultArg->getSourceRange(); - } else { - const VarDecl *VD = nullptr; - if (const auto *BD = dyn_cast(Decl)) - VD = dyn_cast_if_present(BD->getDecomposedDecl()); - else - VD = dyn_cast(Decl); - if (VD) { - // C++ [dcl.fct.default]p7: - // Local variables shall not be used in default argument - // expressions. - // - // C++17 [dcl.fct.default]p7 (by CWG 2082): - // A local variable shall not appear as a potentially-evaluated - // expression in a default argument. - // - // C++20 [dcl.fct.default]p7 (DR as part of P0588R1, see also CWG 2346): - // Note: A local variable cannot be odr-used (6.3) in a default - // argument. - // - if (VD->isLocalVarDecl() && !DRE->isNonOdrUse()) - return S.Diag(DRE->getBeginLoc(), - diag::err_param_default_argument_references_local) - << Decl->getDeclName() << DefaultArg->getSourceRange(); - } + } else if (auto *VD = Decl->getPotentiallyDecomposedVarDecl()) { + // C++ [dcl.fct.default]p7: + // Local variables shall not be used in default argument + // expressions. + // + // C++17 [dcl.fct.default]p7 (by CWG 2082): + // A local variable shall not appear as a potentially-evaluated + // expression in a default argument. + // + // C++20 [dcl.fct.default]p7 (DR as part of P0588R1, see also CWG 2346): + // Note: A local variable cannot be odr-used (6.3) in a default + // argument. + // + if (VD->isLocalVarDecl() && !DRE->isNonOdrUse()) + return S.Diag(DRE->getBeginLoc(), + diag::err_param_default_argument_references_local) + << Decl->getDeclName() << DefaultArg->getSourceRange(); } return false; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18482,10 +18482,13 @@ /// - else capture it in the DeclContext that maps to the /// *FunctionScopeIndexToStopAt on the FunctionScopeInfo stack. static void -MarkVarDeclODRUsed(VarDecl *Var, SourceLocation Loc, Sema &SemaRef, +MarkVarDeclODRUsed(ValueDecl *V, SourceLocation Loc, Sema &SemaRef, const unsigned *const FunctionScopeIndexToStopAt = nullptr) { // Keep track of used but undefined variables. // FIXME: We shouldn't suppress this warning for static data members. + VarDecl *Var = V->getPotentiallyDecomposedVarDecl(); + assert(Var && "expected a capturable variable"); + if (Var->hasDefinition(SemaRef.Context) == VarDecl::DeclarationOnly && (!Var->isExternallyVisible() || Var->isInline() || SemaRef.isExternalWithNoLinkageType(Var)) && @@ -18496,12 +18499,11 @@ } QualType CaptureType, DeclRefType; if (SemaRef.LangOpts.OpenMP) - SemaRef.tryCaptureOpenMPLambdas(Var); - SemaRef.tryCaptureVariable(Var, Loc, Sema::TryCapture_Implicit, - /*EllipsisLoc*/ SourceLocation(), - /*BuildAndDiagnose*/ true, - CaptureType, DeclRefType, - FunctionScopeIndexToStopAt); + SemaRef.tryCaptureOpenMPLambdas(V); + SemaRef.tryCaptureVariable(V, Loc, Sema::TryCapture_Implicit, + /*EllipsisLoc*/ SourceLocation(), + /*BuildAndDiagnose*/ true, CaptureType, + DeclRefType, FunctionScopeIndexToStopAt); if (SemaRef.LangOpts.CUDA && Var->hasGlobalStorage()) { auto *FD = dyn_cast_or_null(SemaRef.CurContext); @@ -18541,10 +18543,10 @@ } } - Var->markUsed(SemaRef.Context); + V->markUsed(SemaRef.Context); } -void Sema::MarkCaptureUsedInEnclosingContext(VarDecl *Capture, +void Sema::MarkCaptureUsedInEnclosingContext(ValueDecl *Capture, SourceLocation Loc, unsigned CapturingScopeIndex) { MarkVarDeclODRUsed(Capture, Loc, *this, &CapturingScopeIndex); @@ -18631,13 +18633,9 @@ if (isa(DC) || isa(DC) || isLambdaCallOperator(DC)) return getLambdaAwareParentOfDeclContext(DC); - ValueDecl *Underlying = Var; - auto *BD = dyn_cast_or_null(Var); - if (BD) - Underlying = BD->getDecomposedDecl(); - - if (auto *VD = dyn_cast(Underlying)) { - if (VD->hasLocalStorage() && Diagnose) + VarDecl *Underlying = Var->getPotentiallyDecomposedVarDecl(); + if (Underlying) { + if (Underlying->hasLocalStorage() && Diagnose) diagnoseUncapturableValueReferenceOrBinding(S, Loc, Var); } return nullptr; @@ -19053,8 +19051,7 @@ if (VD->isInitCapture()) VarDC = VarDC->getParent(); } else { - VD = dyn_cast( - cast(Var)->getDecomposedDecl()); + VD = Var->getPotentiallyDecomposedVarDecl(); } assert(VD && "Cannot capture a null variable"); @@ -19728,6 +19725,38 @@ "MarkVarDeclODRUsed failed to cleanup MaybeODRUseExprs?"); } +static void DoMarkPotentialCapture(Sema &SemaRef, SourceLocation Loc, + ValueDecl *Var, Expr *E) { + VarDecl *VD = Var->getPotentiallyDecomposedVarDecl(); + if (!VD) + return; + + 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()); + } + } +} + static void DoMarkVarDeclReferenced( Sema &SemaRef, SourceLocation Loc, VarDecl *Var, Expr *E, llvm::DenseMap &RefsMinusAssignments) { @@ -19877,36 +19906,13 @@ // odr-used, but we may still need to track them for lambda capture. // FIXME: Do we also need to do this inside dependent typeid expressions // (which are modeled as unevaluated at this point)? - const bool RefersToEnclosingScope = - (SemaRef.CurContext != Var->getDeclContext() && - Var->getDeclContext()->isFunctionOrMethod() && Var->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() || - !Var->isUsableInConstantExpressions(SemaRef.Context)) - LSI->addPotentialCapture(E->IgnoreParens()); - } - } + DoMarkPotentialCapture(SemaRef, Loc, Var, E); break; } } static void DoMarkBindingDeclReferenced(Sema &SemaRef, SourceLocation Loc, - BindingDecl *BD) { + BindingDecl *BD, Expr *E) { BD->setReferenced(); if (BD->isInvalidDecl()) @@ -19920,6 +19926,8 @@ /*BuildAndDiagnose*/ true, CaptureType, DeclRefType, /*FunctionScopeIndexToStopAt*/ nullptr); + } else if (OdrUse == OdrUseContext::Dependent) { + DoMarkPotentialCapture(SemaRef, Loc, BD, E); } } @@ -19943,7 +19951,7 @@ } if (BindingDecl *Decl = dyn_cast(D)) { - DoMarkBindingDeclReferenced(SemaRef, Loc, Decl); + DoMarkBindingDeclReferenced(SemaRef, Loc, Decl, E); return; } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -8283,7 +8283,7 @@ // 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([&] (VarDecl *Var, Expr *VarExpr) { + CurrentLSI->visitPotentialCaptures([&](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. @@ -8299,6 +8299,10 @@ !IsFullExprInstantiationDependent) return; + VarDecl *UnderlyingVar = Var->getPotentiallyDecomposedVarDecl(); + if (!UnderlyingVar) + 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). if (const Optional Index = @@ -8306,7 +8310,7 @@ S.FunctionScopes, Var, S)) S.MarkCaptureUsedInEnclosingContext(Var, VarExpr->getExprLoc(), *Index); const bool IsVarNeverAConstantExpression = - VariableCanNeverBeAConstantExpression(Var, S.Context); + VariableCanNeverBeAConstantExpression(UnderlyingVar, S.Context); if (!IsFullExprInstantiationDependent || IsVarNeverAConstantExpression) { // This full expression is not instantiation dependent or the variable // can not be used in a constant expression - which means diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -62,7 +62,7 @@ static inline Optional getStackIndexOfNearestEnclosingCaptureReadyLambda( ArrayRef FunctionScopes, - VarDecl *VarToCapture) { + ValueDecl *VarToCapture) { // Label failure to capture. const Optional NoLambdaIsCaptureReady; @@ -172,7 +172,7 @@ Optional clang::getStackIndexOfNearestEnclosingCaptureCapableLambda( ArrayRef FunctionScopes, - VarDecl *VarToCapture, Sema &S) { + ValueDecl *VarToCapture, Sema &S) { const Optional NoLambdaIsCaptureCapable; @@ -1232,11 +1232,7 @@ if (Var->isInvalidDecl()) continue; - VarDecl *Underlying; - if (auto *BD = dyn_cast(Var)) - Underlying = dyn_cast(BD->getDecomposedDecl()); - else - Underlying = cast(Var); + VarDecl *Underlying = Var->getPotentiallyDecomposedVarDecl(); if (!Underlying->hasLocalStorage()) { Diag(C->Loc, diag::err_capture_non_automatic_variable) << C->Id; diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -13399,9 +13399,8 @@ } // Transform the captured variable. - VarDecl *CapturedVar - = cast_or_null(getDerived().TransformDecl(C->getLocation(), - C->getCapturedVar())); + auto *CapturedVar = cast_or_null( + getDerived().TransformDecl(C->getLocation(), C->getCapturedVar())); if (!CapturedVar || CapturedVar->isInvalidDecl()) { Invalid = true; continue; diff --git a/clang/test/SemaCXX/cxx20-decomposition.cpp b/clang/test/SemaCXX/cxx20-decomposition.cpp --- a/clang/test/SemaCXX/cxx20-decomposition.cpp +++ b/clang/test/SemaCXX/cxx20-decomposition.cpp @@ -1,5 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s -// expected-no-diagnostics +// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify -Wunused-variable %s template constexpr bool is_same = false; @@ -80,7 +79,17 @@ namespace std { template -struct tuple_size { +struct tuple_size; + +template +struct tuple_size : tuple_size{}; + +template +requires requires { tuple_size::value; } +struct tuple_size : tuple_size{}; + +template <> +struct tuple_size { static constexpr unsigned long value = 2; }; @@ -139,3 +148,37 @@ }; } } + +namespace ODRUseTests { + struct P { int a; int b; }; + void GH57826() { + const auto [a, b] = P{1, 2}; //expected-note 2{{'b' declared here}} \ + //expected-note 3{{'a' declared here}} + (void)[&](auto c) { return b + [&a] { + return a; + }(); }(0); + (void)[&](auto c) { return b + [&a](auto) { + return a; + }(0); }(0); + (void)[=](auto c) { return b + [&a](auto) { + return a; + }(0); }(0); + (void)[&a,&b](auto c) { return b + [&a](auto) { + return a; + }(0); }(0); + (void)[&a,&b](auto c) { return b + [a](auto) { + return a; + }(0); }(0); + (void)[&a](auto c) { return b + [&a](auto) { // expected-error 2{{variable 'b' cannot be implicitly captured}} \ + // expected-note 2{{lambda expression begins here}} \ + // expected-note 4{{capture 'b'}} + return a; + }(0); }(0); // expected-note {{in instantiation}} + (void)[&b](auto c) { return b + [](auto) { // expected-note 3{{lambda expression begins here}} \ + // expected-note 6{{capture 'a'}} \ + // expected-note 6{{default capture}} \ + // expected-note {{in instantiation}} + return a; // expected-error 3{{variable 'a' cannot be implicitly captured}} + }(0); }(0); // expected-note 2{{in instantiation}} + } +}