diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -163,6 +163,9 @@ /// True if this if statement is a constexpr if. unsigned IsConstexpr : 1; + /// True if this if statement is a if consteval statement. + unsigned IsConsteval : 1; + /// True if this if statement has storage for an else statement. unsigned HasElse : 1; @@ -1950,9 +1953,10 @@ unsigned elseOffset() const { return condOffset() + ElseOffsetFromCond; } /// Build an if/then/else statement. - IfStmt(const ASTContext &Ctx, SourceLocation IL, bool IsConstexpr, Stmt *Init, - VarDecl *Var, Expr *Cond, SourceLocation LParenLoc, - SourceLocation RParenLoc, Stmt *Then, SourceLocation EL, Stmt *Else); + IfStmt(const ASTContext &Ctx, SourceLocation IL, bool IsConstexpr, + bool IsConsteval, Stmt *Init, VarDecl *Var, Expr *Cond, + SourceLocation LParenLoc, SourceLocation RParenLoc, Stmt *Then, + SourceLocation EL, Stmt *Else); /// Build an empty if/then/else statement. explicit IfStmt(EmptyShell Empty, bool HasElse, bool HasVar, bool HasInit); @@ -1960,8 +1964,9 @@ public: /// Create an IfStmt. static IfStmt *Create(const ASTContext &Ctx, SourceLocation IL, - bool IsConstexpr, Stmt *Init, VarDecl *Var, Expr *Cond, - SourceLocation LPL, SourceLocation RPL, Stmt *Then, + bool IsConstexpr, bool IsConsteval, Stmt *Init, + VarDecl *Var, Expr *Cond, SourceLocation LPL, + SourceLocation RPL, Stmt *Then, SourceLocation EL = SourceLocation(), Stmt *Else = nullptr); @@ -2080,6 +2085,9 @@ bool isConstexpr() const { return IfStmtBits.IsConstexpr; } void setConstexpr(bool C) { IfStmtBits.IsConstexpr = C; } + bool isConsteval() const { return IfStmtBits.IsConsteval; } + void setConsteval(bool C) { IfStmtBits.IsConsteval = C; } + /// If this is an 'if constexpr', determine which substatement will be taken. /// Otherwise, or if the condition is value-dependent, returns None. Optional getNondiscardedCase(const ASTContext &Ctx) const; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -626,6 +626,13 @@ def warn_cxx14_compat_constexpr_if : Warning< "constexpr if is incompatible with C++ standards before C++17">, DefaultIgnore, InGroup; +def ext_consteval_if : ExtWarn< + "consteval if is a C++2b extension">, + InGroup; +def warn_cxx20_compat_consteval_if : Warning< + "consteval if is incompatible with C++ standards before C++2b">, + InGroup, DefaultIgnore; + def ext_init_statement : ExtWarn< "'%select{if|switch}0' initialization statements are a C++17 extension">, InGroup; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1502,6 +1502,9 @@ def err_static_assert_requirement_failed : Error< "static_assert failed due to requirement '%0'%select{ %2|}1">; +def warn_if_consteval_always_true : Warning< + "if consteval is always true in an %select{unevaluated|immediate}0 context">, InGroup>; + def ext_inline_variable : ExtWarn< "inline variables are a C++17 extension">, InGroup; def warn_cxx14_compat_inline_variable : Warning< @@ -5938,6 +5941,8 @@ "jump bypasses initialization of VLA type alias">; def note_protected_by_constexpr_if : Note< "jump enters controlled statement of constexpr if">; +def note_protected_by_if_consteval : Note< + "jump enters controlled statement of if consteval">; def note_protected_by_if_available : Note< "jump enters controlled statement of if available">; def note_protected_by_vla : Note< 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 @@ -1218,6 +1218,11 @@ /// cases in a switch statement). ConstantEvaluated, + /// In addition of being constant evaluated, the current expression + /// occurs in an immediate function context - either a consteval function + /// or an 'if consteval' function. + ImmediateFunctionContext, + /// The current expression is potentially evaluated at run time, /// which means that code may be generated to evaluate the value of the /// expression at run time. @@ -1306,8 +1311,14 @@ Context == ExpressionEvaluationContext::UnevaluatedAbstract || Context == ExpressionEvaluationContext::UnevaluatedList; } + bool isConstantEvaluated() const { - return Context == ExpressionEvaluationContext::ConstantEvaluated; + return Context == ExpressionEvaluationContext::ConstantEvaluated || + Context == ExpressionEvaluationContext::ImmediateFunctionContext; + } + + bool isImmediate() const { + return Context == ExpressionEvaluationContext::ImmediateFunctionContext; } }; @@ -4709,11 +4720,17 @@ Stmt *SubStmt); class ConditionResult; - StmtResult ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, + enum IfKind { + IfKind_Default, + IfKind_Constexpr, + IfKind_Consteval, + }; + + StmtResult ActOnIfStmt(SourceLocation IfLoc, IfKind Kind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *ThenVal, SourceLocation ElseLoc, Stmt *ElseVal); - StmtResult BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, + StmtResult BuildIfStmt(SourceLocation IfLoc, IfKind Kind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *ThenVal, SourceLocation ElseLoc, Stmt *ElseVal); @@ -9114,6 +9131,19 @@ return ExprEvalContexts.back().isUnevaluated(); } + bool isImmediateFunctionContext() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + for (auto it = ExprEvalContexts.rbegin(); it != ExprEvalContexts.rend(); + it++) { + if (it->isImmediate()) + return true; + if (it->isUnevaluated()) + return false; + } + return false; + } + /// RAII class used to determine whether SFINAE has /// trapped any errors that occur during template argument /// deduction. diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -6360,8 +6360,8 @@ return std::move(Err); return IfStmt::Create(Importer.getToContext(), ToIfLoc, S->isConstexpr(), - ToInit, ToConditionVariable, ToCond, ToLParenLoc, - ToRParenLoc, ToThen, ToElseLoc, ToElse); + S->isConsteval(), ToInit, ToConditionVariable, ToCond, + ToLParenLoc, ToRParenLoc, ToThen, ToElseLoc, ToElse); } ExpectedStmt ASTNodeImporter::VisitSwitchStmt(SwitchStmt *S) { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5195,7 +5195,10 @@ } } bool Cond; - if (!EvaluateCond(Info, IS->getConditionVariable(), IS->getCond(), Cond)) + if (IS->isConsteval()) + Cond = true; + else if (!EvaluateCond(Info, IS->getConditionVariable(), IS->getCond(), + Cond)) return ESR_Failed; if (const Stmt *SubStmt = Cond ? IS->getThen() : IS->getElse()) { diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp --- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -188,6 +188,13 @@ template bool ByteCodeStmtGen::visitIfStmt(const IfStmt *IS) { BlockScope IfScope(this); + + if (IS->isConsteval()) { + if (!visitStmt(IS->getThen())) + return false; + return true; + } + if (auto *CondInit = IS->getInit()) if (!visitStmt(IS->getInit())) return false; diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp --- a/clang/lib/AST/JSONNodeDumper.cpp +++ b/clang/lib/AST/JSONNodeDumper.cpp @@ -1489,6 +1489,7 @@ attributeOnlyIfTrue("hasVar", IS->hasVarStorage()); attributeOnlyIfTrue("hasElse", IS->hasElseStorage()); attributeOnlyIfTrue("isConstexpr", IS->isConstexpr()); + attributeOnlyIfTrue("isConsteval", IS->isConsteval()); } void JSONNodeDumper::VisitSwitchStmt(const SwitchStmt *SS) { diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -913,8 +913,9 @@ } IfStmt::IfStmt(const ASTContext &Ctx, SourceLocation IL, bool IsConstexpr, - Stmt *Init, VarDecl *Var, Expr *Cond, SourceLocation LPL, - SourceLocation RPL, Stmt *Then, SourceLocation EL, Stmt *Else) + bool IsConsteval, Stmt *Init, VarDecl *Var, Expr *Cond, + SourceLocation LPL, SourceLocation RPL, Stmt *Then, + SourceLocation EL, Stmt *Else) : Stmt(IfStmtClass), LParenLoc(LPL), RParenLoc(RPL) { bool HasElse = Else != nullptr; bool HasVar = Var != nullptr; @@ -923,7 +924,10 @@ IfStmtBits.HasVar = HasVar; IfStmtBits.HasInit = HasInit; + assert((!IsConsteval && !IsConstexpr) || IsConsteval != IsConstexpr); + setConstexpr(IsConstexpr); + setConsteval(IsConsteval); setCond(Cond); setThen(Then); @@ -947,9 +951,10 @@ } IfStmt *IfStmt::Create(const ASTContext &Ctx, SourceLocation IL, - bool IsConstexpr, Stmt *Init, VarDecl *Var, Expr *Cond, - SourceLocation LPL, SourceLocation RPL, Stmt *Then, - SourceLocation EL, Stmt *Else) { + bool IsConstexpr, bool IsConsteval, Stmt *Init, + VarDecl *Var, Expr *Cond, SourceLocation LPL, + SourceLocation RPL, Stmt *Then, SourceLocation EL, + Stmt *Else) { bool HasElse = Else != nullptr; bool HasVar = Var != nullptr; bool HasInit = Init != nullptr; @@ -957,8 +962,8 @@ totalSizeToAlloc( NumMandatoryStmtPtr + HasElse + HasVar + HasInit, HasElse), alignof(IfStmt)); - return new (Mem) - IfStmt(Ctx, IL, IsConstexpr, Init, Var, Cond, LPL, RPL, Then, EL, Else); + return new (Mem) IfStmt(Ctx, IL, IsConstexpr, IsConsteval, Init, Var, Cond, + LPL, RPL, Then, EL, Else); } IfStmt *IfStmt::CreateEmpty(const ASTContext &Ctx, bool HasElse, bool HasVar, diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -236,6 +236,19 @@ } void StmtPrinter::PrintRawIfStmt(IfStmt *If) { + if (If->isConsteval()) { + OS << "if consteval"; + OS << NL; + PrintStmt(If->getThen()); + if (If->getElse()) { + Indent(); + OS << "else"; + PrintStmt(If->getThen()); + OS << NL; + } + return; + } + OS << "if ("; if (If->getInit()) PrintInitStmt(If->getInit(), 4); diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -948,6 +948,10 @@ OS << " has_var"; if (Node->hasElseStorage()) OS << " has_else"; + if (Node->isConstexpr()) + OS << " constexpr"; + if (Node->isConsteval()) + OS << " consteval"; } void TextNodeDumper::VisitSwitchStmt(const SwitchStmt *Node) { diff --git a/clang/lib/Analysis/BodyFarm.cpp b/clang/lib/Analysis/BodyFarm.cpp --- a/clang/lib/Analysis/BodyFarm.cpp +++ b/clang/lib/Analysis/BodyFarm.cpp @@ -463,6 +463,7 @@ auto *Out = IfStmt::Create(C, SourceLocation(), /* IsConstexpr=*/false, + /* IsConsteval=*/false, /* Init=*/nullptr, /* Var=*/nullptr, /* Cond=*/FlagCheck, @@ -549,6 +550,7 @@ // (5) Create the 'if' statement. auto *If = IfStmt::Create(C, SourceLocation(), /* IsConstexpr=*/false, + /* IsConsteval=*/false, /* Init=*/nullptr, /* Var=*/nullptr, /* Cond=*/GuardCondition, @@ -660,6 +662,7 @@ auto *If = IfStmt::Create(C, SourceLocation(), /* IsConstexpr=*/false, + /* IsConsteval=*/false, /* Init=*/nullptr, /* Var=*/nullptr, Comparison, /* LPL=*/SourceLocation(), diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -3047,7 +3047,7 @@ // control-flow transfer of '&&' or '||' go directly into the then/else // blocks directly. BinaryOperator *Cond = - I->getConditionVariable() + I->isConsteval() || I->getConditionVariable() ? nullptr : dyn_cast(I->getCond()->IgnoreParens()); CFGBlock *LastBlock; @@ -3061,7 +3061,9 @@ Block->setTerminator(I); // See if this is a known constant. - const TryResult &KnownVal = tryEvaluateBool(I->getCond()); + TryResult KnownVal; + if (!I->isConsteval()) + KnownVal = tryEvaluateBool(I->getCond()); // Add the successors. If we know that specific branches are // unreachable, inform addSuccessor() of that knowledge. diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -712,6 +712,17 @@ } void CodeGenFunction::EmitIfStmt(const IfStmt &S) { + // The else branch of a consteval statement is always the only branch that can + // be runtime evaluated + if (S.isConsteval()) { + const Stmt *Executed = S.getElse(); + if (Executed) { + RunCleanupsScope ExecutedScope(*this); + EmitStmt(Executed); + } + return; + } + // C99 6.8.4.1: The first substatement is executed if the expression compares // unequal to 0. The condition must be a scalar type. LexicalScope ConditionScope(*this, S.getCond()->getSourceRange()); diff --git a/clang/lib/CodeGen/CodeGenPGO.cpp b/clang/lib/CodeGen/CodeGenPGO.cpp --- a/clang/lib/CodeGen/CodeGenPGO.cpp +++ b/clang/lib/CodeGen/CodeGenPGO.cpp @@ -649,6 +649,13 @@ void VisitIfStmt(const IfStmt *S) { RecordStmtCount(S); + + if (S->isConsteval()) { + if (S->getElse()) + Visit(S->getElse()); + return; + } + uint64_t ParentCount = CurrentCount; if (S->getInit()) Visit(S->getInit()); diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -472,6 +472,8 @@ bool isIf(bool AllowConstexprMacro = true) const { return is(tok::kw_if) || endsSequence(tok::kw_constexpr, tok::kw_if) || + endsSequence(tok::kw_consteval, tok::kw_if) || + endsSequence(tok::kw_consteval, tok::exclaim, tok::kw_if) || (endsSequence(tok::identifier, tok::kw_if) && AllowConstexprMacro); } diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -976,9 +976,12 @@ break; case tok::kw_if: case tok::kw_while: - if (Tok->is(tok::kw_if) && CurrentToken && - CurrentToken->isOneOf(tok::kw_constexpr, tok::identifier)) - next(); + if (Tok->is(tok::kw_if) && CurrentToken){ + if(CurrentToken->isOneOf(tok::exclaim, tok::kw_consteval, tok::kw_constexpr, tok::identifier)) + next(); + if(CurrentToken && CurrentToken->is(tok::exclaim)) // if ! consteval + next(); + } if (CurrentToken && CurrentToken->is(tok::l_paren)) { next(); if (!parseParens(/*LookForDecls=*/true)) diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -2058,7 +2058,9 @@ void UnwrappedLineParser::parseIfThenElse() { assert(FormatTok->Tok.is(tok::kw_if) && "'if' expected"); nextToken(); - if (FormatTok->Tok.isOneOf(tok::kw_constexpr, tok::identifier)) + if (FormatTok->Tok.isOneOf(tok::kw_constexpr, tok::exclaim, tok::kw_consteval, tok::identifier)) + nextToken(); + if (FormatTok->Tok.is(tok::kw_consteval)) // if ! consteval nextToken(); if (FormatTok->Tok.is(tok::l_paren)) parseParens(); diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -607,6 +607,7 @@ if (LangOpts.CPlusPlus2b) { Builder.defineMacro("__cpp_implicit_move", "202011L"); Builder.defineMacro("__cpp_size_t_suffix", "202011L"); + Builder.defineMacro("__cpp_if_consteval", "202106L"); } if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "201811L"); diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1338,25 +1338,52 @@ /// 'if' '(' expression ')' statement 'else' statement /// [C++] 'if' '(' condition ')' statement /// [C++] 'if' '(' condition ')' statement 'else' statement +/// [C++] 'if' consteval statement 'else' statement /// StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { assert(Tok.is(tok::kw_if) && "Not an if stmt!"); SourceLocation IfLoc = ConsumeToken(); // eat the 'if'. bool IsConstexpr = false; + bool IsConsteval = false; + SourceLocation NotLocation; + SourceLocation ConstevalLoc; + if (Tok.is(tok::kw_constexpr)) { Diag(Tok, getLangOpts().CPlusPlus17 ? diag::warn_cxx14_compat_constexpr_if : diag::ext_constexpr_if); IsConstexpr = true; ConsumeToken(); + } else { + if (Tok.is(tok::exclaim)) { + NotLocation = ConsumeToken(); + } + + if (Tok.is(tok::kw_consteval)) { + Diag(Tok, getLangOpts().CPlusPlus17 ? diag::warn_cxx14_compat_constexpr_if + : diag::ext_constexpr_if); + IsConsteval = true; + ConstevalLoc = ConsumeToken(); + } + + if (!IsConsteval && NotLocation.isValid()) { + Diag(NotLocation, diag::err_expected_lparen_after) << "if"; + SkipUntil(tok::semi); + return StmtError(); + } } - if (Tok.isNot(tok::l_paren)) { + if (!IsConsteval && Tok.isNot(tok::l_paren)) { Diag(Tok, diag::err_expected_lparen_after) << "if"; SkipUntil(tok::semi); return StmtError(); } + if (IsConsteval) { + Diag(Tok, getLangOpts().CPlusPlus2b ? diag::warn_cxx20_compat_consteval_if + : diag::ext_consteval_if); + } + bool C99orCXX = getLangOpts().C99 || getLangOpts().CPlusPlus; // C99 6.8.4p3 - In C99, the if statement is a block. This is not @@ -1378,15 +1405,18 @@ Sema::ConditionResult Cond; SourceLocation LParen; SourceLocation RParen; - if (ParseParenExprOrCondition(&InitStmt, Cond, IfLoc, - IsConstexpr ? Sema::ConditionKind::ConstexprIf - : Sema::ConditionKind::Boolean, - &LParen, &RParen)) - return StmtError(); - llvm::Optional ConstexprCondition; - if (IsConstexpr) - ConstexprCondition = Cond.getKnownValue(); + if (!IsConsteval) { + + if (ParseParenExprOrCondition(&InitStmt, Cond, IfLoc, + IsConstexpr ? Sema::ConditionKind::ConstexprIf + : Sema::ConditionKind::Boolean, + &LParen, &RParen)) + return StmtError(); + + if (IsConstexpr) + ConstexprCondition = Cond.getKnownValue(); + } bool IsBracedThen = Tok.is(tok::l_brace); @@ -1418,10 +1448,16 @@ SourceLocation InnerStatementTrailingElseLoc; StmtResult ThenStmt; { + bool ShouldEnter = + (ConstexprCondition && !*ConstexprCondition) || IsConsteval; + Sema::ExpressionEvaluationContext Context = + Sema::ExpressionEvaluationContext::DiscardedStatement; + if (NotLocation.isInvalid() && IsConsteval) + Context = Sema::ExpressionEvaluationContext::ImmediateFunctionContext; + EnterExpressionEvaluationContext PotentiallyDiscarded( - Actions, Sema::ExpressionEvaluationContext::DiscardedStatement, nullptr, - Sema::ExpressionEvaluationContextRecord::EK_Other, - /*ShouldEnter=*/ConstexprCondition && !*ConstexprCondition); + Actions, Context, nullptr, + Sema::ExpressionEvaluationContextRecord::EK_Other, ShouldEnter); ThenStmt = ParseStatement(&InnerStatementTrailingElseLoc); } @@ -1456,11 +1492,16 @@ Tok.is(tok::l_brace)); MisleadingIndentationChecker MIChecker(*this, MSK_else, ElseLoc); + bool ShouldEnter = + (ConstexprCondition && *ConstexprCondition) || IsConsteval; + Sema::ExpressionEvaluationContext Context = + Sema::ExpressionEvaluationContext::DiscardedStatement; + if (NotLocation.isValid() && IsConsteval) + Context = Sema::ExpressionEvaluationContext::ImmediateFunctionContext; EnterExpressionEvaluationContext PotentiallyDiscarded( - Actions, Sema::ExpressionEvaluationContext::DiscardedStatement, nullptr, - Sema::ExpressionEvaluationContextRecord::EK_Other, - /*ShouldEnter=*/ConstexprCondition && *ConstexprCondition); + Actions, Context, nullptr, + Sema::ExpressionEvaluationContextRecord::EK_Other, ShouldEnter); ElseStmt = ParseStatement(); if (ElseStmt.isUsable()) @@ -1488,14 +1529,39 @@ return StmtError(); } + if (IsConsteval) { + if (!isa_and_nonnull(ThenStmt.get())) { + Diag(ConstevalLoc, diag::err_expected_after) << "consteval" + << "{"; + return StmtError(); + } + if (!ElseStmt.isUnset() && + !isa_and_nonnull(ElseStmt.get())) { + Diag(ElseLoc, diag::err_expected_after) << "else" + << "{"; + return StmtError(); + } + } + // Now if either are invalid, replace with a ';'. if (ThenStmt.isInvalid()) ThenStmt = Actions.ActOnNullStmt(ThenStmtLoc); if (ElseStmt.isInvalid()) ElseStmt = Actions.ActOnNullStmt(ElseStmtLoc); - return Actions.ActOnIfStmt(IfLoc, IsConstexpr, LParen, InitStmt.get(), Cond, - RParen, ThenStmt.get(), ElseLoc, ElseStmt.get()); + if (IsConsteval && NotLocation.isValid()) { + if (ElseStmt.isUnset()) + ElseStmt = Actions.ActOnNullStmt(ThenStmtLoc); + std::swap(ThenStmt, ElseStmt); + } + Sema::IfKind Kind = Sema::IfKind_Default; + if (IsConstexpr) + Kind = Sema::IfKind_Constexpr; + if (IsConsteval) + Kind = Sema::IfKind_Consteval; + + return Actions.ActOnIfStmt(IfLoc, Kind, LParen, InitStmt.get(), Cond, RParen, + ThenStmt.get(), ElseLoc, ElseStmt.get()); } /// ParseSwitchStatement diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -377,11 +377,15 @@ case Stmt::IfStmtClass: { IfStmt *IS = cast(S); - if (!(IS->isConstexpr() || IS->isObjCAvailabilityCheck())) + if (!(IS->isConstexpr() || IS->isConsteval() || + IS->isObjCAvailabilityCheck())) break; - unsigned Diag = IS->isConstexpr() ? diag::note_protected_by_constexpr_if - : diag::note_protected_by_if_available; + unsigned Diag = diag::note_protected_by_if_available; + if (IS->isConstexpr()) + Diag = diag::note_protected_by_constexpr_if; + else if (IS->isConsteval()) + Diag = diag::note_protected_by_if_consteval; if (VarDecl *Var = IS->getConditionVariable()) BuildScopeInformation(Var, ParentScope); @@ -389,7 +393,9 @@ // Cannot jump into the middle of the condition. unsigned NewParentScope = Scopes.size(); Scopes.push_back(GotoScope(ParentScope, Diag, 0, IS->getBeginLoc())); - BuildScopeInformation(IS->getCond(), NewParentScope); + + if (!IS->isConsteval()) + BuildScopeInformation(IS->getCond(), NewParentScope); // Jumps into either arm of an 'if constexpr' are not allowed. NewParentScope = Scopes.size(); 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 @@ -8202,7 +8202,7 @@ if (ReturnFalse.isInvalid()) return StmtError(); - return S.ActOnIfStmt(Loc, false, Loc, nullptr, + return S.ActOnIfStmt(Loc, Sema::IfKind_Default, Loc, nullptr, S.ActOnCondition(nullptr, Loc, NotCond.get(), Sema::ConditionKind::Boolean), Loc, ReturnFalse.get(), SourceLocation(), nullptr); @@ -8357,7 +8357,7 @@ return StmtError(); // if (...) - return S.ActOnIfStmt(Loc, /*IsConstexpr=*/false, Loc, InitStmt, Cond, Loc, + return S.ActOnIfStmt(Loc, Sema::IfKind_Default, Loc, InitStmt, Cond, Loc, ReturnStmt.get(), /*ElseLoc=*/SourceLocation(), /*Else=*/nullptr); } 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 @@ -25,6 +25,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/ExprOpenMP.h" #include "clang/AST/OperationKinds.h" +#include "clang/AST/ParentMapContext.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/Builtins.h" @@ -16641,7 +16642,7 @@ ExprResult Sema::CheckForImmediateInvocation(ExprResult E, FunctionDecl *Decl) { if (isUnevaluatedContext() || !E.isUsable() || !Decl || !Decl->isConsteval() || isConstantEvaluated() || - RebuildingImmediateInvocation) + RebuildingImmediateInvocation || isImmediateFunctionContext()) return E; /// Opportunistically remove the callee from ReferencesToConsteval if we can. @@ -16912,6 +16913,8 @@ // An expression or conversion is potentially constant evaluated if it is switch (SemaRef.ExprEvalContexts.back().Context) { case Sema::ExpressionEvaluationContext::ConstantEvaluated: + case Sema::ExpressionEvaluationContext::ImmediateFunctionContext: + // -- a manifestly constant-evaluated expression, case Sema::ExpressionEvaluationContext::PotentiallyEvaluated: case Sema::ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed: @@ -17034,6 +17037,7 @@ return OdrUseContext::None; case Sema::ExpressionEvaluationContext::ConstantEvaluated: + case Sema::ExpressionEvaluationContext::ImmediateFunctionContext: case Sema::ExpressionEvaluationContext::PotentiallyEvaluated: Result = OdrUseContext::Used; break; @@ -18925,6 +18929,7 @@ break; case ExpressionEvaluationContext::ConstantEvaluated: + case ExpressionEvaluationContext::ImmediateFunctionContext: // Relevant diagnostics should be produced by constant evaluation. break; diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp --- a/clang/lib/Sema/SemaExprMember.cpp +++ b/clang/lib/Sema/SemaExprMember.cpp @@ -144,6 +144,7 @@ case Sema::ExpressionEvaluationContext::DiscardedStatement: case Sema::ExpressionEvaluationContext::ConstantEvaluated: + case Sema::ExpressionEvaluationContext::ImmediateFunctionContext: case Sema::ExpressionEvaluationContext::PotentiallyEvaluated: case Sema::ExpressionEvaluationContext::PotentiallyEvaluatedIfUsed: break; 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 @@ -1245,7 +1245,7 @@ // cleanups from the enclosing full-expression. PushExpressionEvaluationContext( LSI->CallOperator->isConsteval() - ? ExpressionEvaluationContext::ConstantEvaluated + ? ExpressionEvaluationContext::ImmediateFunctionContext : ExpressionEvaluationContext::PotentiallyEvaluated); } @@ -1948,6 +1948,7 @@ // ratified, it lays out the exact set of conditions where we shouldn't // allow a lambda-expression. case ExpressionEvaluationContext::ConstantEvaluated: + case ExpressionEvaluationContext::ImmediateFunctionContext: // We don't actually diagnose this case immediately, because we // could be within a context where we might find out later that // the expression is potentially evaluated (e.g., for typeid). diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -858,7 +858,7 @@ }; } -StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, +StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc, IfKind Kind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *thenStmt, SourceLocation ElseLoc, @@ -873,15 +873,15 @@ Expr *CondExpr = Cond.get().second; // Only call the CommaVisitor when not C89 due to differences in scope flags. - if ((getLangOpts().C99 || getLangOpts().CPlusPlus) && + if (CondExpr && (getLangOpts().C99 || getLangOpts().CPlusPlus) && !Diags.isIgnored(diag::warn_comma_operator, CondExpr->getExprLoc())) CommaVisitor(*this).Visit(CondExpr); - if (!elseStmt) + if (Kind != IfKind_Consteval && !elseStmt) DiagnoseEmptyStmtBody(CondExpr->getEndLoc(), thenStmt, diag::warn_empty_if_body); - if (IsConstexpr) { + if (Kind == IfKind_Constexpr || Kind == IfKind_Consteval) { auto DiagnoseLikelihood = [&](const Stmt *S) { if (const Attr *A = Stmt::getLikelihoodAttr(S)) { Diags.Report(A->getLocation(), @@ -908,11 +908,25 @@ } } - return BuildIfStmt(IfLoc, IsConstexpr, LParenLoc, InitStmt, Cond, RParenLoc, + if (Kind == IfKind_Consteval) { + bool Immediate = isImmediateFunctionContext(); + if (CurContext->isFunctionOrMethod()) { + const Decl *D = Decl::castFromDeclContext(CurContext); + if (D->getAsFunction() && D->getAsFunction()->isConsteval()) { + Immediate = true; + } + } + if (isUnevaluatedContext() || Immediate) { + Diags.Report(IfLoc, diag::warn_if_consteval_always_true) + << (Immediate ? 1 : 0); + } + } + + return BuildIfStmt(IfLoc, Kind, LParenLoc, InitStmt, Cond, RParenLoc, thenStmt, ElseLoc, elseStmt); } -StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, +StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, IfKind Kind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *thenStmt, SourceLocation ElseLoc, @@ -920,10 +934,12 @@ if (Cond.isInvalid()) return StmtError(); - if (IsConstexpr || isa(Cond.get().second)) + if (Kind != IfKind_Default || + isa(Cond.get().second)) setFunctionHasBranchProtectedScope(); - return IfStmt::Create(Context, IfLoc, IsConstexpr, InitStmt, Cond.get().first, + return IfStmt::Create(Context, IfLoc, Kind == IfKind_Constexpr, + Kind == IfKind_Consteval, InitStmt, Cond.get().first, Cond.get().second, LParenLoc, RParenLoc, thenStmt, ElseLoc, elseStmt); } 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 @@ -1320,12 +1320,12 @@ /// /// By default, performs semantic analysis to build the new statement. /// Subclasses may override this routine to provide different behavior. - StmtResult RebuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, + StmtResult RebuildIfStmt(SourceLocation IfLoc, Sema::IfKind Kind, SourceLocation LParenLoc, Sema::ConditionResult Cond, SourceLocation RParenLoc, Stmt *Init, Stmt *Then, SourceLocation ElseLoc, Stmt *Else) { - return getSema().ActOnIfStmt(IfLoc, IsConstexpr, LParenLoc, Init, Cond, - RParenLoc, Then, ElseLoc, Else); + return getSema().ActOnIfStmt(IfLoc, Kind, LParenLoc, Init, Cond, RParenLoc, + Then, ElseLoc, Else); } /// Start building a new switch statement. @@ -7371,13 +7371,16 @@ if (Init.isInvalid()) return StmtError(); - // Transform the condition - Sema::ConditionResult Cond = getDerived().TransformCondition( - S->getIfLoc(), S->getConditionVariable(), S->getCond(), - S->isConstexpr() ? Sema::ConditionKind::ConstexprIf - : Sema::ConditionKind::Boolean); - if (Cond.isInvalid()) - return StmtError(); + Sema::ConditionResult Cond; + if (!S->isConsteval()) { + // Transform the condition + Cond = getDerived().TransformCondition( + S->getIfLoc(), S->getConditionVariable(), S->getCond(), + S->isConstexpr() ? Sema::ConditionKind::ConstexprIf + : Sema::ConditionKind::Boolean); + if (Cond.isInvalid()) + return StmtError(); + } // If this is a constexpr if, determine which arm we should instantiate. llvm::Optional ConstexprConditionValue; @@ -7386,7 +7389,8 @@ // Transform the "then" branch. StmtResult Then; - if (!ConstexprConditionValue || *ConstexprConditionValue) { + if (S->isConsteval() || !ConstexprConditionValue || + *ConstexprConditionValue) { Then = getDerived().TransformStmt(S->getThen()); if (Then.isInvalid()) return StmtError(); @@ -7396,7 +7400,8 @@ // Transform the "else" branch. StmtResult Else; - if (!ConstexprConditionValue || !*ConstexprConditionValue) { + if (!S->isConsteval() && + (!ConstexprConditionValue || !*ConstexprConditionValue)) { Else = getDerived().TransformStmt(S->getElse()); if (Else.isInvalid()) return StmtError(); @@ -7409,9 +7414,15 @@ Else.get() == S->getElse()) return S; - return getDerived().RebuildIfStmt( - S->getIfLoc(), S->isConstexpr(), S->getLParenLoc(), Cond, - S->getRParenLoc(), Init.get(), Then.get(), S->getElseLoc(), Else.get()); + Sema::IfKind Kind = Sema::IfKind_Default; + if (S->isConstexpr()) + Kind = Sema::IfKind_Constexpr; + if (S->isConsteval()) + Kind = Sema::IfKind_Consteval; + + return getDerived().RebuildIfStmt(S->getIfLoc(), Kind, S->getLParenLoc(), + Cond, S->getRParenLoc(), Init.get(), + Then.get(), S->getElseLoc(), Else.get()); } template diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -213,11 +213,13 @@ void ASTStmtReader::VisitIfStmt(IfStmt *S) { VisitStmt(S); - S->setConstexpr(Record.readInt()); bool HasElse = Record.readInt(); bool HasVar = Record.readInt(); bool HasInit = Record.readInt(); + S->setConstexpr(Record.readInt()); + S->setConsteval(Record.readInt()); + S->setCond(Record.readSubExpr()); S->setThen(Record.readSubStmt()); if (HasElse) @@ -2753,9 +2755,9 @@ case STMT_IF: S = IfStmt::CreateEmpty( Context, - /* HasElse=*/Record[ASTStmtReader::NumStmtFields + 1], - /* HasVar=*/Record[ASTStmtReader::NumStmtFields + 2], - /* HasInit=*/Record[ASTStmtReader::NumStmtFields + 3]); + /* HasElse=*/Record[ASTStmtReader::NumStmtFields], + /* HasVar=*/Record[ASTStmtReader::NumStmtFields + 1], + /* HasInit=*/Record[ASTStmtReader::NumStmtFields + 2]); break; case STMT_SWITCH: diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -138,11 +138,13 @@ bool HasVar = S->getConditionVariableDeclStmt() != nullptr; bool HasInit = S->getInit() != nullptr; - Record.push_back(S->isConstexpr()); Record.push_back(HasElse); Record.push_back(HasVar); Record.push_back(HasInit); + Record.push_back(S->isConstexpr()); + Record.push_back(S->isConsteval()); + Record.AddStmt(S->getCond()); Record.AddStmt(S->getThen()); if (HasElse) diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -1339,7 +1339,10 @@ } bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) { - const Expr *Condition = I->getCond()->IgnoreParenImpCasts(); + const Expr *Condition = I->getCond(); + if (!Condition) + return true; + Condition = Condition->IgnoreParenImpCasts(); if (isCheckingPlurality(Condition)) { MatchingStatements.push_back(I); InMatchingStatement = true; diff --git a/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p4.cpp b/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p4.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p4.cpp @@ -0,0 +1,111 @@ +// RUN: %clang_cc1 -std=c++2b -verify %s + +void test_consteval() { + if consteval (void) + 0; // expected-error {{expected { after consteval}} + if consteval { + (void)0; + } else + (void)0; // expected-error {{expected { after else}} + + static_assert([] { + if consteval { + return 0; + } + return 1; + }() == 0); + + static_assert([] { + if consteval { + return 0; + } else { + return 1; + } + }() == 0); + + static_assert([] { + if !consteval { + return 0; + } else { + return 1; + } + }() == 1); + + static_assert([] { + if not consteval { + return 0; + } + return 1; + }() == 1); +} + +void test_consteval_jumps() { + if consteval { // expected-note 4{{jump enters controlled statement of if consteval}} + goto a; + goto b; // expected-error {{cannot jump from this goto statement to its label}} + a:; + } else { + goto b; + goto a; // expected-error {{cannot jump from this goto statement to its label}} + b:; + } + goto a; // expected-error {{cannot jump from this goto statement to its label}} + goto b; // expected-error {{cannot jump from this goto statement to its label}} +} + +void test_consteval_switch() { + int x = 42; + switch (x) { + if consteval { // expected-note 2{{jump enters controlled statement of if consteval}} + case 1:; // expected-error {{cannot jump from switch statement to this case label}} + default:; // expected-error {{cannot jump from switch statement to this case label}} + } else { + } + } + switch (x) { + if consteval { // expected-note 2{{jump enters controlled statement of if consteval}} + } else { + case 2:; // expected-error {{cannot jump from switch statement to this case label}} + default:; // expected-error {{cannot jump from switch statement to this case label}} + } + } +} + +consteval int f(int i) { return i; } +constexpr int g(int i) { + if consteval { + return f(i); + } else { + return 42; + } +} +static_assert(g(10) == 10); + +constexpr int h(int i) { // expected-note {{declared here}} + if !consteval { + return f(i); // expected-error {{call to consteval function 'f' is not a constant expression}}\ + // expected-note {{cannot be used in a constant expression}} + } + return 0; +} + +consteval void warn_in_consteval() { + if consteval { // expected-warning {{if consteval is always true in an immediate context}} + if consteval { + } // expected-warning {{if consteval is always true in an immediate context}} + } +} + +constexpr void warn_in_consteval2() { + if consteval { + if consteval { + } // expected-warning {{if consteval is always true in an immediate context}} + } +} + +auto y = []() consteval { + if consteval { // expected-warning {{if consteval is always true in an immediate context}} + if consteval { + } // expected-warning {{if consteval is always true in an immediate context}} + } +}; diff --git a/clang/test/CodeGenCXX/cxx2b-consteval-if.cpp b/clang/test/CodeGenCXX/cxx2b-consteval-if.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/cxx2b-consteval-if.cpp @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -std=c++2b %s -emit-llvm -o - | FileCheck %s --implicit-check-not=should_not_be_used + +void should_be_used_1(); +void should_be_used_2(); +void should_be_used_3(); +constexpr void should_not_be_used() {} + +constexpr void f() { + if consteval { + should_not_be_used(); + } else { + should_be_used_1(); + } + + if !consteval { + should_be_used_2(); + } + + if !consteval { + should_be_used_3(); + } else { + should_not_be_used(); + } +} + +void g() { + f(); +} + +// CHECK: should_be_used_1 +// CHECK: should_be_used_2 +// CHECK: should_be_used_3