public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r14-7679] gccrs: Parse semicolons in more cases for statement macros
@ 2024-01-16 17:53 Arthur Cohen
  0 siblings, 0 replies; only message in thread
From: Arthur Cohen @ 2024-01-16 17:53 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:8bc4ce7c40be159c83d623beb4cb074e1b5b0daa

commit r14-7679-g8bc4ce7c40be159c83d623beb4cb074e1b5b0daa
Author: Matthew Jasper <mjjasper1@gmail.com>
Date:   Thu Jun 8 20:14:47 2023 +0100

    gccrs: Parse semicolons in more cases for statement macros
    
    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>

Diff:
---
 gcc/rust/ast/rust-ast.h                            |   4 +-
 gcc/rust/ast/rust-macro.h                          |  11 +-
 gcc/rust/ast/rust-stmt.h                           |  10 +-
 gcc/rust/expand/rust-expand-visitor.cc             |  13 +-
 gcc/rust/expand/rust-macro-expand.cc               |  46 +-
 gcc/rust/expand/rust-macro-expand.h                |   3 +-
 gcc/rust/parse/rust-parse-impl.h                   | 875 +++++++--------------
 gcc/rust/parse/rust-parse.h                        |  22 +-
 gcc/testsuite/rust/compile/braced_macro_arm.rs     |  19 +
 .../rust/compile/braced_macro_statements1.rs       |  15 +
 .../rust/compile/braced_macro_statements2.rs       |  15 +
 .../rust/compile/braced_macro_statements3.rs       |  11 +
 gcc/testsuite/rust/compile/issue-2225.rs           |   2 +-
 gcc/testsuite/rust/compile/macro53.rs              |  10 +
 14 files changed, 419 insertions(+), 637 deletions(-)

diff --git a/gcc/rust/ast/rust-ast.h b/gcc/rust/ast/rust-ast.h
index 09566575a85..3c1f95a973f 100644
--- a/gcc/rust/ast/rust-ast.h
+++ b/gcc/rust/ast/rust-ast.h
@@ -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;
diff --git a/gcc/rust/ast/rust-macro.h b/gcc/rust/ast/rust-macro.h
index 076ab978bc3..a0d1adc7de1 100644
--- a/gcc/rust/ast/rust-macro.h
+++ b/gcc/rust/ast/rust-macro.h
@@ -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
diff --git a/gcc/rust/ast/rust-stmt.h b/gcc/rust/ast/rust-stmt.h
index 7496a376d8a..95a5a62bfa4 100644
--- a/gcc/rust/ast/rust-stmt.h
+++ b/gcc/rust/ast/rust-stmt.h
@@ -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)
   {}
 
diff --git a/gcc/rust/expand/rust-expand-visitor.cc b/gcc/rust/expand/rust-expand-visitor.cc
index 3601287be99..9183a63b9a9 100644
--- a/gcc/rust/expand/rust-expand-visitor.cc
+++ b/gcc/rust/expand/rust-expand-visitor.cc
@@ -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
diff --git a/gcc/rust/expand/rust-macro-expand.cc b/gcc/rust/expand/rust-macro-expand.cc
index a92bbdcb943..225049ad2e4 100644
--- a/gcc/rust/expand/rust-macro-expand.cc
+++ b/gcc/rust/expand/rust-macro-expand.cc
@@ -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 ();
     }
diff --git a/gcc/rust/expand/rust-macro-expand.h b/gcc/rust/expand/rust-macro-expand.h
index 8ac84d513b2..1e3da0b2172 100644
--- a/gcc/rust/expand/rust-macro-expand.h
+++ b/gcc/rust/expand/rust-macro-expand.h
@@ -221,7 +221,8 @@ struct MacroExpander
   enum class ContextType
   {
     ITEM,
-    BLOCK,
+    STMT,
+    EXPR,
     EXTERN,
     TYPE,
     TRAIT,
diff --git a/gcc/rust/parse/rust-parse-impl.h b/gcc/rust/parse/rust-parse-impl.h
index 6eb5eb6e741..ff929d3c625 100644
--- a/gcc/rust/parse/rust-parse-impl.h
+++ b/gcc/rust/parse/rust-parse-impl.h
@@ -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
diff --git a/gcc/rust/parse/rust-parse.h b/gcc/rust/parse/rust-parse.h
index 315d3fcdec6..62025745662 100644
--- a/gcc/rust/parse/rust-parse.h
+++ b/gcc/rust/parse/rust-parse.h
@@ -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
index 00000000000..14468786f50
--- /dev/null
+++ b/gcc/testsuite/rust/compile/braced_macro_arm.rs
@@ -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
index 00000000000..e3ed068ef62
--- /dev/null
+++ b/gcc/testsuite/rust/compile/braced_macro_statements1.rs
@@ -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
index 00000000000..e7b6ab38102
--- /dev/null
+++ b/gcc/testsuite/rust/compile/braced_macro_statements2.rs
@@ -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
index 00000000000..a19f4fcea0a
--- /dev/null
+++ b/gcc/testsuite/rust/compile/braced_macro_statements3.rs
@@ -0,0 +1,11 @@
+macro_rules! unroll {
+    {} => {}
+}
+
+pub fn foo() {
+    let mut _a = 14;
+    unroll! {}
+    let mut _b = 13;
+}
+
+fn main() -> i32 { 0 }
diff --git a/gcc/testsuite/rust/compile/issue-2225.rs b/gcc/testsuite/rust/compile/issue-2225.rs
index 53757c1bae6..4ae40e88ace 100644
--- a/gcc/testsuite/rust/compile/issue-2225.rs
+++ b/gcc/testsuite/rust/compile/issue-2225.rs
@@ -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
index 00000000000..efa2d4ba973
--- /dev/null
+++ b/gcc/testsuite/rust/compile/macro53.rs
@@ -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 }

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2024-01-16 17:53 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-16 17:53 [gcc r14-7679] gccrs: Parse semicolons in more cases for statement macros Arthur Cohen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).