diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 202ad330e96f..30c237ef36dd 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -93,6 +93,13 @@ protected: unsigned NumStmts : 32 - NumStmtBits; }; + class IfStmtBitfields { + friend class IfStmt; + unsigned : NumStmtBits; + + unsigned IsConstexpr : 1; + }; + class ExprBitfields { friend class Expr; friend class DeclRefExpr; // computeDependence @@ -248,6 +255,7 @@ protected: union { StmtBitfields StmtBits; CompoundStmtBitfields CompoundStmtBits; + IfStmtBitfields IfStmtBits; ExprBitfields ExprBits; CharacterLiteralBitfields CharacterLiteralBits; FloatingLiteralBitfields FloatingLiteralBits; @@ -878,7 +886,8 @@ class IfStmt : public Stmt { SourceLocation ElseLoc; public: - IfStmt(const ASTContext &C, SourceLocation IL, VarDecl *var, Expr *cond, + IfStmt(const ASTContext &C, SourceLocation IL, + bool IsConstexpr, VarDecl *var, Expr *cond, Stmt *then, SourceLocation EL = SourceLocation(), Stmt *elsev = nullptr); @@ -918,6 +927,9 @@ public: SourceLocation getElseLoc() const { return ElseLoc; } void setElseLoc(SourceLocation L) { ElseLoc = L; } + bool isConstexpr() const { return IfStmtBits.IsConstexpr; } + void setConstexpr(bool C) { IfStmtBits.IsConstexpr = C; } + SourceLocation getLocStart() const LLVM_READONLY { return IfLoc; } SourceLocation getLocEnd() const LLVM_READONLY { if (SubExprs[ELSE]) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 48e1a2765d84..d7ecef77a2fe 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -512,6 +512,11 @@ def err_function_is_not_record : Error< "unexpected %0 in function call; perhaps remove the %0?">; def err_super_in_using_declaration : Error< "'__super' cannot be used with a using declaration">; +def ext_constexpr_if : ExtWarn< + "constexpr if is a C++1z extension">, InGroup; +def warn_cxx14_compat_constexpr_if : Warning< + "constexpr if is incompatible with C++ standards before C++1z">, + DefaultIgnore, InGroup; // C++ derived classes def err_dup_virtual : Error<"duplicate 'virtual' in base specifier">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index a27b356ab6fd..44d59dc537e5 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -75,10 +75,12 @@ def err_typecheck_converted_constant_expression_indirect : Error< "conversion from %0 to %1 in converted constant expression would " "bind reference to a temporary">; def err_expr_not_cce : Error< - "%select{case value|enumerator value|non-type template argument|array size}0 " + "%select{case value|enumerator value|non-type template argument|" + "array size|constexpr if condition}0 " "is not a constant expression">; def ext_cce_narrowing : ExtWarn< - "%select{case value|enumerator value|non-type template argument|array size}0 " + "%select{case value|enumerator value|non-type template argument|" + "array size|constexpr if condition}0 " "%select{cannot be narrowed from type %2 to %3|" "evaluates to %2, which cannot be narrowed to type %3}1">, InGroup, DefaultError, SFINAEFailure; @@ -4641,6 +4643,8 @@ def note_protected_by_vla_typedef : Note< "jump bypasses initialization of VLA typedef">; def note_protected_by_vla_type_alias : Note< "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_vla : Note< "jump bypasses initialization of variable length array">; def note_protected_by_objc_try : Note< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 7f2a9429621a..7063c6f70650 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -797,6 +797,11 @@ public: /// run time. Unevaluated, + /// \brief The current expression occurs within a discarded statement. + /// This behaves largely similarly to an unevaluated operand in preventing + /// definitions from being required, but not in other ways. + DiscardedStatement, + /// \brief The current expression occurs within an unevaluated /// operand that unconditionally permits abstract references to /// fields, such as a SIZE operator in MS-style inline assembly. @@ -2329,7 +2334,8 @@ public: CCEK_CaseValue, ///< Expression in a case label. CCEK_Enumerator, ///< Enumerator value with fixed underlying type. CCEK_TemplateArg, ///< Value of a non-type template parameter. - CCEK_NewExpr ///< Constant expression in a noptr-new-declarator. + CCEK_NewExpr, ///< Constant expression in a noptr-new-declarator. + CCEK_ConstexprIf ///< Condition in a constexpr if statement. }; ExprResult CheckConvertedConstantExpression(Expr *From, QualType T, llvm::APSInt &Value, CCEKind CCE); @@ -3393,8 +3399,12 @@ public: Stmt *SubStmt); class ConditionResult; - StmtResult ActOnIfStmt(SourceLocation IfLoc, ConditionResult Cond, - Stmt *ThenVal, SourceLocation ElseLoc, Stmt *ElseVal); + StmtResult ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, + ConditionResult Cond, Stmt *ThenVal, + SourceLocation ElseLoc, Stmt *ElseVal); + StmtResult BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, + ConditionResult Cond, Stmt *ThenVal, + SourceLocation ElseLoc, Stmt *ElseVal); StmtResult ActOnStartOfSwitchStmt(SourceLocation SwitchLoc, ConditionResult Cond); StmtResult ActOnFinishSwitchStmt(SourceLocation SwitchLoc, @@ -8919,12 +8929,20 @@ public: Decl *ConditionVar; FullExprArg Condition; bool Invalid; + bool HasKnownValue; + bool KnownValue; friend class Sema; - ConditionResult(Decl *ConditionVar, FullExprArg Condition) - : ConditionVar(ConditionVar), Condition(Condition), Invalid(false) {} + ConditionResult(Sema &S, Decl *ConditionVar, FullExprArg Condition, + bool IsConstexpr) + : ConditionVar(ConditionVar), Condition(Condition), Invalid(false), + HasKnownValue(IsConstexpr && Condition.get() && + !Condition.get()->isValueDependent()), + KnownValue(HasKnownValue && + !!Condition.get()->EvaluateKnownConstInt(S.Context)) {} explicit ConditionResult(bool Invalid) - : ConditionVar(nullptr), Condition(nullptr), Invalid(Invalid) {} + : ConditionVar(nullptr), Condition(nullptr), Invalid(Invalid), + HasKnownValue(false), KnownValue(false) {} public: ConditionResult() : ConditionResult(false) {} @@ -8933,12 +8951,18 @@ public: return std::make_pair(cast_or_null(ConditionVar), Condition.get()); } + llvm::Optional getKnownValue() const { + if (!HasKnownValue) + return None; + return KnownValue; + } }; static ConditionResult ConditionError() { return ConditionResult(true); } enum class ConditionKind { - Boolean, ///< A boolean condition, from 'if', 'while', 'for', or 'do'. - Switch ///< An integral condition for a 'switch' statement. + Boolean, ///< A boolean condition, from 'if', 'while', 'for', or 'do'. + ConstexprIf, ///< A constant boolean condition from 'if constexpr'. + Switch ///< An integral condition for a 'switch' statement. }; ConditionResult ActOnCondition(Scope *S, SourceLocation Loc, @@ -8963,7 +8987,8 @@ public: /// \param Loc - A location associated with the condition, e.g. the /// 'if' keyword. /// \return true iff there were any errors - ExprResult CheckBooleanCondition(SourceLocation Loc, Expr *E); + ExprResult CheckBooleanCondition(SourceLocation Loc, Expr *E, + bool IsConstexpr = false); /// DiagnoseAssignmentAsCondition - Given that an expression is /// being used as a boolean condition, warn if it's an assignment. @@ -8974,7 +8999,7 @@ public: void DiagnoseEqualityWithExtraParens(ParenExpr *ParenE); /// CheckCXXBooleanCondition - Returns true if conversion to bool is invalid. - ExprResult CheckCXXBooleanCondition(Expr *CondExpr); + ExprResult CheckCXXBooleanCondition(Expr *CondExpr, bool IsConstexpr = false); /// ConvertIntegerToTypeWarnOnOverflow - Convert the specified APInt to have /// the specified width and sign. If an overflow occurs, detect it and emit @@ -9533,15 +9558,18 @@ public: /// \brief RAII object that enters a new expression evaluation context. class EnterExpressionEvaluationContext { Sema &Actions; + bool Entered = true; public: EnterExpressionEvaluationContext(Sema &Actions, Sema::ExpressionEvaluationContext NewContext, Decl *LambdaContextDecl = nullptr, - bool IsDecltype = false) - : Actions(Actions) { - Actions.PushExpressionEvaluationContext(NewContext, LambdaContextDecl, - IsDecltype); + bool IsDecltype = false, + bool ShouldEnter = true) + : Actions(Actions), Entered(ShouldEnter) { + if (Entered) + Actions.PushExpressionEvaluationContext(NewContext, LambdaContextDecl, + IsDecltype); } EnterExpressionEvaluationContext(Sema &Actions, Sema::ExpressionEvaluationContext NewContext, @@ -9554,7 +9582,8 @@ public: } ~EnterExpressionEvaluationContext() { - Actions.PopExpressionEvaluationContext(); + if (Entered) + Actions.PopExpressionEvaluationContext(); } }; diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index a70b38fc9026..9a4e4a2894d2 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -4981,7 +4981,8 @@ Stmt *ASTNodeImporter::VisitIfStmt(IfStmt *S) { if (!ToElseStmt && S->getElse()) return nullptr; return new (Importer.getToContext()) IfStmt(Importer.getToContext(), - ToIfLoc, ToConditionVariable, + ToIfLoc, S->isConstexpr(), + ToConditionVariable, ToCondition, ToThenStmt, ToElseLoc, ToElseStmt); } diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp index f514ed21f474..5f1adf7a20fa 100644 --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -763,10 +763,11 @@ void MSAsmStmt::initialize(const ASTContext &C, StringRef asmstr, }); } -IfStmt::IfStmt(const ASTContext &C, SourceLocation IL, VarDecl *var, Expr *cond, - Stmt *then, SourceLocation EL, Stmt *elsev) - : Stmt(IfStmtClass), IfLoc(IL), ElseLoc(EL) -{ +IfStmt::IfStmt(const ASTContext &C, SourceLocation IL, bool IsConstexpr, + VarDecl *var, Expr *cond, Stmt *then, SourceLocation EL, + Stmt *elsev) + : Stmt(IfStmtClass), IfLoc(IL), ElseLoc(EL) { + setConstexpr(IsConstexpr); setConditionVariable(C, var); SubExprs[COND] = cond; SubExprs[THEN] = then; diff --git a/clang/lib/Analysis/BodyFarm.cpp b/clang/lib/Analysis/BodyFarm.cpp index d8b652f68e8f..07167014d68b 100644 --- a/clang/lib/Analysis/BodyFarm.cpp +++ b/clang/lib/Analysis/BodyFarm.cpp @@ -239,7 +239,7 @@ static Stmt *create_dispatch_once(ASTContext &C, const FunctionDecl *D) { SourceLocation()); // (5) Create the 'if' statement. - IfStmt *If = new (C) IfStmt(C, SourceLocation(), nullptr, UO, CS); + IfStmt *If = new (C) IfStmt(C, SourceLocation(), false, nullptr, UO, CS); return If; } @@ -343,7 +343,7 @@ static Stmt *create_OSAtomicCompareAndSwap(ASTContext &C, const FunctionDecl *D) /// Construct the If. Stmt *If = - new (C) IfStmt(C, SourceLocation(), nullptr, Comparison, Body, + new (C) IfStmt(C, SourceLocation(), false, nullptr, Comparison, Body, SourceLocation(), Else); return If; diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index ff70bbc866f4..9a93fce7b00a 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -563,7 +563,8 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) { // If the condition constant folds and can be elided, try to avoid emitting // the condition and the dead arm of the if/else. bool CondConstant; - if (ConstantFoldsToSimpleInteger(S.getCond(), CondConstant)) { + if (ConstantFoldsToSimpleInteger(S.getCond(), CondConstant, + S.isConstexpr())) { // Figure out which block (then or else) is executed. const Stmt *Executed = S.getThen(); const Stmt *Skipped = S.getElse(); @@ -572,7 +573,7 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) { // If the skipped block has no labels in it, just emit the executed block. // This avoids emitting dead code and simplifies the CFG substantially. - if (!ContainsLabel(Skipped)) { + if (S.isConstexpr() || !ContainsLabel(Skipped)) { if (CondConstant) incrementProfileCounter(&S); if (Executed) { diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 1d46eea45fde..2dc4c12ce2e1 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -1107,9 +1107,10 @@ bool CodeGenFunction::containsBreak(const Stmt *S) { /// to a constant, or if it does but contains a label, return false. If it /// constant folds return true and set the boolean result in Result. bool CodeGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond, - bool &ResultBool) { + bool &ResultBool, + bool AllowLabels) { llvm::APSInt ResultInt; - if (!ConstantFoldsToSimpleInteger(Cond, ResultInt)) + if (!ConstantFoldsToSimpleInteger(Cond, ResultInt, AllowLabels)) return false; ResultBool = ResultInt.getBoolValue(); @@ -1119,15 +1120,16 @@ bool CodeGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond, /// ConstantFoldsToSimpleInteger - If the specified expression does not fold /// to a constant, or if it does but contains a label, return false. If it /// constant folds return true and set the folded value. -bool CodeGenFunction:: -ConstantFoldsToSimpleInteger(const Expr *Cond, llvm::APSInt &ResultInt) { +bool CodeGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond, + llvm::APSInt &ResultInt, + bool AllowLabels) { // FIXME: Rename and handle conversion of other evaluatable things // to bool. llvm::APSInt Int; if (!Cond->EvaluateAsInt(Int, getContext())) return false; // Not foldable, not integer or not fully evaluatable. - if (CodeGenFunction::ContainsLabel(Cond)) + if (!AllowLabels && CodeGenFunction::ContainsLabel(Cond)) return false; // Contains a label. ResultInt = Int; diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index b96e6bad8a12..af7e6114af63 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3051,13 +3051,15 @@ public: /// ConstantFoldsToSimpleInteger - If the specified expression does not fold /// to a constant, or if it does but contains a label, return false. If it /// constant folds return true and set the boolean result in Result. - bool ConstantFoldsToSimpleInteger(const Expr *Cond, bool &Result); + bool ConstantFoldsToSimpleInteger(const Expr *Cond, bool &Result, + bool AllowLabels = false); /// ConstantFoldsToSimpleInteger - If the specified expression does not fold /// to a constant, or if it does but contains a label, return false. If it /// constant folds return true and set the folded value. - bool ConstantFoldsToSimpleInteger(const Expr *Cond, llvm::APSInt &Result); - + bool ConstantFoldsToSimpleInteger(const Expr *Cond, llvm::APSInt &Result, + bool AllowLabels = false); + /// EmitBranchOnBoolExpr - Emit a branch on a boolean condition (e.g. for an /// if statement) to the specified blocks. Based on the condition, this might /// try to simplify the codegen of the conditional based on the branch. diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index c849554238e3..5ca993390083 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1108,6 +1108,14 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { assert(Tok.is(tok::kw_if) && "Not an if stmt!"); SourceLocation IfLoc = ConsumeToken(); // eat the 'if'. + bool IsConstexpr = false; + if (Tok.is(tok::kw_constexpr)) { + Diag(Tok, getLangOpts().CPlusPlus1z ? diag::warn_cxx14_compat_constexpr_if + : diag::ext_constexpr_if); + IsConstexpr = true; + ConsumeToken(); + } + if (Tok.isNot(tok::l_paren)) { Diag(Tok, diag::err_expected_lparen_after) << "if"; SkipUntil(tok::semi); @@ -1132,9 +1140,15 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { // Parse the condition. Sema::ConditionResult Cond; - if (ParseParenExprOrCondition(Cond, IfLoc, Sema::ConditionKind::Boolean)) + if (ParseParenExprOrCondition(Cond, IfLoc, + IsConstexpr ? Sema::ConditionKind::ConstexprIf + : Sema::ConditionKind::Boolean)) return StmtError(); + llvm::Optional ConstexprCondition; + if (IsConstexpr) + ConstexprCondition = Cond.getKnownValue(); + // C99 6.8.4p3 - In C99, the body of the if statement is a scope, even if // there is no compound stmt. C90 does not have this clause. We only do this // if the body isn't a compound statement to avoid push/pop in common cases. @@ -1159,7 +1173,13 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { SourceLocation ThenStmtLoc = Tok.getLocation(); SourceLocation InnerStatementTrailingElseLoc; - StmtResult ThenStmt(ParseStatement(&InnerStatementTrailingElseLoc)); + StmtResult ThenStmt; + { + EnterExpressionEvaluationContext PotentiallyDiscarded( + Actions, Sema::DiscardedStatement, nullptr, false, + /*ShouldEnter=*/ConstexprCondition && !*ConstexprCondition); + ThenStmt = ParseStatement(&InnerStatementTrailingElseLoc); + } // Pop the 'if' scope if needed. InnerScope.Exit(); @@ -1185,8 +1205,12 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { // The substatement in a selection-statement (each substatement, in the else // form of the if statement) implicitly defines a local scope. // - ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace)); + ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, + Tok.is(tok::l_brace)); + EnterExpressionEvaluationContext PotentiallyDiscarded( + Actions, Sema::DiscardedStatement, nullptr, false, + /*ShouldEnter=*/ConstexprCondition && *ConstexprCondition); ElseStmt = ParseStatement(); // Pop the 'else' scope if needed. @@ -1217,7 +1241,7 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { if (ElseStmt.isInvalid()) ElseStmt = Actions.ActOnNullStmt(ElseStmtLoc); - return Actions.ActOnIfStmt(IfLoc, Cond, ThenStmt.get(), ElseLoc, + return Actions.ActOnIfStmt(IfLoc, IsConstexpr, Cond, ThenStmt.get(), ElseLoc, ElseStmt.get()); } diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp index 500e761bf163..bd541397e1ae 100644 --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -319,6 +319,37 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S, Jumps.push_back(S); break; + case Stmt::IfStmtClass: { + IfStmt *IS = cast(S); + if (!IS->isConstexpr()) + break; + + if (VarDecl *Var = IS->getConditionVariable()) + BuildScopeInformation(Var, ParentScope); + + // Cannot jump into the middle of the condition. + unsigned NewParentScope = Scopes.size(); + Scopes.push_back(GotoScope(ParentScope, + diag::note_protected_by_constexpr_if, 0, + IS->getLocStart())); + BuildScopeInformation(IS->getCond(), NewParentScope); + + // Jumps into either arm of an 'if constexpr' are not allowed. + NewParentScope = Scopes.size(); + Scopes.push_back(GotoScope(ParentScope, + diag::note_protected_by_constexpr_if, 0, + IS->getLocStart())); + BuildScopeInformation(IS->getThen(), NewParentScope); + if (Stmt *Else = IS->getElse()) { + NewParentScope = Scopes.size(); + Scopes.push_back(GotoScope(ParentScope, + diag::note_protected_by_constexpr_if, 0, + IS->getLocStart())); + BuildScopeInformation(Else, NewParentScope); + } + return; + } + case Stmt::CXXTryStmtClass: { CXXTryStmt *TS = cast(S); { diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index f975b817e006..ab1180ed99f8 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -12903,6 +12903,11 @@ static bool IsPotentiallyEvaluatedContext(Sema &SemaRef) { // definition of a null pointer constant is completely crazy.) return false; + case Sema::DiscardedStatement: + // These are technically a potentially evaluated but they have the effect + // of suppressing use marking. + return false; + case Sema::ConstantEvaluated: case Sema::PotentiallyEvaluated: // We are in a potentially evaluated expression (or a constant-expression @@ -14192,6 +14197,7 @@ bool Sema::DiagRuntimeBehavior(SourceLocation Loc, const Stmt *Statement, switch (ExprEvalContexts.back().Context) { case Unevaluated: case UnevaluatedAbstract: + case DiscardedStatement: // The argument will never be evaluated, so don't complain. break; @@ -14341,7 +14347,8 @@ void Sema::DiagnoseEqualityWithExtraParens(ParenExpr *ParenE) { } } -ExprResult Sema::CheckBooleanCondition(SourceLocation Loc, Expr *E) { +ExprResult Sema::CheckBooleanCondition(SourceLocation Loc, Expr *E, + bool IsConstexpr) { DiagnoseAssignmentAsCondition(E); if (ParenExpr *parenE = dyn_cast(E)) DiagnoseEqualityWithExtraParens(parenE); @@ -14352,7 +14359,7 @@ ExprResult Sema::CheckBooleanCondition(SourceLocation Loc, Expr *E) { if (!E->isTypeDependent()) { if (getLangOpts().CPlusPlus) - return CheckCXXBooleanCondition(E); // C++ 6.4p4 + return CheckCXXBooleanCondition(E, IsConstexpr); // C++ 6.4p4 ExprResult ERes = DefaultFunctionArrayLvalueConversion(E); if (ERes.isInvalid()) @@ -14383,6 +14390,10 @@ Sema::ConditionResult Sema::ActOnCondition(Scope *S, SourceLocation Loc, Cond = CheckBooleanCondition(Loc, SubExpr); break; + case ConditionKind::ConstexprIf: + Cond = CheckBooleanCondition(Loc, SubExpr, true); + break; + case ConditionKind::Switch: Cond = CheckSwitchCondition(Loc, SubExpr); break; @@ -14390,7 +14401,8 @@ Sema::ConditionResult Sema::ActOnCondition(Scope *S, SourceLocation Loc, if (Cond.isInvalid()) return ConditionError(); - return ConditionResult(nullptr, MakeFullExpr(Cond.get(), Loc)); + return ConditionResult(*this, nullptr, MakeFullExpr(Cond.get(), Loc), + CK == ConditionKind::ConstexprIf); } namespace { diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index f97b9c9d81b7..ac87ae5ecd34 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -3061,7 +3061,8 @@ Sema::ConditionResult Sema::ActOnConditionVariable(Decl *ConditionVar, CheckConditionVariable(cast(ConditionVar), StmtLoc, CK); if (E.isInvalid()) return ConditionError(); - return ConditionResult(ConditionVar, MakeFullExpr(E.get(), StmtLoc)); + return ConditionResult(*this, ConditionVar, MakeFullExpr(E.get(), StmtLoc), + CK == ConditionKind::ConstexprIf); } /// \brief Check the use of the given variable as a C++ condition in an if, @@ -3096,6 +3097,9 @@ ExprResult Sema::CheckConditionVariable(VarDecl *ConditionVar, case ConditionKind::Boolean: return CheckBooleanCondition(StmtLoc, Condition.get()); + case ConditionKind::ConstexprIf: + return CheckBooleanCondition(StmtLoc, Condition.get(), true); + case ConditionKind::Switch: return CheckSwitchCondition(StmtLoc, Condition.get()); } @@ -3104,7 +3108,7 @@ ExprResult Sema::CheckConditionVariable(VarDecl *ConditionVar, } /// CheckCXXBooleanCondition - Returns true if a conversion to bool is invalid. -ExprResult Sema::CheckCXXBooleanCondition(Expr *CondExpr) { +ExprResult Sema::CheckCXXBooleanCondition(Expr *CondExpr, bool IsConstexpr) { // C++ 6.4p4: // The value of a condition that is an initialized declaration in a statement // other than a switch statement is the value of the declared variable @@ -3113,7 +3117,12 @@ ExprResult Sema::CheckCXXBooleanCondition(Expr *CondExpr) { // The value of a condition that is an expression is the value of the // expression, implicitly converted to bool. // - return PerformContextuallyConvertToBool(CondExpr); + // FIXME: Return this value to the caller so they don't need to recompute it. + llvm::APSInt Value(/*BitWidth*/1); + return (IsConstexpr && !CondExpr->isValueDependent()) + ? CheckConvertedConstantExpression(CondExpr, Context.BoolTy, Value, + CCEK_ConstexprIf) + : PerformContextuallyConvertToBool(CondExpr); } /// Helper function to determine whether this is the (deprecated) C++ diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp index c9231ed36462..283621889f80 100644 --- a/clang/lib/Sema/SemaExprMember.cpp +++ b/clang/lib/Sema/SemaExprMember.cpp @@ -142,6 +142,7 @@ static IMAKind ClassifyImplicitMemberAccess(Sema &SemaRef, AbstractInstanceResult = IMA_Abstract; break; + case Sema::DiscardedStatement: case Sema::ConstantEvaluated: case Sema::PotentiallyEvaluated: case Sema::PotentiallyEvaluatedIfUsed: diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp index ae768b626771..1f5f63a4144a 100644 --- a/clang/lib/Sema/SemaLambda.cpp +++ b/clang/lib/Sema/SemaLambda.cpp @@ -1635,6 +1635,7 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc, ExprEvalContexts.back().Lambdas.push_back(Lambda); break; + case DiscardedStatement: case PotentiallyEvaluated: case PotentiallyEvaluatedIfUsed: break; diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 8d5bec80a125..5432a7094448 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -504,31 +504,43 @@ public: } StmtResult -Sema::ActOnIfStmt(SourceLocation IfLoc, ConditionResult Cond, +Sema::ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, ConditionResult Cond, Stmt *thenStmt, SourceLocation ElseLoc, Stmt *elseStmt) { - auto CondVal = Cond.get(); - if (Cond.isInvalid()) { - CondVal.first = nullptr; - CondVal.second = new (Context) - OpaqueValueExpr(SourceLocation(), Context.BoolTy, VK_RValue); - } + if (Cond.isInvalid()) + Cond = ConditionResult( + *this, nullptr, + MakeFullExpr(new (Context) OpaqueValueExpr(SourceLocation(), + Context.BoolTy, VK_RValue), + IfLoc), + false); + Expr *CondExpr = Cond.get().second; if (!Diags.isIgnored(diag::warn_comma_operator, - CondVal.second->getExprLoc())) - CommaVisitor(*this).Visit(CondVal.second); + CondExpr->getExprLoc())) + CommaVisitor(*this).Visit(CondExpr); + + if (!elseStmt) + DiagnoseEmptyStmtBody(CondExpr->getLocEnd(), thenStmt, + diag::warn_empty_if_body); + + return BuildIfStmt(IfLoc, IsConstexpr, Cond, thenStmt, ElseLoc, elseStmt); +} + +StmtResult Sema::BuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, + ConditionResult Cond, Stmt *thenStmt, + SourceLocation ElseLoc, Stmt *elseStmt) { + if (Cond.isInvalid()) + return StmtError(); + + if (IsConstexpr) + getCurFunction()->setHasBranchProtectedScope(); DiagnoseUnusedExprResult(thenStmt); - - if (!elseStmt) { - DiagnoseEmptyStmtBody(CondVal.second->getLocEnd(), thenStmt, - diag::warn_empty_if_body); - } - DiagnoseUnusedExprResult(elseStmt); - return new (Context) IfStmt(Context, IfLoc, CondVal.first, CondVal.second, - thenStmt, ElseLoc, elseStmt); + return new (Context) IfStmt(Context, IfLoc, IsConstexpr, Cond.get().first, + Cond.get().second, thenStmt, ElseLoc, elseStmt); } namespace { @@ -2836,8 +2848,21 @@ Sema::ActOnCapScopeReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) { CapturingScopeInfo *CurCap = cast(getCurFunction()); QualType FnRetType = CurCap->ReturnType; LambdaScopeInfo *CurLambda = dyn_cast(CurCap); + bool HasDeducedReturnType = + CurLambda && hasDeducedReturnType(CurLambda->CallOperator); - if (CurLambda && hasDeducedReturnType(CurLambda->CallOperator)) { + if (ExprEvalContexts.back().Context == DiscardedStatement && + (HasDeducedReturnType || CurCap->HasImplicitReturnType)) { + if (RetValExp) { + ExprResult ER = ActOnFinishFullExpr(RetValExp, ReturnLoc); + if (ER.isInvalid()) + return StmtError(); + RetValExp = ER.get(); + } + return new (Context) ReturnStmt(ReturnLoc, RetValExp, nullptr); + } + + if (HasDeducedReturnType) { // In C++1y, the return type may involve 'auto'. // FIXME: Blocks might have a return type of 'auto' explicitly specified. FunctionDecl *FD = CurLambda->CallOperator; @@ -3118,9 +3143,8 @@ StmtResult Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, Scope *CurScope) { StmtResult R = BuildReturnStmt(ReturnLoc, RetValExp); - if (R.isInvalid()) { + if (R.isInvalid() || ExprEvalContexts.back().Context == DiscardedStatement) return R; - } if (VarDecl *VD = const_cast(cast(R.get())->getNRVOCandidate())) { @@ -3169,6 +3193,19 @@ StmtResult Sema::BuildReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) { } else // If we don't have a function/method context, bail. return StmtError(); + // C++1z: discarded return statements are not considered when deducing a + // return type. + if (ExprEvalContexts.back().Context == DiscardedStatement && + FnRetType->getContainedAutoType()) { + if (RetValExp) { + ExprResult ER = ActOnFinishFullExpr(RetValExp, ReturnLoc); + if (ER.isInvalid()) + return StmtError(); + RetValExp = ER.get(); + } + return new (Context) ReturnStmt(ReturnLoc, RetValExp, nullptr); + } + // FIXME: Add a flag to the ScopeInfo to indicate whether we're performing // deduction. if (getLangOpts().CPlusPlus14) { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 5814197a7a7a..a7b994d9869d 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -1174,9 +1174,10 @@ public: /// /// By default, performs semantic analysis to build the new statement. /// Subclasses may override this routine to provide different behavior. - StmtResult RebuildIfStmt(SourceLocation IfLoc, Sema::ConditionResult Cond, - Stmt *Then, SourceLocation ElseLoc, Stmt *Else) { - return getSema().ActOnIfStmt(IfLoc, Cond, Then, ElseLoc, Else); + StmtResult RebuildIfStmt(SourceLocation IfLoc, bool IsConstexpr, + Sema::ConditionResult Cond, Stmt *Then, + SourceLocation ElseLoc, Stmt *Else) { + return getSema().ActOnIfStmt(IfLoc, IsConstexpr, Cond, Then, ElseLoc, Else); } /// \brief Start building a new switch statement. @@ -6228,19 +6229,33 @@ TreeTransform::TransformIfStmt(IfStmt *S) { // Transform the condition Sema::ConditionResult Cond = getDerived().TransformCondition( S->getIfLoc(), S->getConditionVariable(), S->getCond(), - Sema::ConditionKind::Boolean); + 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; + if (S->isConstexpr()) + ConstexprConditionValue = Cond.getKnownValue(); + // Transform the "then" branch. - StmtResult Then = getDerived().TransformStmt(S->getThen()); - if (Then.isInvalid()) - return StmtError(); + StmtResult Then; + if (!ConstexprConditionValue || *ConstexprConditionValue) { + Then = getDerived().TransformStmt(S->getThen()); + if (Then.isInvalid()) + return StmtError(); + } else { + Then = new (getSema().Context) NullStmt(S->getThen()->getLocStart()); + } // Transform the "else" branch. - StmtResult Else = getDerived().TransformStmt(S->getElse()); - if (Else.isInvalid()) - return StmtError(); + StmtResult Else; + if (!ConstexprConditionValue || !*ConstexprConditionValue) { + Else = getDerived().TransformStmt(S->getElse()); + if (Else.isInvalid()) + return StmtError(); + } if (!getDerived().AlwaysRebuild() && Cond.get() == std::make_pair(S->getConditionVariable(), S->getCond()) && @@ -6248,8 +6263,8 @@ TreeTransform::TransformIfStmt(IfStmt *S) { Else.get() == S->getElse()) return S; - return getDerived().RebuildIfStmt(S->getIfLoc(), Cond, Then.get(), - S->getElseLoc(), Else.get()); + return getDerived().RebuildIfStmt(S->getIfLoc(), S->isConstexpr(), Cond, + Then.get(), S->getElseLoc(), Else.get()); } template diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index abb885e81b2b..bff056ebc0da 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -184,6 +184,7 @@ void ASTStmtReader::VisitAttributedStmt(AttributedStmt *S) { void ASTStmtReader::VisitIfStmt(IfStmt *S) { VisitStmt(S); + S->setConstexpr(Record[Idx++]); S->setConditionVariable(Reader.getContext(), ReadDeclAs(Record, Idx)); S->setCond(Reader.ReadSubExpr()); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index cc6f34099626..fe600adea39a 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -128,6 +128,7 @@ void ASTStmtWriter::VisitAttributedStmt(AttributedStmt *S) { void ASTStmtWriter::VisitIfStmt(IfStmt *S) { VisitStmt(S); + Record.push_back(S->isConstexpr()); Record.AddDeclRef(S->getConditionVariable()); Record.AddStmt(S->getCond()); Record.AddStmt(S->getThen()); diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p2-1z.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p2-1z.cpp new file mode 100644 index 000000000000..e41270ee3cd2 --- /dev/null +++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p2-1z.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -std=c++1z -verify %s + +template constexpr bool same = false; +template constexpr bool same = true; + +auto a() { + if constexpr (false) + return 0; +} +static_assert(same); + +auto b() { + if constexpr (false) + return 0; + else + return 0.0; +} +static_assert(same); + +auto c() { + if constexpr (true) + return "foo"; + else + return 'x'; + if constexpr (false) + return 7.6; + else + return 5; // expected-error {{deduced as 'int' here but deduced as 'const char *' in earlier}} +} + +template auto d() { + if constexpr(k == 0) + return 0; + if constexpr(k == 1) + return "foo"; + else if constexpr (k == 2) + return 1.0; +} +static_assert(same()), int>); +static_assert(same()), const char *>); +static_assert(same()), double>); +static_assert(same()), void>); + +auto e = []{ if constexpr (false) return 0; }(); // expected-error {{variable has incomplete type 'void'}} + +auto f = []{ if constexpr (true) return 0; }(); +static_assert(same); diff --git a/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp b/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp new file mode 100644 index 000000000000..d6a2169a5baf --- /dev/null +++ b/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp @@ -0,0 +1,137 @@ +// RUN: %clang_cc1 -std=c++1z -verify %s +// RUN: %clang_cc1 -std=c++1z -verify %s -DUNDEFINED + +#ifdef UNDEFINED +// "used but not defined" errors don't get produced if we have more interesting +// errors. +namespace std_example { + template void g(T &&p, Rest &&... rs) { + // use p + if constexpr(sizeof...(rs) > 0) + g(rs...); + } + void use_g() { + g(1, 2, 3); + } + + static int x(); // no definition of x required + int f() { + if constexpr (true) + return 0; + else if (x()) + return x(); + else + return -x(); + } +} + +namespace odr_use_in_selected_arm { + static int x(); // expected-warning {{is not defined}} + int f() { + if constexpr (false) + return 0; + else if (x()) // expected-note {{here}} + return x(); + else + return -x(); + } +} +#else +namespace ccce { + void f() { + if (5) {} + if constexpr (5) {} // expected-error {{cannot be narrowed}} + } + template void g() { + if constexpr (N) {} // expected-error {{cannot be narrowed}} + } + template void g<5>(); // expected-note {{instantiation of}} +} + +namespace generic_lambda { + // Substituting for T produces a hard error here, even if substituting for + // the type of x would remove the error. + template void f() { + [](auto x) { + if constexpr (sizeof(T) == 1 && sizeof(x) == 1) + T::error(); // expected-error 2{{'::'}} + } (0); + } + + template void g() { + [](auto x) { + if constexpr (sizeof(T) == 1) + if constexpr (sizeof(x) == 1) + T::error(); // expected-error {{'::'}} + } (0); + } + + void use() { + f(); // expected-note {{instantiation of}} + f(); // expected-note {{instantiation of}} + g(); // ok + g(); // expected-note {{instantiation of}} + } +} + +namespace potentially_discarded_branch_target { + void in_switch(int n) { + switch (n) + case 4: if constexpr(sizeof(n) == 4) return; + if constexpr(sizeof(n) == 4) + switch (n) case 4: return; + switch (n) { + if constexpr (sizeof(n) == 4) // expected-note 2{{constexpr if}} + case 4: return; // expected-error {{cannot jump}} + else + default: break; // expected-error {{cannot jump}} + } + } + + template + void in_switch_tmpl(int n) { + switch (n) { + if constexpr (sizeof(T) == 4) // expected-note 2{{constexpr if}} + case 4: return; // expected-error {{cannot jump}} + else + default: break; // expected-error {{cannot jump}} + } + } + + void goto_scope(int n) { + goto foo; // expected-error {{cannot jump}} + if constexpr(sizeof(n) == 4) // expected-note {{constexpr if}} + foo: return; +bar: + if constexpr(sizeof(n) == 4) + goto bar; // ok + } + + template + void goto_scope(int n) { + goto foo; // expected-error {{cannot jump}} + if constexpr(sizeof(n) == 4) // expected-note {{constexpr if}} + foo: return; +bar: + if constexpr(sizeof(n) == 4) + goto bar; // ok + } + + void goto_redef(int n) { +a: if constexpr(sizeof(n) == 4) // expected-error {{redefinition}} expected-note {{constexpr if}} + a: goto a; // expected-note 2{{previous}} + else + a: goto a; // expected-error {{redefinition}} expected-error {{cannot jump}} + } + + void evil_things() { + goto evil_label; // expected-error {{cannot jump}} + if constexpr (true || ({evil_label: false;})) {} // expected-note {{constexpr if}} + + if constexpr (true) // expected-note {{constexpr if}} + goto surprise; // expected-error {{cannot jump}} + else + surprise: {} + } +} +#endif diff --git a/clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp b/clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp new file mode 100644 index 000000000000..80a397f51e9a --- /dev/null +++ b/clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -std=c++1z %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_not_be_used(); +void f() { + if constexpr (false) + should_not_be_used(); + else + should_be_used_1(); + + if constexpr (true || ({ label: false; })) + should_be_used_2(); + else { + goto foo; +foo: should_not_be_used(); + } +} + +// CHECK: should_be_used_1 +// CHECK: should_be_used_2 diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 7d1e746a62ab..121567682d74 100644 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -671,6 +671,12 @@ as the draft C++1z standard evolves.

P0245R1 Yes + + + constexpr if-statements + P0292R2 + SVN +