145 changes: 113 additions & 32 deletions clang/lib/Parse/ParseTentative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,70 @@ Parser::TPResult Parser::TryParseInitDeclaratorList() {
return TPResult::Ambiguous;
}

/// isCXXConditionDeclaration - Disambiguates between a declaration or an
/// expression for a condition of a if/switch/while/for statement.
/// If during the disambiguation process a parsing error is encountered,
/// the function returns true to let the declaration parsing code handle it.
struct Parser::ConditionDeclarationOrInitStatementState {
Parser &P;
bool CanBeExpression = true;
bool CanBeCondition = true;
bool CanBeInitStatement;

ConditionDeclarationOrInitStatementState(Parser &P, bool CanBeInitStatement)
: P(P), CanBeInitStatement(CanBeInitStatement) {}

void markNotExpression() {
CanBeExpression = false;

if (CanBeCondition && CanBeInitStatement) {
// FIXME: Unify the parsing codepaths for condition variables and
// simple-declarations so that we don't need to eagerly figure out which
// kind we have here. (Just parse init-declarators until we reach a
// semicolon or right paren.)
RevertingTentativeParsingAction PA(P);
P.SkipUntil(tok::r_paren, tok::semi, StopBeforeMatch);
if (P.Tok.isNot(tok::r_paren))
CanBeCondition = false;
if (P.Tok.isNot(tok::semi))
CanBeInitStatement = false;
}
}

bool markNotCondition() {
CanBeCondition = false;
return !CanBeInitStatement || !CanBeExpression;
}

bool update(TPResult IsDecl) {
switch (IsDecl) {
case TPResult::True:
markNotExpression();
return true;
case TPResult::False:
CanBeCondition = CanBeInitStatement = false;
return true;
case TPResult::Ambiguous:
return false;
case TPResult::Error:
CanBeExpression = CanBeCondition = CanBeInitStatement = false;
return true;
}
llvm_unreachable("unknown tentative parse result");
}

ConditionOrInitStatement result() const {
assert(CanBeExpression + CanBeCondition + CanBeInitStatement < 2 &&
"result called but not yet resolved");
if (CanBeExpression)
return ConditionOrInitStatement::Expression;
if (CanBeCondition)
return ConditionOrInitStatement::ConditionDecl;
if (CanBeInitStatement)
return ConditionOrInitStatement::InitStmtDecl;
return ConditionOrInitStatement::Error;
}
};

/// \brief Disambiguates between a declaration in a condition, a
/// simple-declaration in an init-statement, and an expression for
/// a condition of a if/switch statement.
///
/// condition:
/// expression
Expand All @@ -342,45 +402,64 @@ Parser::TPResult Parser::TryParseInitDeclaratorList() {
/// [C++11] type-specifier-seq declarator braced-init-list
/// [GNU] type-specifier-seq declarator simple-asm-expr[opt] attributes[opt]
/// '=' assignment-expression
/// simple-declaration:
/// decl-specifier-seq init-declarator-list[opt] ';'
///
bool Parser::isCXXConditionDeclaration() {
TPResult TPR = isCXXDeclarationSpecifier();
if (TPR != TPResult::Ambiguous)
return TPR != TPResult::False; // Returns true for TPResult::True or
// TPResult::Error.
/// Note that, unlike isCXXSimpleDeclaration, we must disambiguate all the way
/// to the ';' to disambiguate cases like 'int(x))' (an expression) from
/// 'int(x);' (a simple-declaration in an init-statement).
Parser::ConditionOrInitStatement
Parser::isCXXConditionDeclarationOrInitStatement(bool CanBeInitStatement) {
ConditionDeclarationOrInitStatementState State(*this, CanBeInitStatement);

// FIXME: Add statistics about the number of ambiguous statements encountered
// and how they were resolved (number of declarations+number of expressions).

// Ok, we have a simple-type-specifier/typename-specifier followed by a '('.
// We need tentative parsing...
if (State.update(isCXXDeclarationSpecifier()))
return State.result();

// It might be a declaration; we need tentative parsing.
RevertingTentativeParsingAction PA(*this);

// type-specifier-seq
TryConsumeDeclarationSpecifier();
// FIXME: A tag definition unambiguously tells us this is an init-statement.
if (State.update(TryConsumeDeclarationSpecifier()))
return State.result();
assert(Tok.is(tok::l_paren) && "Expected '('");

// declarator
TPR = TryParseDeclarator(false/*mayBeAbstract*/);
while (true) {
// Consume a declarator.
if (State.update(TryParseDeclarator(false/*mayBeAbstract*/)))
return State.result();

// Attributes, asm label, or an initializer imply this is not an expression.
// FIXME: Disambiguate properly after an = instead of assuming that it's a
// valid declaration.
if (Tok.isOneOf(tok::equal, tok::kw_asm, tok::kw___attribute) ||
(getLangOpts().CPlusPlus11 && Tok.is(tok::l_brace))) {
State.markNotExpression();
return State.result();
}

// In case of an error, let the declaration parsing code handle it.
if (TPR == TPResult::Error)
TPR = TPResult::True;
// At this point, it can't be a condition any more, because a condition
// must have a brace-or-equal-initializer.
if (State.markNotCondition())
return State.result();

if (TPR == TPResult::Ambiguous) {
// '='
// [GNU] simple-asm-expr[opt] attributes[opt]
if (Tok.isOneOf(tok::equal, tok::kw_asm, tok::kw___attribute))
TPR = TPResult::True;
else if (getLangOpts().CPlusPlus11 && Tok.is(tok::l_brace))
TPR = TPResult::True;
else
TPR = TPResult::False;
// A parenthesized initializer could be part of an expression or a
// simple-declaration.
if (Tok.is(tok::l_paren)) {
ConsumeParen();
SkipUntil(tok::r_paren, StopAtSemi);
}

if (!TryConsumeToken(tok::comma))
break;
}

assert(TPR == TPResult::True || TPR == TPResult::False);
return TPR == TPResult::True;
// We reached the end. If it can now be some kind of decl, then it is.
if (State.CanBeCondition && Tok.is(tok::r_paren))
return ConditionOrInitStatement::ConditionDecl;
else if (State.CanBeInitStatement && Tok.is(tok::semi))
return ConditionOrInitStatement::InitStmtDecl;
else
return ConditionOrInitStatement::Expression;
}

/// \brief Determine whether the next set of tokens contains a type-id.
Expand Down Expand Up @@ -1329,6 +1408,8 @@ Parser::isCXXDeclarationSpecifier(Parser::TPResult BracedCastResult,
*HasMissingTypename = true;
return TPResult::Ambiguous;
}

// FIXME: Fails to either revert or commit the tentative parse!
} else {
// Try to resolve the name. If it doesn't exist, assume it was
// intended to name a type and keep disambiguating.
Expand Down
13 changes: 10 additions & 3 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,13 @@ class CommaVisitor : public EvaluatedExprVisitor<CommaVisitor> {
}

StmtResult
Sema::ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, ConditionResult Cond,
Sema::ActOnIfStmt(SourceLocation IfLoc, bool IsConstexpr, Stmt *InitStmt,
ConditionResult Cond,
Stmt *thenStmt, SourceLocation ElseLoc,
Stmt *elseStmt) {
if (InitStmt)
Diag(InitStmt->getLocStart(), diag::err_init_stmt_not_supported);

if (Cond.isInvalid())
Cond = ConditionResult(
*this, nullptr,
Expand Down Expand Up @@ -659,11 +663,14 @@ ExprResult Sema::CheckSwitchCondition(SourceLocation SwitchLoc, Expr *Cond) {
return UsualUnaryConversions(CondResult.get());
}

StmtResult
Sema::ActOnStartOfSwitchStmt(SourceLocation SwitchLoc, ConditionResult Cond) {
StmtResult Sema::ActOnStartOfSwitchStmt(SourceLocation SwitchLoc,
Stmt *InitStmt, ConditionResult Cond) {
if (Cond.isInvalid())
return StmtError();

if (InitStmt)
Diag(InitStmt->getLocStart(), diag::err_init_stmt_not_supported);

getCurFunction()->setHasBranchIntoScope();

SwitchStmt *SS =
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2883,6 +2883,7 @@ static QualType GetDeclSpecTypeForDeclarator(TypeProcessingState &state,
case Declarator::FileContext:
case Declarator::BlockContext:
case Declarator::ForContext:
case Declarator::InitStmtContext:
case Declarator::ConditionContext:
break;
case Declarator::CXXNewContext:
Expand Down Expand Up @@ -2968,6 +2969,7 @@ static QualType GetDeclSpecTypeForDeclarator(TypeProcessingState &state,
case Declarator::MemberContext:
case Declarator::BlockContext:
case Declarator::ForContext:
case Declarator::InitStmtContext:
case Declarator::BlockLiteralContext:
case Declarator::LambdaExprContext:
// C++11 [dcl.type]p3:
Expand Down Expand Up @@ -3711,6 +3713,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
case Declarator::CXXCatchContext:
case Declarator::CXXNewContext:
case Declarator::ForContext:
case Declarator::InitStmtContext:
case Declarator::LambdaExprContext:
case Declarator::LambdaExprParameterContext:
case Declarator::ObjCCatchContext:
Expand Down Expand Up @@ -4523,6 +4526,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
case Declarator::MemberContext:
case Declarator::BlockContext:
case Declarator::ForContext:
case Declarator::InitStmtContext:
case Declarator::ConditionContext:
case Declarator::CXXCatchContext:
case Declarator::ObjCCatchContext:
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,8 @@ class TreeTransform {
StmtResult RebuildIfStmt(SourceLocation IfLoc, bool IsConstexpr,
Sema::ConditionResult Cond, Stmt *Then,
SourceLocation ElseLoc, Stmt *Else) {
return getSema().ActOnIfStmt(IfLoc, IsConstexpr, Cond, Then, ElseLoc, Else);
return getSema().ActOnIfStmt(IfLoc, IsConstexpr, nullptr, Cond, Then,
ElseLoc, Else);
}

/// \brief Start building a new switch statement.
Expand All @@ -1185,7 +1186,7 @@ class TreeTransform {
/// Subclasses may override this routine to provide different behavior.
StmtResult RebuildSwitchStmtStart(SourceLocation SwitchLoc,
Sema::ConditionResult Cond) {
return getSema().ActOnStartOfSwitchStmt(SwitchLoc, Cond);
return getSema().ActOnStartOfSwitchStmt(SwitchLoc, nullptr, Cond);
}

/// \brief Attach the body to the switch statement.
Expand Down
47 changes: 47 additions & 0 deletions clang/test/Parser/cxx1z-init-statement.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// RUN: %clang_cc1 -std=c++1z -verify %s -Wno-vexing-parse

int g, h;
typedef int T;
int f() {
// init-statement declarations
if (T n = 0; n != 0) {} // expected-error {{not yet supported}}
if (T f(); f()) {} // expected-error {{not yet supported}}
if (T(f()); f()) {} // expected-error {{not yet supported}}
if (T(f()), g, h; f()) {} // expected-error {{not yet supported}}
if (T f(); f()) {} // expected-error {{not yet supported}}
if (T f(), g, h; f()) {} // expected-error {{not yet supported}}

// init-statement expressions
if (T{f()}; f()) {} // expected-error {{not yet supported}}
if (T{f()}, g, h; f()) {} // expected-error {{not yet supported}} expected-warning 2{{unused}}
if (T(f()), g, h + 1; f()) {} // expected-error {{not yet supported}} expected-warning 2{{unused}}

// condition declarations
if (T(n){g}) {}
if (T f()) {} // expected-error {{function type}}
if (T f(), g, h) {} // expected-error {{function type}}

// condition expressions
if (T(f())) {}
if (T{f()}) {}
if (T(f()), g, h) {} // expected-warning 2{{unused}}
if (T{f()}, g, h) {} // expected-warning 2{{unused}}

// none of the above
// FIXME: This causes a typo-correction crash, as does "void f() { +T(n)(g); }"
//if (T(n)(g)) {} // expected-err-FIXME {{not a function}}

// Likewise for 'switch'
switch (int n; n) {} // expected-error {{not yet supported}}
switch (g; int g = 5) {} // expected-error {{not yet supported}}

if (int a, b; int c = a) { // expected-error {{not yet supported}} expected-note 6{{previous}}
int a; // expected-error {{redefinition}}
int b; // expected-error {{redefinition}}
int c; // expected-error {{redefinition}}
} else {
int a; // expected-error {{redefinition}}
int b; // expected-error {{redefinition}}
int c; // expected-error {{redefinition}}
}
}
1 change: 0 additions & 1 deletion clang/test/Parser/extra-semi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

void test1(int a;) { // expected-error{{unexpected ';' before ')'}}
while (a > 5;) {} // expected-error{{unexpected ';' before ')'}}
if (int b = 10;) {} // expected-error{{unexpected ';' before ')'}}
for (int c = 0; c < 21; ++c;) {} // expected-error{{unexpected ';' before ')'}}
int d = int(3 + 4;); // expected-error{{unexpected ';' before ')'}}
int e[5;]; // expected-error{{unexpected ';' before ']'}}
Expand Down