Skip to content

[clang] Consistently handle consteval constructors for variables. #144970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,10 @@ Bug Fixes to C++ Support
- Fixed a crash when constant evaluating some explicit object member assignment operators. (#GH142835)
- Fixed an access checking bug when substituting into concepts (#GH115838)
- Fix a bug where private access specifier of overloaded function not respected. (#GH107629)
- Improved handling of variables with ``consteval`` constructors, to
consistently treat the initializer as manifestly constant-evaluated.
(#GH135281)


Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -6759,6 +6759,7 @@ class Sema final : public SemaBase {
EK_Decltype,
EK_TemplateArgument,
EK_AttrArgument,
EK_VariableInit,
EK_Other
} ExprContext;

Expand Down
1 change: 1 addition & 0 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2710,6 +2710,7 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
break;
}
case InitKind::Uninitialized: {
InitializerScopeRAII InitScope(*this, D, ThisDecl);
Actions.ActOnUninitializedDecl(ThisDecl);
break;
}
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/SemaCoroutine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,8 @@ static bool checkSuspensionContext(Sema &S, SourceLocation Loc,
const auto ExprContext = S.currentEvaluationContext().ExprContext;
const bool BadContext =
S.isUnevaluatedContext() ||
ExprContext != Sema::ExpressionEvaluationContextRecord::EK_Other;
(ExprContext != Sema::ExpressionEvaluationContextRecord::EK_Other &&
ExprContext != Sema::ExpressionEvaluationContextRecord::EK_VariableInit);
if (BadContext) {
S.Diag(Loc, diag::err_coroutine_unevaluated_context) << Keyword;
return false;
Expand Down
26 changes: 2 additions & 24 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18937,7 +18937,8 @@ void Sema::ActOnCXXEnterDeclInitializer(Scope *S, Decl *D) {
EnterDeclaratorContext(S, D->getDeclContext());

PushExpressionEvaluationContext(
ExpressionEvaluationContext::PotentiallyEvaluated, D);
ExpressionEvaluationContext::PotentiallyEvaluated, D,
ExpressionEvaluationContextRecord::EK_VariableInit);
}

void Sema::ActOnCXXExitDeclInitializer(Scope *S, Decl *D) {
Expand All @@ -18946,29 +18947,6 @@ void Sema::ActOnCXXExitDeclInitializer(Scope *S, Decl *D) {
if (S && D->isOutOfLine())
ExitDeclaratorContext(S);

if (getLangOpts().CPlusPlus23) {
// An expression or conversion is 'manifestly constant-evaluated' if it is:
// [...]
// - the initializer of a variable that is usable in constant expressions or
// has constant initialization.
if (auto *VD = dyn_cast<VarDecl>(D);
VD && (VD->isUsableInConstantExpressions(Context) ||
VD->hasConstantInitialization())) {
// An expression or conversion is in an 'immediate function context' if it
// is potentially evaluated and either:
// [...]
// - it is a subexpression of a manifestly constant-evaluated expression
// or conversion.
ExprEvalContexts.back().InImmediateFunctionContext = true;
}
}

// Unless the initializer is in an immediate function context (as determined
// above), this will evaluate all contained immediate function calls as
// constant expressions. If the initializer IS an immediate function context,
// the initializer has been determined to be a constant expression, and all
// such evaluations will be elided (i.e., as if we "knew the whole time" that
// it was a constant expression).
PopExpressionEvaluationContext();
}

Expand Down
19 changes: 19 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17935,6 +17935,25 @@ HandleImmediateInvocations(Sema &SemaRef,
Rec.isImmediateFunctionContext() || SemaRef.RebuildingImmediateInvocation)
return;

// An expression or conversion is 'manifestly constant-evaluated' if it is:
// [...]
// - the initializer of a variable that is usable in constant expressions or
// has constant initialization.
if (SemaRef.getLangOpts().CPlusPlus23 &&
Rec.ExprContext ==
Sema::ExpressionEvaluationContextRecord::EK_VariableInit) {
auto *VD = cast<VarDecl>(Rec.ManglingContextDecl);
if (VD->isUsableInConstantExpressions(SemaRef.Context) ||
VD->hasConstantInitialization()) {
// An expression or conversion is in an 'immediate function context' if it
// is potentially evaluated and either:
// [...]
// - it is a subexpression of a manifestly constant-evaluated expression
// or conversion.
return;
}
}

/// When we have more than 1 ImmediateInvocationCandidates or previously
/// failed immediate invocations, we need to check for nested
/// ImmediateInvocationCandidates in order to avoid duplicate diagnostics.
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6103,7 +6103,8 @@ void Sema::InstantiateVariableInitializer(
ContextRAII SwitchContext(*this, Var->getDeclContext());

EnterExpressionEvaluationContext Evaluated(
*this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, Var);
*this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, Var,
ExpressionEvaluationContextRecord::EK_VariableInit);
currentEvaluationContext().InLifetimeExtendingContext =
parentEvaluationContext().InLifetimeExtendingContext;
currentEvaluationContext().RebuildDefaultArgOrDefaultInit =
Expand Down
34 changes: 34 additions & 0 deletions clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,3 +576,37 @@ int f() {
//expected-note@-2 {{read of non-const variable 'a' is not allowed in a constant expression}}
}
}

#if __cplusplus >= 202302L
namespace GH135281 {
struct B {
const void* p;
consteval B() : p{this} {}
};
B b;
B b2{};
B &&b3{};
void f() {
static B b4;
B b5; // expected-error {{call to consteval function 'GH135281::B::B' is not a constant expression}} \
// expected-note {{pointer to temporary is not a constant expression}} \
// expected-note {{temporary created here}}
}
template<typename T> T temp_var_uninit;
template<typename T> T temp_var_brace_init{};
B* b6 = &temp_var_uninit<B>;
B* b7 = &temp_var_brace_init<B>;
B* b8 = &temp_var_brace_init<B&&>;
template<typename T> void f2() {
static T b9;
T b10; // expected-error {{call to consteval function 'GH135281::B::B' is not a constant expression}} \
// expected-note {{pointer to temporary is not a constant expression}} \
// expected-note {{temporary created here}}
static B b11;
B b12; // expected-error 2 {{call to consteval function 'GH135281::B::B' is not a constant expression}} \
// expected-note 2 {{pointer to temporary is not a constant expression}} \
// expected-note 2 {{temporary created here}}
}
void (*ff)() = f2<B>; // expected-note {{instantiation of function template specialization}}
}
#endif