31 changes: 31 additions & 0 deletions clang/lib/Sema/JumpDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,37 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
Jumps.push_back(S);
break;

case Stmt::IfStmtClass: {
IfStmt *IS = cast<IfStmt>(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<CXXTryStmt>(S);
{
Expand Down
18 changes: 15 additions & 3 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<ParenExpr>(E))
DiagnoseEqualityWithExtraParens(parenE);
Expand All @@ -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())
Expand Down Expand Up @@ -14383,14 +14390,19 @@ 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;
}
if (Cond.isInvalid())
return ConditionError();

return ConditionResult(nullptr, MakeFullExpr(Cond.get(), Loc));
return ConditionResult(*this, nullptr, MakeFullExpr(Cond.get(), Loc),
CK == ConditionKind::ConstexprIf);
}

namespace {
Expand Down
15 changes: 12 additions & 3 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3061,7 +3061,8 @@ Sema::ConditionResult Sema::ActOnConditionVariable(Decl *ConditionVar,
CheckConditionVariable(cast<VarDecl>(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,
Expand Down Expand Up @@ -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());
}
Expand All @@ -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
Expand All @@ -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++
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/SemaExprMember.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/SemaLambda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
77 changes: 57 additions & 20 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -504,31 +504,43 @@ class CommaVisitor : public EvaluatedExprVisitor<CommaVisitor> {
}

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);

DiagnoseUnusedExprResult(thenStmt);
CondExpr->getExprLoc()))
CommaVisitor(*this).Visit(CondExpr);

if (!elseStmt) {
DiagnoseEmptyStmtBody(CondVal.second->getLocEnd(), thenStmt,
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);
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 {
Expand Down Expand Up @@ -2836,8 +2848,21 @@ Sema::ActOnCapScopeReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) {
CapturingScopeInfo *CurCap = cast<CapturingScopeInfo>(getCurFunction());
QualType FnRetType = CurCap->ReturnType;
LambdaScopeInfo *CurLambda = dyn_cast<LambdaScopeInfo>(CurCap);
bool HasDeducedReturnType =
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 (CurLambda && hasDeducedReturnType(CurLambda->CallOperator)) {
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;
Expand Down Expand Up @@ -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<VarDecl*>(cast<ReturnStmt>(R.get())->getNRVOCandidate())) {
Expand Down Expand Up @@ -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) {
Expand Down
39 changes: 27 additions & 12 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -1174,9 +1174,10 @@ class TreeTransform {
///
/// 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.
Expand Down Expand Up @@ -6228,28 +6229,42 @@ TreeTransform<Derived>::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<bool> 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()) &&
Then.get() == S->getThen() &&
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<typename Derived>
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Serialization/ASTReaderStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<VarDecl>(Record, Idx));
S->setCond(Reader.ReadSubExpr());
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Serialization/ASTWriterStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
47 changes: 47 additions & 0 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p2-1z.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// RUN: %clang_cc1 -std=c++1z -verify %s

template<typename T, typename U> constexpr bool same = false;
template<typename T> constexpr bool same<T, T> = true;

auto a() {
if constexpr (false)
return 0;
}
static_assert(same<decltype(a()), void>);

auto b() {
if constexpr (false)
return 0;
else
return 0.0;
}
static_assert(same<decltype(b()), double>);

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<int k> 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<decltype(d<0>()), int>);
static_assert(same<decltype(d<1>()), const char *>);
static_assert(same<decltype(d<2>()), double>);
static_assert(same<decltype(d<3>()), 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<decltype(e), int>);
137 changes: 137 additions & 0 deletions clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp
Original file line number Diff line number Diff line change
@@ -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 <typename T, typename... Rest> 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<int N> 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<typename T> void f() {
[](auto x) {
if constexpr (sizeof(T) == 1 && sizeof(x) == 1)
T::error(); // expected-error 2{{'::'}}
} (0);
}

template<typename T> void g() {
[](auto x) {
if constexpr (sizeof(T) == 1)
if constexpr (sizeof(x) == 1)
T::error(); // expected-error {{'::'}}
} (0);
}

void use() {
f<int>(); // expected-note {{instantiation of}}
f<char>(); // expected-note {{instantiation of}}
g<int>(); // ok
g<char>(); // 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<typename T>
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<typename T>
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
21 changes: 21 additions & 0 deletions clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions clang/www/cxx_status.html
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ <h2 id="cxx17">C++1z implementation status</h2>
<td><a href="http://wg21.link/p0245r1">P0245R1</a></td>
<td class="full" align="center">Yes</td>
</tr>
<!-- Oulu papers -->
<tr>
<td><tt>constexpr</tt> <em>if-statement</em>s</td>
<td><a href="http://wg21.link/p0292r2">P0292R2</a></td>
<td class="svn" align="center">SVN</td>
</tr>
</table>

<p>
Expand Down