Skip to content

Commit eef1ee2

Browse files
Merge #1049
1049: Add better restrictions around semicolons in statements r=CohenArthur a=CohenArthur When parsing macro invocations, rustc does not actually consume the statement's trailing semicolon. Let's take the following example: ```rust macro_rules! one_stmt { ($s:stmt) => {}; } macro_rules! one_or_more_stmt { ($($s:stmt)*) => {}; } one_stmt!(let a = 1); one_stmt!(let b = 2;); // error one_or_more_stmt!(;); // valid one_or_more_stmt!(let a = 15;); // valid, two statements! one_or_more_stmt!(let a = 15 let b = 13); // valid, two statements again ``` A semicolon can count as a valid empty statement, but cannot be part of a statement (in macro invocations). This commit adds more restrictions that allow the parser to not always expect a semicolon token after the statement. Furthermore, this fixes a test that was previously accepted by the compiler but not by rustc. Fixes #1046 Co-authored-by: Arthur Cohen <arthur.cohen@embecosm.com>
2 parents b9720ca + ef56381 commit eef1ee2

File tree

5 files changed

+53
-34
lines changed

5 files changed

+53
-34
lines changed

gcc/rust/expand/rust-macro-expand.cc

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,12 @@ MacroExpander::match_fragment (Parser<MacroInvocLexer> &parser,
477477
parser.parse_visibility ();
478478
break;
479479

480-
case AST::MacroFragSpec::STMT:
481-
parser.parse_stmt (/* allow_no_semi */ true);
482-
break;
480+
case AST::MacroFragSpec::STMT: {
481+
auto restrictions = ParseRestrictions ();
482+
restrictions.consume_semi = false;
483+
parser.parse_stmt (restrictions);
484+
break;
485+
}
483486

484487
case AST::MacroFragSpec::LIFETIME:
485488
parser.parse_lifetime_params ();
@@ -887,11 +890,14 @@ transcribe_many_trait_impl_items (Parser<MacroInvocLexer> &parser,
887890
static std::vector<AST::SingleASTNode>
888891
transcribe_many_stmts (Parser<MacroInvocLexer> &parser, TokenId &delimiter)
889892
{
893+
auto restrictions = ParseRestrictions ();
894+
restrictions.consume_semi = false;
895+
890896
// FIXME: This is invalid! It needs to also handle cases where the macro
891897
// transcriber is an expression, but since the macro call is followed by
892898
// a semicolon, it's a valid ExprStmt
893-
return parse_many (parser, delimiter, [&parser] () {
894-
auto stmt = parser.parse_stmt (/* allow_no_semi */ true);
899+
return parse_many (parser, delimiter, [&parser, restrictions] () {
900+
auto stmt = parser.parse_stmt (restrictions);
895901
return AST::SingleASTNode (std::move (stmt));
896902
});
897903
}

gcc/rust/parse/rust-parse-impl.h

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6100,7 +6100,7 @@ Parser<ManagedTokenSource>::parse_named_function_param (
61006100
// Parses a statement (will further disambiguate any statement).
61016101
template <typename ManagedTokenSource>
61026102
std::unique_ptr<AST::Stmt>
6103-
Parser<ManagedTokenSource>::parse_stmt (bool allow_no_semi)
6103+
Parser<ManagedTokenSource>::parse_stmt (ParseRestrictions restrictions)
61046104
{
61056105
// quick exit for empty statement
61066106
// FIXME: Can we have empty statements without semicolons? Just nothing?
@@ -6125,7 +6125,7 @@ Parser<ManagedTokenSource>::parse_stmt (bool allow_no_semi)
61256125
{
61266126
case LET:
61276127
// let statement
6128-
return parse_let_stmt (std::move (outer_attrs), allow_no_semi);
6128+
return parse_let_stmt (std::move (outer_attrs), restrictions);
61296129
case PUB:
61306130
case MOD:
61316131
case EXTERN_TOK:
@@ -6180,7 +6180,7 @@ Parser<ManagedTokenSource>::parse_stmt (bool allow_no_semi)
61806180
// TODO: find out how to disable gcc "implicit fallthrough" warning
61816181
default:
61826182
// fallback: expression statement
6183-
return parse_expr_stmt (std::move (outer_attrs), allow_no_semi);
6183+
return parse_expr_stmt (std::move (outer_attrs), restrictions);
61846184
break;
61856185
}
61866186
}
@@ -6189,7 +6189,7 @@ Parser<ManagedTokenSource>::parse_stmt (bool allow_no_semi)
61896189
template <typename ManagedTokenSource>
61906190
std::unique_ptr<AST::LetStmt>
61916191
Parser<ManagedTokenSource>::parse_let_stmt (AST::AttrVec outer_attrs,
6192-
bool allow_no_semi)
6192+
ParseRestrictions restrictions)
61936193
{
61946194
Location locus = lexer.peek_token ()->get_locus ();
61956195
skip_token (LET);
@@ -6244,13 +6244,9 @@ Parser<ManagedTokenSource>::parse_let_stmt (AST::AttrVec outer_attrs,
62446244
}
62456245
}
62466246

6247-
if (!maybe_skip_token (SEMICOLON) && !allow_no_semi)
6248-
{
6249-
// skip after somewhere
6247+
if (restrictions.consume_semi)
6248+
if (!skip_token (SEMICOLON))
62506249
return nullptr;
6251-
/* TODO: how wise is it to ditch a mostly-valid let statement just
6252-
* because a semicolon is missing? */
6253-
}
62546250

62556251
return std::unique_ptr<AST::LetStmt> (
62566252
new AST::LetStmt (std::move (pattern), std::move (expr), std::move (type),
@@ -7085,7 +7081,7 @@ Parser<ManagedTokenSource>::parse_method ()
70857081
template <typename ManagedTokenSource>
70867082
std::unique_ptr<AST::ExprStmt>
70877083
Parser<ManagedTokenSource>::parse_expr_stmt (AST::AttrVec outer_attrs,
7088-
bool allow_no_semi)
7084+
ParseRestrictions restrictions)
70897085
{
70907086
/* potential thoughts - define new virtual method "has_block()" on expr. parse
70917087
* expr and then determine whether semicolon is needed as a result of this
@@ -7125,7 +7121,7 @@ Parser<ManagedTokenSource>::parse_expr_stmt (AST::AttrVec outer_attrs,
71257121
else
71267122
{
71277123
return parse_expr_stmt_without_block (std::move (outer_attrs),
7128-
allow_no_semi);
7124+
restrictions);
71297125
}
71307126
}
71317127
case UNSAFE: {
@@ -7139,7 +7135,7 @@ Parser<ManagedTokenSource>::parse_expr_stmt (AST::AttrVec outer_attrs,
71397135
else
71407136
{
71417137
return parse_expr_stmt_without_block (std::move (outer_attrs),
7142-
allow_no_semi);
7138+
restrictions);
71437139
}
71447140
}
71457141
default:
@@ -7148,7 +7144,7 @@ Parser<ManagedTokenSource>::parse_expr_stmt (AST::AttrVec outer_attrs,
71487144
* initial tokens in order to prevent more syntactical errors at parse
71497145
* time. */
71507146
return parse_expr_stmt_without_block (std::move (outer_attrs),
7151-
allow_no_semi);
7147+
restrictions);
71527148
}
71537149
}
71547150

@@ -7264,7 +7260,7 @@ Parser<ManagedTokenSource>::parse_expr_stmt_with_block (
72647260
template <typename ManagedTokenSource>
72657261
std::unique_ptr<AST::ExprStmtWithoutBlock>
72667262
Parser<ManagedTokenSource>::parse_expr_stmt_without_block (
7267-
AST::AttrVec outer_attrs, bool allow_no_semi)
7263+
AST::AttrVec outer_attrs, ParseRestrictions restrictions)
72687264
{
72697265
/* TODO: maybe move more logic for expr without block in here for better error
72707266
* handling */
@@ -7273,7 +7269,6 @@ Parser<ManagedTokenSource>::parse_expr_stmt_without_block (
72737269
std::unique_ptr<AST::ExprWithoutBlock> expr = nullptr;
72747270
Location locus = lexer.peek_token ()->get_locus ();
72757271

7276-
auto restrictions = ParseRestrictions ();
72777272
restrictions.expr_can_be_stmt = true;
72787273

72797274
expr = parse_expr_without_block (std::move (outer_attrs), restrictions);
@@ -7288,12 +7283,9 @@ Parser<ManagedTokenSource>::parse_expr_stmt_without_block (
72887283
return nullptr;
72897284
}
72907285

7291-
// skip semicolon at end that is required
7292-
if (!maybe_skip_token (SEMICOLON) && !allow_no_semi)
7293-
{
7294-
// skip somewhere?
7286+
if (restrictions.consume_semi)
7287+
if (!skip_token (SEMICOLON))
72957288
return nullptr;
7296-
}
72977289

72987290
return std::unique_ptr<AST::ExprStmtWithoutBlock> (
72997291
new AST::ExprStmtWithoutBlock (std::move (expr), locus));

gcc/rust/parse/rust-parse.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ struct ParseRestrictions
8181
bool entered_from_unary = false;
8282
bool expr_can_be_null = false;
8383
bool expr_can_be_stmt = false;
84+
bool consume_semi = true;
8485
};
8586

8687
// Parser implementation for gccrs.
@@ -129,11 +130,9 @@ template <typename ManagedTokenSource> class Parser
129130
* | LetStatement
130131
* | ExpressionStatement
131132
* | MacroInvocationSemi
132-
*
133-
* @param allow_no_semi Allow the parser to not parse a semicolon after
134-
* the statement without erroring out
135133
*/
136-
std::unique_ptr<AST::Stmt> parse_stmt (bool allow_no_semi = false);
134+
std::unique_ptr<AST::Stmt> parse_stmt (ParseRestrictions restrictions
135+
= ParseRestrictions ());
137136
std::unique_ptr<AST::Type> parse_type ();
138137
std::unique_ptr<AST::ExternalItem> parse_external_item ();
139138
std::unique_ptr<AST::TraitItem> parse_trait_item ();
@@ -616,14 +615,17 @@ template <typename ManagedTokenSource> class Parser
616615
* semicolon to follow it
617616
*/
618617
std::unique_ptr<AST::LetStmt> parse_let_stmt (AST::AttrVec outer_attrs,
619-
bool allow_no_semi = false);
618+
ParseRestrictions restrictions
619+
= ParseRestrictions ());
620620
std::unique_ptr<AST::ExprStmt> parse_expr_stmt (AST::AttrVec outer_attrs,
621-
bool allow_no_semi = false);
621+
ParseRestrictions restrictions
622+
= ParseRestrictions ());
622623
std::unique_ptr<AST::ExprStmtWithBlock>
623624
parse_expr_stmt_with_block (AST::AttrVec outer_attrs);
624625
std::unique_ptr<AST::ExprStmtWithoutBlock>
625626
parse_expr_stmt_without_block (AST::AttrVec outer_attrs,
626-
bool allow_no_semi = false);
627+
ParseRestrictions restrictions
628+
= ParseRestrictions ());
627629
ExprOrStmt parse_stmt_or_expr_without_block ();
628630
ExprOrStmt parse_stmt_or_expr_with_block (AST::AttrVec outer_attrs);
629631
ExprOrStmt parse_macro_invocation_maybe_semi (AST::AttrVec outer_attrs);

gcc/testsuite/rust/compile/macro18.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ macro_rules! take_stmt {
77
}
88

99
fn main() -> i32 {
10-
take_stmt!(let complete = 15;);
10+
take_stmt!(let complete = 15;); // { dg-error "Failed to match any rule within macro" }
1111
take_stmt!(let lacking = 14);
1212

1313
0

gcc/testsuite/rust/compile/macro32.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
macro_rules! s {
2+
($s:stmt) => {{}};
3+
}
4+
5+
macro_rules! multi_s {
6+
($($s:stmt)+) => {{}};
7+
}
8+
9+
fn main() -> i32 {
10+
s!(let a = 15);
11+
s!(;); // Empty statement
12+
s!(let a = 15;); // { dg-error "Failed to match any rule within macro" }
13+
multi_s!(let a = 15;);
14+
// ^ this actually gets parsed as two statements - one LetStmt and one
15+
// empty statement. This is the same behavior as rustc, which you can
16+
// see using a count!() macro
17+
18+
32
19+
}

0 commit comments

Comments
 (0)