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 @@ -20,6 +20,7 @@ #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Basic/Specifiers.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/BitmaskEnum.h" #include "llvm/ADT/PointerIntPair.h" @@ -160,8 +161,8 @@ unsigned : NumStmtBits; - /// True if this if statement is a constexpr if. - unsigned IsConstexpr : 1; + /// Whether this is a constexpr if, or a consteval if, or neither. + unsigned Kind : 3; /// True if this if statement has storage for an else statement. unsigned HasElse : 1; @@ -1950,8 +1951,8 @@ 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, + IfStmt(const ASTContext &Ctx, SourceLocation IL, IfStatementKind Kind, + Stmt *Init, VarDecl *Var, Expr *Cond, SourceLocation LParenLoc, SourceLocation RParenLoc, Stmt *Then, SourceLocation EL, Stmt *Else); /// Build an empty if/then/else statement. @@ -1960,9 +1961,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, - SourceLocation EL = SourceLocation(), + IfStatementKind Kind, Stmt *Init, VarDecl *Var, + Expr *Cond, SourceLocation LPL, SourceLocation RPL, + Stmt *Then, SourceLocation EL = SourceLocation(), Stmt *Else = nullptr); /// Create an empty IfStmt optionally with storage for an else statement, @@ -2077,8 +2078,30 @@ *getTrailingObjects() = ElseLoc; } - bool isConstexpr() const { return IfStmtBits.IsConstexpr; } - void setConstexpr(bool C) { IfStmtBits.IsConstexpr = C; } + bool isConsteval() const { + return getStatementKind() == IfStatementKind::ConstevalNonNegated || + getStatementKind() == IfStatementKind::ConstevalNegated; + } + + bool isNonNegatedConsteval() const { + return getStatementKind() == IfStatementKind::ConstevalNonNegated; + } + + bool isNegatedConsteval() const { + return getStatementKind() == IfStatementKind::ConstevalNegated; + } + + bool isConstexpr() const { + return getStatementKind() == IfStatementKind::Constexpr; + } + + void setStatementKind(IfStatementKind Kind) { + IfStmtBits.Kind = static_cast(Kind); + } + + IfStatementKind getStatementKind() const { + return static_cast(IfStmtBits.Kind); + } /// If this is an 'if constexpr', determine which substatement will be taken. /// Otherwise, or if the condition is value-dependent, returns None. @@ -2101,13 +2124,19 @@ // Iterators over subexpressions. The iterators will include iterating // over the initialization expression referenced by the condition variable. child_range children() { - return child_range(getTrailingObjects(), + // We always store a condition, but there is none for consteval if + // statements, so skip it. + return child_range(getTrailingObjects() + + (isConsteval() ? thenOffset() : 0), getTrailingObjects() + numTrailingObjects(OverloadToken())); } const_child_range children() const { - return const_child_range(getTrailingObjects(), + // We always store a condition, but there is none for consteval if + // statements, so skip it. + return const_child_range(getTrailingObjects() + + (isConsteval() ? thenOffset() : 0), getTrailingObjects() + numTrailingObjects(OverloadToken())); } 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 @@ -1505,6 +1505,10 @@ def err_static_assert_requirement_failed : Error< "static_assert failed due to requirement '%0'%select{ %2|}1">; +def warn_consteval_if_always_true : Warning< + "consteval if 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< @@ -3312,11 +3316,11 @@ InGroup; def note_attribute_has_no_effect_on_infinite_loop_here : Note< "annotating the infinite loop here">; -def warn_attribute_has_no_effect_on_if_constexpr : Warning< - "attribute %0 has no effect when annotating an 'if constexpr' statement">, +def warn_attribute_has_no_effect_on_compile_time_if : Warning< + "attribute %0 has no effect when annotating an 'if %select{constexpr|consteval}1' statement">, InGroup; -def note_attribute_has_no_effect_on_if_constexpr_here : Note< - "annotating the 'if constexpr' statement here">; +def note_attribute_has_no_effect_on_compile_time_if_here : Note< + "annotating the 'if %select{constexpr|consteval}0' statement here">; def err_decl_attribute_invalid_on_stmt : Error< "%0 attribute cannot be applied to a statement">; def err_stmt_attribute_invalid_on_decl : Error< @@ -5941,6 +5945,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_consteval_if : Note< + "jump enters controlled statement of consteval if">; 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/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h --- a/clang/include/clang/Basic/Specifiers.h +++ b/clang/include/clang/Basic/Specifiers.h @@ -31,6 +31,15 @@ /// Define the kind of constexpr specifier. enum class ConstexprSpecKind { Unspecified, Constexpr, Consteval, Constinit }; + /// In an if statement, this denotes whether the the statement is + /// a constexpr or consteval if statement. + enum class IfStatementKind : unsigned { + Ordinary, + Constexpr, + ConstevalNonNegated, + ConstevalNegated + }; + /// Specifies the width of a type, e.g., short, long, or long long. enum class TypeSpecifierWidth { Unspecified, Short, Long, LongLong }; 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 @@ -1214,6 +1214,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 a consteval if 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. @@ -1302,8 +1307,14 @@ Context == ExpressionEvaluationContext::UnevaluatedAbstract || Context == ExpressionEvaluationContext::UnevaluatedList; } + bool isConstantEvaluated() const { - return Context == ExpressionEvaluationContext::ConstantEvaluated; + return Context == ExpressionEvaluationContext::ConstantEvaluated || + Context == ExpressionEvaluationContext::ImmediateFunctionContext; + } + + bool isImmediateFunctionContext() const { + return Context == ExpressionEvaluationContext::ImmediateFunctionContext; } }; @@ -4705,11 +4716,12 @@ Stmt *SubStmt); class ConditionResult; - StmtResult ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, + + StmtResult ActOnIfStmt(SourceLocation IfLoc, IfStatementKind StatementKind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *ThenVal, SourceLocation ElseLoc, Stmt *ElseVal); - StmtResult BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, + StmtResult BuildIfStmt(SourceLocation IfLoc, IfStatementKind StatementKind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *ThenVal, SourceLocation ElseLoc, Stmt *ElseVal); @@ -9120,6 +9132,19 @@ return ExprEvalContexts.back().isUnevaluated(); } + bool isImmediateFunctionContext() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + for (const ExpressionEvaluationContextRecord &context : + llvm::reverse(ExprEvalContexts)) { + if (context.isImmediateFunctionContext()) + return true; + if (context.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 @@ -6361,7 +6361,7 @@ if (Err) return std::move(Err); - return IfStmt::Create(Importer.getToContext(), ToIfLoc, S->isConstexpr(), + return IfStmt::Create(Importer.getToContext(), ToIfLoc, S->getStatementKind(), ToInit, ToConditionVariable, ToCond, ToLParenLoc, ToRParenLoc, ToThen, ToElseLoc, ToElse); } 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 = IS->isNonNegatedConsteval(); + 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,12 @@ template bool ByteCodeStmtGen::visitIfStmt(const IfStmt *IS) { BlockScope IfScope(this); + + if (IS->isNonNegatedConsteval()) + return visitStmt(IS->getThen()); + if (IS->isNegatedConsteval()) + return IS->getElse() ? visitStmt(IS->getElse()) : 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,8 @@ attributeOnlyIfTrue("hasVar", IS->hasVarStorage()); attributeOnlyIfTrue("hasElse", IS->hasElseStorage()); attributeOnlyIfTrue("isConstexpr", IS->isConstexpr()); + attributeOnlyIfTrue("isConsteval", IS->isConsteval()); + attributeOnlyIfTrue("constevalIsNegated", IS->isNegatedConsteval()); } 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 @@ -912,7 +912,7 @@ }); } -IfStmt::IfStmt(const ASTContext &Ctx, SourceLocation IL, bool IsConstexpr, +IfStmt::IfStmt(const ASTContext &Ctx, SourceLocation IL, IfStatementKind Kind, Stmt *Init, VarDecl *Var, Expr *Cond, SourceLocation LPL, SourceLocation RPL, Stmt *Then, SourceLocation EL, Stmt *Else) : Stmt(IfStmtClass), LParenLoc(LPL), RParenLoc(RPL) { @@ -923,7 +923,7 @@ IfStmtBits.HasVar = HasVar; IfStmtBits.HasInit = HasInit; - setConstexpr(IsConstexpr); + setStatementKind(Kind); setCond(Cond); setThen(Then); @@ -947,9 +947,9 @@ } 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) { + IfStatementKind Kind, 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; @@ -958,7 +958,7 @@ NumMandatoryStmtPtr + HasElse + HasVar + HasInit, HasElse), alignof(IfStmt)); return new (Mem) - IfStmt(Ctx, IL, IsConstexpr, Init, Var, Cond, LPL, RPL, Then, EL, Else); + IfStmt(Ctx, IL, Kind, 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,22 @@ } void StmtPrinter::PrintRawIfStmt(IfStmt *If) { + if (If->isConsteval()) { + OS << "if "; + if (If->isNegatedConsteval()) + OS << "!"; + OS << "consteval"; + OS << NL; + PrintStmt(If->getThen()); + if (Stmt *Else = If->getElse()) { + Indent(); + OS << "else"; + PrintStmt(Else); + 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,14 @@ OS << " has_var"; if (Node->hasElseStorage()) OS << " has_else"; + if (Node->isConstexpr()) + OS << " constexpr"; + if (Node->isConsteval()) { + OS << " "; + if (Node->isNegatedConsteval()) + OS << "!"; + 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 @@ -461,8 +461,7 @@ DerefType); auto *Out = - IfStmt::Create(C, SourceLocation(), - /* IsConstexpr=*/false, + IfStmt::Create(C, SourceLocation(), IfStatementKind::Ordinary, /* Init=*/nullptr, /* Var=*/nullptr, /* Cond=*/FlagCheck, @@ -547,8 +546,7 @@ Expr *GuardCondition = M.makeComparison(LValToRval, DoneValue, BO_NE); // (5) Create the 'if' statement. - auto *If = IfStmt::Create(C, SourceLocation(), - /* IsConstexpr=*/false, + auto *If = IfStmt::Create(C, SourceLocation(), IfStatementKind::Ordinary, /* Init=*/nullptr, /* Var=*/nullptr, /* Cond=*/GuardCondition, @@ -658,8 +656,7 @@ /// Construct the If. auto *If = - IfStmt::Create(C, SourceLocation(), - /* IsConstexpr=*/false, + IfStmt::Create(C, SourceLocation(), IfStatementKind::Ordinary, /* 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 @@ -713,6 +713,17 @@ } void CodeGenFunction::EmitIfStmt(const IfStmt &S) { + // The else branch of a consteval if statement is always the only branch that + // can be runtime evaluated. + if (S.isConsteval()) { + const Stmt *Executed = S.isNegatedConsteval() ? S.getThen() : 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,14 @@ void VisitIfStmt(const IfStmt *S) { RecordStmtCount(S); + + if (S->isConsteval()) { + const Stmt *Stm = S->isNegatedConsteval() ? S->getThen() : S->getElse(); + if (Stm) + Visit(Stm); + return; + } + uint64_t ParentCount = CurrentCount; if (S->getInit()) Visit(S->getInit()); 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,20 +1338,36 @@ /// 'if' '(' expression ')' statement 'else' statement /// [C++] 'if' '(' condition ')' statement /// [C++] 'if' '(' condition ')' statement 'else' statement +/// [C++23] 'if' '!' [opt] consteval compound-statement +/// [C++23] 'if' '!' [opt] consteval compound-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.isNot(tok::l_paren)) { + if (Tok.is(tok::kw_consteval)) { + Diag(Tok, getLangOpts().CPlusPlus2b ? diag::warn_cxx20_compat_consteval_if + : diag::ext_consteval_if); + IsConsteval = true; + ConstevalLoc = ConsumeToken(); + } + } + if (!IsConsteval && (NotLocation.isValid() || Tok.isNot(tok::l_paren))) { Diag(Tok, diag::err_expected_lparen_after) << "if"; SkipUntil(tok::semi); return StmtError(); @@ -1378,15 +1394,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 +1437,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 +1481,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 +1518,40 @@ return StmtError(); } + if (IsConsteval) { + auto IsCompoundStatement = [](const Stmt *S) { + if (const auto *Outer = dyn_cast_or_null(S)) + S = Outer->getSubStmt(); + return isa_and_nonnull(S); + }; + + if (!IsCompoundStatement(ThenStmt.get())) { + Diag(ConstevalLoc, diag::err_expected_after) << "consteval" + << "{"; + return StmtError(); + } + if (!ElseStmt.isUnset() && !IsCompoundStatement(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()); + IfStatementKind Kind = IfStatementKind::Ordinary; + if (IsConstexpr) + Kind = IfStatementKind::Constexpr; + else if (IsConsteval) + Kind = NotLocation.isValid() ? IfStatementKind::ConstevalNegated + : IfStatementKind::ConstevalNonNegated; + + 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_consteval_if; 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, IfStatementKind::Ordinary, Loc, nullptr, S.ActOnCondition(nullptr, Loc, NotCond.get(), Sema::ConditionKind::Boolean), Loc, ReturnFalse.get(), SourceLocation(), nullptr); @@ -8357,8 +8357,8 @@ return StmtError(); // if (...) - return S.ActOnIfStmt(Loc, /*IsConstexpr=*/false, Loc, InitStmt, Cond, Loc, - ReturnStmt.get(), + return S.ActOnIfStmt(Loc, IfStatementKind::Ordinary, 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" @@ -16642,7 +16643,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. @@ -16913,6 +16914,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: @@ -17035,6 +17038,7 @@ return OdrUseContext::None; case Sema::ExpressionEvaluationContext::ConstantEvaluated: + case Sema::ExpressionEvaluationContext::ImmediateFunctionContext: case Sema::ExpressionEvaluationContext::PotentiallyEvaluated: Result = OdrUseContext::Used; break; @@ -18958,6 +18962,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 @@ -862,7 +862,8 @@ }; } -StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, +StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc, + IfStatementKind StatementKind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *thenStmt, SourceLocation ElseLoc, @@ -875,25 +876,36 @@ IfLoc), false); + bool ConstevalOrNegatedConsteval = + StatementKind == IfStatementKind::ConstevalNonNegated || + StatementKind == IfStatementKind::ConstevalNegated; + Expr *CondExpr = Cond.get().second; + assert((CondExpr || ConstevalOrNegatedConsteval) && + "If statement: missing condition"); // 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 (!ConstevalOrNegatedConsteval && !elseStmt) DiagnoseEmptyStmtBody(CondExpr->getEndLoc(), thenStmt, diag::warn_empty_if_body); - if (IsConstexpr) { + if (ConstevalOrNegatedConsteval || + StatementKind == IfStatementKind::Constexpr) { auto DiagnoseLikelihood = [&](const Stmt *S) { if (const Attr *A = Stmt::getLikelihoodAttr(S)) { Diags.Report(A->getLocation(), - diag::warn_attribute_has_no_effect_on_if_constexpr) - << A << A->getRange(); + diag::warn_attribute_has_no_effect_on_compile_time_if) + << A << ConstevalOrNegatedConsteval << A->getRange(); Diags.Report(IfLoc, - diag::note_attribute_has_no_effect_on_if_constexpr_here) - << SourceRange(IfLoc, LParenLoc.getLocWithOffset(-1)); + diag::note_attribute_has_no_effect_on_compile_time_if_here) + << ConstevalOrNegatedConsteval + << SourceRange(IfLoc, (ConstevalOrNegatedConsteval + ? thenStmt->getBeginLoc() + : LParenLoc) + .getLocWithOffset(-1)); } }; DiagnoseLikelihood(thenStmt); @@ -912,11 +924,24 @@ } } - return BuildIfStmt(IfLoc, IsConstexpr, LParenLoc, InitStmt, Cond, RParenLoc, + if (ConstevalOrNegatedConsteval) { + bool Immediate = isImmediateFunctionContext(); + if (CurContext->isFunctionOrMethod()) { + const auto *FD = + dyn_cast(Decl::castFromDeclContext(CurContext)); + if (FD && FD->isConsteval()) + Immediate = true; + } + if (isUnevaluatedContext() || Immediate) + Diags.Report(IfLoc, diag::warn_consteval_if_always_true) << Immediate; + } + + return BuildIfStmt(IfLoc, StatementKind, LParenLoc, InitStmt, Cond, RParenLoc, thenStmt, ElseLoc, elseStmt); } -StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, +StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, + IfStatementKind StatementKind, SourceLocation LParenLoc, Stmt *InitStmt, ConditionResult Cond, SourceLocation RParenLoc, Stmt *thenStmt, SourceLocation ElseLoc, @@ -924,12 +949,13 @@ if (Cond.isInvalid()) return StmtError(); - if (IsConstexpr || isa(Cond.get().second)) + if (StatementKind != IfStatementKind::Ordinary || + isa(Cond.get().second)) setFunctionHasBranchProtectedScope(); - return IfStmt::Create(Context, IfLoc, IsConstexpr, InitStmt, Cond.get().first, - Cond.get().second, LParenLoc, RParenLoc, thenStmt, - ElseLoc, elseStmt); + return IfStmt::Create(Context, IfLoc, StatementKind, InitStmt, + Cond.get().first, Cond.get().second, LParenLoc, + RParenLoc, thenStmt, ElseLoc, elseStmt); } namespace { 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, IfStatementKind 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; @@ -7410,7 +7413,7 @@ return S; return getDerived().RebuildIfStmt( - S->getIfLoc(), S->isConstexpr(), S->getLParenLoc(), Cond, + S->getIfLoc(), S->getStatementKind(), S->getLParenLoc(), Cond, S->getRParenLoc(), Init.get(), Then.get(), S->getElseLoc(), Else.get()); } 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,11 @@ void ASTStmtReader::VisitIfStmt(IfStmt *S) { VisitStmt(S); - S->setConstexpr(Record.readInt()); bool HasElse = Record.readInt(); bool HasVar = Record.readInt(); bool HasInit = Record.readInt(); + S->setStatementKind(static_cast(Record.readInt())); S->setCond(Record.readSubExpr()); S->setThen(Record.readSubStmt()); if (HasElse) @@ -2753,9 +2753,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,10 @@ 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(static_cast(S->getStatementKind())); 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/AST/Interp/if_consteval.cpp b/clang/test/AST/Interp/if_consteval.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/Interp/if_consteval.cpp @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -std=c++2b -fsyntax-only -fexperimental-new-constant-interpreter %s -verify +// RUN: %clang_cc1 -std=c++2b -fsyntax-only %s -verify +// expected-no-diagnostics + +constexpr void f() { + int i = 0; + if consteval { + i = 1; + } + else { + i = 2; + } + + if consteval { + i = 1; + } + + if !consteval { + i = 1; + } + + if !consteval { + i = 1; + } + else { + i = 1; + } +} diff --git a/clang/test/AST/ast-dump-if-json.cpp b/clang/test/AST/ast-dump-if-json.cpp --- a/clang/test/AST/ast-dump-if-json.cpp +++ b/clang/test/AST/ast-dump-if-json.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-pc-linux -std=c++17 -ast-dump=json %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-pc-linux -std=c++2b -ast-dump=json %s | FileCheck %s void func(int val) { if (val) @@ -24,11 +24,20 @@ if (int i = 12; i) ; + + if consteval {} + + if consteval {} else {} + + if not consteval {} + + if not consteval {} else {} } // NOTE: CHECK lines have been autogenerated by gen_ast_dump_json_test.py // using --filters=IfStmt + // CHECK: "kind": "IfStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { @@ -140,6 +149,7 @@ // CHECK-NEXT: ] // CHECK-NEXT: } + // CHECK: "kind": "IfStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { @@ -269,6 +279,7 @@ // CHECK-NEXT: ] // CHECK-NEXT: } + // CHECK: "kind": "IfStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { @@ -511,6 +522,7 @@ // CHECK-NEXT: ] // CHECK-NEXT: } + // CHECK: "kind": "IfStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { @@ -637,6 +649,7 @@ // CHECK-NEXT: ] // CHECK-NEXT: } + // CHECK: "kind": "IfStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { @@ -818,6 +831,7 @@ // CHECK-NEXT: ] // CHECK-NEXT: } + // CHECK: "kind": "IfStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { @@ -998,3 +1012,183 @@ // CHECK-NEXT: } // CHECK-NEXT: ] // CHECK-NEXT: } + + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 298, +// CHECK-NEXT: "line": 28, +// CHECK-NEXT: "col": 3, +// CHECK-NEXT: "tokLen": 2 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 312, +// CHECK-NEXT: "col": 17, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "isConsteval": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 311, +// CHECK-NEXT: "col": 16, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 312, +// CHECK-NEXT: "col": 17, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } + + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 317, +// CHECK-NEXT: "line": 30, +// CHECK-NEXT: "col": 3, +// CHECK-NEXT: "tokLen": 2 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 339, +// CHECK-NEXT: "col": 25, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "hasElse": true, +// CHECK-NEXT: "isConsteval": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 330, +// CHECK-NEXT: "col": 16, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 331, +// CHECK-NEXT: "col": 17, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 338, +// CHECK-NEXT: "col": 24, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 339, +// CHECK-NEXT: "col": 25, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } + + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 344, +// CHECK-NEXT: "line": 32, +// CHECK-NEXT: "col": 3, +// CHECK-NEXT: "tokLen": 2 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 362, +// CHECK-NEXT: "col": 21, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "isConsteval": true, +// CHECK-NEXT: "constevalIsNegated": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 361, +// CHECK-NEXT: "col": 20, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 362, +// CHECK-NEXT: "col": 21, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } + + +// CHECK: "kind": "IfStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 367, +// CHECK-NEXT: "line": 34, +// CHECK-NEXT: "col": 3, +// CHECK-NEXT: "tokLen": 2 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 393, +// CHECK-NEXT: "col": 29, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "hasElse": true, +// CHECK-NEXT: "isConsteval": true, +// CHECK-NEXT: "constevalIsNegated": true, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 384, +// CHECK-NEXT: "col": 20, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 385, +// CHECK-NEXT: "col": 21, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 392, +// CHECK-NEXT: "col": 28, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 393, +// CHECK-NEXT: "col": 29, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ] +// CHECK-NEXT: } diff --git a/clang/test/AST/ast-dump-stmt.cpp b/clang/test/AST/ast-dump-stmt.cpp --- a/clang/test/AST/ast-dump-stmt.cpp +++ b/clang/test/AST/ast-dump-stmt.cpp @@ -1,10 +1,10 @@ // Test without serialization: -// RUN: %clang_cc1 -std=c++2a -triple x86_64-linux-gnu -fcxx-exceptions -ast-dump %s \ +// RUN: %clang_cc1 -std=c++2b -triple x86_64-linux-gnu -fcxx-exceptions -ast-dump %s \ // RUN: | FileCheck -strict-whitespace %s // // Test with serialization: -// RUN: %clang_cc1 -std=c++2a -triple x86_64-linux-gnu -fcxx-exceptions -emit-pch -o %t %s -// RUN: %clang_cc1 -x c++ -std=c++2a -triple x86_64-linux-gnu -fcxx-exceptions -include-pch %t -ast-dump-all /dev/null \ +// RUN: %clang_cc1 -std=c++2b -triple x86_64-linux-gnu -fcxx-exceptions -emit-pch -o %t %s +// RUN: %clang_cc1 -x c++ -std=c++2b -triple x86_64-linux-gnu -fcxx-exceptions -include-pch %t -ast-dump-all /dev/null \ // RUN: | sed -e "s/ //" -e "s/ imported//" \ // RUN: | FileCheck -strict-whitespace %s @@ -154,6 +154,16 @@ // CHECK-NEXT: IntegerLiteral // CHECK-NEXT: NullStmt // CHECK-NEXT: NullStmt + + if consteval {} + // CHECK: IfStmt 0x{{[^ ]*}} consteval + // CHECK-NEXT: CompoundStmt + + if ! consteval {} + else {} + // CHECK: IfStmt 0x{{[^ ]*}} has_else !consteval + // CHECK-NEXT: CompoundStmt + // CHECK-NEXT: CompoundStmt } struct Container { 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,158 @@ +// RUN: %clang_cc1 -std=c++2b -verify %s + +void test_consteval() { + if consteval ({(void)1;}); // expected-error {{expected { after 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); + + if consteval [[likely]] { // expected-warning {{attribute 'likely' has no effect when annotating an 'if consteval' statement}}\ + // expected-note 2{{annotating the 'if consteval' statement here}} + + + } + else [[unlikely]] { // expected-warning {{attribute 'unlikely' has no effect when annotating an 'if consteval' statement}} + + } + +} + +void test_consteval_jumps() { + if consteval { // expected-note 4{{jump enters controlled statement of consteval if}} + 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 consteval if}} + 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 consteval if}} + } 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 {{consteval if is always true in an immediate context}} + if consteval {} // expected-warning {{consteval if is always true in an immediate context}} + } +} + +constexpr void warn_in_consteval2() { + if consteval { + if consteval {} // expected-warning {{consteval if is always true in an immediate context}} + } +} + +auto y = []() consteval { + if consteval { // expected-warning {{consteval if is always true in an immediate context}} + if consteval {} // expected-warning {{consteval if is always true in an immediate context}} + } +}; + +namespace test_transform { +int f(auto n) { + if consteval { + n.foo; //expected-error {{no member named}} + } + else { + } + + if !consteval { + n.foo; //expected-error {{no member named}} + } + else { + } + + return 0; +} + +constexpr int g(auto n) { + if consteval { + } + else { + n.foo; //expected-error {{no member named}} + } + + if !consteval { + } + else { + n.foo; //expected-error {{no member named}} + } + + return 0; +} + +struct S {}; +void test() { + f(S{}); //expected-note {{in instantiation}} + g(S{}); //expected-note {{in instantiation}} +} + +} 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,28 @@ +// RUN: %clang_cc1 -std=c++2b %s -emit-llvm -o - | FileCheck %s + +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(); // CHECK-NOT: call {{.*}}should_not_be_used + } else { + should_be_used_1(); // CHECK: call {{.*}}should_be_used_1 + } + + if !consteval { + should_be_used_2(); // CHECK: call {{.*}}should_be_used_2 + } + + if !consteval { + should_be_used_3(); // CHECK: call {{.*}}should_be_used_3 + } else { + should_not_be_used(); // CHECK-NOT: call {{.*}}should_not_be_used + } +} + +void g() { + f(); +}