]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
gccrs: Parse semicolons in more cases for statement macros
authorMatthew Jasper <mjjasper1@gmail.com>
Thu, 8 Jun 2023 19:14:47 +0000 (20:14 +0100)
committerArthur Cohen <arthur.cohen@embecosm.com>
Tue, 16 Jan 2024 17:46:28 +0000 (18:46 +0100)
gccrs: Parse statement macros as statements.

gcc/rust/ChangeLog:

* ast/rust-ast.h (MacroInvocation::add_semicolon): New method.
(Expr::to_stmt): Remove method.
* ast/rust-macro.h (MacroInvocation::add_semicolon): Add override.
(MacroInvocation::to_stmt): Remove override.
* ast/rust-stmt.h: Remove use of Expr::to_stmt.
(ExprStmt::add_semicolon): Add override.
* expand/rust-macro-expand.h (struct MacroExpander):
Add EXPR/STMT and remove BLOCK from ContextType.
* expand/rust-expand-visitor.cc (ExpandVisitor::maybe_expand_expr): Use EXPR context.
(ExpandVisitor::expand_inner_stmts): Use STMT context.
(ExpandVisitor::visitor): Remove use of BLOCK context.
* expand/rust-macro-expand.cc (parse_many): Pass enum by value.
(transcribe_on_delimiter): Remove function.
(transcribe_context): Use EXPR/STMT contexts.
(MacroExpander::parse_proc_macro_output): Use EXPR/STMT contexts.
(transcribe_many_stmts): Parse statements with semicolons.
* parse/rust-parse-impl.h (Parser::parse_stmt):
Delegate macro parsing to parse_expr_stmt, check for ! after macro_rules.
(Parser::parse_let_stmt): Work around lack of NT tokens.
(Parser::parse_expr_stmt): Handle statements at end of macro expansions.
(Parser::parse_expr_stmt): Parse macro statements/expression statements
starting with a macro.
(Parser::parse_match_expr): Don't modify flag unnecessarily.
(Parser::parse_stmt_or_expr):
Parse macro statements/expression statements starting with a macro.
(Parser::parse_path_based_stmt_or_expr): Remove method.
(Parser::parse_macro_invocation_maybe_semi): Remove method.
(Parser::parse_expr): Move code into left_denotations.
(Parser::left_denotations): New method.
(Parser::null_denotation): Split out methods for cases with and without paths.
(Parser::null_denotation_path): New method.
(Parser::null_denotation_not_path): New method.
(Parser::parse_macro_invocation_partial): Don't check for semicolon here.
* parse/rust-parse.h: Update declarations.
(struct ParseRestrictions): Additional flag.

gcc/testsuite/ChangeLog:

* rust/compile/braced_macro_arm.rs: New test.
* rust/compile/braced_macro_statements1.rs: New test.
* rust/compile/braced_macro_statements2.rs: New test.
* rust/compile/braced_macro_statements3.rs: New test.
* rust/compile/issue-2225.rs: Update test.
* rust/compile/macro53.rs: New test.

Signed-off-by: Matthew Jasper <mjjasper1@gmail.com>
14 files changed:
gcc/rust/ast/rust-ast.h
gcc/rust/ast/rust-macro.h
gcc/rust/ast/rust-stmt.h
gcc/rust/expand/rust-expand-visitor.cc
gcc/rust/expand/rust-macro-expand.cc
gcc/rust/expand/rust-macro-expand.h
gcc/rust/parse/rust-parse-impl.h
gcc/rust/parse/rust-parse.h
gcc/testsuite/rust/compile/braced_macro_arm.rs [new file with mode: 0644]
gcc/testsuite/rust/compile/braced_macro_statements1.rs [new file with mode: 0644]
gcc/testsuite/rust/compile/braced_macro_statements2.rs [new file with mode: 0644]
gcc/testsuite/rust/compile/braced_macro_statements3.rs [new file with mode: 0644]
gcc/testsuite/rust/compile/issue-2225.rs
gcc/testsuite/rust/compile/macro53.rs [new file with mode: 0644]

index 09566575a85eb501029820e0b592ecbb5141167d..3c1f95a973f462fc9b86fa1c6e267f487b6c65e6 100644 (file)
@@ -904,6 +904,8 @@ public:
 
   virtual bool is_expr () const { return false; }
 
+  virtual void add_semicolon () {}
+
 protected:
   Stmt () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
 
@@ -986,8 +988,6 @@ public:
 
   virtual std::vector<Attribute> &get_outer_attrs () = 0;
 
-  virtual Expr *to_stmt () const { return clone_expr_impl (); }
-
   // TODO: think of less hacky way to implement this kind of thing
   // Sets outer attributes.
   virtual void set_outer_attrs (std::vector<Attribute>) = 0;
index 076ab978bc35f7e7e6a9e1743ca446ec0db5dec9..a0d1adc7de19561a3e004e75e754ec968b83fecf 100644 (file)
@@ -787,6 +787,8 @@ public:
     return new MacroInvocation (*this);
   }
 
+  void add_semicolon () override { is_semi_coloned = true; }
+
 protected:
   Item *clone_item_impl () const override
   {
@@ -809,15 +811,6 @@ protected:
   {
     return clone_macro_invocation_impl ();
   }
-
-  Expr *to_stmt () const override
-
-  {
-    auto new_impl = clone_macro_invocation_impl ();
-    new_impl->is_semi_coloned = true;
-
-    return new_impl;
-  }
 };
 
 // more generic meta item path-only form
index 7496a376d8a51097c316bec742d653e179dc070d..95a5a62bfa4abfcbaa1cd5545488e2cf69bca5c2 100644 (file)
@@ -192,12 +192,18 @@ public:
   bool is_item () const override final { return false; }
 
   bool is_expr () const override final { return true; }
+
+  // Used for the last statement for statement macros with a trailing
+  // semicolon.
+  void add_semicolon () override final { semicolon_followed = true; }
+
   std::string as_string () const override;
 
   std::vector<LetStmt *> locals;
 
-  ExprStmt (std::unique_ptr<Expr> expr, Location locus, bool semicolon_followed)
-    : expr (expr->to_stmt ()), locus (locus),
+  ExprStmt (std::unique_ptr<Expr> &&expr, Location locus,
+           bool semicolon_followed)
+    : expr (std::move (expr)), locus (locus),
       semicolon_followed (semicolon_followed)
   {}
 
index 3601287be9963423c0eeef0e939487be09d6cead..9183a63b9a9ff16406fcf76853882607d57cfa54 100644 (file)
@@ -232,7 +232,7 @@ void
 ExpandVisitor::expand_inner_stmts (
   std::vector<std::unique_ptr<AST::Stmt>> &stmts)
 {
-  expander.push_context (MacroExpander::ContextType::BLOCK);
+  expander.push_context (MacroExpander::ContextType::STMT);
 
   for (auto it = stmts.begin (); it != stmts.end (); it++)
     {
@@ -272,10 +272,9 @@ ExpandVisitor::expand_inner_stmts (
 void
 ExpandVisitor::maybe_expand_expr (std::unique_ptr<AST::Expr> &expr)
 {
-  // FIXME: ARTHUR: Why isn't there a ContextType::EXPR? We can only
-  // reach `parse_expr` once in MacroExpander::transcribe_rule(), but it
-  // would make things clearer wouldn't it?
+  expander.push_context (MacroExpander::ContextType::EXPR);
   expr->accept_vis (*this);
+  expander.pop_context ();
 
   auto final_fragment = expander.take_expanded_fragment ();
   if (final_fragment.should_expand ()
@@ -732,12 +731,8 @@ ExpandVisitor::visit (AST::BlockExpr &expr)
 {
   expand_inner_stmts (expr.get_statements ());
 
-  expander.push_context (MacroExpander::ContextType::BLOCK);
-
   if (expr.has_tail_expr ())
     maybe_expand_expr (expr.get_tail_expr ());
-
-  expander.pop_context ();
 }
 
 void
@@ -1438,7 +1433,7 @@ ExpandVisitor::visit (AST::LetStmt &stmt)
 void
 ExpandVisitor::visit (AST::ExprStmt &stmt)
 {
-  visit (stmt.get_expr ());
+  maybe_expand_expr (stmt.get_expr ());
 }
 
 void
index a92bbdcb943371afd38310a7cdbc548917a23e88..225049ad2e4ced80db2650f9d28e275542a9aed5 100644 (file)
@@ -724,7 +724,7 @@ MacroExpander::match_repetition (Parser<MacroInvocLexer> &parser,
  * Helper function to refactor calling a parsing function 0 or more times
  */
 static AST::Fragment
-parse_many (Parser<MacroInvocLexer> &parser, TokenId &delimiter,
+parse_many (Parser<MacroInvocLexer> &parser, TokenId delimiter,
            std::function<AST::SingleASTNode ()> parse_fn)
 {
   auto &lexer = parser.get_token_source ();
@@ -836,18 +836,22 @@ transcribe_many_trait_impl_items (Parser<MacroInvocLexer> &parser,
  * @param delimiter Id of the token on which parsing should stop
  */
 static AST::Fragment
-transcribe_many_stmts (Parser<MacroInvocLexer> &parser, TokenId &delimiter)
+transcribe_many_stmts (Parser<MacroInvocLexer> &parser, TokenId delimiter,
+                      bool semicolon)
 {
   auto restrictions = ParseRestrictions ();
-  restrictions.consume_semi = false;
-
-  // FIXME: This is invalid! It needs to also handle cases where the macro
-  // transcriber is an expression, but since the macro call is followed by
-  // a semicolon, it's a valid ExprStmt
-  return parse_many (parser, delimiter, [&parser, restrictions] () {
-    auto stmt = parser.parse_stmt (restrictions);
-    return AST::SingleASTNode (std::move (stmt));
-  });
+  restrictions.allow_close_after_expr_stmt = true;
+
+  return parse_many (parser, delimiter,
+                    [&parser, restrictions, delimiter, semicolon] () {
+                      auto stmt = parser.parse_stmt (restrictions);
+                      if (semicolon && stmt
+                          && parser.peek_current_token ()->get_id ()
+                               == delimiter)
+                        stmt->add_semicolon ();
+
+                      return AST::SingleASTNode (std::move (stmt));
+                    });
 }
 
 /**
@@ -890,16 +894,6 @@ transcribe_type (Parser<MacroInvocLexer> &parser)
   return AST::Fragment ({std::move (type)}, lexer.get_token_slice (start, end));
 }
 
-static AST::Fragment
-transcribe_on_delimiter (Parser<MacroInvocLexer> &parser, bool semicolon,
-                        AST::DelimType delimiter, TokenId last_token_id)
-{
-  if (semicolon || delimiter == AST::DelimType::CURLY)
-    return transcribe_many_stmts (parser, last_token_id);
-  else
-    return transcribe_expression (parser);
-} // namespace Rust
-
 static AST::Fragment
 transcribe_context (MacroExpander::ContextType ctx,
                    Parser<MacroInvocLexer> &parser, bool semicolon,
@@ -941,9 +935,12 @@ transcribe_context (MacroExpander::ContextType ctx,
     case MacroExpander::ContextType::TYPE:
       return transcribe_type (parser);
       break;
+    case MacroExpander::ContextType::STMT:
+      return transcribe_many_stmts (parser, last_token_id, semicolon);
+    case MacroExpander::ContextType::EXPR:
+      return transcribe_expression (parser);
     default:
-      return transcribe_on_delimiter (parser, semicolon, delimiter,
-                                     last_token_id);
+      gcc_unreachable ();
     }
 }
 
@@ -1107,7 +1104,7 @@ MacroExpander::parse_proc_macro_output (ProcMacro::TokenStream ts)
          nodes.push_back ({std::move (result)});
        }
       break;
-    case ContextType::BLOCK:
+    case ContextType::STMT:
       while (lex.peek_token ()->get_id () != END_OF_FILE)
        {
          auto result = parser.parse_stmt ();
@@ -1121,6 +1118,7 @@ MacroExpander::parse_proc_macro_output (ProcMacro::TokenStream ts)
     case ContextType::TRAIT_IMPL:
     case ContextType::EXTERN:
     case ContextType::TYPE:
+    case ContextType::EXPR:
     default:
       gcc_unreachable ();
     }
index 8ac84d513b2bd05d8310704d0183ce61ad794470..1e3da0b21721fbfb62b294c919ea06c7a3f5e744 100644 (file)
@@ -221,7 +221,8 @@ struct MacroExpander
   enum class ContextType
   {
     ITEM,
-    BLOCK,
+    STMT,
+    EXPR,
     EXTERN,
     TYPE,
     TRAIT,
index 6eb5eb6e741d3d431d3b69b77a525f1f25050b35..ff929d3c625ac75392cb52fc53f2e3aea3788776 100644 (file)
@@ -6266,19 +6266,12 @@ Parser<ManagedTokenSource>::parse_stmt (ParseRestrictions restrictions)
          return parse_vis_item (std::move (outer_attrs));
          // or should this go straight to parsing union?
        }
-      else if (t->get_str () == "macro_rules")
+      else if (t->get_str () == "macro_rules"
+              && lexer.peek_token (1)->get_id () == EXCLAM)
        {
          // macro_rules! macro item
          return parse_macro_rules_def (std::move (outer_attrs));
        }
-      else if (lexer.peek_token (1)->get_id () == SCOPE_RESOLUTION
-              || lexer.peek_token (1)->get_id () == EXCLAM)
-       {
-         // FIXME: ensure doesn't take any expressions by mistake
-         /* path (probably) or macro invocation, so probably a macro
-          * invocation semi */
-         return parse_macro_invocation_semi (std::move (outer_attrs));
-       }
       gcc_fallthrough ();
       // TODO: find out how to disable gcc "implicit fallthrough" warning
     default:
@@ -6348,8 +6341,16 @@ Parser<ManagedTokenSource>::parse_let_stmt (AST::AttrVec outer_attrs,
     }
 
   if (restrictions.consume_semi)
-    if (!skip_token (SEMICOLON))
-      return nullptr;
+    {
+      // `stmt` macro variables are parsed without a semicolon, but should be
+      // parsed as a full statement when interpolated. This should be handled
+      // by having the interpolated statement be distinguishable from normal
+      // tokens, e.g. by NT tokens.
+      if (restrictions.allow_close_after_expr_stmt)
+       maybe_skip_token (SEMICOLON);
+      else if (!skip_token (SEMICOLON))
+       return nullptr;
+    }
 
   return std::unique_ptr<AST::LetStmt> (
     new AST::LetStmt (std::move (pattern), std::move (expr), std::move (type),
@@ -7261,19 +7262,77 @@ Parser<ManagedTokenSource>::parse_method ()
                      AST::Visibility::create_error (), AST::AttrVec (), locus);
 }
 
-/* Parses an expression statement. */
+/* Parses an expression or macro statement. */
 template <typename ManagedTokenSource>
-std::unique_ptr<AST::ExprStmt>
+std::unique_ptr<AST::Stmt>
 Parser<ManagedTokenSource>::parse_expr_stmt (AST::AttrVec outer_attrs,
                                             ParseRestrictions restrictions)
 {
   Location locus = lexer.peek_token ()->get_locus ();
 
-  restrictions.expr_can_be_stmt = true;
+  std::unique_ptr<AST::Expr> expr;
+
+  switch (lexer.peek_token ()->get_id ())
+    {
+    case IDENTIFIER:
+    case CRATE:
+    case SUPER:
+    case SELF:
+    case SELF_ALIAS:
+    case DOLLAR_SIGN:
+      case SCOPE_RESOLUTION: {
+       AST::PathInExpression path = parse_path_in_expression ();
+       std::unique_ptr<AST::Expr> null_denotation;
+
+       if (lexer.peek_token ()->get_id () == EXCLAM)
+         {
+           // Bind a reference to avoid -Wredundant-move on post-P1825R0
+           // compilers. Change to non-reference type and remove the moves
+           // below once C++20 is required to build gcc.
+           std::unique_ptr<AST::MacroInvocation> &&invoc
+             = parse_macro_invocation_partial (std::move (path),
+                                               std::move (outer_attrs));
+
+           if (restrictions.consume_semi && maybe_skip_token (SEMICOLON))
+             {
+               invoc->add_semicolon ();
+               // Macro invocation with semicolon.
+               return std::move (invoc);
+             }
+
+           TokenId after_macro = lexer.peek_token ()->get_id ();
+
+           if (restrictions.allow_close_after_expr_stmt
+               && (after_macro == RIGHT_PAREN || after_macro == RIGHT_CURLY
+                   || after_macro == RIGHT_SQUARE))
+             return std::move (invoc);
+
+           if (invoc->get_invoc_data ().get_delim_tok_tree ().get_delim_type ()
+                 == AST::CURLY
+               && after_macro != DOT && after_macro != QUESTION_MARK)
+             {
+               rust_debug ("braced macro statement");
+               return std::move (invoc);
+             }
+
+           null_denotation = std::move (invoc);
+         }
+       else
+         {
+           null_denotation
+             = null_denotation_path (std::move (path), {}, restrictions);
+         }
+
+       expr = left_denotations (std::move (null_denotation), LBP_LOWEST,
+                                std::move (outer_attrs), restrictions);
+       break;
+      }
+    default:
+      restrictions.expr_can_be_stmt = true;
+      expr = parse_expr (std::move (outer_attrs), restrictions);
+      break;
+    }
 
-  // attempt to parse via parse_expr_without_block - seems to work
-  std::unique_ptr<AST::Expr> expr
-    = parse_expr (std::move (outer_attrs), restrictions);
   if (expr == nullptr)
     {
       // expr is required, error
@@ -7289,10 +7348,27 @@ Parser<ManagedTokenSource>::parse_expr_stmt (AST::AttrVec outer_attrs,
 
   if (restrictions.consume_semi)
     {
-      if (skip_token (SEMICOLON))
-       has_semi = true;
+      if (maybe_skip_token (SEMICOLON))
+       {
+         has_semi = true;
+       }
       else if (expr->is_expr_without_block ())
-       return nullptr;
+       {
+         if (restrictions.allow_close_after_expr_stmt)
+           {
+             TokenId id = lexer.peek_token ()->get_id ();
+             if (id != RIGHT_PAREN && id != RIGHT_CURLY && id != RIGHT_SQUARE)
+               {
+                 expect_token (SEMICOLON);
+                 return nullptr;
+               }
+           }
+         else
+           {
+             expect_token (SEMICOLON);
+             return nullptr;
+           }
+       }
     }
 
   return std::unique_ptr<AST::ExprStmt> (
@@ -8441,7 +8517,6 @@ Parser<ManagedTokenSource>::parse_match_expr (AST::AttrVec outer_attrs,
 
       ParseRestrictions restrictions;
       restrictions.expr_can_be_stmt = true;
-      restrictions.consume_semi = false;
 
       std::unique_ptr<AST::Expr> expr = parse_expr ({}, restrictions);
 
@@ -11537,6 +11612,7 @@ Parser<ManagedTokenSource>::parse_stmt_or_expr ()
   AST::AttrVec outer_attrs = parse_outer_attributes ();
   ParseRestrictions restrictions;
   restrictions.expr_can_be_stmt = true;
+  std::unique_ptr<AST::Expr> expr;
 
   // parsing this will be annoying because of the many different possibilities
   /* best may be just to copy paste in parse_item switch, and failing that try
@@ -11589,6 +11665,7 @@ Parser<ManagedTokenSource>::parse_stmt_or_expr ()
          {
            case LEFT_CURLY: {
              // unsafe block: parse as expression
+             expr = parse_expr (std::move (outer_attrs), restrictions);
              break;
            }
          case AUTO:
@@ -11624,8 +11701,6 @@ Parser<ManagedTokenSource>::parse_stmt_or_expr ()
       }
       /* FIXME: this is either a macro invocation or macro invocation semi.
        * start parsing to determine which one it is. */
-      // FIXME: or this is another path-based thing - struct/enum or path
-      // itself return parse_path_based_stmt_or_expr(std::move(outer_attrs));
       // FIXME: old code there
 
     // crappy hack to do union "keyword"
@@ -11638,27 +11713,68 @@ Parser<ManagedTokenSource>::parse_stmt_or_expr ()
          return ExprOrStmt (std::move (item));
          // or should this go straight to parsing union?
        }
-      else if (t->get_str () == "macro_rules")
+      else if (t->get_str () == "macro_rules"
+              && lexer.peek_token (1)->get_id () == EXCLAM)
        {
          // macro_rules! macro item
          std::unique_ptr<AST::Item> item (
            parse_macro_rules_def (std::move (outer_attrs)));
          return ExprOrStmt (std::move (item));
        }
-      else
-       {
-         break;
-       }
+      gcc_fallthrough ();
+    case SUPER:
+    case SELF:
+    case SELF_ALIAS:
+    case CRATE:
+    case SCOPE_RESOLUTION:
+      case DOLLAR_SIGN: {
+       AST::PathInExpression path = parse_path_in_expression ();
+       std::unique_ptr<AST::Expr> null_denotation;
+
+       if (lexer.peek_token ()->get_id () == EXCLAM)
+         {
+           std::unique_ptr<AST::MacroInvocation> invoc
+             = parse_macro_invocation_partial (std::move (path),
+                                               std::move (outer_attrs));
+
+           if (restrictions.consume_semi && maybe_skip_token (SEMICOLON))
+             {
+               invoc->add_semicolon ();
+               // Macro invocation with semicolon.
+               return ExprOrStmt (
+                 std::unique_ptr<AST::Stmt> (std::move (invoc)));
+             }
+
+           TokenId after_macro = lexer.peek_token ()->get_id ();
+
+           if (invoc->get_invoc_data ().get_delim_tok_tree ().get_delim_type ()
+                 == AST::CURLY
+               && after_macro != DOT && after_macro != QUESTION_MARK)
+             {
+               rust_debug ("braced macro statement");
+               return ExprOrStmt (
+                 std::unique_ptr<AST::Stmt> (std::move (invoc)));
+             }
+
+           null_denotation = std::move (invoc);
+         }
+       else
+         {
+           null_denotation
+             = null_denotation_path (std::move (path), {}, restrictions);
+         }
+
+       expr = left_denotations (std::move (null_denotation), LBP_LOWEST,
+                                std::move (outer_attrs), restrictions);
+       break;
+      }
     default:
+      /* expression statement or expression itself - parse
+       * expression then make it statement if semi afterwards */
+      expr = parse_expr (std::move (outer_attrs), restrictions);
       break;
     }
 
-  /* expression statement or expression itself - parse
-   * expression then make it statement if semi afterwards */
-
-  std::unique_ptr<AST::Expr> expr
-    = parse_expr (std::move (outer_attrs), restrictions);
-
   const_TokenPtr after_expr = lexer.peek_token ();
   if (after_expr->get_id () == SEMICOLON)
     {
@@ -11690,251 +11806,6 @@ Parser<ManagedTokenSource>::parse_stmt_or_expr ()
   return ExprOrStmt (std::move (expr));
 }
 
-/* Parses a statement or expression beginning with a path (i.e. macro,
- * struct/enum, or path expr) */
-template <typename ManagedTokenSource>
-ExprOrStmt
-Parser<ManagedTokenSource>::parse_path_based_stmt_or_expr (
-  AST::AttrVec outer_attrs)
-{
-  // attempt to parse path
-  Location stmt_or_expr_loc = lexer.peek_token ()->get_locus ();
-  AST::PathInExpression path = parse_path_in_expression ();
-
-  // branch on next token
-  const_TokenPtr t2 = lexer.peek_token ();
-  switch (t2->get_id ())
-    {
-      case EXCLAM: {
-       /* macro invocation or macro invocation semi - depends on whether
-        * there is a final ';' */
-       // convert path in expr to simple path (as that is used in macros)
-       AST::SimplePath macro_path = path.as_simple_path ();
-       if (macro_path.is_empty ())
-         {
-           Error error (t2->get_locus (),
-                        "failed to convert parsed path to simple "
-                        "path (for macro invocation or semi)");
-           add_error (std::move (error));
-
-           return ExprOrStmt::create_error ();
-         }
-
-       // skip exclamation mark
-       lexer.skip_token ();
-
-       const_TokenPtr t3 = lexer.peek_token ();
-       Location tok_tree_loc = t3->get_locus ();
-
-       AST::DelimType type = AST::PARENS;
-       switch (t3->get_id ())
-         {
-         case LEFT_PAREN:
-           type = AST::PARENS;
-           break;
-         case LEFT_SQUARE:
-           type = AST::SQUARE;
-           break;
-         case LEFT_CURLY:
-           type = AST::CURLY;
-           break;
-         default:
-           add_error (
-             Error (t3->get_locus (),
-                    "unrecognised token %qs in macro invocation - (opening) "
-                    "delimiter expected",
-                    t3->get_token_description ()));
-
-           return ExprOrStmt::create_error ();
-         }
-       lexer.skip_token ();
-
-       // parse actual token trees
-       std::vector<std::unique_ptr<AST::TokenTree>> token_trees;
-       auto delim_open
-         = std::unique_ptr<AST::Token> (new AST::Token (std::move (t3)));
-       token_trees.push_back (std::move (delim_open));
-
-       t3 = lexer.peek_token ();
-       // parse token trees until the initial delimiter token is found again
-       while (!token_id_matches_delims (t3->get_id (), type))
-         {
-           std::unique_ptr<AST::TokenTree> tree = parse_token_tree ();
-
-           if (tree == nullptr)
-             {
-               Error error (t3->get_locus (),
-                            "failed to parse token tree for macro "
-                            "invocation (or semi) - "
-                            "found %qs",
-                            t3->get_token_description ());
-               add_error (std::move (error));
-
-               return ExprOrStmt::create_error ();
-             }
-
-           token_trees.push_back (std::move (tree));
-
-           t3 = lexer.peek_token ();
-         }
-
-       auto delim_close
-         = std::unique_ptr<AST::Token> (new AST::Token (std::move (t3)));
-       token_trees.push_back (std::move (delim_close));
-
-       // parse end delimiters
-       t3 = lexer.peek_token ();
-       if (token_id_matches_delims (t3->get_id (), type))
-         {
-           // tokens match opening delimiter, so skip.
-           lexer.skip_token ();
-
-           /* with curly bracketed macros, assume it is a macro invocation
-            * unless a semicolon is explicitly put at the end. this is not
-            * necessarily true (i.e. context-dependence) and so may have to
-            * be fixed up via HACKs in semantic analysis (by checking whether
-            * it is the last elem in the vector). */
-
-           AST::DelimTokenTree delim_tok_tree (type, std::move (token_trees),
-                                               tok_tree_loc);
-           AST::MacroInvocData invoc_data (std::move (macro_path),
-                                           std::move (delim_tok_tree));
-
-           if (lexer.peek_token ()->get_id () == SEMICOLON)
-             {
-               lexer.skip_token ();
-
-               auto stmt
-                 = AST::MacroInvocation::Regular (std::move (invoc_data),
-                                                  std::move (outer_attrs),
-                                                  stmt_or_expr_loc, true);
-               return ExprOrStmt (std::move (stmt));
-             }
-
-           // otherwise, create macro invocation
-           auto expr = AST::MacroInvocation::Regular (std::move (invoc_data),
-                                                      std::move (outer_attrs),
-                                                      stmt_or_expr_loc, false);
-           return ExprOrStmt (std::move (expr));
-         }
-       else
-         {
-           // tokens don't match opening delimiters, so produce error
-           Error error (
-             t2->get_locus (),
-             "unexpected token %qs - expecting closing delimiter %qs (for a "
-             "macro invocation)",
-             t2->get_token_description (),
-             (type == AST::PARENS ? ")" : (type == AST::SQUARE ? "]" : "}")));
-           add_error (std::move (error));
-
-           return ExprOrStmt::create_error ();
-         }
-      }
-      case LEFT_CURLY: {
-       /* definitely not a block:
-        *  path '{' ident ','
-        *  path '{' ident ':' [anything] ','
-        *  path '{' ident ':' [not a type]
-        * otherwise, assume block expr and thus path */
-       bool not_a_block = lexer.peek_token (1)->get_id () == IDENTIFIER
-                          && (lexer.peek_token (2)->get_id () == COMMA
-                              || (lexer.peek_token (2)->get_id () == COLON
-                                  && (lexer.peek_token (4)->get_id () == COMMA
-                                      || !can_tok_start_type (
-                                        lexer.peek_token (3)->get_id ()))));
-       std::unique_ptr<AST::ExprWithoutBlock> expr = nullptr;
-
-       if (not_a_block)
-         {
-           /* assume struct expr struct (as struct-enum disambiguation
-            * requires name lookup) again, make statement if final ';' */
-           expr = parse_struct_expr_struct_partial (std::move (path),
-                                                    std::move (outer_attrs));
-           if (expr == nullptr)
-             {
-               Error error (t2->get_locus (),
-                            "failed to parse struct expr struct");
-               add_error (std::move (error));
-
-               return ExprOrStmt::create_error ();
-             }
-         }
-       else
-         {
-           // assume path - make statement if final ';'
-           // lexer.skip_token();
-
-           // HACK: add outer attrs to path
-           path.set_outer_attrs (std::move (outer_attrs));
-           expr = std::unique_ptr<AST::PathInExpression> (
-             new AST::PathInExpression (std::move (path)));
-         }
-
-       // determine if statement if ends with semicolon
-       if (lexer.peek_token ()->get_id () == SEMICOLON)
-         {
-           // statement
-           lexer.skip_token ();
-           std::unique_ptr<AST::ExprStmt> stmt (
-             new AST::ExprStmt (std::move (expr), stmt_or_expr_loc, true));
-           return ExprOrStmt (std::move (stmt));
-         }
-
-       // otherwise, expression
-       return ExprOrStmt (std::move (expr));
-      }
-      case LEFT_PAREN: {
-       /* assume struct expr tuple (as struct-enum disambiguation requires
-        * name lookup) again, make statement if final ';' */
-       std::unique_ptr<AST::CallExpr> struct_expr
-         = parse_struct_expr_tuple_partial (std::move (path),
-                                            std::move (outer_attrs));
-       if (struct_expr == nullptr)
-         {
-           Error error (t2->get_locus (), "failed to parse struct expr tuple");
-           add_error (std::move (error));
-
-           return ExprOrStmt::create_error ();
-         }
-
-       // determine if statement if ends with semicolon
-       if (lexer.peek_token ()->get_id () == SEMICOLON)
-         {
-           // statement
-           lexer.skip_token ();
-           std::unique_ptr<AST::ExprStmt> stmt (
-             new AST::ExprStmt (std::move (struct_expr), stmt_or_expr_loc,
-                                true));
-           return ExprOrStmt (std::move (stmt));
-         }
-
-       // otherwise, expression
-       return ExprOrStmt (std::move (struct_expr));
-      }
-      default: {
-       // assume path - make statement if final ';'
-       // lexer.skip_token();
-
-       // HACK: replace outer attributes in path
-       path.set_outer_attrs (std::move (outer_attrs));
-       std::unique_ptr<AST::PathInExpression> expr (
-         new AST::PathInExpression (std::move (path)));
-
-       if (lexer.peek_token ()->get_id () == SEMICOLON)
-         {
-           lexer.skip_token ();
-
-           std::unique_ptr<AST::ExprStmt> stmt (
-             new AST::ExprStmt (std::move (expr), stmt_or_expr_loc, true));
-           return ExprOrStmt (std::move (stmt));
-         }
-
-       return ExprOrStmt (std::move (expr));
-      }
-    }
-}
-
 // Parses a struct expression field.
 template <typename ManagedTokenSource>
 std::unique_ptr<AST::StructExprField>
@@ -12020,135 +11891,6 @@ Parser<ManagedTokenSource>::parse_struct_expr_field ()
     }
 }
 
-// Parses a macro invocation or macro invocation semi.
-template <typename ManagedTokenSource>
-ExprOrStmt
-Parser<ManagedTokenSource>::parse_macro_invocation_maybe_semi (
-  AST::AttrVec outer_attrs)
-{
-  Location macro_locus = lexer.peek_token ()->get_locus ();
-  AST::SimplePath macro_path = parse_simple_path ();
-  if (macro_path.is_empty ())
-    {
-      Error error (lexer.peek_token ()->get_locus (),
-                  "failed to parse simple path in macro invocation or semi");
-      add_error (std::move (error));
-
-      return ExprOrStmt::create_error ();
-    }
-
-  if (!skip_token (EXCLAM))
-    {
-      return ExprOrStmt::create_error ();
-    }
-
-  const_TokenPtr t3 = lexer.peek_token ();
-  Location tok_tree_loc = t3->get_locus ();
-
-  AST::DelimType type = AST::PARENS;
-  switch (t3->get_id ())
-    {
-    case LEFT_PAREN:
-      type = AST::PARENS;
-      break;
-    case LEFT_SQUARE:
-      type = AST::SQUARE;
-      break;
-    case LEFT_CURLY:
-      type = AST::CURLY;
-      break;
-    default:
-      add_error (
-       Error (t3->get_locus (),
-              "unrecognised token %qs in macro invocation - (opening) "
-              "delimiter expected",
-              t3->get_token_description ()));
-
-      return ExprOrStmt::create_error ();
-    }
-  lexer.skip_token ();
-
-  // parse actual token trees
-  std::vector<std::unique_ptr<AST::TokenTree>> token_trees;
-  auto delim_open
-    = std::unique_ptr<AST::Token> (new AST::Token (std::move (t3)));
-  token_trees.push_back (std::move (delim_open));
-
-  t3 = lexer.peek_token ();
-  // parse token trees until the initial delimiter token is found again
-  while (!token_id_matches_delims (t3->get_id (), type))
-    {
-      std::unique_ptr<AST::TokenTree> tree = parse_token_tree ();
-
-      if (tree == nullptr)
-       {
-         Error error (t3->get_locus (),
-                      "failed to parse token tree for macro invocation (or "
-                      "semi) - found %qs",
-                      t3->get_token_description ());
-         add_error (std::move (error));
-
-         return ExprOrStmt::create_error ();
-       }
-
-      token_trees.push_back (std::move (tree));
-
-      t3 = lexer.peek_token ();
-    }
-  auto delim_close
-    = std::unique_ptr<AST::Token> (new AST::Token (std::move (t3)));
-  token_trees.push_back (std::move (delim_close));
-
-  // parse end delimiters
-  t3 = lexer.peek_token ();
-  if (token_id_matches_delims (t3->get_id (), type))
-    {
-      // tokens match opening delimiter, so skip.
-      lexer.skip_token ();
-
-      /* with curly bracketed macros, assume it is a macro invocation unless
-       * a semicolon is explicitly put at the end. this is not necessarily
-       * true (i.e. context-dependence) and so may have to be fixed up via
-       * HACKs in semantic analysis (by checking whether it is the last elem
-       * in the vector). */
-
-      AST::DelimTokenTree delim_tok_tree (type, std::move (token_trees),
-                                         tok_tree_loc);
-      AST::MacroInvocData invoc_data (std::move (macro_path),
-                                     std::move (delim_tok_tree));
-
-      if (lexer.peek_token ()->get_id () == SEMICOLON)
-       {
-         lexer.skip_token ();
-
-         auto stmt = AST::MacroInvocation::Regular (std::move (invoc_data),
-                                                    std::move (outer_attrs),
-                                                    macro_locus, true);
-         return ExprOrStmt (std::move (stmt));
-       }
-
-      // otherwise, create macro invocation
-      auto expr
-       = AST::MacroInvocation::Regular (std::move (invoc_data),
-                                        std::move (outer_attrs), macro_locus);
-      return ExprOrStmt (std::move (expr));
-    }
-  else
-    {
-      const_TokenPtr t = lexer.peek_token ();
-      // tokens don't match opening delimiters, so produce error
-      Error error (
-       t->get_locus (),
-       "unexpected token %qs - expecting closing delimiter %qs (for a "
-       "macro invocation)",
-       t->get_token_description (),
-       (type == AST::PARENS ? ")" : (type == AST::SQUARE ? "]" : "}")));
-      add_error (std::move (error));
-
-      return ExprOrStmt::create_error ();
-    }
-}
-
 // "Unexpected token" panic mode - flags gcc error at unexpected token
 template <typename ManagedTokenSource>
 void
@@ -12351,13 +12093,24 @@ Parser<ManagedTokenSource>::parse_expr (int right_binding_power,
 
   lexer.skip_token ();
 
-  bool expr_can_be_stmt = restrictions.expr_can_be_stmt;
-  restrictions.expr_can_be_stmt = false;
+  ParseRestrictions null_denotation_restrictions = restrictions;
+  null_denotation_restrictions.expr_can_be_stmt = false;
 
   // parse null denotation (unary part of expression)
   std::unique_ptr<AST::Expr> expr
-    = null_denotation (current_token, {}, restrictions);
+    = null_denotation (current_token, {}, null_denotation_restrictions);
 
+  return left_denotations (std::move (expr), right_binding_power,
+                          std::move (outer_attrs), restrictions);
+}
+
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Expr>
+Parser<ManagedTokenSource>::left_denotations (std::unique_ptr<AST::Expr> expr,
+                                             int right_binding_power,
+                                             AST::AttrVec outer_attrs,
+                                             ParseRestrictions restrictions)
+{
   if (expr == nullptr)
     {
       // DEBUG
@@ -12365,9 +12118,9 @@ Parser<ManagedTokenSource>::parse_expr (int right_binding_power,
       return nullptr;
     }
 
-  current_token = lexer.peek_token ();
+  const_TokenPtr current_token = lexer.peek_token ();
 
-  if (expr_can_be_stmt && !expr->is_expr_without_block ()
+  if (restrictions.expr_can_be_stmt && !expr->is_expr_without_block ()
       && current_token->get_id () != DOT
       && current_token->get_id () != QUESTION_MARK)
     {
@@ -12376,6 +12129,8 @@ Parser<ManagedTokenSource>::parse_expr (int right_binding_power,
       return expr;
     }
 
+  restrictions.expr_can_be_stmt = false;
+
   // stop parsing if find lower priority token - parse higher priority first
   while (right_binding_power < left_binding_power (current_token))
     {
@@ -12424,7 +12179,12 @@ Parser<ManagedTokenSource>::null_denotation (const_TokenPtr tok,
 
   switch (tok->get_id ())
     {
-      case IDENTIFIER: {
+    case IDENTIFIER:
+    case SELF:
+    case SELF_ALIAS:
+    case DOLLAR_SIGN:
+    case CRATE:
+      case SUPER: {
        // DEBUG
        rust_debug ("beginning null denotation identifier handling");
 
@@ -12432,88 +12192,126 @@ Parser<ManagedTokenSource>::null_denotation (const_TokenPtr tok,
         * struct/enum, or just path info from it */
        AST::PathInExpression path = parse_path_in_expression_pratt (tok);
 
-       // DEBUG:
-       rust_debug ("finished null denotation identifier path parsing - "
-                   "next is branching");
+       return null_denotation_path (std::move (path), std::move (outer_attrs),
+                                    restrictions);
+      }
+      case SCOPE_RESOLUTION: {
+       // TODO: fix: this is for global paths, i.e. std::string::whatever
+       Error error (tok->get_locus (),
+                    "found null denotation scope resolution operator, and "
+                    "have not written handling for it");
+       add_error (std::move (error));
 
-       // branch on next token
-       const_TokenPtr t = lexer.peek_token ();
-       switch (t->get_id ())
+       return nullptr;
+      }
+    default:
+      return null_denotation_not_path (std::move (tok), std::move (outer_attrs),
+                                      restrictions);
+    }
+}
+
+// Handling of expresions that start with a path for `null_denotation`.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Expr>
+Parser<ManagedTokenSource>::null_denotation_path (
+  AST::PathInExpression path, AST::AttrVec outer_attrs,
+  ParseRestrictions restrictions)
+{
+  rust_debug ("parsing null denotation after path");
+
+  // HACK: always make "self" by itself a path (regardless of next
+  // tokens)
+  if (path.is_single_segment () && path.get_segments ()[0].is_lower_self_seg ())
+    {
+      // HACK: add outer attrs to path
+      path.set_outer_attrs (std::move (outer_attrs));
+      return std::unique_ptr<AST::PathInExpression> (
+       new AST::PathInExpression (std::move (path)));
+    }
+
+  // branch on next token
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case EXCLAM:
+      // macro
+      return parse_macro_invocation_partial (std::move (path),
+                                            std::move (outer_attrs));
+      case LEFT_CURLY: {
+       bool not_a_block = lexer.peek_token (1)->get_id () == IDENTIFIER
+                          && (lexer.peek_token (2)->get_id () == COMMA
+                              || (lexer.peek_token (2)->get_id () == COLON
+                                  && (lexer.peek_token (4)->get_id () == COMMA
+                                      || !can_tok_start_type (
+                                        lexer.peek_token (3)->get_id ()))));
+
+       /* definitely not a block:
+        *  path '{' ident ','
+        *  path '{' ident ':' [anything] ','
+        *  path '{' ident ':' [not a type]
+        * otherwise, assume block expr and thus path */
+       // DEBUG
+       rust_debug ("values of lookahead: '%s' '%s' '%s' '%s' ",
+                   lexer.peek_token (1)->get_token_description (),
+                   lexer.peek_token (2)->get_token_description (),
+                   lexer.peek_token (3)->get_token_description (),
+                   lexer.peek_token (4)->get_token_description ());
+
+       rust_debug ("can be struct expr: '%s', not a block: '%s'",
+                   restrictions.can_be_struct_expr ? "true" : "false",
+                   not_a_block ? "true" : "false");
+
+       // struct/enum expr struct
+       if (!restrictions.can_be_struct_expr && !not_a_block)
          {
-         case EXCLAM:
-           // macro
-           return parse_macro_invocation_partial (std::move (path),
-                                                  std::move (outer_attrs),
-                                                  restrictions);
-           case LEFT_CURLY: {
-             bool not_a_block
-               = lexer.peek_token (1)->get_id () == IDENTIFIER
-                 && (lexer.peek_token (2)->get_id () == COMMA
-                     || (lexer.peek_token (2)->get_id () == COLON
-                         && (lexer.peek_token (4)->get_id () == COMMA
-                             || !can_tok_start_type (
-                               lexer.peek_token (3)->get_id ()))));
-
-             /* definitely not a block:
-              *  path '{' ident ','
-              *  path '{' ident ':' [anything] ','
-              *  path '{' ident ':' [not a type]
-              * otherwise, assume block expr and thus path */
-             // DEBUG
-             rust_debug ("values of lookahead: '%s' '%s' '%s' '%s' ",
-                         lexer.peek_token (1)->get_token_description (),
-                         lexer.peek_token (2)->get_token_description (),
-                         lexer.peek_token (3)->get_token_description (),
-                         lexer.peek_token (4)->get_token_description ());
-
-             rust_debug ("can be struct expr: '%s', not a block: '%s'",
-                         restrictions.can_be_struct_expr ? "true" : "false",
-                         not_a_block ? "true" : "false");
-
-             // struct/enum expr struct
-             if (!restrictions.can_be_struct_expr && !not_a_block)
-               {
-                 // HACK: add outer attrs to path
-                 path.set_outer_attrs (std::move (outer_attrs));
-                 return std::unique_ptr<AST::PathInExpression> (
-                   new AST::PathInExpression (std::move (path)));
-               }
-             return parse_struct_expr_struct_partial (std::move (path),
-                                                      std::move (outer_attrs));
-           }
-         case LEFT_PAREN:
-           // struct/enum expr tuple
-           if (!restrictions.can_be_struct_expr)
-             {
-               // HACK: add outer attrs to path
-               path.set_outer_attrs (std::move (outer_attrs));
-               return std::unique_ptr<AST::PathInExpression> (
-                 new AST::PathInExpression (std::move (path)));
-             }
-           return parse_struct_expr_tuple_partial (std::move (path),
-                                                   std::move (outer_attrs));
-         default:
-           // assume path is returned if not single segment
-           if (path.is_single_segment ())
-             {
-               // have to return an identifier expression or something, idk
-               /* HACK: may have to become permanent, but this is my current
-                * identifier expression */
-               return std::unique_ptr<AST::IdentifierExpr> (
-                 new AST::IdentifierExpr (tok->get_str (), {},
-                                          tok->get_locus ()));
-             }
            // HACK: add outer attrs to path
            path.set_outer_attrs (std::move (outer_attrs));
            return std::unique_ptr<AST::PathInExpression> (
              new AST::PathInExpression (std::move (path)));
          }
-       gcc_unreachable ();
+       return parse_struct_expr_struct_partial (std::move (path),
+                                                std::move (outer_attrs));
       }
-      /* FIXME: delegate to parse_literal_expr instead? would have to rejig
-       * tokens and whatever. */
-      /* FIXME: could also be path expression (and hence macro expression,
-       * struct/enum expr) */
+    case LEFT_PAREN:
+      // struct/enum expr tuple
+      if (!restrictions.can_be_struct_expr)
+       {
+         // assume path is returned
+         // HACK: add outer attributes to path
+         path.set_outer_attrs (std::move (outer_attrs));
+         return std::unique_ptr<AST::PathInExpression> (
+           new AST::PathInExpression (std::move (path)));
+       }
+      return parse_struct_expr_tuple_partial (std::move (path),
+                                             std::move (outer_attrs));
+    default:
+      // assume path is returned if not single segment
+      if (path.is_single_segment ())
+       {
+         // FIXME: This should probably be returned as a path.
+         /* HACK: may have to become permanent, but this is my current
+          * identifier expression */
+         return std::unique_ptr<AST::IdentifierExpr> (new AST::IdentifierExpr (
+           path.get_segments ()[0].get_ident_segment ().as_string (), {},
+           path.get_locus ()));
+       }
+      // HACK: add outer attrs to path
+      path.set_outer_attrs (std::move (outer_attrs));
+      return std::unique_ptr<AST::PathInExpression> (
+       new AST::PathInExpression (std::move (path)));
+    }
+  gcc_unreachable ();
+}
+
+// Handling of expresions that do not start with a path for `null_denotation`.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Expr>
+Parser<ManagedTokenSource>::null_denotation_not_path (
+  const_TokenPtr tok, AST::AttrVec outer_attrs, ParseRestrictions restrictions)
+{
+  switch (tok->get_id ())
+    {
+    // FIXME: Handle in null_denotation_path?
     case LEFT_SHIFT:
       case LEFT_ANGLE: {
        // qualified path
@@ -12524,8 +12322,10 @@ Parser<ManagedTokenSource>::null_denotation (const_TokenPtr tok,
        return std::unique_ptr<AST::QualifiedPathInExpression> (
          new AST::QualifiedPathInExpression (std::move (path)));
       }
-    // FIXME: for literal exprs, should outer attrs be passed in or just
-    // ignored?
+    // FIXME: delegate to parse_literal_expr instead? would have to rejig
+    // tokens and whatever.
+    // FIXME: for literal exprs, outer attrs should be passed in, and later
+    // error if it does not make up the entire statement.
     case INT_LITERAL:
       // we should check the range, but ignore for now
       // encode as int?
@@ -12696,97 +12496,6 @@ Parser<ManagedTokenSource>::null_denotation (const_TokenPtr tok,
          new AST::BorrowExpr (std::move (expr), is_mut_borrow, true,
                               std::move (outer_attrs), tok->get_locus ()));
       }
-      case SCOPE_RESOLUTION: {
-       // TODO: fix: this is for global paths, i.e. std::string::whatever
-       Error error (tok->get_locus (),
-                    "found null denotation scope resolution operator, and "
-                    "have not written handling for it");
-       add_error (std::move (error));
-
-       return nullptr;
-      }
-    case SELF:
-    case SELF_ALIAS:
-    case DOLLAR_SIGN:
-    case CRATE:
-      case SUPER: {
-       // DEBUG
-       rust_debug ("beginning null denotation "
-                   "self/self-alias/dollar/crate/super handling");
-
-       /* best option: parse as path, then extract identifier, macro,
-        * struct/enum, or just path info from it */
-       AST::PathInExpression path = parse_path_in_expression_pratt (tok);
-
-       // DEBUG
-       rust_debug (
-         "just finished parsing path (going to disambiguate) - peeked "
-         "token is '%s'",
-         lexer.peek_token ()->get_token_description ());
-
-       // HACK: always make "self" by itself a path (regardless of next
-       // tokens)
-       if (tok->get_id () == SELF && path.is_single_segment ())
-         {
-           // HACK: add outer attrs to path
-           path.set_outer_attrs (std::move (outer_attrs));
-           return std::unique_ptr<AST::PathInExpression> (
-             new AST::PathInExpression (std::move (path)));
-         }
-
-       // branch on next token
-       const_TokenPtr t = lexer.peek_token ();
-       switch (t->get_id ())
-         {
-         case EXCLAM:
-           // macro
-           return parse_macro_invocation_partial (std::move (path),
-                                                  std::move (outer_attrs));
-           case LEFT_CURLY: {
-             // struct/enum expr struct
-             rust_debug ("can_be_struct_expr: %s",
-                         restrictions.can_be_struct_expr ? "true" : "false");
-
-             bool not_a_block
-               = lexer.peek_token (1)->get_id () == IDENTIFIER
-                 && (lexer.peek_token (2)->get_id () == COMMA
-                     || (lexer.peek_token (2)->get_id () == COLON
-                         && (lexer.peek_token (4)->get_id () == COMMA
-                             || !can_tok_start_type (
-                               lexer.peek_token (3)->get_id ()))));
-
-             if (!restrictions.can_be_struct_expr && !not_a_block)
-               {
-                 // assume path is returned
-                 // HACK: add outer attributes to path
-                 path.set_outer_attrs (std::move (outer_attrs));
-                 return std::unique_ptr<AST::PathInExpression> (
-                   new AST::PathInExpression (std::move (path)));
-               }
-             return parse_struct_expr_struct_partial (std::move (path),
-                                                      std::move (outer_attrs));
-           }
-         case LEFT_PAREN:
-           // struct/enum expr tuple
-           if (!restrictions.can_be_struct_expr)
-             {
-               // assume path is returned
-               // HACK: add outer attributes to path
-               path.set_outer_attrs (std::move (outer_attrs));
-               return std::unique_ptr<AST::PathInExpression> (
-                 new AST::PathInExpression (std::move (path)));
-             }
-           return parse_struct_expr_tuple_partial (std::move (path),
-                                                   std::move (outer_attrs));
-         default:
-           // assume path is returned
-           // HACK: add outer attributes to path
-           path.set_outer_attrs (std::move (outer_attrs));
-           return std::unique_ptr<AST::PathInExpression> (
-             new AST::PathInExpression (std::move (path)));
-         }
-       gcc_unreachable ();
-      }
     case OR:
     case PIPE:
     case MOVE:
@@ -14382,9 +14091,7 @@ Parser<ManagedTokenSource>::parse_macro_invocation_partial (
 
   return AST::MacroInvocation::Regular (
     AST::MacroInvocData (std::move (converted_path), std::move (tok_tree)),
-    std::move (outer_attrs), macro_locus,
-    restrictions.expr_can_be_stmt
-      && lexer.peek_token ()->get_id () == SEMICOLON);
+    std::move (outer_attrs), macro_locus);
 }
 
 /* Parses a struct expr struct with a path in expression already parsed (but
index 315d3fcdec6cdde8aa464b8f8dc41e0ed1dc88e9..620257456620f18cf8dec318bcb0be3dc3077f7a 100644 (file)
@@ -87,6 +87,9 @@ struct ParseRestrictions
   bool expr_can_be_null = false;
   bool expr_can_be_stmt = false;
   bool consume_semi = true;
+  /* Macro invocations that are statements can expand without a semicolon after
+   * the final statement, if it's an expression statement. */
+  bool allow_close_after_expr_stmt = false;
 };
 
 // Parser implementation for gccrs.
@@ -337,6 +340,17 @@ private:
   null_denotation (const_TokenPtr t, AST::AttrVec outer_attrs = AST::AttrVec (),
                   ParseRestrictions restrictions = ParseRestrictions ());
   std::unique_ptr<AST::Expr>
+  null_denotation_path (AST::PathInExpression path, AST::AttrVec outer_attrs,
+                       ParseRestrictions restrictions = ParseRestrictions ());
+  std::unique_ptr<AST::Expr>
+  null_denotation_not_path (const_TokenPtr t, AST::AttrVec outer_attrs,
+                           ParseRestrictions restrictions
+                           = ParseRestrictions ());
+  std::unique_ptr<AST::Expr>
+  left_denotations (std::unique_ptr<AST::Expr> null_denotation,
+                   int right_binding_power, AST::AttrVec outer_attrs,
+                   ParseRestrictions restrictions = ParseRestrictions ());
+  std::unique_ptr<AST::Expr>
   left_denotation (const_TokenPtr t, std::unique_ptr<AST::Expr> left,
                   AST::AttrVec outer_attrs = AST::AttrVec (),
                   ParseRestrictions restrictions = ParseRestrictions ());
@@ -634,12 +648,10 @@ private:
   std::unique_ptr<AST::LetStmt> parse_let_stmt (AST::AttrVec outer_attrs,
                                                ParseRestrictions restrictions
                                                = ParseRestrictions ());
-  std::unique_ptr<AST::ExprStmt> parse_expr_stmt (AST::AttrVec outer_attrs,
-                                                 ParseRestrictions restrictions
-                                                 = ParseRestrictions ());
+  std::unique_ptr<AST::Stmt> parse_expr_stmt (AST::AttrVec outer_attrs,
+                                             ParseRestrictions restrictions
+                                             = ParseRestrictions ());
   ExprOrStmt parse_stmt_or_expr ();
-  ExprOrStmt parse_macro_invocation_maybe_semi (AST::AttrVec outer_attrs);
-  ExprOrStmt parse_path_based_stmt_or_expr (AST::AttrVec outer_attrs);
 
   // Pattern-related
   std::unique_ptr<AST::Pattern> parse_literal_or_range_pattern ();
diff --git a/gcc/testsuite/rust/compile/braced_macro_arm.rs b/gcc/testsuite/rust/compile/braced_macro_arm.rs
new file mode 100644 (file)
index 0000000..1446878
--- /dev/null
@@ -0,0 +1,19 @@
+// Braced macro invocations are not allowed as match arms without a semicolon,
+// even though they are allowed as statements without a semicolon.
+
+macro_rules! m {
+    () => { 1 }
+}
+
+fn h(c: bool) {
+    match c {
+        // { dg-error "failed to parse statement or expression in block expression" "" { target *-*-* } .-1 }
+        true => m! {}
+        false => ()
+        // { dg-error "exprwithoutblock requires comma after match case expression in match arm \\(if not final case\\)" "" { target *-*-* } .-1 }
+        // { dg-error "unrecognised token .false. for start of item" "" { target *-*-* } .-2 }
+        // { dg-error "failed to parse item in crate" "" { target *-*-* } .-3 }
+    };
+}
+
+fn main () {}
diff --git a/gcc/testsuite/rust/compile/braced_macro_statements1.rs b/gcc/testsuite/rust/compile/braced_macro_statements1.rs
new file mode 100644 (file)
index 0000000..e3ed068
--- /dev/null
@@ -0,0 +1,15 @@
+macro_rules! m {
+    () => { bar() }
+}
+
+fn bar () {}
+
+fn foo() {
+    m!{}
+    m!{}
+}
+
+fn main() -> i32 {
+    foo();
+    0
+}
diff --git a/gcc/testsuite/rust/compile/braced_macro_statements2.rs b/gcc/testsuite/rust/compile/braced_macro_statements2.rs
new file mode 100644 (file)
index 0000000..e7b6ab3
--- /dev/null
@@ -0,0 +1,15 @@
+// Output of statement macros is always parsed as a statement, so no semicolon
+// is needed on the inner macro.
+
+macro_rules! m {
+    (macro) => { m!(stmts) };
+    (stmts) => { let x = 3; x - 3 }
+}
+
+fn foo() -> i32 {
+    m!{macro}
+}
+
+fn main() -> i32 {
+    foo()
+}
diff --git a/gcc/testsuite/rust/compile/braced_macro_statements3.rs b/gcc/testsuite/rust/compile/braced_macro_statements3.rs
new file mode 100644 (file)
index 0000000..a19f4fc
--- /dev/null
@@ -0,0 +1,11 @@
+macro_rules! unroll {
+    {} => {}
+}
+
+pub fn foo() {
+    let mut _a = 14;
+    unroll! {}
+    let mut _b = 13;
+}
+
+fn main() -> i32 { 0 }
index 53757c1bae66f739e2d14ec07451c81a6c9b5a23..4ae40e88aced21fdb18fd0848587d44cadcee743 100644 (file)
@@ -4,7 +4,7 @@ macro_rules! foo {
 }
 
 macro_rules! bar {
-    () => {let $_a = 12;} // { dg-error "unrecognised token" }
+    () => {let $_a = 12;} // { dg-error "expecting .;. but .\\$. found" }
 }
 
 pub fn main() -> i32 {
diff --git a/gcc/testsuite/rust/compile/macro53.rs b/gcc/testsuite/rust/compile/macro53.rs
new file mode 100644 (file)
index 0000000..efa2d4b
--- /dev/null
@@ -0,0 +1,10 @@
+macro_rules! numbers {
+    {} => { 1 2 }
+    // { dg-error "expecting .;. but .integer literal. found" "" { target *-*-* } .-1 }
+}
+
+pub fn foo() {
+    numbers!();
+}
+
+fn main() -> i32 { 0 }