Skip to content

[clang] add -fimplicit-constexpr flag #136436

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 7 commits into
base: main
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ New Compiler Flags
The feature has `existed <https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#running-the-instrumented-program>`_)
for a while and this is just a user facing option.

- New option ``-fimplicit-constexpr`` which implicitly makes all inlined and defined functions ``constexpr``.

Deprecated Compiler Flags
-------------------------

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,9 @@ class FunctionDecl : public DeclaratorDecl,
bool isConstexpr() const {
return getConstexprKind() != ConstexprSpecKind::Unspecified;
}
/// Support for `-fimplicit-constexpr`
bool isConstexprOrImplicitlyCanBe(const LangOptions &LangOpts,
bool MustBeInlined = true) const;
void setConstexprKind(ConstexprSpecKind CSK) {
FunctionDeclBits.ConstexprKind = static_cast<uint64_t>(CSK);
}
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def note_constexpr_lshift_discards : Note<"signed left shift discards bits">;
def note_constexpr_invalid_function : Note<
"%select{non-constexpr|undefined}0 %select{function|constructor}1 %2 cannot "
"be used in a constant expression">;
def note_constexpr_implicit_constexpr_must_be_inlined
: Note<"non-inline function %0 is not implicitly constexpr">;
def note_constexpr_invalid_inhctor : Note<
"constructor inherited from base class %0 cannot be used in a "
"constant expression; derived class cannot be implicitly initialized">;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ EXTENSION(matrix_types, LangOpts.MatrixTypes)
EXTENSION(matrix_types_scalar_division, true)
EXTENSION(cxx_attributes_on_using_declarations, LangOpts.CPlusPlus11)
EXTENSION(datasizeof, LangOpts.CPlusPlus)
EXTENSION(cxx_implicit_constexpr, LangOpts.ImplicitConstexpr)

FEATURE(cxx_abi_relative_vtable, LangOpts.CPlusPlus && LangOpts.RelativeCXXABIVTables)

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ BENIGN_LANGOPT(ArrowDepth, 32, 256,
"maximum number of operator->s to follow")
BENIGN_LANGOPT(InstantiationDepth, 32, 1024,
"maximum template instantiation depth")
COMPATIBLE_LANGOPT(ImplicitConstexpr, 1, 0, "make functions implicitly 'constexpr'")
BENIGN_LANGOPT(ConstexprCallDepth, 32, 512,
"maximum constexpr call depth")
BENIGN_LANGOPT(ConstexprStepLimit, 32, 1048576,
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,12 @@ defm constant_cfstrings : BoolFOption<"constant-cfstrings",
"Disable creation of CodeFoundation-type constant strings">,
PosFlag<SetFalse>>;
def fconstant_string_class_EQ : Joined<["-"], "fconstant-string-class=">, Group<f_Group>;
def fimplicit_constexpr
: Joined<["-"], "fimplicit-constexpr">,
Group<f_Group>,
Visibility<[ClangOption, CC1Option]>,
HelpText<"All function declarations will be implicitly constexpr.">,
MarshallingInfoFlag<LangOpts<"ImplicitConstexpr">>;
def fconstexpr_depth_EQ : Joined<["-"], "fconstexpr-depth=">, Group<f_Group>,
Visibility<[ClangOption, CC1Option]>,
HelpText<"Set the maximum depth of recursive constexpr function calls">,
Expand Down
7 changes: 4 additions & 3 deletions clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ void ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl,
Func->setDefined(true);

// Lambda static invokers are a special case that we emit custom code for.
bool IsEligibleForCompilation = Func->isLambdaStaticInvoker() ||
FuncDecl->isConstexpr() ||
FuncDecl->hasAttr<MSConstexprAttr>();
bool IsEligibleForCompilation =
Func->isLambdaStaticInvoker() ||
FuncDecl->isConstexprOrImplicitlyCanBe(Ctx.getLangOpts()) ||
FuncDecl->hasAttr<MSConstexprAttr>();

// Compile the function body.
if (!IsEligibleForCompilation || !visitFunc(FuncDecl)) {
Expand Down
24 changes: 17 additions & 7 deletions clang/lib/AST/ByteCode/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,8 @@ bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) {
return false;

if (F->isConstexpr() && F->hasBody() &&
(F->getDecl()->isConstexpr() || F->getDecl()->hasAttr<MSConstexprAttr>()))
(F->getDecl()->isConstexprOrImplicitlyCanBe(S.getLangOpts()) ||
F->getDecl()->hasAttr<MSConstexprAttr>()))
return true;

// Implicitly constexpr.
Expand All @@ -846,7 +847,7 @@ bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) {
const auto *CD = dyn_cast<CXXConstructorDecl>(DiagDecl);
if (CD && CD->isInheritingConstructor()) {
const auto *Inherited = CD->getInheritedConstructor().getConstructor();
if (!Inherited->isConstexpr())
if (!Inherited->isConstexprOrImplicitlyCanBe(S.getLangOpts()))
DiagDecl = CD = Inherited;
}

Expand All @@ -868,19 +869,28 @@ bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) {
// for a constant expression. It might be defined at the point we're
// actually calling it.
bool IsExtern = DiagDecl->getStorageClass() == SC_Extern;
if (!DiagDecl->isDefined() && !IsExtern && DiagDecl->isConstexpr() &&
if (!DiagDecl->isDefined() && !IsExtern &&
DiagDecl->isConstexprOrImplicitlyCanBe(S.getLangOpts()) &&
S.checkingPotentialConstantExpression())
return false;

// If the declaration is defined, declared 'constexpr' _and_ has a body,
// the below diagnostic doesn't add anything useful.
if (DiagDecl->isDefined() && DiagDecl->isConstexpr() &&
if (DiagDecl->isDefined() &&
DiagDecl->isConstexprOrImplicitlyCanBe(S.getLangOpts()) &&
DiagDecl->hasBody())
return false;

S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_invalid_function, 1)
<< DiagDecl->isConstexpr() << (bool)CD << DiagDecl;
if (S.getLangOpts().ImplicitConstexpr && !F->getDecl()->isInlined()) {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_implicit_constexpr_must_be_inlined, 1)
<< DiagDecl;
} else {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_invalid_function, 1)
<< DiagDecl->isConstexprOrImplicitlyCanBe(S.getLangOpts())
<< (bool)CD << DiagDecl;
}

if (DiagDecl->getDefinition())
S.Note(DiagDecl->getDefinition()->getLocation(),
Expand Down
21 changes: 21 additions & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3242,6 +3242,27 @@ bool FunctionDecl::isDefined(const FunctionDecl *&Definition,
return false;
}

bool FunctionDecl::isConstexprOrImplicitlyCanBe(const LangOptions &LangOpts,
bool MustBeInlined) const {
if (isConstexpr())
return true;

if (!LangOpts.ImplicitConstexpr)
return false;

// Constexpr function in C++11 couldn't contain anything other then return
// expression. It wouldn't make sense to allow it (GCC doesn't do it neither).
if (!LangOpts.CPlusPlus14)
return false;

// Free functions must be inlined, but sometimes we want to skip this check.
// And in order to keep logic on one place, the check is here.
if (MustBeInlined)
return isInlined();

return true;
}

Stmt *FunctionDecl::getBody(const FunctionDecl *&Definition) const {
if (!hasBody(Definition))
return nullptr;
Expand Down
24 changes: 19 additions & 5 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5968,8 +5968,9 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc,

// Can we evaluate this function call?
if (Definition && Body &&
(Definition->isConstexpr() || (Info.CurrentCall->CanEvalMSConstexpr &&
Definition->hasAttr<MSConstexprAttr>())))
(Definition->isConstexprOrImplicitlyCanBe(Info.Ctx.getLangOpts()) ||
(Info.CurrentCall->CanEvalMSConstexpr &&
Definition->hasAttr<MSConstexprAttr>())))
return true;

if (Info.getLangOpts().CPlusPlus11) {
Expand All @@ -5987,12 +5988,25 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc,
// FIXME: If DiagDecl is an implicitly-declared special member function
// or an inheriting constructor, we should be much more explicit about why
// it's not constexpr.
if (CD && CD->isInheritingConstructor())
if (CD && CD->isInheritingConstructor()) {
Info.FFDiag(CallLoc, diag::note_constexpr_invalid_inhctor, 1)
<< CD->getInheritedConstructor().getConstructor()->getParent();
else

} else if (Definition && !DiagDecl->isInlined() &&
Info.Ctx.getLangOpts().ImplicitConstexpr) {
Info.FFDiag(CallLoc,
diag::note_constexpr_implicit_constexpr_must_be_inlined)
<< DiagDecl;

} else {
// Using implicit constexpr check here, so we see a missing body as main
// problem and not missing constexpr with -fimplicit-constexpr.
Info.FFDiag(CallLoc, diag::note_constexpr_invalid_function, 1)
<< DiagDecl->isConstexpr() << (bool)CD << DiagDecl;
<< DiagDecl->isConstexprOrImplicitlyCanBe(Info.Ctx.getLangOpts(),
false)
<< (bool)CD << DiagDecl;
}

Info.Note(DiagDecl->getLocation(), diag::note_declared_at);
} else {
Info.FFDiag(CallLoc, diag::note_invalid_subexpr_in_const_expr);
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6612,6 +6612,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_depth_EQ);
Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_steps_EQ);

if (types::isCXX(InputType))
Args.AddLastArg(CmdArgs, options::OPT_fimplicit_constexpr);

Args.AddLastArg(CmdArgs, options::OPT_fexperimental_library);

if (Args.hasArg(options::OPT_fexperimental_new_constant_interpreter))
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Frontend/InitPreprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,9 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,

// TODO: Final number?
Builder.defineMacro("__cpp_type_aware_allocators", "202500L");

if (LangOpts.ImplicitConstexpr) // same value as GCC
Builder.defineMacro("__cpp_implicit_constexpr", "20211111");
Comment on lines +782 to +784
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't usually define macros for non-standard feature. Instead users should use __has_extension(cxx_implicit_constexpr)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to mirror GCC's behavior, but I'm happy to remove it

}

/// InitializeOpenCLFeatureTestMacros - Define OpenCL macros based on target
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18384,16 +18384,18 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
}

if (FirstInstantiation || TSK != TSK_ImplicitInstantiation ||
Func->isConstexpr()) {
Func->isConstexprOrImplicitlyCanBe(getLangOpts())) {
if (isa<CXXRecordDecl>(Func->getDeclContext()) &&
cast<CXXRecordDecl>(Func->getDeclContext())->isLocalClass() &&
CodeSynthesisContexts.size())
PendingLocalImplicitInstantiations.push_back(
std::make_pair(Func, PointOfInstantiation));
else if (Func->isConstexpr())
else if (Func->isConstexprOrImplicitlyCanBe(getLangOpts()))
// Do not defer instantiations of constexpr functions, to avoid the
// expression evaluator needing to call back into Sema if it sees a
// call to such a function.
// (When -fimplicit-instantiation is enabled, all functions are
// implicitly constexpr)
InstantiateFunctionDefinition(PointOfInstantiation, Func);
else {
Func->setInstantiationIsPending(true);
Expand Down
117 changes: 117 additions & 0 deletions clang/test/Sema/implicit-constexpr-basic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL14,BOTH14,ALL_PRE20,ALLNORMAL,NORMAL_PRE20,ALL -std=c++14 %s
// RUN: %clang_cc1 -verify=IMPLICIT14,IMPLICIT_PRE20,BOTH14,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++14 %s
// RUN: %clang_cc1 -verify=IMPLICIT14,IMPLICIT_PRE20,BOTH14,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++14 %s -fexperimental-new-constant-interpreter

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL17,BOTH17,ALL_PRE20,ALLNORMAL,NORMAL_PRE20,ALL -std=c++17 %s
// RUN: %clang_cc1 -verify=IMPLICIT17,IMPLICIT_PRE20,BOTH17,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++17 %s
// RUN: %clang_cc1 -verify=IMPLICIT17,IMPLICIT_PRE20,BOTH17,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++17 %s -fexperimental-new-constant-interpreter

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL20,BOTH20,ALLNORMAL,ALL -std=c++20 %s
// RUN: %clang_cc1 -verify=IMPLICIT20,BOTH20,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++20 %s
// RUN: %clang_cc1 -verify=IMPLICIT20,BOTH20,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++20 %s -fexperimental-new-constant-interpreter

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL23,BOTH23,ALLNORMAL,ALL -std=c++23 %s
// RUN: %clang_cc1 -verify=IMPLICIT23,BOTH23,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++23 %s
// RUN: %clang_cc1 -verify=IMPLICIT23,BOTH23,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++23 %s -fexperimental-new-constant-interpreter




// =============================================
// 1) simple uninlined function

bool noinline_fnc() {
// ALL-note@-1 {{declared here}}
return true;
}

constexpr bool result_noinline_fnc = noinline_fnc();
// ALL-error@-1 {{constexpr variable 'result_noinline_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'noinline_fnc' cannot be used in a constant expression}}
// ALLIMPLICIT-note@-3 {{non-inline function 'noinline_fnc' is not implicitly constexpr}}


// =============================================
// 2) simple inlined function

inline bool inline_fnc() {
// ALLNORMAL-note@-1 {{declared here}}
return true;
}

constexpr bool result_inline_fnc = inline_fnc();
// ALLNORMAL-error@-1 {{constexpr variable 'result_inline_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'inline_fnc' cannot be used in a constant expression}}


// =============================================
// 3) undefined uninlined function

bool noinline_undefined_fnc();
// ALL-note@-1 {{declared here}}

constexpr bool result_noinline_undefined_fnc = noinline_undefined_fnc();
// ALL-error@-1 {{constexpr variable 'result_noinline_undefined_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'noinline_undefined_fnc' cannot be used in a constant expression}}
// ALLIMPLICITOLD-note@-3 {{undefined function 'noinline_undefined_fnc' cannot be used in a constant expression}}
// ALLIMPLICITNEW-note@-4 {{non-inline function 'noinline_undefined_fnc' is not implicitly constexpr}}

// =============================================
// 4) undefined inline function

inline bool inline_undefined_fnc();
// ALL-note@-1 {{declared here}}

constexpr bool result_inline_undefined_fnc = inline_undefined_fnc();
// ALL-error@-1 {{constexpr variable 'result_inline_undefined_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'inline_undefined_fnc' cannot be used in a constant expression}}
// ALLIMPLICIT-note@-3 {{undefined function 'inline_undefined_fnc' cannot be used in a constant expression}}

// =============================================
// 5) lambda function

auto lambda = [](int x) { return x > 0; };
// NORMAL14-note@-1 {{declared here}}

constexpr bool result_lambda = lambda(10);
// NORMAL14-error@-1 {{constexpr variable 'result_lambda' must be initialized by a constant expression}}
// NORMAL14-note@-2 {{non-constexpr function 'operator()' cannot be used in a constant expression}}


// =============================================
// 6) virtual functions

struct type {
virtual bool dispatch() const noexcept {
return false;
}
};

struct child_of_type: type {
bool dispatch() const noexcept override {
// NORMAL20-note@-1 {{declared here}}
// NORMAL23-note@-2 {{declared here}}
return true;
}
};

constexpr bool result_virtual = static_cast<const type &>(child_of_type{}).dispatch();
// ALL_NORMAL-error@-1 {{constexpr variable 'result_virtual' must be initialized by a constant expression}}
// NORMAL_PRE20-note@-2 {{cannot evaluate call to virtual function in a constant expression in C++ standards before C++20}}
// IMPLICIT_PRE20-error@-3 {{constexpr variable 'result_virtual' must be initialized by a constant expression}}
// IMPLICIT_PRE20-note@-4 {{cannot evaluate call to virtual function in a constant expression in C++ standards before C++20}}
// NORMAL20-note@-5 {{non-constexpr function 'dispatch' cannot be used in a constant expression}}
// NORMAL20-note@-6 {{declared here}}
// NORMAL23-note@-7 {{non-constexpr function 'dispatch' cannot be used in a constant expression}}
// NORMAL23-note@-8 {{declared here}}


#if defined(__cpp_constexpr) && __cpp_constexpr >= 201907L
static_assert(result_virtual == true, "virtual should work");
// ALL_NORMAL-error@-1 {{static assertion expression is not an integral constant expression}}
// ALL_NORMAL-note@-2 {{initializer of 'result_virtual' is not a constant expression}}
// IMPLICIT_PRE20-note@-3 {{initializer of 'result_virtual' is not a constant expression}}
#endif


Loading
Loading