[PATCH] D61790: [C++20] add consteval specifier
Richard Smith - zygoloid via Phabricator via cfe-commits
cfe-commits at lists.llvm.org
Tue May 14 12:44:03 PDT 2019
rsmith added a comment.
Thanks for working on this!
Comments on the general approach:
- We should only evaluate each immediate invocation once (this will become essential once we start supporting reflection -- and particularly operations that mutate the AST -- inside `consteval` functions).
- Each time an immediate invocation is formed, you should create a `ConstantExpr` AST node wrapping that call to reflect that it is a constant.
- You should change `ConstantExpr` to store an `APValue` representing the evaluated value of the expression.
Most of the above should be done as a separate preparatory change; we should be able to remove a lot of the existing ad-hoc caching of evaluated values (eg, for enumerators and the initializers of variables) at the same time.
Please also split this into smaller pieces. If you split off a patch to just add the keyword, parsing, and AST representation for the `consteval` specifier (but treat it identically to `constexpr` for constant evaluation purposes), that'd be a good first patch for this feature.
================
Comment at: clang/include/clang/AST/Decl.h:2102
/// Whether this is a (C++11) constexpr function or constexpr constructor.
bool isConstexpr() const { return FunctionDeclBits.IsConstexpr; }
void setConstexpr(bool IC) { FunctionDeclBits.IsConstexpr = IC; }
----------------
Please rename this to `isConstexprSpecified()` (compare this to how we model `inline`, which has similar behavior: it can be explicit, or can be implied by other properties of the function).
================
Comment at: clang/include/clang/AST/Decl.h:2109
+
+ bool isConstexprOrConsteval() const { return isConstexpr() || isConsteval(); }
+
----------------
Both the `constexpr` and `consteval` specifiers make a function a "constexpr function", so I think this should be called simply `isConstexpr`; callers that want to distinguish `constexpr` from `consteval` can use `isConsteval()` (or we can add a `getConstexprKind()` or similar function).
================
Comment at: clang/include/clang/AST/DeclCXX.h:2115
+ QualType T, TypeSourceInfo *TInfo, StorageClass SC,
+ bool isInline, bool isConstexpr, bool isConsteval,
+ SourceLocation EndLocation)
----------------
Instead of the three bool flags in a row here (which will make call sites hard to read), consider using an enum, perhaps:
```
enum class ConstexprSpecifierKind { None, Constexpr, Consteval };
```
================
Comment at: clang/include/clang/Basic/DiagnosticSemaKinds.td:2297-2300
+def err_consteval_cannot_be_constant_eval : Error<
+ "call to %0 cannot be constant evaluated">;
+def note_argument_n_cannot_be_constant_eval : Note<
+ "argument %0 cannot be constant evaluated">;
----------------
It would be useful in these diagnostics to explain why the call is required to be constant evaluated; please also use the standard terminology "constant expression" here. (Eg, "call to consteval function %0 is not a constant expression")
================
Comment at: clang/include/clang/Basic/DiagnosticSemaKinds.td:2308-2314
def err_constexpr_tag : Error<
"%select{class|struct|interface|union|enum}0 cannot be marked constexpr">;
-def err_constexpr_dtor : Error<"destructor cannot be marked constexpr">;
+def err_constexpr_dtor : Error<"destructor cannot be marked %0">;
+def err_invalid_consteval_decl_kind : Error<
+ "operator %select{new|delete|new[]|delete[]}0 cannot be marked consteval">;
+def err_take_adress_of_consteval_decl : Error<
+ "taking address of a %0">;
----------------
In these diagnostics (including the existing one for tags), we should say "declared constexpr" not "marked constexpr".
================
Comment at: clang/include/clang/Basic/TokenKinds.def:390-391
+// C++ consteval proposal
+CXX2A_KEYWORD(consteval , 0)
+
----------------
This and `char8_t` are both adopted C++2a features now (they'e not just proposals any more); please change the comment to just "C++2a keywords".
================
Comment at: clang/include/clang/Sema/DeclSpec.h:404
SourceLocation FS_forceinlineLoc;
- SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
+ SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc, ConstevalLoc;
SourceLocation TQ_pipeLoc;
----------------
I think it would be preferable to track only one location here (for the `constexpr` / `consteval` specifier) and store a constexpr specifier kind instead of `Constexpr_specified`, like we do for the other kinds of specifier for which we allow only one of a set of keywords.
================
Comment at: clang/include/clang/Sema/Sema.h:985-986
+ /// cases in a switch statement).
+ /// - "immediate function context" in C++2a terms, a call to a function
+ /// marked as consteval
ConstantEvaluated,
----------------
An "immediate function context" is also "potentially evaluated"; I think what we want to say here is something like "The current context is "potentially evaluated", but will only ever be constant evaluated; runtime code will never be generated for it. (Eg, the case values of a switch, or the body of a consteval function.)"
================
Comment at: clang/include/clang/Sema/Sema.h:4052-4053
+ /// CheckInvalidConstevalCall - detect and diagnose invalid calls to
+ /// consteval function.
+ /// return true if the call is invalid.
----------------
Please don't use the "FunctionName - " style in new code.
================
Comment at: clang/include/clang/Sema/Sema.h:4054
+ /// consteval function.
+ /// return true if the call is invalid.
+ bool CheckInvalidConstevalCall(FunctionDecl *FDecl, SourceLocation Loc,
----------------
return -> \return
================
Comment at: clang/include/clang/Sema/Sema.h:4055-4056
+ /// return true if the call is invalid.
+ bool CheckInvalidConstevalCall(FunctionDecl *FDecl, SourceLocation Loc,
+ ArrayRef<Expr *> Args, Expr *This = nullptr);
+
----------------
Drop the "Invalid" from this function name; it doesn't add anything. But I think this is the wrong interface anyway -- I'll get to that later.
================
Comment at: clang/lib/AST/ExprConstant.cpp:4474-4475
const LValue *ResultSlot) {
+ EvalInfo::EnterConstantContextRAII ContextRAII(
+ Info, Callee->isConsteval() || Info.InConstantContext);
ArgVector ArgValues(Args.size());
----------------
We should not need to do this; instead, when the evaluator hits a `ConstantExpr` node, it should use the already-evaluated value of that node.
================
Comment at: clang/lib/Parse/ParseDecl.cpp:2480-2487
+ Diag(DS.getInlineSpecLoc(), diag::err_typename_invalid_specifier)
+ << "function";
if (DS.isVirtualSpecified())
- Diag(DS.getVirtualSpecLoc(), diag::err_typename_invalid_functionspec);
+ Diag(DS.getVirtualSpecLoc(), diag::err_typename_invalid_specifier)
+ << "function";
if (DS.hasExplicitSpecifier())
+ Diag(DS.getExplicitSpecLoc(), diag::err_typename_invalid_specifier)
----------------
Do not pass random English words into diagnostics; this makes translation impossible. Use a `%select` or two different diagnostics instead. (Passing in C++ syntax such as `constexpr` or `consteval` is OK, since they don't need to be translated.)
================
Comment at: clang/lib/Parse/ParseExprCXX.cpp:1073-1075
+ case tok::identifier:
+ if (P.getCurToken().getIdentifierInfo()->getName() != "consteval")
+ return;
----------------
It doesn't seem worthwhile to me to support `consteval` in earlier language modes only for lambdas; if we really want to allow `consteval` as an extension in earlier language modes, the better approach would be to add a `__consteval` keyword. But regardless of whether we do that, please remove this.
================
Comment at: clang/lib/Parse/ParseExprCXX.cpp:1303-1305
+ case tok::kw_consteval:
+ TokKind = 4;
+ break;
----------------
Please follow the formatting of the adjacent code.
================
Comment at: clang/lib/Sema/DeclSpec.cpp:1326-1332
+ // C++2a [dcl.spec]p2: The constexpr and consteval decl-specifiers shall not
+ // both appear in a decl-specifier-seq.
+ if (isConstevalSpecified() && isConstexprSpecified()) {
+ S.Diag(ConstexprLoc, diag::err_incompatible_constexpr_consteval)
+ << FixItHint::CreateRemoval(ConstexprLoc);
+ Constexpr_specified = false;
+ }
----------------
This should be handled by `SetConstevalSpec` instead; please look for calls to `BadSpecifier` to see how we handle this for other kinds of specifier.
================
Comment at: clang/lib/Sema/SemaChecking.cpp:10471
+ bool IsConstantContext = S.ExprEvalContexts.back().isConstantEvaluated();
+
----------------
Are the changes to this file really specific to `consteval` evaluation? They look more general than that; I think this would fix the evaluation of `std::is_constant_evaluated()` in any constant-evaluated context that we analyzed. Can this be split out of this patch into a separate change?
================
Comment at: clang/lib/Sema/SemaDeclCXX.cpp:640-648
if (New->isConstexpr() != Old->isConstexpr()) {
Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
- << New << New->isConstexpr();
+ << New << New->isConstexpr() << "constexpr";
+ Diag(Old->getLocation(), diag::note_previous_declaration);
+ Invalid = true;
+ } else if (New->isConsteval() != Old->isConsteval()) {
+ Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
----------------
This will produce diagnostics such as "non-constexpr declaration follows constexpr declaration" for a `consteval` declaration following a `constexpr` declaration, which seems misleading since a `consteval` declaration does declare a `constexpr` function. I think the order of these two checks should be reversed, so that that case will instead be diagnosed as "consteval declaration follows non-consteval declaration".
================
Comment at: clang/lib/Sema/SemaDeclCXX.cpp:6655
MD->getTemplatedKind() == FunctionDecl::TK_NonTemplate) {
Diag(MD->getBeginLoc(), diag::err_incorrect_defaulted_constexpr) << CSM;
// FIXME: Explain why the special member can't be constexpr.
----------------
The diagnostic text here is:
```
error: defaulted definition of <thing> is not constexpr
```
which might be confusing if the user wrote `consteval thing = default;`. Maybe change the diagnostic to:
"defaulted definition of <thing> cannot be declared %select{constexpr|consteval}1 because its implicit definition would not be constexpr"
or something like that?
================
Comment at: clang/lib/Sema/SemaExpr.cpp:5720-5769
+bool Sema::CheckInvalidConstevalCall(FunctionDecl *FDecl, SourceLocation Loc,
+ ArrayRef<Expr *> Args, Expr *This) {
+ bool Failed = false;
+ if (FDecl && FDecl->isConsteval() &&
+ !ExprEvalContexts.back().isConstantEvaluated()) {
+
+ SmallVector<PartialDiagnosticAt, 8> Diags;
----------------
This checking doesn't make sense: we should be checking that the invocation as a whole is a constant expression, not whether the arguments (evaluated in isolation) are constant expressions. This check should be applied to the `Expr` representing the call, and should wrap it in a `ConstantExpr` holding the evaluated value if evaluation succeeds.
================
Comment at: clang/lib/Sema/SemaExpr.cpp:13417-13435
+bool Sema::CheckInvalidConstevalTakeAddress(Expr *Input) {
+ if (Input) {
+ if (auto *DeclRef = dyn_cast<DeclRefExpr>(Input->IgnoreParens())) {
+ if (auto *Function = dyn_cast<FunctionDecl>(DeclRef->getDecl())) {
+ if (Function->isConsteval()) {
+ const char *DeclName = Function->isOverloadedOperator()
+ ? "consteval operator"
----------------
This isn't correct: you can take the address of a `consteval` function within an immediate invocation. In general, we need to delay this checking until we've finished processing any potentially-enclosing immediate invocations. Trying to capture all of the places where we might form a reference to a `consteval` function name without forming an immediate invocation is also fragile: you'll miss some, and as Clang is extended, there'll be more cases introduced that you miss.
Here's how I was intending to handle this:
* Change `Sema::MarkFunctionReferenced` to take an `Expr*` instead of a location, and update all of its callers. (We'll need a separate function for marking destructors referenced, because destructor calls typically don't have expressions, but destructors can't be `consteval` so that's OK.)
* In `Sema::MarkFunctionReferenced`, if `Func` is a `consteval` function and our current context is not a `consteval` function, add it to a list on the `ExpressionEvaluationContext`.
* When forming an immediate invocation, walk all of its subexpressions and remove them all from the list on the `ExpressionEvaluationContext`
* When popping the `ExpressionEvaluationContext`, diagnose any remaining expressions in its list.
(The first step should be done in a separate patch to keep the diffs smaller and easier to review.)
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D61790/new/
https://reviews.llvm.org/D61790
More information about the cfe-commits
mailing list