diff --git a/gcc/rust/parse/rust-parse-impl.h b/gcc/rust/parse/rust-parse-impl.h
index 84a27816b11..d925aca05e9 100644
--- a/gcc/rust/parse/rust-parse-impl.h
+++ b/gcc/rust/parse/rust-parse-impl.h
@@ -6894,3 +6894,8034 @@ Parser<ManagedTokenSource>::parse_self_param ()
     }
 }
 
+/* Parses a method. Note that this function is probably useless because using
+ * lookahead to determine whether a function is a method is a PITA (maybe not
+ * even doable), so most places probably parse a "function or method" and then
+ * resolve it into whatever it is afterward. As such, this is only here for
+ * algorithmically defining the grammar rule. */
+template <typename ManagedTokenSource>
+AST::Method
+Parser<ManagedTokenSource>::parse_method ()
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+  /* Note: as a result of the above, this will not attempt to disambiguate a
+   * function parse qualifiers */
+  AST::FunctionQualifiers qualifiers = parse_function_qualifiers ();
+
+  skip_token (FN_TOK);
+
+  const_TokenPtr ident_tok = expect_token (IDENTIFIER);
+  if (ident_tok == nullptr)
+    {
+      skip_after_next_block ();
+      return AST::Method::create_error ();
+    }
+  Identifier method_name = ident_tok->get_str ();
+
+  // parse generic params - if exist
+  std::vector<std::unique_ptr<AST::GenericParam>> generic_params
+    = parse_generic_params_in_angles ();
+
+  if (!skip_token (LEFT_PAREN))
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "method missing opening parentheses before parameter list");
+      add_error (std::move (error));
+
+      skip_after_next_block ();
+      return AST::Method::create_error ();
+    }
+
+  // parse self param
+  AST::SelfParam self_param = parse_self_param ();
+  if (self_param.is_error ())
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "could not parse self param in method");
+      add_error (std::move (error));
+
+      skip_after_next_block ();
+      return AST::Method::create_error ();
+    }
+
+  // skip comma if it exists
+  if (lexer.peek_token ()->get_id () == COMMA)
+    lexer.skip_token ();
+
+  // parse function parameters
+  std::vector<AST::FunctionParam> function_params
+    = parse_function_params ([] (TokenId id) { return id == RIGHT_PAREN; });
+
+  if (!skip_token (RIGHT_PAREN))
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "method declaration missing closing parentheses after "
+		   "parameter list");
+      add_error (std::move (error));
+
+      skip_after_next_block ();
+      return AST::Method::create_error ();
+    }
+
+  // parse function return type - if exists
+  std::unique_ptr<AST::Type> return_type = parse_function_return_type ();
+
+  // parse where clause - if exists
+  AST::WhereClause where_clause = parse_where_clause ();
+
+  // parse block expression
+  std::unique_ptr<AST::BlockExpr> block_expr = parse_block_expr ();
+  if (block_expr == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "method declaration missing block expression");
+      add_error (std::move (error));
+
+      skip_after_end_block ();
+      return AST::Method::create_error ();
+    }
+
+  // does not parse visibility, but this method isn't used, so doesn't matter
+  return AST::Method (std::move (method_name), std::move (qualifiers),
+		      std::move (generic_params), std::move (self_param),
+		      std::move (function_params), std::move (return_type),
+		      std::move (where_clause), std::move (block_expr),
+		      AST::Visibility::create_error (), AST::AttrVec (), locus);
+}
+
+/* Parses an expression statement (disambiguates to expression with or without
+ * block statement). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ExprStmt>
+Parser<ManagedTokenSource>::parse_expr_stmt (AST::AttrVec outer_attrs,
+					     ParseRestrictions restrictions)
+{
+  /* potential thoughts - define new virtual method "has_block()" on expr.
+   * parse expr and then determine whether semicolon is needed as a result of
+   * this method. but then this would require dynamic_cast, which is not
+   * allowed. */
+
+  /* okay new thought - big switch to disambiguate exprs with blocks - either
+   * block expr, async block expr, unsafe block expr, loop expr, if expr, if
+   * let expr, or match expr. So all others are exprs without block. */
+  /* new thought: possible initial tokens: 'loop', 'while', 'for', lifetime
+   * (and then ':' and then loop), 'if', 'match', '{', 'async', 'unsafe' (and
+   * then
+   * '{')). This seems to have no ambiguity. */
+
+  const_TokenPtr t = lexer.peek_token ();
+  /* TODO: should the switch just directly call the individual parse methods
+   * rather than adding another layer of indirection with
+   * parse_expr_stmt_with_block()? */
+  switch (t->get_id ())
+    {
+    case LOOP:
+    case WHILE:
+    case FOR:
+    case IF:
+    case MATCH_TOK:
+    case LEFT_CURLY:
+    case ASYNC:
+      // expression with block
+      return parse_expr_stmt_with_block (std::move (outer_attrs));
+      case LIFETIME: {
+	/* FIXME: are there any expressions without blocks that can have
+	 * lifetime as their first token? Or is loop expr the only one? */
+	// safe side for now:
+	if (lexer.peek_token (1)->get_id () == COLON
+	    && lexer.peek_token (2)->get_id () == LOOP)
+	  {
+	    return parse_expr_stmt_with_block (std::move (outer_attrs));
+	  }
+	else
+	  {
+	    return parse_expr_stmt_without_block (std::move (outer_attrs),
+						  restrictions);
+	  }
+      }
+      case UNSAFE: {
+	/* FIXME: are there any expressions without blocks that can have
+	 * unsafe as their first token? Or is unsafe the only one? */
+	// safe side for now
+	if (lexer.peek_token (1)->get_id () == LEFT_CURLY)
+	  {
+	    return parse_expr_stmt_with_block (std::move (outer_attrs));
+	  }
+	else
+	  {
+	    return parse_expr_stmt_without_block (std::move (outer_attrs),
+						  restrictions);
+	  }
+      }
+    default:
+      // not a parse expr with block, so must be expr without block
+      /* TODO: if possible, be more selective about possible expr without
+       * block initial tokens in order to prevent more syntactical errors at
+       * parse time. */
+      return parse_expr_stmt_without_block (std::move (outer_attrs),
+					    restrictions);
+    }
+}
+
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ExprWithBlock>
+Parser<ManagedTokenSource>::parse_expr_with_block (AST::AttrVec outer_attrs)
+{
+  std::unique_ptr<AST::ExprWithBlock> expr_parsed = nullptr;
+
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case IF:
+      // if or if let, so more lookahead to find out
+      if (lexer.peek_token (1)->get_id () == LET)
+	{
+	  // if let expr
+	  expr_parsed = parse_if_let_expr (std::move (outer_attrs));
+	  break;
+	}
+      else
+	{
+	  // if expr
+	  expr_parsed = parse_if_expr (std::move (outer_attrs));
+	  break;
+	}
+    case LOOP:
+      // infinite loop
+      expr_parsed = parse_loop_expr (std::move (outer_attrs));
+      break;
+    case FOR:
+      // "for" iterator loop
+      expr_parsed = parse_for_loop_expr (std::move (outer_attrs));
+      break;
+      case WHILE: {
+	// while or while let, so more lookahead to find out
+	if (lexer.peek_token (1)->get_id () == LET)
+	  {
+	    // while let loop expr
+	    expr_parsed = parse_while_let_loop_expr (std::move (outer_attrs));
+	    break;
+	  }
+	else
+	  {
+	    // while loop expr
+	    expr_parsed = parse_while_loop_expr (std::move (outer_attrs));
+	    break;
+	  }
+      }
+    case MATCH_TOK:
+      // match expression
+      expr_parsed = parse_match_expr (std::move (outer_attrs));
+      break;
+    case LEFT_CURLY:
+      // block expression
+      expr_parsed = parse_block_expr (std::move (outer_attrs));
+      break;
+    case ASYNC:
+      // async block expression
+      expr_parsed = parse_async_block_expr (std::move (outer_attrs));
+      break;
+    case UNSAFE:
+      // unsafe block expression
+      expr_parsed = parse_unsafe_block_expr (std::move (outer_attrs));
+      break;
+    case LIFETIME:
+      // some kind of loop expr (with loop label)
+      expr_parsed = parse_labelled_loop_expr (std::move (outer_attrs));
+      break;
+    default:
+      add_error (Error (
+	t->get_locus (),
+	"could not recognise expr beginning with %qs as an expr with block in"
+	" parsing expr statement",
+	t->get_token_description ()));
+
+      skip_after_next_block ();
+      return nullptr;
+    }
+
+  // ensure expr parsed exists
+  if (expr_parsed == nullptr)
+    {
+      Error error (t->get_locus (),
+		   "failed to parse expr with block in parsing expr statement");
+      add_error (std::move (error));
+
+      skip_after_end_block ();
+      return nullptr;
+    }
+
+  return expr_parsed;
+}
+
+/* Parses a expression statement containing an expression with block.
+ * Disambiguates internally. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ExprStmtWithBlock>
+Parser<ManagedTokenSource>::parse_expr_stmt_with_block (
+  AST::AttrVec outer_attrs)
+{
+  auto expr_parsed = parse_expr_with_block (std::move (outer_attrs));
+  auto locus = expr_parsed->get_locus ();
+
+  // return expr stmt created from expr
+  return std::unique_ptr<AST::ExprStmtWithBlock> (
+    new AST::ExprStmtWithBlock (std::move (expr_parsed), locus,
+				lexer.peek_token ()->get_id () == SEMICOLON));
+}
+
+/* Parses an expression statement containing an expression without block.
+ * Disambiguates further. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ExprStmtWithoutBlock>
+Parser<ManagedTokenSource>::parse_expr_stmt_without_block (
+  AST::AttrVec outer_attrs, ParseRestrictions restrictions)
+{
+  /* TODO: maybe move more logic for expr without block in here for better
+   * error handling */
+
+  // attempt to parse via parse_expr_without_block - seems to work
+  std::unique_ptr<AST::ExprWithoutBlock> expr = nullptr;
+  Location locus = lexer.peek_token ()->get_locus ();
+
+  restrictions.expr_can_be_stmt = true;
+
+  expr = parse_expr_without_block (std::move (outer_attrs), restrictions);
+  if (expr == nullptr)
+    {
+      // expr is required, error
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse expr without block in expr statement");
+      add_error (std::move (error));
+
+      skip_after_semicolon ();
+      return nullptr;
+    }
+
+  if (restrictions.consume_semi)
+    if (!skip_token (SEMICOLON))
+      return nullptr;
+
+  return std::unique_ptr<AST::ExprStmtWithoutBlock> (
+    new AST::ExprStmtWithoutBlock (std::move (expr), locus));
+}
+
+/* Parses an expression without a block associated with it (further
+ * disambiguates). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ExprWithoutBlock>
+Parser<ManagedTokenSource>::parse_expr_without_block (
+  AST::AttrVec outer_attrs, ParseRestrictions restrictions)
+{
+  /* Notes on types of expr without block:
+   *  - literal expr          tokens that are literals
+   *  - path expr             path_in_expr or qual_path_in_expr
+   *  - operator expr         many different types
+   *     unary:
+   *      borrow expr         ( '&' | '&&' ) 'mut'? expr
+   *      dereference expr    '*' expr
+   *      error propagation   expr '?'
+   *      negation            '-' expr
+   *      not                 '!' expr
+   *     binary: all start with expr
+   *  - grouped/paren expr    '(' inner_attributes expr ')'
+   *  - array expr            '[' inner_attributes array_elems? ']'
+   *  - await expr            expr '.' 'await'
+   *  - (array/slice) index expr  expr '[' expr ']'
+   *  - tuple expr            '(' inner_attributes tuple_elems? ')'
+   *      note that a single elem tuple is distinguished from a grouped expr
+   * by a trailing comma, i.e. a grouped expr is preferred over a tuple expr
+   *  - tuple index expr      expr '.' tuple_index
+   *  - struct expr           path_in_expr (and optional other stuff)
+   *  - enum variant expr     path_in_expr (and optional other stuff)
+   *      this means that there is no syntactic difference between an enum
+   * variant and a struct
+   *      - only name resolution can tell the difference. Thus, maybe rework
+   * AST to take this into account ("struct or enum" nodes?)
+   *  - (function) call expr  expr '(' call_params? ')'
+   *  - method call expr      expr '.' path_expr_segment '(' call_params? ')'
+   *  - field expr            expr '.' identifier
+   *      note that method call expr is preferred, i.e. field expr must not be
+   * followed by parenthesised expression sequence.
+   *  - closure expr          'move'? ( '||' | '|' closure_params? '|' ) (
+   * expr | '->' type_no_bounds block_expr )
+   *  - continue expr         'continue' labelled_lifetime?
+   *  - break expr            'break' labelled_lifetime? expr?
+   *  - range expr            many different types but all involve '..' or
+   * '..='
+   *  - return expr           'return' as 1st tok
+   *  - macro invocation      identifier then :: or identifier then !
+   * (simple_path '!')
+   *
+   * any that have rules beginning with 'expr' should probably be
+   * pratt-parsed,
+   * with parsing type to use determined by token AND lookahead. */
+
+  // ok well at least can do easy ones
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case RETURN_TOK:
+      // return expr
+      return parse_return_expr (std::move (outer_attrs));
+    case BREAK:
+      // break expr
+      return parse_break_expr (std::move (outer_attrs));
+    case CONTINUE:
+      // continue expr
+      return parse_continue_expr (std::move (outer_attrs));
+    case MOVE:
+      // closure expr (though not all closure exprs require this)
+      return parse_closure_expr (std::move (outer_attrs));
+    case LEFT_SQUARE:
+      // array expr (creation, not index)
+      return parse_array_expr (std::move (outer_attrs));
+      default: {
+	/* HACK: piggyback on pratt parsed expr and abuse polymorphism to
+	 * essentially downcast */
+
+	std::unique_ptr<AST::Expr> expr
+	  = parse_expr (std::move (outer_attrs), restrictions);
+
+	if (expr == nullptr)
+	  {
+	    Error error (t->get_locus (),
+			 "failed to parse expression for expression without "
+			 "block (pratt-parsed expression is null)");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	std::unique_ptr<AST::ExprWithoutBlock> expr_without_block (
+	  expr->as_expr_without_block ());
+
+	if (expr_without_block != nullptr)
+	  {
+	    return expr_without_block;
+	  }
+	else
+	  {
+	    Error error (t->get_locus (),
+			 "converted expr without block is null");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+      }
+    }
+}
+
+// Parses a block expression, including the curly braces at start and end.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::BlockExpr>
+Parser<ManagedTokenSource>::parse_block_expr (AST::AttrVec outer_attrs,
+					      Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      if (!skip_token (LEFT_CURLY))
+	{
+	  skip_after_end_block ();
+	  return nullptr;
+	}
+    }
+
+  AST::AttrVec inner_attrs = parse_inner_attributes ();
+
+  // parse statements and expression
+  std::vector<std::unique_ptr<AST::Stmt>> stmts;
+  std::unique_ptr<AST::Expr> expr = nullptr;
+
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () != RIGHT_CURLY)
+    {
+      ExprOrStmt expr_or_stmt = parse_stmt_or_expr_without_block ();
+      if (expr_or_stmt.is_error ())
+	{
+	  Error error (t->get_locus (),
+		       "failed to parse statement or expression without "
+		       "block in block expression");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+
+      t = lexer.peek_token ();
+
+      if (expr_or_stmt.stmt != nullptr)
+	{
+	  stmts.push_back (std::move (expr_or_stmt.stmt));
+	}
+      else
+	{
+	  // assign to expression and end parsing inside
+	  expr = std::move (expr_or_stmt.expr);
+	  break;
+	}
+    }
+
+  Location end_locus = t->get_locus ();
+
+  if (!skip_token (RIGHT_CURLY))
+    {
+      Error error (t->get_locus (),
+		   "error may be from having an expression (as opposed to "
+		   "statement) in the body of the function but not last");
+      add_error (std::move (error));
+
+      skip_after_end_block ();
+      return nullptr;
+    }
+
+  // grammar allows for empty block expressions
+
+  stmts.shrink_to_fit ();
+
+  return std::unique_ptr<AST::BlockExpr> (
+    new AST::BlockExpr (std::move (stmts), std::move (expr),
+			std::move (inner_attrs), std::move (outer_attrs), locus,
+			end_locus));
+}
+
+/* Parses a "grouped" expression (expression in parentheses), used to control
+ * precedence. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::GroupedExpr>
+Parser<ManagedTokenSource>::parse_grouped_expr (AST::AttrVec outer_attrs)
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+  skip_token (LEFT_PAREN);
+
+  AST::AttrVec inner_attrs = parse_inner_attributes ();
+
+  // parse required expr inside parentheses
+  std::unique_ptr<AST::Expr> expr_in_parens = parse_expr ();
+  if (expr_in_parens == nullptr)
+    {
+      // skip after somewhere?
+      // error?
+      return nullptr;
+    }
+
+  if (!skip_token (RIGHT_PAREN))
+    {
+      // skip after somewhere?
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::GroupedExpr> (
+    new AST::GroupedExpr (std::move (expr_in_parens), std::move (inner_attrs),
+			  std::move (outer_attrs), locus));
+}
+
+// Parses a closure expression (closure definition).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ClosureExpr>
+Parser<ManagedTokenSource>::parse_closure_expr (AST::AttrVec outer_attrs)
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+  // detect optional "move"
+  bool has_move = false;
+  if (lexer.peek_token ()->get_id () == MOVE)
+    {
+      lexer.skip_token ();
+      has_move = true;
+    }
+
+  // handle parameter list
+  std::vector<AST::ClosureParam> params;
+
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case OR:
+      // skip token, no parameters
+      lexer.skip_token ();
+      break;
+    case PIPE:
+      // actually may have parameters
+      lexer.skip_token ();
+
+      while (t->get_id () != PIPE)
+	{
+	  AST::ClosureParam param = parse_closure_param ();
+	  if (param.is_error ())
+	    {
+	      // TODO is this really an error?
+	      Error error (t->get_locus (), "could not parse closure param");
+	      add_error (std::move (error));
+
+	      break;
+	    }
+	  params.push_back (std::move (param));
+
+	  if (lexer.peek_token ()->get_id () != COMMA)
+	    {
+	      // not an error but means param list is done
+	      break;
+	    }
+	  // skip comma
+	  lexer.skip_token ();
+
+	  t = lexer.peek_token ();
+	}
+      params.shrink_to_fit ();
+      break;
+    default:
+      add_error (Error (t->get_locus (),
+			"unexpected token %qs in closure expression - expected "
+			"%<|%> or %<||%>",
+			t->get_token_description ()));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // again branch based on next token
+  t = lexer.peek_token ();
+  if (t->get_id () == RETURN_TYPE)
+    {
+      // must be return type closure with block expr
+
+      // skip "return type" token
+      lexer.skip_token ();
+
+      // parse actual type, which is required
+      std::unique_ptr<AST::TypeNoBounds> type = parse_type_no_bounds ();
+      if (type == nullptr)
+	{
+	  // error
+	  Error error (t->get_locus (), "failed to parse type for closure");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      // parse block expr, which is required
+      std::unique_ptr<AST::BlockExpr> block = parse_block_expr ();
+      if (block == nullptr)
+	{
+	  // error
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse block expr in closure");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      return std::unique_ptr<AST::ClosureExprInnerTyped> (
+	new AST::ClosureExprInnerTyped (std::move (type), std::move (block),
+					std::move (params), locus, has_move,
+					std::move (outer_attrs)));
+    }
+  else
+    {
+      // must be expr-only closure
+
+      // parse expr, which is required
+      std::unique_ptr<AST::Expr> expr = parse_expr ();
+      if (expr == nullptr)
+	{
+	  Error error (t->get_locus (),
+		       "failed to parse expression in closure");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      return std::unique_ptr<AST::ClosureExprInner> (
+	new AST::ClosureExprInner (std::move (expr), std::move (params), locus,
+				   has_move, std::move (outer_attrs)));
+    }
+}
+
+// Parses a literal token (to literal expression).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::LiteralExpr>
+Parser<ManagedTokenSource>::parse_literal_expr (AST::AttrVec outer_attrs)
+{
+  // TODO: change if literal representation in lexer changes
+
+  std::string literal_value;
+  AST::Literal::LitType type = AST::Literal::STRING;
+
+  // branch based on token
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case CHAR_LITERAL:
+      type = AST::Literal::CHAR;
+      literal_value = t->get_str ();
+      lexer.skip_token ();
+      break;
+    case STRING_LITERAL:
+      type = AST::Literal::STRING;
+      literal_value = t->get_str ();
+      lexer.skip_token ();
+      break;
+    case BYTE_CHAR_LITERAL:
+      type = AST::Literal::BYTE;
+      literal_value = t->get_str ();
+      lexer.skip_token ();
+      break;
+    case BYTE_STRING_LITERAL:
+      type = AST::Literal::BYTE_STRING;
+      literal_value = t->get_str ();
+      lexer.skip_token ();
+      break;
+    case INT_LITERAL:
+      type = AST::Literal::INT;
+      literal_value = t->get_str ();
+      lexer.skip_token ();
+      break;
+    case FLOAT_LITERAL:
+      type = AST::Literal::FLOAT;
+      literal_value = t->get_str ();
+      lexer.skip_token ();
+      break;
+    // case BOOL_LITERAL
+    // use true and false keywords rather than "bool literal" Rust terminology
+    case TRUE_LITERAL:
+      type = AST::Literal::BOOL;
+      literal_value = "true";
+      lexer.skip_token ();
+      break;
+    case FALSE_LITERAL:
+      type = AST::Literal::BOOL;
+      literal_value = "false";
+      lexer.skip_token ();
+      break;
+    default:
+      // error - cannot be a literal expr
+      add_error (Error (t->get_locus (),
+			"unexpected token %qs when parsing literal expression",
+			t->get_token_description ()));
+
+      // skip?
+      return nullptr;
+    }
+
+  // create literal based on stuff in switch
+  return std::unique_ptr<AST::LiteralExpr> (
+    new AST::LiteralExpr (std::move (literal_value), std::move (type),
+			  t->get_type_hint (), std::move (outer_attrs),
+			  t->get_locus ()));
+}
+
+// Parses a return expression (including any expression to return).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ReturnExpr>
+Parser<ManagedTokenSource>::parse_return_expr (AST::AttrVec outer_attrs,
+					       Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      skip_token (RETURN_TOK);
+    }
+
+  // parse expression to return, if it exists
+  ParseRestrictions restrictions;
+  restrictions.expr_can_be_null = true;
+  std::unique_ptr<AST::Expr> returned_expr
+    = parse_expr (AST::AttrVec (), restrictions);
+
+  return std::unique_ptr<AST::ReturnExpr> (
+    new AST::ReturnExpr (std::move (returned_expr), std::move (outer_attrs),
+			 locus));
+}
+
+/* Parses a break expression (including any label to break to AND any return
+ * expression). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::BreakExpr>
+Parser<ManagedTokenSource>::parse_break_expr (AST::AttrVec outer_attrs,
+					      Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      skip_token (BREAK);
+    }
+
+  // parse label (lifetime) if it exists - create dummy first
+  AST::Lifetime label = AST::Lifetime::error ();
+  if (lexer.peek_token ()->get_id () == LIFETIME)
+    {
+      label = parse_lifetime ();
+    }
+
+  // parse break return expression if it exists
+  ParseRestrictions restrictions;
+  restrictions.expr_can_be_null = true;
+  std::unique_ptr<AST::Expr> return_expr
+    = parse_expr (AST::AttrVec (), restrictions);
+
+  return std::unique_ptr<AST::BreakExpr> (
+    new AST::BreakExpr (std::move (label), std::move (return_expr),
+			std::move (outer_attrs), locus));
+}
+
+// Parses a continue expression (including any label to continue from).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ContinueExpr>
+Parser<ManagedTokenSource>::parse_continue_expr (AST::AttrVec outer_attrs,
+						 Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      skip_token (CONTINUE);
+    }
+
+  // parse label (lifetime) if it exists - create dummy first
+  AST::Lifetime label = AST::Lifetime::error ();
+  if (lexer.peek_token ()->get_id () == LIFETIME)
+    {
+      label = parse_lifetime ();
+    }
+
+  return std::unique_ptr<AST::ContinueExpr> (
+    new AST::ContinueExpr (std::move (label), std::move (outer_attrs), locus));
+}
+
+// Parses a loop label used in loop expressions.
+template <typename ManagedTokenSource>
+AST::LoopLabel
+Parser<ManagedTokenSource>::parse_loop_label ()
+{
+  // parse lifetime - if doesn't exist, assume no label
+  const_TokenPtr t = lexer.peek_token ();
+  if (t->get_id () != LIFETIME)
+    {
+      // not necessarily an error
+      return AST::LoopLabel::error ();
+    }
+  /* FIXME: check for named lifetime requirement here? or check in semantic
+   * analysis phase? */
+  AST::Lifetime label = parse_lifetime ();
+
+  if (!skip_token (COLON))
+    {
+      // skip somewhere?
+      return AST::LoopLabel::error ();
+    }
+
+  return AST::LoopLabel (std::move (label), t->get_locus ());
+}
+
+/* Parses an if expression of any kind, including with else, else if, else if
+ * let, and neither. Note that any outer attributes will be ignored because if
+ * expressions don't support them. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::IfExpr>
+Parser<ManagedTokenSource>::parse_if_expr (AST::AttrVec outer_attrs,
+					   Location pratt_parsed_loc)
+{
+  // TODO: make having outer attributes an error?
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      if (!skip_token (IF))
+	{
+	  skip_after_end_block ();
+	  return nullptr;
+	}
+    }
+
+  // detect accidental if let
+  if (lexer.peek_token ()->get_id () == LET)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "if let expression probably exists, but is being parsed "
+		   "as an if expression. This may be a parser error");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  /* parse required condition expr - HACK to prevent struct expr from being
+   * parsed */
+  ParseRestrictions no_struct_expr;
+  no_struct_expr.can_be_struct_expr = false;
+  std::unique_ptr<AST::Expr> condition = parse_expr ({}, no_struct_expr);
+  if (condition == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse condition expression in if expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // parse required block expr
+  std::unique_ptr<AST::BlockExpr> if_body = parse_block_expr ();
+  if (if_body == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse if body block expression in if expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // branch to parse end or else (and then else, else if, or else if let)
+  if (lexer.peek_token ()->get_id () != ELSE)
+    {
+      // single selection - end of if expression
+      return std::unique_ptr<AST::IfExpr> (
+	new AST::IfExpr (std::move (condition), std::move (if_body),
+			 std::move (outer_attrs), locus));
+    }
+  else
+    {
+      // double or multiple selection - branch on end, else if, or else if let
+
+      // skip "else"
+      lexer.skip_token ();
+
+      // branch on whether next token is '{' or 'if'
+      const_TokenPtr t = lexer.peek_token ();
+      switch (t->get_id ())
+	{
+	  case LEFT_CURLY: {
+	    // double selection - else
+	    // parse else block expr (required)
+	    std::unique_ptr<AST::BlockExpr> else_body = parse_block_expr ();
+	    if (else_body == nullptr)
+	      {
+		Error error (lexer.peek_token ()->get_locus (),
+			     "failed to parse else body block expression in "
+			     "if expression");
+		add_error (std::move (error));
+
+		// skip somewhere?
+		return nullptr;
+	      }
+
+	    return std::unique_ptr<AST::IfExprConseqElse> (
+	      new AST::IfExprConseqElse (std::move (condition),
+					 std::move (if_body),
+					 std::move (else_body),
+					 std::move (outer_attrs), locus));
+	  }
+	  case IF: {
+	    // multiple selection - else if or else if let
+	    // branch on whether next token is 'let' or not
+	    if (lexer.peek_token (1)->get_id () == LET)
+	      {
+		// parse if let expr (required)
+		std::unique_ptr<AST::IfLetExpr> if_let_expr
+		  = parse_if_let_expr ();
+		if (if_let_expr == nullptr)
+		  {
+		    Error error (lexer.peek_token ()->get_locus (),
+				 "failed to parse (else) if let expression "
+				 "after if expression");
+		    add_error (std::move (error));
+
+		    // skip somewhere?
+		    return nullptr;
+		  }
+
+		return std::unique_ptr<AST::IfExprConseqIfLet> (
+		  new AST::IfExprConseqIfLet (std::move (condition),
+					      std::move (if_body),
+					      std::move (if_let_expr),
+					      std::move (outer_attrs), locus));
+	      }
+	    else
+	      {
+		// parse if expr (required)
+		std::unique_ptr<AST::IfExpr> if_expr = parse_if_expr ();
+		if (if_expr == nullptr)
+		  {
+		    Error error (lexer.peek_token ()->get_locus (),
+				 "failed to parse (else) if expression after "
+				 "if expression");
+		    add_error (std::move (error));
+
+		    // skip somewhere?
+		    return nullptr;
+		  }
+
+		return std::unique_ptr<AST::IfExprConseqIf> (
+		  new AST::IfExprConseqIf (std::move (condition),
+					   std::move (if_body),
+					   std::move (if_expr),
+					   std::move (outer_attrs), locus));
+	      }
+	  }
+	default:
+	  // error - invalid token
+	  add_error (Error (t->get_locus (),
+			    "unexpected token %qs after else in if expression",
+			    t->get_token_description ()));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+    }
+}
+
+/* Parses an if let expression of any kind, including with else, else if, else
+ * if let, and none. Note that any outer attributes will be ignored as if let
+ * expressions don't support them. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::IfLetExpr>
+Parser<ManagedTokenSource>::parse_if_let_expr (AST::AttrVec outer_attrs,
+					       Location pratt_parsed_loc)
+{
+  // TODO: make having outer attributes an error?
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      if (!skip_token (IF))
+	{
+	  skip_after_end_block ();
+	  return nullptr;
+	}
+    }
+
+  // detect accidental if expr parsed as if let expr
+  if (lexer.peek_token ()->get_id () != LET)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "if expression probably exists, but is being parsed as an "
+		   "if let expression. This may be a parser error");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+  lexer.skip_token ();
+
+  // parse match arm patterns (which are required)
+  std::vector<std::unique_ptr<AST::Pattern>> match_arm_patterns
+    = parse_match_arm_patterns (EQUAL);
+  if (match_arm_patterns.empty ())
+    {
+      Error error (
+	lexer.peek_token ()->get_locus (),
+	"failed to parse any match arm patterns in if let expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  if (!skip_token (EQUAL))
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // parse expression (required) - HACK to prevent struct expr being parsed
+  ParseRestrictions no_struct_expr;
+  no_struct_expr.can_be_struct_expr = false;
+  std::unique_ptr<AST::Expr> scrutinee_expr = parse_expr ({}, no_struct_expr);
+  if (scrutinee_expr == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse scrutinee expression in if let expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+  /* TODO: check for expression not being a struct expression or lazy boolean
+   * expression here? or actually probably in semantic analysis. */
+
+  // parse block expression (required)
+  std::unique_ptr<AST::BlockExpr> if_let_body = parse_block_expr ();
+  if (if_let_body == nullptr)
+    {
+      Error error (
+	lexer.peek_token ()->get_locus (),
+	"failed to parse if let body block expression in if let expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // branch to parse end or else (and then else, else if, or else if let)
+  if (lexer.peek_token ()->get_id () != ELSE)
+    {
+      // single selection - end of if let expression
+      return std::unique_ptr<AST::IfLetExpr> (
+	new AST::IfLetExpr (std::move (match_arm_patterns),
+			    std::move (scrutinee_expr), std::move (if_let_body),
+			    std::move (outer_attrs), locus));
+    }
+  else
+    {
+      // double or multiple selection - branch on end, else if, or else if let
+
+      // skip "else"
+      lexer.skip_token ();
+
+      // branch on whether next token is '{' or 'if'
+      const_TokenPtr t = lexer.peek_token ();
+      switch (t->get_id ())
+	{
+	  case LEFT_CURLY: {
+	    // double selection - else
+	    // parse else block expr (required)
+	    std::unique_ptr<AST::BlockExpr> else_body = parse_block_expr ();
+	    if (else_body == nullptr)
+	      {
+		Error error (lexer.peek_token ()->get_locus (),
+			     "failed to parse else body block expression in "
+			     "if let expression");
+		add_error (std::move (error));
+
+		// skip somewhere?
+		return nullptr;
+	      }
+
+	    return std::unique_ptr<AST::IfLetExprConseqElse> (
+	      new AST::IfLetExprConseqElse (std::move (match_arm_patterns),
+					    std::move (scrutinee_expr),
+					    std::move (if_let_body),
+					    std::move (else_body),
+					    std::move (outer_attrs), locus));
+	  }
+	  case IF: {
+	    // multiple selection - else if or else if let
+	    // branch on whether next token is 'let' or not
+	    if (lexer.peek_token (1)->get_id () == LET)
+	      {
+		// parse if let expr (required)
+		std::unique_ptr<AST::IfLetExpr> if_let_expr
+		  = parse_if_let_expr ();
+		if (if_let_expr == nullptr)
+		  {
+		    Error error (lexer.peek_token ()->get_locus (),
+				 "failed to parse (else) if let expression "
+				 "after if let expression");
+		    add_error (std::move (error));
+
+		    // skip somewhere?
+		    return nullptr;
+		  }
+
+		return std::unique_ptr<AST::IfLetExprConseqIfLet> (
+		  new AST::IfLetExprConseqIfLet (
+		    std::move (match_arm_patterns), std::move (scrutinee_expr),
+		    std::move (if_let_body), std::move (if_let_expr),
+		    std::move (outer_attrs), locus));
+	      }
+	    else
+	      {
+		// parse if expr (required)
+		std::unique_ptr<AST::IfExpr> if_expr = parse_if_expr ();
+		if (if_expr == nullptr)
+		  {
+		    Error error (lexer.peek_token ()->get_locus (),
+				 "failed to parse (else) if expression after "
+				 "if let expression");
+		    add_error (std::move (error));
+
+		    // skip somewhere?
+		    return nullptr;
+		  }
+
+		return std::unique_ptr<AST::IfLetExprConseqIf> (
+		  new AST::IfLetExprConseqIf (std::move (match_arm_patterns),
+					      std::move (scrutinee_expr),
+					      std::move (if_let_body),
+					      std::move (if_expr),
+					      std::move (outer_attrs), locus));
+	      }
+	  }
+	default:
+	  // error - invalid token
+	  add_error (
+	    Error (t->get_locus (),
+		   "unexpected token %qs after else in if let expression",
+		   t->get_token_description ()));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+    }
+}
+
+/* TODO: possibly decide on different method of handling label (i.e. not
+ * parameter) */
+
+/* Parses a "loop" infinite loop expression. Label is not parsed and should be
+ * parsed via parse_labelled_loop_expr, which would call this. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::LoopExpr>
+Parser<ManagedTokenSource>::parse_loop_expr (AST::AttrVec outer_attrs,
+					     AST::LoopLabel label,
+					     Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      if (label.is_error ())
+	locus = lexer.peek_token ()->get_locus ();
+      else
+	locus = label.get_locus ();
+
+      if (!skip_token (LOOP))
+	{
+	  skip_after_end_block ();
+	  return nullptr;
+	}
+    }
+  else
+    {
+      if (!label.is_error ())
+	locus = label.get_locus ();
+    }
+
+  // parse loop body, which is required
+  std::unique_ptr<AST::BlockExpr> loop_body = parse_block_expr ();
+  if (loop_body == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "could not parse loop body in (infinite) loop expression");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::LoopExpr> (
+    new AST::LoopExpr (std::move (loop_body), locus, std::move (label),
+		       std::move (outer_attrs)));
+}
+
+/* Parses a "while" loop expression. Label is not parsed and should be parsed
+ * via parse_labelled_loop_expr, which would call this. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::WhileLoopExpr>
+Parser<ManagedTokenSource>::parse_while_loop_expr (AST::AttrVec outer_attrs,
+						   AST::LoopLabel label,
+						   Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      if (label.is_error ())
+	locus = lexer.peek_token ()->get_locus ();
+      else
+	locus = label.get_locus ();
+
+      if (!skip_token (WHILE))
+	{
+	  skip_after_end_block ();
+	  return nullptr;
+	}
+    }
+  else
+    {
+      if (!label.is_error ())
+	locus = label.get_locus ();
+    }
+
+  // ensure it isn't a while let loop
+  if (lexer.peek_token ()->get_id () == LET)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "appears to be while let loop but is being parsed by "
+		   "while loop - this may be a compiler issue");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // parse loop predicate (required) with HACK to prevent struct expr parsing
+  ParseRestrictions no_struct_expr;
+  no_struct_expr.can_be_struct_expr = false;
+  std::unique_ptr<AST::Expr> predicate = parse_expr ({}, no_struct_expr);
+  if (predicate == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse predicate expression in while loop");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+  /* TODO: check that it isn't struct expression here? actually, probably in
+   * semantic analysis */
+
+  // parse loop body (required)
+  std::unique_ptr<AST::BlockExpr> body = parse_block_expr ();
+  if (body == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse loop body block expression in while loop");
+      add_error (std::move (error));
+
+      // skip somewhere
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::WhileLoopExpr> (
+    new AST::WhileLoopExpr (std::move (predicate), std::move (body), locus,
+			    std::move (label), std::move (outer_attrs)));
+}
+
+/* Parses a "while let" loop expression. Label is not parsed and should be
+ * parsed via parse_labelled_loop_expr, which would call this. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::WhileLetLoopExpr>
+Parser<ManagedTokenSource>::parse_while_let_loop_expr (AST::AttrVec outer_attrs,
+						       AST::LoopLabel label)
+{
+  Location locus = Linemap::unknown_location ();
+  if (label.is_error ())
+    locus = lexer.peek_token ()->get_locus ();
+  else
+    locus = label.get_locus ();
+  skip_token (WHILE);
+
+  /* check for possible accidental recognition of a while loop as a while let
+   * loop */
+  if (lexer.peek_token ()->get_id () != LET)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "appears to be a while loop but is being parsed by "
+		   "while let loop - this may be a compiler issue");
+      add_error (std::move (error));
+
+      // skip somewhere
+      return nullptr;
+    }
+  // as this token is definitely let now, save the computation of comparison
+  lexer.skip_token ();
+
+  // parse predicate patterns
+  std::vector<std::unique_ptr<AST::Pattern>> predicate_patterns
+    = parse_match_arm_patterns (EQUAL);
+  // TODO: have to ensure that there is at least 1 pattern?
+
+  if (!skip_token (EQUAL))
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+
+  /* parse predicate expression, which is required (and HACK to prevent struct
+   * expr) */
+  ParseRestrictions no_struct_expr;
+  no_struct_expr.can_be_struct_expr = false;
+  std::unique_ptr<AST::Expr> predicate_expr = parse_expr ({}, no_struct_expr);
+  if (predicate_expr == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse predicate expression in while let loop");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+  /* TODO: ensure that struct expression is not parsed? Actually, probably in
+   * semantic analysis. */
+
+  // parse loop body, which is required
+  std::unique_ptr<AST::BlockExpr> body = parse_block_expr ();
+  if (body == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse block expr (loop body) of while let loop");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::WhileLetLoopExpr> (new AST::WhileLetLoopExpr (
+    std::move (predicate_patterns), std::move (predicate_expr),
+    std::move (body), locus, std::move (label), std::move (outer_attrs)));
+}
+
+/* Parses a "for" iterative loop. Label is not parsed and should be parsed via
+ * parse_labelled_loop_expr, which would call this. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ForLoopExpr>
+Parser<ManagedTokenSource>::parse_for_loop_expr (AST::AttrVec outer_attrs,
+						 AST::LoopLabel label)
+{
+  Location locus = Linemap::unknown_location ();
+  if (label.is_error ())
+    locus = lexer.peek_token ()->get_locus ();
+  else
+    locus = label.get_locus ();
+  skip_token (FOR);
+
+  // parse pattern, which is required
+  std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+  if (pattern == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse iterator pattern in for loop");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  if (!skip_token (IN))
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+
+  /* parse iterator expression, which is required - also HACK to prevent
+   * struct expr */
+  ParseRestrictions no_struct_expr;
+  no_struct_expr.can_be_struct_expr = false;
+  std::unique_ptr<AST::Expr> expr = parse_expr ({}, no_struct_expr);
+  if (expr == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse iterator expression in for loop");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+  // TODO: check to ensure this isn't struct expr? Or in semantic analysis.
+
+  // parse loop body, which is required
+  std::unique_ptr<AST::BlockExpr> body = parse_block_expr ();
+  if (body == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse loop body block expression in for loop");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::ForLoopExpr> (
+    new AST::ForLoopExpr (std::move (pattern), std::move (expr),
+			  std::move (body), locus, std::move (label),
+			  std::move (outer_attrs)));
+}
+
+// Parses a loop expression with label (any kind of loop - disambiguates).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::BaseLoopExpr>
+Parser<ManagedTokenSource>::parse_labelled_loop_expr (AST::AttrVec outer_attrs)
+{
+  /* TODO: decide whether it should not work if there is no label, or parse it
+   * with no label at the moment, I will make it not work with no label
+   * because that's the implication. */
+
+  if (lexer.peek_token ()->get_id () != LIFETIME)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "expected lifetime in labelled loop expr (to parse loop "
+		   "label) - found %qs",
+		   lexer.peek_token ()->get_token_description ());
+      add_error (std::move (error));
+
+      // skip?
+      return nullptr;
+    }
+
+  // parse loop label (required)
+  AST::LoopLabel label = parse_loop_label ();
+  if (label.is_error ())
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse loop label in labelled loop expr");
+      add_error (std::move (error));
+
+      // skip?
+      return nullptr;
+    }
+
+  // branch on next token
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case LOOP:
+      return parse_loop_expr (std::move (outer_attrs), std::move (label));
+    case FOR:
+      return parse_for_loop_expr (std::move (outer_attrs), std::move (label));
+    case WHILE:
+      // further disambiguate into while vs while let
+      if (lexer.peek_token (1)->get_id () == LET)
+	{
+	  return parse_while_let_loop_expr (std::move (outer_attrs),
+					    std::move (label));
+	}
+      else
+	{
+	  return parse_while_loop_expr (std::move (outer_attrs),
+					std::move (label));
+	}
+    default:
+      // error
+      add_error (Error (t->get_locus (),
+			"unexpected token %qs when parsing labelled loop",
+			t->get_token_description ()));
+
+      // skip?
+      return nullptr;
+    }
+}
+
+// Parses a match expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::MatchExpr>
+Parser<ManagedTokenSource>::parse_match_expr (AST::AttrVec outer_attrs,
+					      Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      skip_token (MATCH_TOK);
+    }
+
+  /* parse scrutinee expression, which is required (and HACK to prevent struct
+   * expr) */
+  ParseRestrictions no_struct_expr;
+  no_struct_expr.can_be_struct_expr = false;
+  std::unique_ptr<AST::Expr> scrutinee = parse_expr ({}, no_struct_expr);
+  if (scrutinee == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse scrutinee expression in match expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+  /* TODO: check for scrutinee expr not being struct expr? or do so in
+   * semantic analysis */
+
+  if (!skip_token (LEFT_CURLY))
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // parse inner attributes (if they exist)
+  AST::AttrVec inner_attrs = parse_inner_attributes ();
+
+  // parse match arms (if they exist)
+  // std::vector<std::unique_ptr<AST::MatchCase> > match_arms;
+  std::vector<AST::MatchCase> match_arms;
+
+  // parse match cases
+  while (lexer.peek_token ()->get_id () != RIGHT_CURLY)
+    {
+      // parse match arm itself, which is required
+      AST::MatchArm arm = parse_match_arm ();
+      if (arm.is_error ())
+	{
+	  // TODO is this worth throwing everything away?
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse match arm in match arms");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+
+      if (!skip_token (MATCH_ARROW))
+	{
+	  // skip after somewhere?
+	  // TODO is returning here a good idea? or is break better?
+	  return nullptr;
+	}
+
+      ParseRestrictions restrictions;
+      restrictions.expr_can_be_stmt = true;
+      restrictions.consume_semi = false;
+
+      std::unique_ptr<AST::ExprStmt> expr = parse_expr_stmt ({}, restrictions);
+      if (expr == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse expr in match arm in match expr");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+      bool is_expr_without_block
+	= expr->get_type () == AST::ExprStmt::ExprStmtType::WITHOUT_BLOCK;
+
+      // construct match case expr and add to cases
+      switch (expr->get_type ())
+	{
+	  case AST::ExprStmt::ExprStmtType::WITH_BLOCK: {
+	    AST::ExprStmtWithBlock *cast
+	      = static_cast<AST::ExprStmtWithBlock *> (expr.get ());
+	    std::unique_ptr<AST::Expr> e = cast->get_expr ()->clone_expr ();
+	    match_arms.push_back (
+	      AST::MatchCase (std::move (arm), std::move (e)));
+	  }
+	  break;
+
+	  case AST::ExprStmt::ExprStmtType::WITHOUT_BLOCK: {
+	    AST::ExprStmtWithoutBlock *cast
+	      = static_cast<AST::ExprStmtWithoutBlock *> (expr.get ());
+	    std::unique_ptr<AST::Expr> e = cast->get_expr ()->clone_expr ();
+	    match_arms.push_back (
+	      AST::MatchCase (std::move (arm), std::move (e)));
+	  }
+	  break;
+	}
+
+      // handle comma presence
+      if (lexer.peek_token ()->get_id () != COMMA)
+	{
+	  if (!is_expr_without_block)
+	    {
+	      // allowed even if not final case
+	      continue;
+	    }
+	  else if (is_expr_without_block
+		   && lexer.peek_token ()->get_id () != RIGHT_CURLY)
+	    {
+	      // not allowed if not final case
+	      Error error (lexer.peek_token ()->get_locus (),
+			   "exprwithoutblock requires comma after match case "
+			   "expression in match arm (if not final case)");
+	      add_error (std::move (error));
+
+	      return nullptr;
+	    }
+	  else
+	    {
+	      // otherwise, must be final case, so fine
+	      break;
+	    }
+	}
+      lexer.skip_token ();
+    }
+
+  if (!skip_token (RIGHT_CURLY))
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+
+  match_arms.shrink_to_fit ();
+
+  return std::unique_ptr<AST::MatchExpr> (
+    new AST::MatchExpr (std::move (scrutinee), std::move (match_arms),
+			std::move (inner_attrs), std::move (outer_attrs),
+			locus));
+}
+
+// Parses the "pattern" part of the match arm (the 'case x:' equivalent).
+template <typename ManagedTokenSource>
+AST::MatchArm
+Parser<ManagedTokenSource>::parse_match_arm ()
+{
+  // parse optional outer attributes
+  AST::AttrVec outer_attrs = parse_outer_attributes ();
+
+  // DEBUG
+  rust_debug ("about to start parsing match arm patterns");
+
+  // break early if find right curly
+  if (lexer.peek_token ()->get_id () == RIGHT_CURLY)
+    {
+      // not an error
+      return AST::MatchArm::create_error ();
+    }
+
+  // parse match arm patterns - at least 1 is required
+  std::vector<std::unique_ptr<AST::Pattern>> match_arm_patterns
+    = parse_match_arm_patterns (RIGHT_CURLY);
+  if (match_arm_patterns.empty ())
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse any patterns in match arm");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return AST::MatchArm::create_error ();
+    }
+
+  // DEBUG
+  rust_debug ("successfully parsed match arm patterns");
+
+  // parse match arm guard expr if it exists
+  std::unique_ptr<AST::Expr> guard_expr = nullptr;
+  if (lexer.peek_token ()->get_id () == IF)
+    {
+      lexer.skip_token ();
+
+      guard_expr = parse_expr ();
+      if (guard_expr == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse guard expression in match arm");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return AST::MatchArm::create_error ();
+	}
+    }
+
+  // DEBUG
+  rust_debug ("successfully parsed match arm");
+
+  return AST::MatchArm (std::move (match_arm_patterns),
+			lexer.peek_token ()->get_locus (),
+			std::move (guard_expr), std::move (outer_attrs));
+}
+
+/* Parses the patterns used in a match arm. End token id is the id of the
+ * token that would exist after the patterns are done (e.g. '}' for match
+ * expr, '=' for if let and while let). */
+template <typename ManagedTokenSource>
+std::vector<std::unique_ptr<AST::Pattern>>
+Parser<ManagedTokenSource>::parse_match_arm_patterns (TokenId end_token_id)
+{
+  // skip optional leading '|'
+  if (lexer.peek_token ()->get_id () == PIPE)
+    lexer.skip_token ();
+  /* TODO: do I even need to store the result of this? can't be used.
+   * If semantically different, I need a wrapped "match arm patterns" object
+   * for this. */
+
+  std::vector<std::unique_ptr<AST::Pattern>> patterns;
+
+  // quick break out if end_token_id
+  if (lexer.peek_token ()->get_id () == end_token_id)
+    return patterns;
+
+  // parse required pattern - if doesn't exist, return empty
+  std::unique_ptr<AST::Pattern> initial_pattern = parse_pattern ();
+  if (initial_pattern == nullptr)
+    {
+      // FIXME: should this be an error?
+      return patterns;
+    }
+  patterns.push_back (std::move (initial_pattern));
+
+  // DEBUG
+  rust_debug ("successfully parsed initial match arm pattern");
+
+  // parse new patterns as long as next char is '|'
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () == PIPE)
+    {
+      // skip pipe token
+      lexer.skip_token ();
+
+      // break if hit end token id
+      if (lexer.peek_token ()->get_id () == end_token_id)
+	break;
+
+      // parse pattern
+      std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+      if (pattern == nullptr)
+	{
+	  // this is an error
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse pattern in match arm patterns");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return {};
+	}
+
+      patterns.push_back (std::move (pattern));
+
+      t = lexer.peek_token ();
+    }
+
+  patterns.shrink_to_fit ();
+
+  return patterns;
+}
+
+// Parses an async block expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::AsyncBlockExpr>
+Parser<ManagedTokenSource>::parse_async_block_expr (AST::AttrVec outer_attrs)
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+  skip_token (ASYNC);
+
+  // detect optional move token
+  bool has_move = false;
+  if (lexer.peek_token ()->get_id () == MOVE)
+    {
+      lexer.skip_token ();
+      has_move = true;
+    }
+
+  // parse block expression (required)
+  std::unique_ptr<AST::BlockExpr> block_expr = parse_block_expr ();
+  if (block_expr == nullptr)
+    {
+      Error error (
+	lexer.peek_token ()->get_locus (),
+	"failed to parse block expression of async block expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::AsyncBlockExpr> (
+    new AST::AsyncBlockExpr (std::move (block_expr), has_move,
+			     std::move (outer_attrs), locus));
+}
+
+// Parses an unsafe block expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::UnsafeBlockExpr>
+Parser<ManagedTokenSource>::parse_unsafe_block_expr (AST::AttrVec outer_attrs,
+						     Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      skip_token (UNSAFE);
+    }
+
+  // parse block expression (required)
+  std::unique_ptr<AST::BlockExpr> block_expr = parse_block_expr ();
+  if (block_expr == nullptr)
+    {
+      Error error (
+	lexer.peek_token ()->get_locus (),
+	"failed to parse block expression of unsafe block expression");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::UnsafeBlockExpr> (
+    new AST::UnsafeBlockExpr (std::move (block_expr), std::move (outer_attrs),
+			      locus));
+}
+
+// Parses an array definition expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArrayExpr>
+Parser<ManagedTokenSource>::parse_array_expr (AST::AttrVec outer_attrs,
+					      Location pratt_parsed_loc)
+{
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      skip_token (LEFT_SQUARE);
+    }
+
+  // parse optional inner attributes
+  AST::AttrVec inner_attrs = parse_inner_attributes ();
+
+  // parse the "array elements" section, which is optional
+  if (lexer.peek_token ()->get_id () == RIGHT_SQUARE)
+    {
+      // no array elements
+      lexer.skip_token ();
+
+      std::vector<std::unique_ptr<AST::Expr>> exprs;
+      auto array_elems
+	= Rust::make_unique<AST::ArrayElemsValues> (std::move (exprs), locus);
+      return Rust::make_unique<AST::ArrayExpr> (std::move (array_elems),
+						std::move (inner_attrs),
+						std::move (outer_attrs), locus);
+    }
+  else
+    {
+      // should have array elements
+      // parse initial expression, which is required for either
+      std::unique_ptr<AST::Expr> initial_expr = parse_expr ();
+      if (initial_expr == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "could not parse expression in array expression "
+		       "(even though arrayelems seems to be present)");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      if (lexer.peek_token ()->get_id () == SEMICOLON)
+	{
+	  // copy array elems
+	  lexer.skip_token ();
+
+	  // parse copy amount expression (required)
+	  std::unique_ptr<AST::Expr> copy_amount = parse_expr ();
+	  if (copy_amount == nullptr)
+	    {
+	      Error error (lexer.peek_token ()->get_locus (),
+			   "could not parse copy amount expression in array "
+			   "expression (arrayelems)");
+	      add_error (std::move (error));
+
+	      // skip somewhere?
+	      return nullptr;
+	    }
+
+	  skip_token (RIGHT_SQUARE);
+
+	  std::unique_ptr<AST::ArrayElemsCopied> copied_array_elems (
+	    new AST::ArrayElemsCopied (std::move (initial_expr),
+				       std::move (copy_amount), locus));
+	  return std::unique_ptr<AST::ArrayExpr> (
+	    new AST::ArrayExpr (std::move (copied_array_elems),
+				std::move (inner_attrs),
+				std::move (outer_attrs), locus));
+	}
+      else if (lexer.peek_token ()->get_id () == RIGHT_SQUARE)
+	{
+	  // single-element array expression
+	  std::vector<std::unique_ptr<AST::Expr>> exprs;
+	  exprs.reserve (1);
+	  exprs.push_back (std::move (initial_expr));
+	  exprs.shrink_to_fit ();
+
+	  skip_token (RIGHT_SQUARE);
+
+	  std::unique_ptr<AST::ArrayElemsValues> array_elems (
+	    new AST::ArrayElemsValues (std::move (exprs), locus));
+	  return std::unique_ptr<AST::ArrayExpr> (
+	    new AST::ArrayExpr (std::move (array_elems),
+				std::move (inner_attrs),
+				std::move (outer_attrs), locus));
+	}
+      else if (lexer.peek_token ()->get_id () == COMMA)
+	{
+	  // multi-element array expression (or trailing comma)
+	  std::vector<std::unique_ptr<AST::Expr>> exprs;
+	  exprs.push_back (std::move (initial_expr));
+
+	  const_TokenPtr t = lexer.peek_token ();
+	  while (t->get_id () == COMMA)
+	    {
+	      lexer.skip_token ();
+
+	      // quick break if right square bracket
+	      if (lexer.peek_token ()->get_id () == RIGHT_SQUARE)
+		break;
+
+	      // parse expression (required)
+	      std::unique_ptr<AST::Expr> expr = parse_expr ();
+	      if (expr == nullptr)
+		{
+		  Error error (lexer.peek_token ()->get_locus (),
+			       "failed to parse element in array expression");
+		  add_error (std::move (error));
+
+		  // skip somewhere?
+		  return nullptr;
+		}
+	      exprs.push_back (std::move (expr));
+
+	      t = lexer.peek_token ();
+	    }
+
+	  skip_token (RIGHT_SQUARE);
+
+	  exprs.shrink_to_fit ();
+
+	  std::unique_ptr<AST::ArrayElemsValues> array_elems (
+	    new AST::ArrayElemsValues (std::move (exprs), locus));
+	  return std::unique_ptr<AST::ArrayExpr> (
+	    new AST::ArrayExpr (std::move (array_elems),
+				std::move (inner_attrs),
+				std::move (outer_attrs), locus));
+	}
+      else
+	{
+	  // error
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "unexpected token %qs in array expression (arrayelems)",
+		       lexer.peek_token ()->get_token_description ());
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+    }
+}
+
+// Parses a single parameter used in a closure definition.
+template <typename ManagedTokenSource>
+AST::ClosureParam
+Parser<ManagedTokenSource>::parse_closure_param ()
+{
+  AST::AttrVec outer_attrs = parse_outer_attributes ();
+
+  // parse pattern (which is required)
+  std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+  if (pattern == nullptr)
+    {
+      // not necessarily an error
+      return AST::ClosureParam::create_error ();
+    }
+
+  // parse optional type of param
+  std::unique_ptr<AST::Type> type = nullptr;
+  if (lexer.peek_token ()->get_id () == COLON)
+    {
+      lexer.skip_token ();
+
+      // parse type, which is now required
+      type = parse_type ();
+      if (type == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse type in closure parameter");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return AST::ClosureParam::create_error ();
+	}
+    }
+
+  return AST::ClosureParam (std::move (pattern), pattern->get_locus (),
+			    std::move (type), std::move (outer_attrs));
+}
+
+// Parses a grouped or tuple expression (disambiguates).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ExprWithoutBlock>
+Parser<ManagedTokenSource>::parse_grouped_or_tuple_expr (
+  AST::AttrVec outer_attrs, Location pratt_parsed_loc)
+{
+  // adjustment to allow Pratt parsing to reuse function without copy-paste
+  Location locus = pratt_parsed_loc;
+  if (locus == Linemap::unknown_location ())
+    {
+      locus = lexer.peek_token ()->get_locus ();
+      skip_token (LEFT_PAREN);
+    }
+
+  // parse optional inner attributes
+  AST::AttrVec inner_attrs = parse_inner_attributes ();
+
+  if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+    {
+      // must be empty tuple
+      lexer.skip_token ();
+
+      // create tuple with empty tuple elems
+      return std::unique_ptr<AST::TupleExpr> (
+	new AST::TupleExpr (std::vector<std::unique_ptr<AST::Expr>> (),
+			    std::move (inner_attrs), std::move (outer_attrs),
+			    locus));
+    }
+
+  // parse first expression (required)
+  std::unique_ptr<AST::Expr> first_expr = parse_expr ();
+  if (first_expr == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse expression in grouped or tuple expression");
+      add_error (std::move (error));
+
+      // skip after somewhere?
+      return nullptr;
+    }
+
+  // detect whether grouped expression with right parentheses as next token
+  if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+    {
+      // must be grouped expr
+      lexer.skip_token ();
+
+      // create grouped expr
+      return std::unique_ptr<AST::GroupedExpr> (
+	new AST::GroupedExpr (std::move (first_expr), std::move (inner_attrs),
+			      std::move (outer_attrs), locus));
+    }
+  else if (lexer.peek_token ()->get_id () == COMMA)
+    {
+      // tuple expr
+      std::vector<std::unique_ptr<AST::Expr>> exprs;
+      exprs.push_back (std::move (first_expr));
+
+      // parse potential other tuple exprs
+      const_TokenPtr t = lexer.peek_token ();
+      while (t->get_id () == COMMA)
+	{
+	  lexer.skip_token ();
+
+	  // break out if right paren
+	  if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+	    break;
+
+	  // parse expr, which is now required
+	  std::unique_ptr<AST::Expr> expr = parse_expr ();
+	  if (expr == nullptr)
+	    {
+	      Error error (lexer.peek_token ()->get_locus (),
+			   "failed to parse expr in tuple expr");
+	      add_error (std::move (error));
+
+	      // skip somewhere?
+	      return nullptr;
+	    }
+	  exprs.push_back (std::move (expr));
+
+	  t = lexer.peek_token ();
+	}
+
+      // skip right paren
+      skip_token (RIGHT_PAREN);
+
+      return std::unique_ptr<AST::TupleExpr> (
+	new AST::TupleExpr (std::move (exprs), std::move (inner_attrs),
+			    std::move (outer_attrs), locus));
+    }
+  else
+    {
+      // error
+      const_TokenPtr t = lexer.peek_token ();
+      Error error (t->get_locus (),
+		   "unexpected token %qs in grouped or tuple expression "
+		   "(parenthesised expression) - expected %<)%> for grouped "
+		   "expr and %<,%> for tuple expr",
+		   t->get_token_description ());
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+}
+
+// Parses a type (will further disambiguate any type).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Type>
+Parser<ManagedTokenSource>::parse_type (bool save_errors)
+{
+  /* rules for all types:
+   * NeverType:               '!'
+   * SliceType:               '[' Type ']'
+   * InferredType:            '_'
+   * MacroInvocation:         SimplePath '!' DelimTokenTree
+   * ParenthesisedType:       '(' Type ')'
+   * ImplTraitType:           'impl' TypeParamBounds
+   *  TypeParamBounds (not type)  TypeParamBound ( '+' TypeParamBound )* '+'?
+   *  TypeParamBound          Lifetime | TraitBound
+   * ImplTraitTypeOneBound:   'impl' TraitBound
+   * TraitObjectType:         'dyn'? TypeParamBounds
+   * TraitObjectTypeOneBound: 'dyn'? TraitBound
+   *  TraitBound              '?'? ForLifetimes? TypePath | '(' '?'?
+   * ForLifetimes? TypePath ')' BareFunctionType:        ForLifetimes?
+   * FunctionQualifiers 'fn' etc. ForLifetimes (not type) 'for' '<'
+   * LifetimeParams '>' FunctionQualifiers      ( 'async' | 'const' )?
+   * 'unsafe'?
+   * ('extern' abi?)? QualifiedPathInType:     '<' Type ( 'as' TypePath )? '>'
+   * (
+   * '::' TypePathSegment )+ TypePath:                '::'? TypePathSegment (
+   * '::' TypePathSegment)* ArrayType:               '[' Type ';' Expr ']'
+   * ReferenceType:           '&' Lifetime? 'mut'? TypeNoBounds
+   * RawPointerType:          '*' ( 'mut' | 'const' ) TypeNoBounds
+   * TupleType:               '(' Type etc. - regular tuple stuff. Also
+   * regular tuple vs parenthesised precedence
+   *
+   * Disambiguate between macro and type path via type path being parsed, and
+   * then if '!' found, convert type path to simple path for macro. Usual
+   * disambiguation for tuple vs parenthesised. For ImplTraitType and
+   * TraitObjectType individual disambiguations, they seem more like "special
+   * cases", so probably just try to parse the more general ImplTraitType or
+   * TraitObjectType and return OneBound versions if they satisfy those
+   * criteria. */
+
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case EXCLAM:
+      // never type - can't be macro as no path beforehand
+      lexer.skip_token ();
+      return std::unique_ptr<AST::NeverType> (
+	new AST::NeverType (t->get_locus ()));
+    case LEFT_SQUARE:
+      // slice type or array type - requires further disambiguation
+      return parse_slice_or_array_type ();
+      case LEFT_ANGLE: {
+	// qualified path in type
+	AST::QualifiedPathInType path = parse_qualified_path_in_type ();
+	if (path.is_error ())
+	  {
+	    if (save_errors)
+	      {
+		Error error (t->get_locus (),
+			     "failed to parse qualified path in type");
+		add_error (std::move (error));
+	      }
+
+	    return nullptr;
+	  }
+	return std::unique_ptr<AST::QualifiedPathInType> (
+	  new AST::QualifiedPathInType (std::move (path)));
+      }
+    case UNDERSCORE:
+      // inferred type
+      lexer.skip_token ();
+      return std::unique_ptr<AST::InferredType> (
+	new AST::InferredType (t->get_locus ()));
+    case ASTERISK:
+      // raw pointer type
+      return parse_raw_pointer_type ();
+    case AMP: // does this also include AMP_AMP?
+      // reference type
+      return parse_reference_type ();
+      case LIFETIME: {
+	/* probably a lifetime bound, so probably type param bounds in
+	 * TraitObjectType */
+	std::vector<std::unique_ptr<AST::TypeParamBound>> bounds
+	  = parse_type_param_bounds ();
+
+	return std::unique_ptr<AST::TraitObjectType> (
+	  new AST::TraitObjectType (std::move (bounds), t->get_locus (),
+				    false));
+      }
+    case IDENTIFIER:
+    case SUPER:
+    case SELF:
+    case SELF_ALIAS:
+    case CRATE:
+    case DOLLAR_SIGN:
+      case SCOPE_RESOLUTION: {
+	// macro invocation or type path - requires further disambiguation.
+	/* for parsing path component of each rule, perhaps parse it as a
+	 * typepath and attempt conversion to simplepath if a trailing '!' is
+	 * found */
+	/* Type path also includes TraitObjectTypeOneBound BUT if it starts
+	 * with it, it is exactly the same as a TypePath syntactically, so
+	 * this is a syntactical ambiguity. As such, the parser will parse it
+	 * as a TypePath. This, however, does not prevent TraitObjectType from
+	 * starting with a typepath. */
+
+	// parse path as type path
+	AST::TypePath path = parse_type_path ();
+	if (path.is_error ())
+	  {
+	    if (save_errors)
+	      {
+		Error error (t->get_locus (),
+			     "failed to parse path as first component of type");
+		add_error (std::move (error));
+	      }
+
+	    return nullptr;
+	  }
+	Location locus = path.get_locus ();
+
+	// branch on next token
+	t = lexer.peek_token ();
+	switch (t->get_id ())
+	  {
+	    case EXCLAM: {
+	      // macro invocation
+	      // convert to simple path
+	      AST::SimplePath macro_path = path.as_simple_path ();
+	      if (macro_path.is_empty ())
+		{
+		  if (save_errors)
+		    {
+		      Error error (t->get_locus (),
+				   "failed to parse simple path in macro "
+				   "invocation (for type)");
+		      add_error (std::move (error));
+		    }
+
+		  return nullptr;
+		}
+
+	      lexer.skip_token ();
+
+	      AST::DelimTokenTree tok_tree = parse_delim_token_tree ();
+
+	      return std::unique_ptr<AST::MacroInvocation> (
+		new AST::MacroInvocation (
+		  AST::MacroInvocData (std::move (macro_path),
+				       std::move (tok_tree)),
+		  {}, locus));
+	    }
+	    case PLUS: {
+	      // type param bounds
+	      std::vector<std::unique_ptr<AST::TypeParamBound>> bounds;
+
+	      // convert type path to trait bound
+	      std::unique_ptr<AST::TraitBound> path_bound (
+		new AST::TraitBound (std::move (path), locus, false, false));
+	      bounds.push_back (std::move (path_bound));
+
+	      /* parse rest of bounds - FIXME: better way to find when to stop
+	       * parsing */
+	      while (t->get_id () == PLUS)
+		{
+		  lexer.skip_token ();
+
+		  // parse bound if it exists - if not, assume end of sequence
+		  std::unique_ptr<AST::TypeParamBound> bound
+		    = parse_type_param_bound ();
+		  if (bound == nullptr)
+		    {
+		      break;
+		    }
+		  bounds.push_back (std::move (bound));
+
+		  t = lexer.peek_token ();
+		}
+
+	      return std::unique_ptr<AST::TraitObjectType> (
+		new AST::TraitObjectType (std::move (bounds), locus, false));
+	    }
+	  default:
+	    // assume that this is a type path and not an error
+	    return std::unique_ptr<AST::TypePath> (
+	      new AST::TypePath (std::move (path)));
+	  }
+      }
+    case LEFT_PAREN:
+      /* tuple type or parenthesised type - requires further disambiguation
+       * (the usual). ok apparently can be a parenthesised TraitBound too, so
+       * could be TraitObjectTypeOneBound or TraitObjectType */
+      return parse_paren_prefixed_type ();
+    case FOR:
+      // TraitObjectTypeOneBound or BareFunctionType
+      return parse_for_prefixed_type ();
+    case ASYNC:
+    case CONST:
+    case UNSAFE:
+    case EXTERN_TOK:
+    case FN_TOK:
+      // bare function type (with no for lifetimes)
+      return parse_bare_function_type (std::vector<AST::LifetimeParam> ());
+    case IMPL:
+      lexer.skip_token ();
+      if (lexer.peek_token ()->get_id () == LIFETIME)
+	{
+	  /* cannot be one bound because lifetime prevents it from being
+	   * traitbound */
+	  std::vector<std::unique_ptr<AST::TypeParamBound>> bounds
+	    = parse_type_param_bounds ();
+
+	  return std::unique_ptr<AST::ImplTraitType> (
+	    new AST::ImplTraitType (std::move (bounds), t->get_locus ()));
+	}
+      else
+	{
+	  // should be trait bound, so parse trait bound
+	  std::unique_ptr<AST::TraitBound> initial_bound = parse_trait_bound ();
+	  if (initial_bound == nullptr)
+	    {
+	      if (save_errors)
+		{
+		  Error error (lexer.peek_token ()->get_locus (),
+			       "failed to parse ImplTraitType initial bound");
+		  add_error (std::move (error));
+		}
+
+	      return nullptr;
+	    }
+
+	  Location locus = t->get_locus ();
+
+	  // short cut if next token isn't '+'
+	  t = lexer.peek_token ();
+	  if (t->get_id () != PLUS)
+	    {
+	      // convert trait bound to value object
+	      AST::TraitBound value_bound (*initial_bound);
+
+	      // DEBUG: removed as unique ptr, so should auto-delete
+	      // delete initial_bound;
+
+	      return std::unique_ptr<AST::ImplTraitTypeOneBound> (
+		new AST::ImplTraitTypeOneBound (std::move (value_bound),
+						locus));
+	    }
+
+	  // parse additional type param bounds
+	  std::vector<std::unique_ptr<AST::TypeParamBound>> bounds;
+	  bounds.push_back (std::move (initial_bound));
+	  while (t->get_id () == PLUS)
+	    {
+	      lexer.skip_token ();
+
+	      // parse bound if it exists
+	      std::unique_ptr<AST::TypeParamBound> bound
+		= parse_type_param_bound ();
+	      if (bound == nullptr)
+		{
+		  // not an error as trailing plus may exist
+		  break;
+		}
+	      bounds.push_back (std::move (bound));
+
+	      t = lexer.peek_token ();
+	    }
+
+	  return std::unique_ptr<AST::ImplTraitType> (
+	    new AST::ImplTraitType (std::move (bounds), locus));
+	}
+    case DYN:
+      case QUESTION_MARK: {
+	// either TraitObjectType or TraitObjectTypeOneBound
+	bool has_dyn = false;
+	if (t->get_id () == DYN)
+	  {
+	    lexer.skip_token ();
+	    has_dyn = true;
+	  }
+
+	if (lexer.peek_token ()->get_id () == LIFETIME)
+	  {
+	    /* cannot be one bound because lifetime prevents it from being
+	     * traitbound */
+	    std::vector<std::unique_ptr<AST::TypeParamBound>> bounds
+	      = parse_type_param_bounds ();
+
+	    return std::unique_ptr<AST::TraitObjectType> (
+	      new AST::TraitObjectType (std::move (bounds), t->get_locus (),
+					has_dyn));
+	  }
+	else
+	  {
+	    // should be trait bound, so parse trait bound
+	    std::unique_ptr<AST::TraitBound> initial_bound
+	      = parse_trait_bound ();
+	    if (initial_bound == nullptr)
+	      {
+		if (save_errors)
+		  {
+		    Error error (
+		      lexer.peek_token ()->get_locus (),
+		      "failed to parse TraitObjectType initial bound");
+		    add_error (std::move (error));
+		  }
+
+		return nullptr;
+	      }
+
+	    // short cut if next token isn't '+'
+	    t = lexer.peek_token ();
+	    if (t->get_id () != PLUS)
+	      {
+		// convert trait bound to value object
+		AST::TraitBound value_bound (*initial_bound);
+
+		// DEBUG: removed as unique ptr, so should auto delete
+		// delete initial_bound;
+
+		return std::unique_ptr<AST::TraitObjectTypeOneBound> (
+		  new AST::TraitObjectTypeOneBound (std::move (value_bound),
+						    t->get_locus (), has_dyn));
+	      }
+
+	    // parse additional type param bounds
+	    std::vector<std::unique_ptr<AST::TypeParamBound>> bounds;
+	    bounds.push_back (std::move (initial_bound));
+	    while (t->get_id () == PLUS)
+	      {
+		lexer.skip_token ();
+
+		// parse bound if it exists
+		std::unique_ptr<AST::TypeParamBound> bound
+		  = parse_type_param_bound ();
+		if (bound == nullptr)
+		  {
+		    // not an error as trailing plus may exist
+		    break;
+		  }
+		bounds.push_back (std::move (bound));
+
+		t = lexer.peek_token ();
+	      }
+
+	    return std::unique_ptr<AST::TraitObjectType> (
+	      new AST::TraitObjectType (std::move (bounds), t->get_locus (),
+					has_dyn));
+	  }
+      }
+    default:
+      if (save_errors)
+	add_error (Error (t->get_locus (), "unrecognised token %qs in type",
+			  t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+/* Parses a type that has '(' as its first character. Returns a tuple type,
+ * parenthesised type, TraitObjectTypeOneBound, or TraitObjectType depending
+ * on following characters. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Type>
+Parser<ManagedTokenSource>::parse_paren_prefixed_type ()
+{
+  /* NOTE: Syntactical ambiguity of a parenthesised trait bound is considered
+   * a trait bound, not a parenthesised type, so that it can still be used in
+   * type param bounds. */
+
+  /* NOTE: this implementation is really shit but I couldn't think of a better
+   * one. It requires essentially breaking polymorphism and downcasting via
+   * virtual method abuse, as it was copied from the rustc implementation (in
+   * which types are reified due to tagged union), after a more OOP attempt by
+   * me failed. */
+  Location left_delim_locus = lexer.peek_token ()->get_locus ();
+
+  // skip left delim
+  lexer.skip_token ();
+  /* while next token isn't close delim, parse comma-separated types, saving
+   * whether trailing comma happens */
+  const_TokenPtr t = lexer.peek_token ();
+  bool trailing_comma = true;
+  std::vector<std::unique_ptr<AST::Type>> types;
+
+  while (t->get_id () != RIGHT_PAREN)
+    {
+      std::unique_ptr<AST::Type> type = parse_type ();
+      if (type == nullptr)
+	{
+	  Error error (t->get_locus (),
+		       "failed to parse type inside parentheses (probably "
+		       "tuple or parenthesised)");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      types.push_back (std::move (type));
+
+      t = lexer.peek_token ();
+      if (t->get_id () != COMMA)
+	{
+	  trailing_comma = false;
+	  break;
+	}
+      lexer.skip_token ();
+
+      t = lexer.peek_token ();
+    }
+
+  if (!skip_token (RIGHT_PAREN))
+    {
+      return nullptr;
+    }
+
+  // if only one type and no trailing comma, then not a tuple type
+  if (types.size () == 1 && !trailing_comma)
+    {
+      // must be a TraitObjectType (with more than one bound)
+      if (lexer.peek_token ()->get_id () == PLUS)
+	{
+	  // create type param bounds vector
+	  std::vector<std::unique_ptr<AST::TypeParamBound>> bounds;
+
+	  // HACK: convert type to traitbound and add to bounds
+	  std::unique_ptr<AST::Type> released_ptr = std::move (types[0]);
+	  std::unique_ptr<AST::TraitBound> converted_bound (
+	    released_ptr->to_trait_bound (true));
+	  if (converted_bound == nullptr)
+	    {
+	      Error error (
+		lexer.peek_token ()->get_locus (),
+		"failed to hackily converted parsed type to trait bound");
+	      add_error (std::move (error));
+
+	      return nullptr;
+	    }
+	  bounds.push_back (std::move (converted_bound));
+
+	  t = lexer.peek_token ();
+	  while (t->get_id () == PLUS)
+	    {
+	      lexer.skip_token ();
+
+	      // attempt to parse typeparambound
+	      std::unique_ptr<AST::TypeParamBound> bound
+		= parse_type_param_bound ();
+	      if (bound == nullptr)
+		{
+		  // not an error if null
+		  break;
+		}
+	      bounds.push_back (std::move (bound));
+
+	      t = lexer.peek_token ();
+	    }
+
+	  return std::unique_ptr<AST::TraitObjectType> (
+	    new AST::TraitObjectType (std::move (bounds), left_delim_locus,
+				      false));
+	}
+      else
+	{
+	  // release vector pointer
+	  std::unique_ptr<AST::Type> released_ptr = std::move (types[0]);
+	  /* HACK: attempt to convert to trait bound. if fails, parenthesised
+	   * type */
+	  std::unique_ptr<AST::TraitBound> converted_bound (
+	    released_ptr->to_trait_bound (true));
+	  if (converted_bound == nullptr)
+	    {
+	      // parenthesised type
+	      return std::unique_ptr<AST::ParenthesisedType> (
+		new AST::ParenthesisedType (std::move (released_ptr),
+					    left_delim_locus));
+	    }
+	  else
+	    {
+	      // trait object type (one bound)
+
+	      // get value semantics trait bound
+	      AST::TraitBound value_bound (*converted_bound);
+
+	      return std::unique_ptr<AST::TraitObjectTypeOneBound> (
+		new AST::TraitObjectTypeOneBound (value_bound,
+						  left_delim_locus));
+	    }
+	}
+    }
+  else
+    {
+      return std::unique_ptr<AST::TupleType> (
+	new AST::TupleType (std::move (types), left_delim_locus));
+    }
+  /* TODO: ensure that this ensures that dynamic dispatch for traits is not
+   * lost somehow */
+}
+
+/* Parses a type that has 'for' as its first character. This means it has a
+ * "for lifetimes", so returns either a BareFunctionType, TraitObjectType, or
+ * TraitObjectTypeOneBound depending on following characters. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Type>
+Parser<ManagedTokenSource>::parse_for_prefixed_type ()
+{
+  Location for_locus = lexer.peek_token ()->get_locus ();
+  // parse for lifetimes in type
+  std::vector<AST::LifetimeParam> for_lifetimes = parse_for_lifetimes ();
+
+  // branch on next token - either function or a trait type
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case ASYNC:
+    case CONST:
+    case UNSAFE:
+    case EXTERN_TOK:
+    case FN_TOK:
+      return parse_bare_function_type (std::move (for_lifetimes));
+    case SCOPE_RESOLUTION:
+    case IDENTIFIER:
+    case SUPER:
+    case SELF:
+    case SELF_ALIAS:
+    case CRATE:
+      case DOLLAR_SIGN: {
+	// path, so trait type
+
+	// parse type path to finish parsing trait bound
+	AST::TypePath path = parse_type_path ();
+
+	t = lexer.peek_token ();
+	if (t->get_id () != PLUS)
+	  {
+	    // must be one-bound trait type
+	    // create trait bound value object
+	    AST::TraitBound bound (std::move (path), for_locus, false, false,
+				   std::move (for_lifetimes));
+
+	    return std::unique_ptr<AST::TraitObjectTypeOneBound> (
+	      new AST::TraitObjectTypeOneBound (std::move (bound), for_locus));
+	  }
+
+	/* more than one bound trait type (or at least parsed as it - could be
+	 * trailing '+') create trait bound pointer and bounds */
+	std::unique_ptr<AST::TraitBound> initial_bound (
+	  new AST::TraitBound (std::move (path), for_locus, false, false,
+			       std::move (for_lifetimes)));
+	std::vector<std::unique_ptr<AST::TypeParamBound>> bounds;
+	bounds.push_back (std::move (initial_bound));
+
+	while (t->get_id () == PLUS)
+	  {
+	    lexer.skip_token ();
+
+	    // parse type param bound if it exists
+	    std::unique_ptr<AST::TypeParamBound> bound
+	      = parse_type_param_bound ();
+	    if (bound == nullptr)
+	      {
+		// not an error - e.g. trailing plus
+		return nullptr;
+	      }
+	    bounds.push_back (std::move (bound));
+
+	    t = lexer.peek_token ();
+	  }
+
+	return std::unique_ptr<AST::TraitObjectType> (
+	  new AST::TraitObjectType (std::move (bounds), for_locus, false));
+      }
+    default:
+      // error
+      add_error (Error (t->get_locus (),
+			"unrecognised token %qs in bare function type or trait "
+			"object type or trait object type one bound",
+			t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+// Parses a maybe named param used in bare function types.
+template <typename ManagedTokenSource>
+AST::MaybeNamedParam
+Parser<ManagedTokenSource>::parse_maybe_named_param (AST::AttrVec outer_attrs)
+{
+  /* Basically guess that param is named if first token is identifier or
+   * underscore and second token is semicolon. This should probably have no
+   * exceptions. rustc uses backtracking to parse these, but at the time of
+   * writing gccrs has no backtracking capabilities. */
+  const_TokenPtr current = lexer.peek_token ();
+  const_TokenPtr next = lexer.peek_token (1);
+
+  Identifier name;
+  AST::MaybeNamedParam::ParamKind kind = AST::MaybeNamedParam::UNNAMED;
+
+  if (current->get_id () == IDENTIFIER && next->get_id () == COLON)
+    {
+      // named param
+      name = current->get_str ();
+      kind = AST::MaybeNamedParam::IDENTIFIER;
+      lexer.skip_token (1);
+    }
+  else if (current->get_id () == UNDERSCORE && next->get_id () == COLON)
+    {
+      // wildcard param
+      name = "_";
+      kind = AST::MaybeNamedParam::WILDCARD;
+      lexer.skip_token (1);
+    }
+
+  // parse type (required)
+  std::unique_ptr<AST::Type> type = parse_type ();
+  if (type == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse type in maybe named param");
+      add_error (std::move (error));
+
+      return AST::MaybeNamedParam::create_error ();
+    }
+
+  return AST::MaybeNamedParam (std::move (name), kind, std::move (type),
+			       std::move (outer_attrs), current->get_locus ());
+}
+
+/* Parses a bare function type (with the given for lifetimes for convenience -
+ * does not parse them itself). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::BareFunctionType>
+Parser<ManagedTokenSource>::parse_bare_function_type (
+  std::vector<AST::LifetimeParam> for_lifetimes)
+{
+  // TODO: pass in for lifetime location as param
+  Location best_try_locus = lexer.peek_token ()->get_locus ();
+
+  AST::FunctionQualifiers qualifiers = parse_function_qualifiers ();
+
+  if (!skip_token (FN_TOK))
+    return nullptr;
+
+  if (!skip_token (LEFT_PAREN))
+    return nullptr;
+
+  // parse function params, if they exist
+  std::vector<AST::MaybeNamedParam> params;
+  bool is_variadic = false;
+  AST::AttrVec variadic_attrs;
+
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () != RIGHT_PAREN)
+    {
+      AST::AttrVec temp_attrs = parse_outer_attributes ();
+
+      if (lexer.peek_token ()->get_id () == ELLIPSIS)
+	{
+	  lexer.skip_token ();
+	  is_variadic = true;
+	  variadic_attrs = std::move (temp_attrs);
+
+	  t = lexer.peek_token ();
+
+	  if (t->get_id () != RIGHT_PAREN)
+	    {
+	      Error error (t->get_locus (),
+			   "expected right parentheses after variadic in maybe "
+			   "named function "
+			   "parameters, found %qs",
+			   t->get_token_description ());
+	      add_error (std::move (error));
+
+	      return nullptr;
+	    }
+
+	  break;
+	}
+
+      AST::MaybeNamedParam param
+	= parse_maybe_named_param (std::move (temp_attrs));
+      if (param.is_error ())
+	{
+	  Error error (
+	    lexer.peek_token ()->get_locus (),
+	    "failed to parse maybe named param in bare function type");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      params.push_back (std::move (param));
+
+      if (lexer.peek_token ()->get_id () != COMMA)
+	break;
+
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  if (!skip_token (RIGHT_PAREN))
+    return nullptr;
+
+  // bare function return type, if exists
+  std::unique_ptr<AST::TypeNoBounds> return_type = nullptr;
+  if (lexer.peek_token ()->get_id () == RETURN_TYPE)
+    {
+      lexer.skip_token ();
+
+      // parse required TypeNoBounds
+      return_type = parse_type_no_bounds ();
+      if (return_type == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse return type (type no bounds) in bare "
+		       "function type");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+    }
+
+  return std::unique_ptr<AST::BareFunctionType> (
+    new AST::BareFunctionType (std::move (for_lifetimes),
+			       std::move (qualifiers), std::move (params),
+			       is_variadic, std::move (variadic_attrs),
+			       std::move (return_type), best_try_locus));
+}
+
+// Parses a reference type (mutable or immutable, with given lifetime).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ReferenceType>
+Parser<ManagedTokenSource>::parse_reference_type ()
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+  skip_token (AMP);
+
+  // parse optional lifetime
+  AST::Lifetime lifetime = AST::Lifetime::error ();
+  if (lexer.peek_token ()->get_id () == LIFETIME)
+    {
+      lifetime = parse_lifetime ();
+      if (lifetime.is_error ())
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse lifetime in reference type");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+    }
+
+  bool is_mut = false;
+  if (lexer.peek_token ()->get_id () == MUT)
+    {
+      lexer.skip_token ();
+      is_mut = true;
+    }
+
+  // parse type no bounds, which is required
+  std::unique_ptr<AST::TypeNoBounds> type = parse_type_no_bounds ();
+  if (type == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse referenced type in reference type");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::ReferenceType> (
+    new AST::ReferenceType (is_mut, std::move (type), locus,
+			    std::move (lifetime)));
+}
+
+// Parses a raw (unsafe) pointer type.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::RawPointerType>
+Parser<ManagedTokenSource>::parse_raw_pointer_type ()
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+  skip_token (ASTERISK);
+
+  AST::RawPointerType::PointerType kind = AST::RawPointerType::CONST;
+
+  // branch on next token for pointer kind info
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case MUT:
+      kind = AST::RawPointerType::MUT;
+      lexer.skip_token ();
+      break;
+    case CONST:
+      kind = AST::RawPointerType::CONST;
+      lexer.skip_token ();
+      break;
+    default:
+      add_error (Error (t->get_locus (),
+			"unrecognised token %qs in raw pointer type",
+			t->get_token_description ()));
+
+      return nullptr;
+    }
+
+  // parse type no bounds (required)
+  std::unique_ptr<AST::TypeNoBounds> type = parse_type_no_bounds ();
+  if (type == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse pointed type of raw pointer type");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::RawPointerType> (
+    new AST::RawPointerType (kind, std::move (type), locus));
+}
+
+/* Parses a slice or array type, depending on following arguments (as
+ * lookahead is not possible). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::TypeNoBounds>
+Parser<ManagedTokenSource>::parse_slice_or_array_type ()
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+  skip_token (LEFT_SQUARE);
+
+  // parse inner type (required)
+  std::unique_ptr<AST::Type> inner_type = parse_type ();
+  if (inner_type == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse inner type in slice or array type");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  // branch on next token
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case RIGHT_SQUARE:
+      // slice type
+      lexer.skip_token ();
+
+      return std::unique_ptr<AST::SliceType> (
+	new AST::SliceType (std::move (inner_type), locus));
+      case SEMICOLON: {
+	// array type
+	lexer.skip_token ();
+
+	// parse required array size expression
+	std::unique_ptr<AST::Expr> size = parse_expr ();
+	if (size == nullptr)
+	  {
+	    Error error (lexer.peek_token ()->get_locus (),
+			 "failed to parse size expression in array type");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	if (!skip_token (RIGHT_SQUARE))
+	  {
+	    return nullptr;
+	  }
+
+	return std::unique_ptr<AST::ArrayType> (
+	  new AST::ArrayType (std::move (inner_type), std::move (size), locus));
+      }
+    default:
+      // error
+      add_error (
+	Error (t->get_locus (),
+	       "unrecognised token %qs in slice or array type after inner type",
+	       t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+// Parses a type, taking into account type boundary disambiguation.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::TypeNoBounds>
+Parser<ManagedTokenSource>::parse_type_no_bounds ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case EXCLAM:
+      // never type - can't be macro as no path beforehand
+      lexer.skip_token ();
+      return std::unique_ptr<AST::NeverType> (
+	new AST::NeverType (t->get_locus ()));
+    case LEFT_SQUARE:
+      // slice type or array type - requires further disambiguation
+      return parse_slice_or_array_type ();
+      case LEFT_ANGLE: {
+	// qualified path in type
+	AST::QualifiedPathInType path = parse_qualified_path_in_type ();
+	if (path.is_error ())
+	  {
+	    Error error (t->get_locus (),
+			 "failed to parse qualified path in type");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+	return std::unique_ptr<AST::QualifiedPathInType> (
+	  new AST::QualifiedPathInType (std::move (path)));
+      }
+    case UNDERSCORE:
+      // inferred type
+      lexer.skip_token ();
+      return std::unique_ptr<AST::InferredType> (
+	new AST::InferredType (t->get_locus ()));
+    case ASTERISK:
+      // raw pointer type
+      return parse_raw_pointer_type ();
+    case AMP: // does this also include AMP_AMP?
+      // reference type
+      return parse_reference_type ();
+    case LIFETIME:
+      /* probably a lifetime bound, so probably type param bounds in
+       * TraitObjectType. this is not allowed, but detection here for error
+       * message */
+      add_error (Error (t->get_locus (),
+			"lifetime bounds (i.e. in type param bounds, in "
+			"TraitObjectType) are not allowed as TypeNoBounds"));
+
+      return nullptr;
+    case IDENTIFIER:
+    case SUPER:
+    case SELF:
+    case SELF_ALIAS:
+    case CRATE:
+    case DOLLAR_SIGN:
+      case SCOPE_RESOLUTION: {
+	// macro invocation or type path - requires further disambiguation.
+	/* for parsing path component of each rule, perhaps parse it as a
+	 * typepath and attempt conversion to simplepath if a trailing '!' is
+	 * found */
+	/* Type path also includes TraitObjectTypeOneBound BUT if it starts
+	 * with it, it is exactly the same as a TypePath syntactically, so
+	 * this is a syntactical ambiguity. As such, the parser will parse it
+	 * as a TypePath. This, however, does not prevent TraitObjectType from
+	 * starting with a typepath. */
+
+	// parse path as type path
+	AST::TypePath path = parse_type_path ();
+	if (path.is_error ())
+	  {
+	    Error error (
+	      t->get_locus (),
+	      "failed to parse path as first component of type no bounds");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+	Location locus = path.get_locus ();
+
+	// branch on next token
+	t = lexer.peek_token ();
+	switch (t->get_id ())
+	  {
+	    case EXCLAM: {
+	      // macro invocation
+	      // convert to simple path
+	      AST::SimplePath macro_path = path.as_simple_path ();
+	      if (macro_path.is_empty ())
+		{
+		  Error error (t->get_locus (),
+			       "failed to parse simple path in macro "
+			       "invocation (for type)");
+		  add_error (std::move (error));
+
+		  return nullptr;
+		}
+
+	      lexer.skip_token ();
+
+	      AST::DelimTokenTree tok_tree = parse_delim_token_tree ();
+
+	      return std::unique_ptr<AST::MacroInvocation> (
+		new AST::MacroInvocation (
+		  AST::MacroInvocData (std::move (macro_path),
+				       std::move (tok_tree)),
+		  {}, locus));
+	    }
+	  default:
+	    // assume that this is a type path and not an error
+	    return std::unique_ptr<AST::TypePath> (
+	      new AST::TypePath (std::move (path)));
+	  }
+      }
+    case LEFT_PAREN:
+      /* tuple type or parenthesised type - requires further disambiguation
+       * (the usual). ok apparently can be a parenthesised TraitBound too, so
+       * could be TraitObjectTypeOneBound */
+      return parse_paren_prefixed_type_no_bounds ();
+    case FOR:
+    case ASYNC:
+    case CONST:
+    case UNSAFE:
+    case EXTERN_TOK:
+    case FN_TOK:
+      // bare function type (with no for lifetimes)
+      return parse_bare_function_type (std::vector<AST::LifetimeParam> ());
+    case IMPL:
+      lexer.skip_token ();
+      if (lexer.peek_token ()->get_id () == LIFETIME)
+	{
+	  /* cannot be one bound because lifetime prevents it from being
+	   * traitbound not allowed as type no bounds, only here for error
+	   * message */
+	  Error error (
+	    lexer.peek_token ()->get_locus (),
+	    "lifetime (probably lifetime bound, in type param "
+	    "bounds, in ImplTraitType) is not allowed in TypeNoBounds");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      else
+	{
+	  // should be trait bound, so parse trait bound
+	  std::unique_ptr<AST::TraitBound> initial_bound = parse_trait_bound ();
+	  if (initial_bound == nullptr)
+	    {
+	      Error error (lexer.peek_token ()->get_locus (),
+			   "failed to parse ImplTraitTypeOneBound bound");
+	      add_error (std::move (error));
+
+	      return nullptr;
+	    }
+
+	  Location locus = t->get_locus ();
+
+	  // ensure not a trait with multiple bounds
+	  t = lexer.peek_token ();
+	  if (t->get_id () == PLUS)
+	    {
+	      Error error (t->get_locus (),
+			   "plus after trait bound means an ImplTraitType, "
+			   "which is not allowed as a TypeNoBounds");
+	      add_error (std::move (error));
+
+	      return nullptr;
+	    }
+
+	  // convert trait bound to value object
+	  AST::TraitBound value_bound (*initial_bound);
+
+	  return std::unique_ptr<AST::ImplTraitTypeOneBound> (
+	    new AST::ImplTraitTypeOneBound (std::move (value_bound), locus));
+	}
+    case DYN:
+      case QUESTION_MARK: {
+	// either TraitObjectTypeOneBound
+	bool has_dyn = false;
+	if (t->get_id () == DYN)
+	  {
+	    lexer.skip_token ();
+	    has_dyn = true;
+	  }
+
+	if (lexer.peek_token ()->get_id () == LIFETIME)
+	  {
+	    /* means that cannot be TraitObjectTypeOneBound - so here for
+	     * error message */
+	    Error error (lexer.peek_token ()->get_locus (),
+			 "lifetime as bound in TraitObjectTypeOneBound "
+			 "is not allowed, so cannot be TypeNoBounds");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	// should be trait bound, so parse trait bound
+	std::unique_ptr<AST::TraitBound> initial_bound = parse_trait_bound ();
+	if (initial_bound == nullptr)
+	  {
+	    Error error (
+	      lexer.peek_token ()->get_locus (),
+	      "failed to parse TraitObjectTypeOneBound initial bound");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	Location locus = t->get_locus ();
+
+	// detect error with plus as next token
+	t = lexer.peek_token ();
+	if (t->get_id () == PLUS)
+	  {
+	    Error error (t->get_locus (),
+			 "plus after trait bound means a TraitObjectType, "
+			 "which is not allowed as a TypeNoBounds");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	// convert trait bound to value object
+	AST::TraitBound value_bound (*initial_bound);
+
+	return std::unique_ptr<AST::TraitObjectTypeOneBound> (
+	  new AST::TraitObjectTypeOneBound (std::move (value_bound), locus,
+					    has_dyn));
+      }
+    default:
+      add_error (Error (t->get_locus (),
+			"unrecognised token %qs in type no bounds",
+			t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+// Parses a type no bounds beginning with '('.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::TypeNoBounds>
+Parser<ManagedTokenSource>::parse_paren_prefixed_type_no_bounds ()
+{
+  /* NOTE: this could probably be parsed without the HACK solution of
+   * parse_paren_prefixed_type, but I was lazy. So FIXME for future.*/
+
+  /* NOTE: again, syntactical ambiguity of a parenthesised trait bound is
+   * considered a trait bound, not a parenthesised type, so that it can still
+   * be used in type param bounds. */
+
+  Location left_paren_locus = lexer.peek_token ()->get_locus ();
+
+  // skip left delim
+  lexer.skip_token ();
+  /* while next token isn't close delim, parse comma-separated types, saving
+   * whether trailing comma happens */
+  const_TokenPtr t = lexer.peek_token ();
+  bool trailing_comma = true;
+  std::vector<std::unique_ptr<AST::Type>> types;
+
+  while (t->get_id () != RIGHT_PAREN)
+    {
+      std::unique_ptr<AST::Type> type = parse_type ();
+      if (type == nullptr)
+	{
+	  Error error (t->get_locus (),
+		       "failed to parse type inside parentheses (probably "
+		       "tuple or parenthesised)");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      types.push_back (std::move (type));
+
+      t = lexer.peek_token ();
+      if (t->get_id () != COMMA)
+	{
+	  trailing_comma = false;
+	  break;
+	}
+      lexer.skip_token ();
+
+      t = lexer.peek_token ();
+    }
+
+  if (!skip_token (RIGHT_PAREN))
+    {
+      return nullptr;
+    }
+
+  // if only one type and no trailing comma, then not a tuple type
+  if (types.size () == 1 && !trailing_comma)
+    {
+      // must be a TraitObjectType (with more than one bound)
+      if (lexer.peek_token ()->get_id () == PLUS)
+	{
+	  // error - this is not allowed for type no bounds
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "plus (implying TraitObjectType as type param "
+		       "bounds) is not allowed in type no bounds");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      else
+	{
+	  // release vector pointer
+	  std::unique_ptr<AST::Type> released_ptr = std::move (types[0]);
+	  /* HACK: attempt to convert to trait bound. if fails, parenthesised
+	   * type */
+	  std::unique_ptr<AST::TraitBound> converted_bound (
+	    released_ptr->to_trait_bound (true));
+	  if (converted_bound == nullptr)
+	    {
+	      // parenthesised type
+	      return std::unique_ptr<AST::ParenthesisedType> (
+		new AST::ParenthesisedType (std::move (released_ptr),
+					    left_paren_locus));
+	    }
+	  else
+	    {
+	      // trait object type (one bound)
+
+	      // get value semantics trait bound
+	      AST::TraitBound value_bound (*converted_bound);
+
+	      return std::unique_ptr<AST::TraitObjectTypeOneBound> (
+		new AST::TraitObjectTypeOneBound (value_bound,
+						  left_paren_locus));
+	    }
+	}
+    }
+  else
+    {
+      return std::unique_ptr<AST::TupleType> (
+	new AST::TupleType (std::move (types), left_paren_locus));
+    }
+  /* TODO: ensure that this ensures that dynamic dispatch for traits is not
+   * lost somehow */
+}
+
+/* Parses a literal pattern or range pattern. Assumes that literals passed in
+ * are valid range pattern bounds. Do not pass in paths in expressions, for
+ * instance. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Pattern>
+Parser<ManagedTokenSource>::parse_literal_or_range_pattern ()
+{
+  const_TokenPtr range_lower = lexer.peek_token ();
+  AST::Literal::LitType type = AST::Literal::STRING;
+  bool has_minus = false;
+
+  // get lit type
+  switch (range_lower->get_id ())
+    {
+    case CHAR_LITERAL:
+      type = AST::Literal::CHAR;
+      lexer.skip_token ();
+      break;
+    case BYTE_CHAR_LITERAL:
+      type = AST::Literal::BYTE;
+      lexer.skip_token ();
+      break;
+    case INT_LITERAL:
+      type = AST::Literal::INT;
+      lexer.skip_token ();
+      break;
+    case FLOAT_LITERAL:
+      type = AST::Literal::FLOAT;
+      lexer.skip_token ();
+      break;
+    case MINUS:
+      // branch on next token
+      range_lower = lexer.peek_token (1);
+      switch (range_lower->get_id ())
+	{
+	case INT_LITERAL:
+	  type = AST::Literal::INT;
+	  has_minus = true;
+	  lexer.skip_token (1);
+	  break;
+	case FLOAT_LITERAL:
+	  type = AST::Literal::FLOAT;
+	  has_minus = true;
+	  lexer.skip_token (1);
+	  break;
+	default:
+	  add_error (Error (range_lower->get_locus (),
+			    "token type %qs cannot be parsed as range pattern "
+			    "bound or literal after minus symbol",
+			    range_lower->get_token_description ()));
+
+	  return nullptr;
+	}
+      break;
+    default:
+      add_error (
+	Error (range_lower->get_locus (),
+	       "token type %qs cannot be parsed as range pattern bound",
+	       range_lower->get_token_description ()));
+
+      return nullptr;
+    }
+
+  const_TokenPtr next = lexer.peek_token ();
+  if (next->get_id () == DOT_DOT_EQ || next->get_id () == ELLIPSIS)
+    {
+      // range pattern
+      lexer.skip_token ();
+      std::unique_ptr<AST::RangePatternBound> lower (
+	new AST::RangePatternBoundLiteral (
+	  AST::Literal (range_lower->get_str (), type,
+			PrimitiveCoreType::CORETYPE_UNKNOWN),
+	  range_lower->get_locus (), has_minus));
+
+      std::unique_ptr<AST::RangePatternBound> upper
+	= parse_range_pattern_bound ();
+      if (upper == nullptr)
+	{
+	  Error error (next->get_locus (),
+		       "failed to parse range pattern bound in range pattern");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+
+      return std::unique_ptr<AST::RangePattern> (
+	new AST::RangePattern (std::move (lower), std::move (upper),
+			       range_lower->get_locus ()));
+    }
+  else
+    {
+      // literal pattern
+      return std::unique_ptr<AST::LiteralPattern> (
+	new AST::LiteralPattern (range_lower->get_str (), type,
+				 range_lower->get_locus ()));
+    }
+}
+
+// Parses a range pattern bound (value only).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::RangePatternBound>
+Parser<ManagedTokenSource>::parse_range_pattern_bound ()
+{
+  const_TokenPtr range_lower = lexer.peek_token ();
+  Location range_lower_locus = range_lower->get_locus ();
+
+  // get lit type
+  switch (range_lower->get_id ())
+    {
+    case CHAR_LITERAL:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::RangePatternBoundLiteral> (
+	new AST::RangePatternBoundLiteral (
+	  AST::Literal (range_lower->get_str (), AST::Literal::CHAR,
+			range_lower->get_type_hint ()),
+	  range_lower_locus));
+    case BYTE_CHAR_LITERAL:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::RangePatternBoundLiteral> (
+	new AST::RangePatternBoundLiteral (
+	  AST::Literal (range_lower->get_str (), AST::Literal::BYTE,
+			range_lower->get_type_hint ()),
+	  range_lower_locus));
+    case INT_LITERAL:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::RangePatternBoundLiteral> (
+	new AST::RangePatternBoundLiteral (
+	  AST::Literal (range_lower->get_str (), AST::Literal::INT,
+			range_lower->get_type_hint ()),
+	  range_lower_locus));
+    case FLOAT_LITERAL:
+      lexer.skip_token ();
+      rust_debug ("warning: used deprecated float range pattern bound");
+      return std::unique_ptr<AST::RangePatternBoundLiteral> (
+	new AST::RangePatternBoundLiteral (
+	  AST::Literal (range_lower->get_str (), AST::Literal::FLOAT,
+			range_lower->get_type_hint ()),
+	  range_lower_locus));
+    case MINUS:
+      // branch on next token
+      range_lower = lexer.peek_token (1);
+      switch (range_lower->get_id ())
+	{
+	case INT_LITERAL:
+	  lexer.skip_token (1);
+	  return std::unique_ptr<AST::RangePatternBoundLiteral> (
+	    new AST::RangePatternBoundLiteral (
+	      AST::Literal (range_lower->get_str (), AST::Literal::INT,
+			    range_lower->get_type_hint ()),
+	      range_lower_locus, true));
+	case FLOAT_LITERAL:
+	  lexer.skip_token (1);
+	  rust_debug ("warning: used deprecated float range pattern bound");
+	  return std::unique_ptr<AST::RangePatternBoundLiteral> (
+	    new AST::RangePatternBoundLiteral (
+	      AST::Literal (range_lower->get_str (), AST::Literal::FLOAT,
+			    range_lower->get_type_hint ()),
+	      range_lower_locus, true));
+	default:
+	  add_error (Error (range_lower->get_locus (),
+			    "token type %qs cannot be parsed as range pattern "
+			    "bound after minus symbol",
+			    range_lower->get_token_description ()));
+
+	  return nullptr;
+	}
+    case IDENTIFIER:
+    case SUPER:
+    case SELF:
+    case SELF_ALIAS:
+    case CRATE:
+    case SCOPE_RESOLUTION:
+      case DOLLAR_SIGN: {
+	// path in expression
+	AST::PathInExpression path = parse_path_in_expression ();
+	if (path.is_error ())
+	  {
+	    Error error (
+	      range_lower->get_locus (),
+	      "failed to parse path in expression range pattern bound");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+	return std::unique_ptr<AST::RangePatternBoundPath> (
+	  new AST::RangePatternBoundPath (std::move (path)));
+      }
+      case LEFT_ANGLE: {
+	// qualified path in expression
+	AST::QualifiedPathInExpression path
+	  = parse_qualified_path_in_expression ();
+	if (path.is_error ())
+	  {
+	    Error error (range_lower->get_locus (),
+			 "failed to parse qualified path in expression range "
+			 "pattern bound");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+	return std::unique_ptr<AST::RangePatternBoundQualPath> (
+	  new AST::RangePatternBoundQualPath (std::move (path)));
+      }
+    default:
+      add_error (
+	Error (range_lower->get_locus (),
+	       "token type %qs cannot be parsed as range pattern bound",
+	       range_lower->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+// Parses a pattern (will further disambiguate any pattern).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Pattern>
+Parser<ManagedTokenSource>::parse_pattern ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case TRUE_LITERAL:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::LiteralPattern> (
+	new AST::LiteralPattern ("true", AST::Literal::BOOL, t->get_locus ()));
+    case FALSE_LITERAL:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::LiteralPattern> (
+	new AST::LiteralPattern ("false", AST::Literal::BOOL, t->get_locus ()));
+    case CHAR_LITERAL:
+    case BYTE_CHAR_LITERAL:
+    case INT_LITERAL:
+    case FLOAT_LITERAL:
+      return parse_literal_or_range_pattern ();
+    case STRING_LITERAL:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::LiteralPattern> (
+	new AST::LiteralPattern (t->get_str (), AST::Literal::STRING,
+				 t->get_locus ()));
+    case BYTE_STRING_LITERAL:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::LiteralPattern> (
+	new AST::LiteralPattern (t->get_str (), AST::Literal::BYTE_STRING,
+				 t->get_locus ()));
+    // raw string and raw byte string literals too if they are readded to
+    // lexer
+    case MINUS:
+      if (lexer.peek_token (1)->get_id () == INT_LITERAL)
+	{
+	  return parse_literal_or_range_pattern ();
+	}
+      else if (lexer.peek_token (1)->get_id () == FLOAT_LITERAL)
+	{
+	  return parse_literal_or_range_pattern ();
+	}
+      else
+	{
+	  Error error (t->get_locus (), "unexpected token %<-%> in pattern - "
+					"did you forget an integer literal");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+    case UNDERSCORE:
+      lexer.skip_token ();
+      return std::unique_ptr<AST::WildcardPattern> (
+	new AST::WildcardPattern (t->get_locus ()));
+    case REF:
+    case MUT:
+      return parse_identifier_pattern ();
+    case IDENTIFIER:
+      /* if identifier with no scope resolution afterwards, identifier
+       * pattern. if scope resolution afterwards, path pattern (or range
+       * pattern or struct pattern or tuple struct pattern) or macro
+       * invocation */
+      return parse_ident_leading_pattern ();
+    case AMP:
+    case LOGICAL_AND:
+      // reference pattern
+      return parse_reference_pattern ();
+    case LEFT_PAREN:
+      // tuple pattern or grouped pattern
+      return parse_grouped_or_tuple_pattern ();
+    case LEFT_SQUARE:
+      // slice pattern
+      return parse_slice_pattern ();
+      case LEFT_ANGLE: {
+	// qualified path in expression or qualified range pattern bound
+	AST::QualifiedPathInExpression path
+	  = parse_qualified_path_in_expression ();
+
+	if (lexer.peek_token ()->get_id () == DOT_DOT_EQ
+	    || lexer.peek_token ()->get_id () == ELLIPSIS)
+	  {
+	    // qualified range pattern bound, so parse rest of range pattern
+	    bool has_ellipsis_syntax
+	      = lexer.peek_token ()->get_id () == ELLIPSIS;
+	    lexer.skip_token ();
+
+	    std::unique_ptr<AST::RangePatternBoundQualPath> lower_bound (
+	      new AST::RangePatternBoundQualPath (std::move (path)));
+	    std::unique_ptr<AST::RangePatternBound> upper_bound
+	      = parse_range_pattern_bound ();
+
+	    return std::unique_ptr<AST::RangePattern> (
+	      new AST::RangePattern (std::move (lower_bound),
+				     std::move (upper_bound), t->get_locus (),
+				     has_ellipsis_syntax));
+	  }
+	else
+	  {
+	    // just qualified path in expression
+	    return std::unique_ptr<AST::QualifiedPathInExpression> (
+	      new AST::QualifiedPathInExpression (std::move (path)));
+	  }
+      }
+    case SUPER:
+    case SELF:
+    case SELF_ALIAS:
+    case CRATE:
+    case SCOPE_RESOLUTION:
+      case DOLLAR_SIGN: {
+	// path in expression or range pattern bound
+	AST::PathInExpression path = parse_path_in_expression ();
+
+	const_TokenPtr next = lexer.peek_token ();
+	switch (next->get_id ())
+	  {
+	  case DOT_DOT_EQ:
+	    case ELLIPSIS: {
+	      // qualified range pattern bound, so parse rest of range pattern
+	      bool has_ellipsis_syntax
+		= lexer.peek_token ()->get_id () == ELLIPSIS;
+	      lexer.skip_token ();
+
+	      std::unique_ptr<AST::RangePatternBoundPath> lower_bound (
+		new AST::RangePatternBoundPath (std::move (path)));
+	      std::unique_ptr<AST::RangePatternBound> upper_bound
+		= parse_range_pattern_bound ();
+
+	      return std::unique_ptr<AST::RangePattern> (new AST::RangePattern (
+		std::move (lower_bound), std::move (upper_bound),
+		Linemap::unknown_location (), has_ellipsis_syntax));
+	    }
+	  case EXCLAM:
+	    return parse_macro_invocation_partial (std::move (path),
+						   AST::AttrVec ());
+	    case LEFT_PAREN: {
+	      // tuple struct
+	      lexer.skip_token ();
+
+	      // check if empty tuple
+	      if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+		{
+		  lexer.skip_token ();
+		  return std::unique_ptr<AST::TupleStructPattern> (
+		    new AST::TupleStructPattern (std::move (path), nullptr));
+		}
+
+	      // parse items
+	      std::unique_ptr<AST::TupleStructItems> items
+		= parse_tuple_struct_items ();
+	      if (items == nullptr)
+		{
+		  Error error (lexer.peek_token ()->get_locus (),
+			       "failed to parse tuple struct items");
+		  add_error (std::move (error));
+
+		  return nullptr;
+		}
+
+	      if (!skip_token (RIGHT_PAREN))
+		{
+		  return nullptr;
+		}
+
+	      return std::unique_ptr<AST::TupleStructPattern> (
+		new AST::TupleStructPattern (std::move (path),
+					     std::move (items)));
+	    }
+	    case LEFT_CURLY: {
+	      // struct
+	      lexer.skip_token ();
+
+	      // parse elements (optional)
+	      AST::StructPatternElements elems = parse_struct_pattern_elems ();
+
+	      if (!skip_token (RIGHT_CURLY))
+		{
+		  return nullptr;
+		}
+
+	      return std::unique_ptr<AST::StructPattern> (
+		new AST::StructPattern (std::move (path), t->get_locus (),
+					std::move (elems)));
+	    }
+	  default:
+	    // assume path in expression
+	    return std::unique_ptr<AST::PathInExpression> (
+	      new AST::PathInExpression (std::move (path)));
+	  }
+      }
+    default:
+      add_error (Error (t->get_locus (), "unexpected token %qs in pattern",
+			t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+// Parses a single or double reference pattern.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ReferencePattern>
+Parser<ManagedTokenSource>::parse_reference_pattern ()
+{
+  // parse double or single ref
+  bool is_double_ref = false;
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case AMP:
+      // still false
+      lexer.skip_token ();
+      break;
+    case LOGICAL_AND:
+      is_double_ref = true;
+      lexer.skip_token ();
+      break;
+    default:
+      add_error (Error (t->get_locus (),
+			"unexpected token %qs in reference pattern",
+			t->get_token_description ()));
+
+      return nullptr;
+    }
+
+  // parse mut (if it exists)
+  bool is_mut = false;
+  if (lexer.peek_token ()->get_id () == MUT)
+    {
+      is_mut = true;
+      lexer.skip_token ();
+    }
+
+  // parse pattern to get reference of (required)
+  std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+  if (pattern == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse pattern in reference pattern");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::ReferencePattern> (
+    new AST::ReferencePattern (std::move (pattern), is_mut, is_double_ref,
+			       t->get_locus ()));
+}
+
+/* Parses a grouped pattern or tuple pattern. Prefers grouped over tuple if
+ * only a single element with no commas. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Pattern>
+Parser<ManagedTokenSource>::parse_grouped_or_tuple_pattern ()
+{
+  Location paren_locus = lexer.peek_token ()->get_locus ();
+  skip_token (LEFT_PAREN);
+
+  // detect '..' token (ranged with no lower range)
+  if (lexer.peek_token ()->get_id () == DOT_DOT)
+    {
+      lexer.skip_token ();
+
+      // parse new patterns while next token is a comma
+      std::vector<std::unique_ptr<AST::Pattern>> patterns;
+
+      const_TokenPtr t = lexer.peek_token ();
+      while (t->get_id () == COMMA)
+	{
+	  lexer.skip_token ();
+
+	  // break if next token is ')'
+	  if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+	    {
+	      break;
+	    }
+
+	  // parse pattern, which is required
+	  std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+	  if (pattern == nullptr)
+	    {
+	      Error error (
+		lexer.peek_token ()->get_locus (),
+		"failed to parse pattern inside ranged tuple pattern");
+	      add_error (std::move (error));
+
+	      // skip somewhere?
+	      return nullptr;
+	    }
+	  patterns.push_back (std::move (pattern));
+
+	  t = lexer.peek_token ();
+	}
+
+      if (!skip_token (RIGHT_PAREN))
+	{
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      // create ranged tuple pattern items with only upper items
+      std::unique_ptr<AST::TuplePatternItemsRanged> items (
+	new AST::TuplePatternItemsRanged (
+	  std::vector<std::unique_ptr<AST::Pattern>> (), std::move (patterns)));
+      return std::unique_ptr<AST::TuplePattern> (
+	new AST::TuplePattern (std::move (items), paren_locus));
+    }
+
+  // parse initial pattern (required)
+  std::unique_ptr<AST::Pattern> initial_pattern = parse_pattern ();
+  if (initial_pattern == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse pattern in grouped or tuple pattern");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  // branch on whether next token is a comma or not
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case RIGHT_PAREN:
+      // grouped pattern
+      lexer.skip_token ();
+
+      return std::unique_ptr<AST::GroupedPattern> (
+	new AST::GroupedPattern (std::move (initial_pattern), paren_locus));
+      case COMMA: {
+	// tuple pattern
+	lexer.skip_token ();
+
+	// create vector of patterns
+	std::vector<std::unique_ptr<AST::Pattern>> patterns;
+	patterns.push_back (std::move (initial_pattern));
+
+	t = lexer.peek_token ();
+	while (t->get_id () != RIGHT_PAREN && t->get_id () != DOT_DOT)
+	  {
+	    // parse pattern (required)
+	    std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+	    if (pattern == nullptr)
+	      {
+		Error error (t->get_locus (),
+			     "failed to parse pattern in tuple pattern");
+		add_error (std::move (error));
+
+		return nullptr;
+	      }
+	    patterns.push_back (std::move (pattern));
+
+	    if (lexer.peek_token ()->get_id () != COMMA)
+	      break;
+
+	    lexer.skip_token ();
+	    t = lexer.peek_token ();
+	  }
+
+	t = lexer.peek_token ();
+	if (t->get_id () == RIGHT_PAREN)
+	  {
+	    // non-ranged tuple pattern
+	    lexer.skip_token ();
+
+	    std::unique_ptr<AST::TuplePatternItemsMultiple> items (
+	      new AST::TuplePatternItemsMultiple (std::move (patterns)));
+	    return std::unique_ptr<AST::TuplePattern> (
+	      new AST::TuplePattern (std::move (items), paren_locus));
+	  }
+	else if (t->get_id () == DOT_DOT)
+	  {
+	    // ranged tuple pattern
+	    lexer.skip_token ();
+
+	    // parse upper patterns
+	    std::vector<std::unique_ptr<AST::Pattern>> upper_patterns;
+	    t = lexer.peek_token ();
+	    while (t->get_id () == COMMA)
+	      {
+		lexer.skip_token ();
+
+		// break if end
+		if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+		  break;
+
+		// parse pattern (required)
+		std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+		if (pattern == nullptr)
+		  {
+		    Error error (lexer.peek_token ()->get_locus (),
+				 "failed to parse pattern in tuple pattern");
+		    add_error (std::move (error));
+
+		    return nullptr;
+		  }
+		upper_patterns.push_back (std::move (pattern));
+
+		t = lexer.peek_token ();
+	      }
+
+	    if (!skip_token (RIGHT_PAREN))
+	      {
+		return nullptr;
+	      }
+
+	    std::unique_ptr<AST::TuplePatternItemsRanged> items (
+	      new AST::TuplePatternItemsRanged (std::move (patterns),
+						std::move (upper_patterns)));
+	    return std::unique_ptr<AST::TuplePattern> (
+	      new AST::TuplePattern (std::move (items), paren_locus));
+	  }
+	else
+	  {
+	    // some kind of error
+	    Error error (t->get_locus (),
+			 "failed to parse tuple pattern (probably) or maybe "
+			 "grouped pattern");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+      }
+    default:
+      // error
+      add_error (Error (t->get_locus (),
+			"unrecognised token %qs in grouped or tuple pattern "
+			"after first pattern",
+			t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+/* Parses a slice pattern that can match arrays or slices. Parses the square
+ * brackets too. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::SlicePattern>
+Parser<ManagedTokenSource>::parse_slice_pattern ()
+{
+  Location square_locus = lexer.peek_token ()->get_locus ();
+  skip_token (LEFT_SQUARE);
+
+  // parse initial pattern (required)
+  std::unique_ptr<AST::Pattern> initial_pattern = parse_pattern ();
+  if (initial_pattern == nullptr)
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse initial pattern in slice pattern");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  std::vector<std::unique_ptr<AST::Pattern>> patterns;
+  patterns.push_back (std::move (initial_pattern));
+
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () == COMMA)
+    {
+      lexer.skip_token ();
+
+      // break if end bracket
+      if (lexer.peek_token ()->get_id () == RIGHT_SQUARE)
+	break;
+
+      // parse pattern (required)
+      std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+      if (pattern == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse pattern in slice pattern");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      patterns.push_back (std::move (pattern));
+
+      t = lexer.peek_token ();
+    }
+
+  if (!skip_token (RIGHT_SQUARE))
+    {
+      return nullptr;
+    }
+
+  return std::unique_ptr<AST::SlicePattern> (
+    new AST::SlicePattern (std::move (patterns), square_locus));
+}
+
+/* Parses an identifier pattern (pattern that binds a value matched to a
+ * variable). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::IdentifierPattern>
+Parser<ManagedTokenSource>::parse_identifier_pattern ()
+{
+  Location locus = lexer.peek_token ()->get_locus ();
+
+  bool has_ref = false;
+  if (lexer.peek_token ()->get_id () == REF)
+    {
+      has_ref = true;
+      lexer.skip_token ();
+
+      // DEBUG
+      rust_debug ("parsed ref in identifier pattern");
+    }
+
+  bool has_mut = false;
+  if (lexer.peek_token ()->get_id () == MUT)
+    {
+      has_mut = true;
+      lexer.skip_token ();
+    }
+
+  // parse identifier (required)
+  const_TokenPtr ident_tok = expect_token (IDENTIFIER);
+  if (ident_tok == nullptr)
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+  Identifier ident = ident_tok->get_str ();
+
+  // DEBUG
+  rust_debug ("parsed identifier in identifier pattern");
+
+  // parse optional pattern binding thing
+  std::unique_ptr<AST::Pattern> bind_pattern = nullptr;
+  if (lexer.peek_token ()->get_id () == PATTERN_BIND)
+    {
+      lexer.skip_token ();
+
+      // parse required pattern to bind
+      bind_pattern = parse_pattern ();
+      if (bind_pattern == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse pattern to bind in identifier pattern");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+    }
+
+  // DEBUG
+  rust_debug ("about to return identifier pattern");
+
+  return std::unique_ptr<AST::IdentifierPattern> (
+    new AST::IdentifierPattern (std::move (ident), locus, has_ref, has_mut,
+				std::move (bind_pattern)));
+}
+
+/* Parses a pattern that opens with an identifier. This includes identifier
+ * patterns, path patterns (and derivatives such as struct patterns, tuple
+ * struct patterns, and macro invocations), and ranges. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Pattern>
+Parser<ManagedTokenSource>::parse_ident_leading_pattern ()
+{
+  // ensure first token is actually identifier
+  const_TokenPtr initial_tok = lexer.peek_token ();
+  if (initial_tok->get_id () != IDENTIFIER)
+    {
+      return nullptr;
+    }
+
+  // save initial identifier as it may be useful (but don't skip)
+  std::string initial_ident = initial_tok->get_str ();
+
+  // parse next tokens as a PathInExpression
+  AST::PathInExpression path = parse_path_in_expression ();
+
+  // branch on next token
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case EXCLAM:
+      return parse_macro_invocation_partial (std::move (path), AST::AttrVec ());
+      case LEFT_PAREN: {
+	// tuple struct
+	lexer.skip_token ();
+
+	// DEBUG
+	rust_debug ("parsing tuple struct pattern");
+
+	// check if empty tuple
+	if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+	  {
+	    lexer.skip_token ();
+	    return std::unique_ptr<AST::TupleStructPattern> (
+	      new AST::TupleStructPattern (std::move (path), nullptr));
+	  }
+
+	// parse items
+	std::unique_ptr<AST::TupleStructItems> items
+	  = parse_tuple_struct_items ();
+	if (items == nullptr)
+	  {
+	    Error error (lexer.peek_token ()->get_locus (),
+			 "failed to parse tuple struct items");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	// DEBUG
+	rust_debug ("successfully parsed tuple struct items");
+
+	if (!skip_token (RIGHT_PAREN))
+	  {
+	    return nullptr;
+	  }
+
+	// DEBUG
+	rust_debug ("successfully parsed tuple struct pattern");
+
+	return std::unique_ptr<AST::TupleStructPattern> (
+	  new AST::TupleStructPattern (std::move (path), std::move (items)));
+      }
+      case LEFT_CURLY: {
+	// struct
+	lexer.skip_token ();
+
+	// parse elements (optional)
+	AST::StructPatternElements elems = parse_struct_pattern_elems ();
+
+	if (!skip_token (RIGHT_CURLY))
+	  {
+	    return nullptr;
+	  }
+
+	// DEBUG
+	rust_debug ("successfully parsed struct pattern");
+
+	return std::unique_ptr<AST::StructPattern> (
+	  new AST::StructPattern (std::move (path), initial_tok->get_locus (),
+				  std::move (elems)));
+      }
+    case DOT_DOT_EQ:
+      case ELLIPSIS: {
+	// range
+	bool has_ellipsis_syntax = lexer.peek_token ()->get_id () == ELLIPSIS;
+
+	lexer.skip_token ();
+
+	std::unique_ptr<AST::RangePatternBoundPath> lower_bound (
+	  new AST::RangePatternBoundPath (std::move (path)));
+	std::unique_ptr<AST::RangePatternBound> upper_bound
+	  = parse_range_pattern_bound ();
+
+	return std::unique_ptr<AST::RangePattern> (new AST::RangePattern (
+	  std::move (lower_bound), std::move (upper_bound),
+	  Linemap::unknown_location (), has_ellipsis_syntax));
+      }
+      case PATTERN_BIND: {
+	// only allow on single-segment paths
+	if (path.is_single_segment ())
+	  {
+	    // identifier with pattern bind
+	    lexer.skip_token ();
+
+	    std::unique_ptr<AST::Pattern> bind_pattern = parse_pattern ();
+	    if (bind_pattern == nullptr)
+	      {
+		Error error (
+		  t->get_locus (),
+		  "failed to parse pattern to bind to identifier pattern");
+		add_error (std::move (error));
+
+		return nullptr;
+	      }
+	    return std::unique_ptr<AST::IdentifierPattern> (
+	      new AST::IdentifierPattern (std::move (initial_ident),
+					  initial_tok->get_locus (), false,
+					  false, std::move (bind_pattern)));
+	  }
+	Error error (
+	  t->get_locus (),
+	  "failed to parse pattern bind to a path, not an identifier");
+	add_error (std::move (error));
+
+	return nullptr;
+      }
+    default:
+      // assume identifier if single segment
+      if (path.is_single_segment ())
+	{
+	  return std::unique_ptr<AST::IdentifierPattern> (
+	    new AST::IdentifierPattern (std::move (initial_ident),
+					initial_tok->get_locus ()));
+	}
+      // return path otherwise
+      return std::unique_ptr<AST::PathInExpression> (
+	new AST::PathInExpression (std::move (path)));
+    }
+}
+
+// Parses tuple struct items if they exist. Does not parse parentheses.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::TupleStructItems>
+Parser<ManagedTokenSource>::parse_tuple_struct_items ()
+{
+  std::vector<std::unique_ptr<AST::Pattern>> lower_patterns;
+
+  // DEBUG
+  rust_debug ("started parsing tuple struct items");
+
+  // check for '..' at front
+  if (lexer.peek_token ()->get_id () == DOT_DOT)
+    {
+      // only parse upper patterns
+      lexer.skip_token ();
+
+      // DEBUG
+      rust_debug ("'..' at front in tuple struct items detected");
+
+      std::vector<std::unique_ptr<AST::Pattern>> upper_patterns;
+
+      const_TokenPtr t = lexer.peek_token ();
+      while (t->get_id () == COMMA)
+	{
+	  lexer.skip_token ();
+
+	  // break if right paren
+	  if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+	    break;
+
+	  // parse pattern, which is now required
+	  std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+	  if (pattern == nullptr)
+	    {
+	      Error error (lexer.peek_token ()->get_locus (),
+			   "failed to parse pattern in tuple struct items");
+	      add_error (std::move (error));
+
+	      return nullptr;
+	    }
+	  upper_patterns.push_back (std::move (pattern));
+
+	  t = lexer.peek_token ();
+	}
+
+      // DEBUG
+      rust_debug (
+	"finished parsing tuple struct items ranged (upper/none only)");
+
+      return std::unique_ptr<AST::TupleStructItemsRange> (
+	new AST::TupleStructItemsRange (std::move (lower_patterns),
+					std::move (upper_patterns)));
+    }
+
+  // has at least some lower patterns
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () != RIGHT_PAREN && t->get_id () != DOT_DOT)
+    {
+      // DEBUG
+      rust_debug ("about to parse pattern in tuple struct items");
+
+      // parse pattern, which is required
+      std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+      if (pattern == nullptr)
+	{
+	  Error error (t->get_locus (),
+		       "failed to parse pattern in tuple struct items");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      lower_patterns.push_back (std::move (pattern));
+
+      // DEBUG
+      rust_debug ("successfully parsed pattern in tuple struct items");
+
+      if (lexer.peek_token ()->get_id () != COMMA)
+	{
+	  // DEBUG
+	  rust_debug ("broke out of parsing patterns in tuple struct "
+		      "items as no comma");
+
+	  break;
+	}
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  // branch on next token
+  t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case RIGHT_PAREN:
+      return std::unique_ptr<AST::TupleStructItemsNoRange> (
+	new AST::TupleStructItemsNoRange (std::move (lower_patterns)));
+      case DOT_DOT: {
+	// has an upper range that must be parsed separately
+	lexer.skip_token ();
+
+	std::vector<std::unique_ptr<AST::Pattern>> upper_patterns;
+
+	t = lexer.peek_token ();
+	while (t->get_id () == COMMA)
+	  {
+	    lexer.skip_token ();
+
+	    // break if next token is right paren
+	    if (lexer.peek_token ()->get_id () == RIGHT_PAREN)
+	      break;
+
+	    // parse pattern, which is required
+	    std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+	    if (pattern == nullptr)
+	      {
+		Error error (lexer.peek_token ()->get_locus (),
+			     "failed to parse pattern in tuple struct items");
+		add_error (std::move (error));
+
+		return nullptr;
+	      }
+	    upper_patterns.push_back (std::move (pattern));
+
+	    t = lexer.peek_token ();
+	  }
+
+	return std::unique_ptr<AST::TupleStructItemsRange> (
+	  new AST::TupleStructItemsRange (std::move (lower_patterns),
+					  std::move (upper_patterns)));
+      }
+    default:
+      // error
+      add_error (Error (t->get_locus (),
+			"unexpected token %qs in tuple struct items",
+			t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+// Parses struct pattern elements if they exist.
+template <typename ManagedTokenSource>
+AST::StructPatternElements
+Parser<ManagedTokenSource>::parse_struct_pattern_elems ()
+{
+  std::vector<std::unique_ptr<AST::StructPatternField>> fields;
+
+  AST::AttrVec etc_attrs;
+  bool has_etc = false;
+
+  // try parsing struct pattern fields
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () != RIGHT_CURLY)
+    {
+      AST::AttrVec outer_attrs = parse_outer_attributes ();
+
+      // parse etc (must be last in struct pattern, so breaks)
+      if (lexer.peek_token ()->get_id () == DOT_DOT)
+	{
+	  lexer.skip_token ();
+	  etc_attrs = std::move (outer_attrs);
+	  has_etc = true;
+	  break;
+	}
+
+      std::unique_ptr<AST::StructPatternField> field
+	= parse_struct_pattern_field_partial (std::move (outer_attrs));
+      if (field == nullptr)
+	{
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse struct pattern field");
+	  add_error (std::move (error));
+
+	  // skip after somewhere?
+	  return AST::StructPatternElements::create_empty ();
+	}
+      fields.push_back (std::move (field));
+
+      if (lexer.peek_token ()->get_id () != COMMA)
+	break;
+
+      // skip comma
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  if (has_etc)
+    return AST::StructPatternElements (std::move (fields),
+				       std::move (etc_attrs));
+  else
+    return AST::StructPatternElements (std::move (fields));
+}
+
+/* Parses a struct pattern field (tuple index/pattern, identifier/pattern, or
+ * identifier). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::StructPatternField>
+Parser<ManagedTokenSource>::parse_struct_pattern_field ()
+{
+  // parse outer attributes (if they exist)
+  AST::AttrVec outer_attrs = parse_outer_attributes ();
+
+  return parse_struct_pattern_field_partial (std::move (outer_attrs));
+}
+
+/* Parses a struct pattern field (tuple index/pattern, identifier/pattern, or
+ * identifier), with outer attributes passed in. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::StructPatternField>
+Parser<ManagedTokenSource>::parse_struct_pattern_field_partial (
+  AST::AttrVec outer_attrs)
+{
+  // branch based on next token
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+      case INT_LITERAL: {
+	// tuple index
+	std::string index_str = t->get_str ();
+	int index = atoi (index_str.c_str ());
+
+	if (!skip_token (COLON))
+	  {
+	    return nullptr;
+	  }
+
+	// parse required pattern
+	std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+	if (pattern == nullptr)
+	  {
+	    Error error (
+	      t->get_locus (),
+	      "failed to parse pattern in tuple index struct pattern field");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	return std::unique_ptr<AST::StructPatternFieldTuplePat> (
+	  new AST::StructPatternFieldTuplePat (index, std::move (pattern),
+					       std::move (outer_attrs),
+					       t->get_locus ()));
+      }
+    case IDENTIFIER:
+      // identifier-pattern OR only identifier
+      // branch on next token
+      switch (lexer.peek_token (1)->get_id ())
+	{
+	  case COLON: {
+	    // identifier-pattern
+	    Identifier ident = t->get_str ();
+	    lexer.skip_token ();
+
+	    skip_token (COLON);
+
+	    // parse required pattern
+	    std::unique_ptr<AST::Pattern> pattern = parse_pattern ();
+	    if (pattern == nullptr)
+	      {
+		Error error (t->get_locus (),
+			     "failed to parse pattern in struct pattern field");
+		add_error (std::move (error));
+
+		return nullptr;
+	      }
+
+	    return std::unique_ptr<AST::StructPatternFieldIdentPat> (
+	      new AST::StructPatternFieldIdentPat (std::move (ident),
+						   std::move (pattern),
+						   std::move (outer_attrs),
+						   t->get_locus ()));
+	  }
+	case COMMA:
+	  case RIGHT_CURLY: {
+	    // identifier only
+	    Identifier ident = t->get_str ();
+	    lexer.skip_token ();
+
+	    return std::unique_ptr<AST::StructPatternFieldIdent> (
+	      new AST::StructPatternFieldIdent (std::move (ident), false, false,
+						std::move (outer_attrs),
+						t->get_locus ()));
+	  }
+	default:
+	  // error
+	  add_error (Error (t->get_locus (),
+			    "unrecognised token %qs in struct pattern field",
+			    t->get_token_description ()));
+
+	  return nullptr;
+	}
+    case REF:
+      case MUT: {
+	// only identifier
+	bool has_ref = false;
+	if (t->get_id () == REF)
+	  {
+	    has_ref = true;
+	    lexer.skip_token ();
+	  }
+
+	bool has_mut = false;
+	if (lexer.peek_token ()->get_id () == MUT)
+	  {
+	    has_mut = true;
+	    lexer.skip_token ();
+	  }
+
+	const_TokenPtr ident_tok = expect_token (IDENTIFIER);
+	if (ident_tok == nullptr)
+	  {
+	    return nullptr;
+	  }
+	Identifier ident = ident_tok->get_str ();
+
+	return std::unique_ptr<AST::StructPatternFieldIdent> (
+	  new AST::StructPatternFieldIdent (std::move (ident), has_ref, has_mut,
+					    std::move (outer_attrs),
+					    t->get_locus ()));
+      }
+    default:
+      // not necessarily an error
+      return nullptr;
+    }
+}
+
+template <typename ManagedTokenSource>
+ExprOrStmt
+Parser<ManagedTokenSource>::parse_stmt_or_expr_with_block (
+  AST::AttrVec outer_attrs)
+{
+  auto expr = parse_expr_with_block (std::move (outer_attrs));
+  if (expr == nullptr)
+    return ExprOrStmt::create_error ();
+
+  auto tok = lexer.peek_token ();
+
+  // tail expr in a block expr
+  if (tok->get_id () == RIGHT_CURLY)
+    return ExprOrStmt (std::move (expr));
+
+  // internal block expr must either have semicolons followed, or evaluate to
+  // ()
+  auto locus = expr->get_locus ();
+  std::unique_ptr<AST::ExprStmtWithBlock> stmt (
+    new AST::ExprStmtWithBlock (std::move (expr), locus,
+				tok->get_id () == SEMICOLON));
+  if (tok->get_id () == SEMICOLON)
+    lexer.skip_token ();
+
+  return ExprOrStmt (std::move (stmt));
+}
+
+/* Parses a statement or expression (depending on whether a trailing semicolon
+ * exists). Useful for block expressions where it cannot be determined through
+ * lookahead whether it is a statement or expression to be parsed. */
+template <typename ManagedTokenSource>
+ExprOrStmt
+Parser<ManagedTokenSource>::parse_stmt_or_expr_without_block ()
+{
+  // quick exit for empty statement
+  const_TokenPtr t = lexer.peek_token ();
+  if (t->get_id () == SEMICOLON)
+    {
+      lexer.skip_token ();
+      std::unique_ptr<AST::EmptyStmt> stmt (
+	new AST::EmptyStmt (t->get_locus ()));
+      return ExprOrStmt (std::move (stmt));
+    }
+
+  // parse outer attributes
+  AST::AttrVec outer_attrs = parse_outer_attributes ();
+
+  // 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
+   * to parse outer attributes, and then pass them in to either a let
+   * statement or (fallback) expression statement. */
+  // FIXME: think of a way to do this without such a large switch?
+
+  /* FIXME: for expressions at least, the only way that they can really be
+   * parsed properly in this way is if they don't support operators on them.
+   * They must be pratt-parsed otherwise. As such due to composability, only
+   * explicit statements will have special cases here. This should roughly
+   * correspond to "expr-with-block", but this warning is here in case it
+   * isn't the case. */
+  t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+      case LET: {
+	// let statement
+	std::unique_ptr<AST::LetStmt> stmt (
+	  parse_let_stmt (std::move (outer_attrs)));
+	return ExprOrStmt (std::move (stmt));
+      }
+    case PUB:
+    case MOD:
+    case EXTERN_TOK:
+    case USE:
+    case FN_TOK:
+    case TYPE:
+    case STRUCT_TOK:
+    case ENUM_TOK:
+    case CONST:
+    case STATIC_TOK:
+    case TRAIT:
+      case IMPL: {
+	std::unique_ptr<AST::VisItem> item (
+	  parse_vis_item (std::move (outer_attrs)));
+	return ExprOrStmt (std::move (item));
+      }
+      /* TODO: implement union keyword but not really because of
+       * context-dependence crappy hack way to parse a union written below to
+       * separate it from the good code. */
+      // case UNION:
+      case UNSAFE: { // maybe - unsafe traits are a thing
+	/* if any of these (should be all possible VisItem prefixes), parse a
+	 * VisItem - can't parse item because would require reparsing outer
+	 * attributes */
+	const_TokenPtr t2 = lexer.peek_token (1);
+	switch (t2->get_id ())
+	  {
+	    case LEFT_CURLY: {
+	      // unsafe block
+	      return parse_stmt_or_expr_with_block (std::move (outer_attrs));
+	    }
+	    case TRAIT: {
+	      // unsafe trait
+	      std::unique_ptr<AST::VisItem> item (
+		parse_vis_item (std::move (outer_attrs)));
+	      return ExprOrStmt (std::move (item));
+	    }
+	  case EXTERN_TOK:
+	    case FN_TOK: {
+	      // unsafe function
+	      std::unique_ptr<AST::VisItem> item (
+		parse_vis_item (std::move (outer_attrs)));
+	      return ExprOrStmt (std::move (item));
+	    }
+	    case IMPL: {
+	      // unsafe trait impl
+	      std::unique_ptr<AST::VisItem> item (
+		parse_vis_item (std::move (outer_attrs)));
+	      return ExprOrStmt (std::move (item));
+	    }
+	  default:
+	    add_error (Error (t2->get_locus (),
+			      "unrecognised token %qs after parsing unsafe - "
+			      "expected beginning of expression or statement",
+			      t->get_token_description ()));
+
+	    // skip somewhere?
+	    return ExprOrStmt::create_error ();
+	  }
+      }
+    case SUPER:
+    case SELF:
+    case CRATE:
+      case DOLLAR_SIGN: {
+	/* path-based thing so struct/enum or path or macro invocation of a
+	 * kind. however, the expressions are composable (i think) */
+
+	std::unique_ptr<AST::ExprWithoutBlock> expr
+	  = parse_expr_without_block ();
+
+	if (lexer.peek_token ()->get_id () == SEMICOLON)
+	  {
+	    // must be expression statement
+	    lexer.skip_token ();
+
+	    std::unique_ptr<AST::ExprStmtWithoutBlock> stmt (
+	      new AST::ExprStmtWithoutBlock (std::move (expr),
+					     t->get_locus ()));
+	    return ExprOrStmt (std::move (stmt));
+	  }
+
+	// return expression
+	return ExprOrStmt (std::move (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
+    case LOOP:
+    case WHILE:
+    case FOR:
+    case IF:
+    case MATCH_TOK:
+    case LEFT_CURLY:
+      case ASYNC: {
+	return parse_stmt_or_expr_with_block (std::move (outer_attrs));
+      }
+      case LIFETIME: {
+	/* FIXME: are there any expressions without blocks that can have
+	 * lifetime as their first token? Or is loop expr the only one? */
+	// safe side for now:
+	const_TokenPtr t2 = lexer.peek_token (2);
+	if (lexer.peek_token (1)->get_id () == COLON
+	    && (t2->get_id () == LOOP || t2->get_id () == WHILE
+		|| t2->get_id () == FOR))
+	  {
+	    return parse_stmt_or_expr_with_block (std::move (outer_attrs));
+	  }
+	else
+	  {
+	    // should be expr without block
+	    std::unique_ptr<AST::ExprWithoutBlock> expr
+	      = parse_expr_without_block (std::move (outer_attrs));
+
+	    if (lexer.peek_token ()->get_id () == SEMICOLON)
+	      {
+		// must be expression statement
+		lexer.skip_token ();
+
+		std::unique_ptr<AST::ExprStmtWithoutBlock> stmt (
+		  new AST::ExprStmtWithoutBlock (std::move (expr),
+						 t->get_locus ()));
+		return ExprOrStmt (std::move (stmt));
+	      }
+
+	    // return expression
+	    return ExprOrStmt (std::move (expr));
+	  }
+      }
+    // crappy hack to do union "keyword"
+    case IDENTIFIER:
+      if (t->get_str () == "union"
+	  && lexer.peek_token (1)->get_id () == IDENTIFIER)
+	{
+	  std::unique_ptr<AST::VisItem> item (
+	    parse_vis_item (std::move (outer_attrs)));
+	  return ExprOrStmt (std::move (item));
+	  // or should this go straight to parsing union?
+	}
+      else if (t->get_str () == "macro_rules")
+	{
+	  // macro_rules! macro item
+	  std::unique_ptr<AST::MacroItem> item (
+	    parse_macro_item (std::move (outer_attrs)));
+	  return ExprOrStmt (std::move (item));
+	}
+      else if (lexer.peek_token (1)->get_id () == SCOPE_RESOLUTION
+	       || lexer.peek_token (1)->get_id () == EXCLAM
+	       || lexer.peek_token (1)->get_id () == LEFT_CURLY)
+	{
+	  /* path (probably) or macro invocation or struct or enum, so
+	   * probably a macro invocation semi decide how to parse - probably
+	   * parse path and then get macro from it */
+
+	  // FIXME: old code was good until composability was required
+	  // return parse_path_based_stmt_or_expr(std::move(outer_attrs));
+	  std::unique_ptr<AST::ExprWithoutBlock> expr
+	    = parse_expr_without_block (std::move (outer_attrs));
+
+	  if (lexer.peek_token ()->get_id () == SEMICOLON)
+	    {
+	      // must be expression statement
+	      lexer.skip_token ();
+
+	      std::unique_ptr<AST::ExprStmtWithoutBlock> stmt (
+		new AST::ExprStmtWithoutBlock (std::move (expr),
+					       t->get_locus ()));
+	      return ExprOrStmt (std::move (stmt));
+	    }
+
+	  // return expression
+	  return ExprOrStmt (std::move (expr));
+	}
+      gcc_fallthrough ();
+      // TODO: find out how to disable gcc "implicit fallthrough" warning
+      default: {
+	/* expression statement (without block) or expression itself - parse
+	 * expression then make it statement if semi afterwards */
+
+	std::unique_ptr<AST::ExprWithoutBlock> expr
+	  = parse_expr_without_block (std::move (outer_attrs));
+
+	if (lexer.peek_token ()->get_id () == SEMICOLON)
+	  {
+	    // must be expression statement
+	    lexer.skip_token ();
+
+	    std::unique_ptr<AST::ExprStmtWithoutBlock> stmt (
+	      new AST::ExprStmtWithoutBlock (std::move (expr),
+					     t->get_locus ()));
+	    return ExprOrStmt (std::move (stmt));
+	  }
+
+	// return expression
+	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 ();
+
+		std::unique_ptr<AST::MacroInvocation> stmt (
+		  new AST::MacroInvocation (std::move (invoc_data),
+					    std::move (outer_attrs),
+					    stmt_or_expr_loc, true));
+		return ExprOrStmt (std::move (stmt));
+	      }
+
+	    // otherwise, create macro invocation
+	    std::unique_ptr<AST::MacroInvocation> expr (
+	      new AST::MacroInvocation (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::ExprStmtWithoutBlock> stmt (
+	      new AST::ExprStmtWithoutBlock (std::move (expr),
+					     stmt_or_expr_loc));
+	    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::ExprStmtWithoutBlock> stmt (
+	      new AST::ExprStmtWithoutBlock (std::move (struct_expr),
+					     stmt_or_expr_loc));
+	    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::ExprStmtWithoutBlock> stmt (
+	      new AST::ExprStmtWithoutBlock (std::move (expr),
+					     stmt_or_expr_loc));
+	    return ExprOrStmt (std::move (stmt));
+	  }
+
+	return ExprOrStmt (std::move (expr));
+      }
+    }
+}
+
+// Parses a struct expression field.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::StructExprField>
+Parser<ManagedTokenSource>::parse_struct_expr_field ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+  switch (t->get_id ())
+    {
+    case IDENTIFIER:
+      if (lexer.peek_token (1)->get_id () == COLON)
+	{
+	  // struct expr field with identifier and expr
+	  Identifier ident = t->get_str ();
+	  lexer.skip_token (1);
+
+	  // parse expression (required)
+	  std::unique_ptr<AST::Expr> expr = parse_expr ();
+	  if (expr == nullptr)
+	    {
+	      Error error (t->get_locus (),
+			   "failed to parse struct expression field with "
+			   "identifier and expression");
+	      add_error (std::move (error));
+
+	      return nullptr;
+	    }
+
+	  return std::unique_ptr<AST::StructExprFieldIdentifierValue> (
+	    new AST::StructExprFieldIdentifierValue (std::move (ident),
+						     std::move (expr),
+						     t->get_locus ()));
+	}
+      else
+	{
+	  // struct expr field with identifier only
+	  Identifier ident = t->get_str ();
+	  lexer.skip_token ();
+
+	  return std::unique_ptr<AST::StructExprFieldIdentifier> (
+	    new AST::StructExprFieldIdentifier (std::move (ident),
+						t->get_locus ()));
+	}
+      case INT_LITERAL: {
+	// parse tuple index field
+	int index = atoi (t->get_str ().c_str ());
+	lexer.skip_token ();
+
+	if (!skip_token (COLON))
+	  {
+	    // skip somewhere?
+	    return nullptr;
+	  }
+
+	// parse field expression (required)
+	std::unique_ptr<AST::Expr> expr = parse_expr ();
+	if (expr == nullptr)
+	  {
+	    Error error (t->get_locus (),
+			 "failed to parse expr in struct (or enum) expr "
+			 "field with tuple index");
+	    add_error (std::move (error));
+
+	    return nullptr;
+	  }
+
+	return std::unique_ptr<AST::StructExprFieldIndexValue> (
+	  new AST::StructExprFieldIndexValue (index, std::move (expr),
+					      t->get_locus ()));
+      }
+    case DOT_DOT:
+      /* this is a struct base and can't be parsed here, so just return
+       * nothing without erroring */
+
+      return nullptr;
+    default:
+      add_error (
+	Error (t->get_locus (),
+	       "unrecognised token %qs as first token of struct expr field - "
+	       "expected identifier or integer literal",
+	       t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+// 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 ();
+
+	  std::unique_ptr<AST::MacroInvocation> stmt (
+	    new AST::MacroInvocation (std::move (invoc_data),
+				      std::move (outer_attrs), macro_locus,
+				      true));
+	  return ExprOrStmt (std::move (stmt));
+	}
+
+      // otherwise, create macro invocation
+      std::unique_ptr<AST::MacroInvocation> expr (
+	new AST::MacroInvocation (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
+Parser<ManagedTokenSource>::unexpected_token (const_TokenPtr t)
+{
+  Error error (t->get_locus (), "unexpected token %qs\n",
+	       t->get_token_description ());
+  add_error (std::move (error));
+}
+
+/* Crappy "error recovery" performed after error by skipping tokens until a
+ * semi-colon is found */
+template <typename ManagedTokenSource>
+void
+Parser<ManagedTokenSource>::skip_after_semicolon ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+
+  while (t->get_id () != END_OF_FILE && t->get_id () != SEMICOLON)
+    {
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  if (t->get_id () == SEMICOLON)
+    lexer.skip_token ();
+}
+
+/* Checks if current token has inputted id - skips it and returns true if so,
+ * diagnoses an error and returns false otherwise. */
+template <typename ManagedTokenSource>
+bool
+Parser<ManagedTokenSource>::skip_token (TokenId token_id)
+{
+  return expect_token (token_id) != const_TokenPtr ();
+}
+
+/* Checks if current token has inputted id - skips it and returns true if so,
+ * returns false otherwise without diagnosing an error */
+template <typename ManagedTokenSource>
+bool
+Parser<ManagedTokenSource>::maybe_skip_token (TokenId token_id)
+{
+  if (lexer.peek_token ()->get_id () != token_id)
+    return false;
+  else
+    return skip_token (token_id);
+}
+
+/* Checks the current token - if id is same as expected, skips and returns it,
+ * otherwise diagnoses error and returns null. */
+template <typename ManagedTokenSource>
+const_TokenPtr
+Parser<ManagedTokenSource>::expect_token (TokenId token_id)
+{
+  const_TokenPtr t = lexer.peek_token ();
+  if (t->get_id () == token_id)
+    {
+      lexer.skip_token ();
+      return t;
+    }
+  else
+    {
+      Error error (t->get_locus (), "expecting %qs but %qs found",
+		   get_token_description (token_id),
+		   t->get_token_description ());
+      add_error (std::move (error));
+
+      return const_TokenPtr ();
+    }
+}
+
+// Skips all tokens until EOF or }. Don't use.
+template <typename ManagedTokenSource>
+void
+Parser<ManagedTokenSource>::skip_after_end ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+
+  while (t->get_id () != END_OF_FILE && t->get_id () != RIGHT_CURLY)
+    {
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  if (t->get_id () == RIGHT_CURLY)
+    {
+      lexer.skip_token ();
+    }
+}
+
+/* A slightly more aware error-handler that skips all tokens until it reaches
+ * the end of the block scope (i.e. when left curly brackets = right curly
+ * brackets). Note: assumes currently in the middle of a block. Use
+ * skip_after_next_block to skip based on the assumption that the block
+ * has not been entered yet. */
+template <typename ManagedTokenSource>
+void
+Parser<ManagedTokenSource>::skip_after_end_block ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+  int curly_count = 1;
+
+  while (curly_count > 0 && t->get_id () != END_OF_FILE)
+    {
+      switch (t->get_id ())
+	{
+	case LEFT_CURLY:
+	  curly_count++;
+	  break;
+	case RIGHT_CURLY:
+	  curly_count--;
+	  break;
+	default:
+	  break;
+	}
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+}
+
+/* Skips tokens until the end of the next block. i.e. assumes that the block
+ * has not been entered yet. */
+template <typename ManagedTokenSource>
+void
+Parser<ManagedTokenSource>::skip_after_next_block ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+
+  // initial loop - skip until EOF if no left curlies encountered
+  while (t->get_id () != END_OF_FILE && t->get_id () != LEFT_CURLY)
+    {
+      lexer.skip_token ();
+
+      t = lexer.peek_token ();
+    }
+
+  // if next token is left, skip it and then skip after the block ends
+  if (t->get_id () == LEFT_CURLY)
+    {
+      lexer.skip_token ();
+
+      skip_after_end_block ();
+    }
+  // otherwise, do nothing as EOF
+}
+
+/* Skips all tokens until ] (the end of an attribute) - does not skip the ]
+ * (as designed for attribute body use) */
+template <typename ManagedTokenSource>
+void
+Parser<ManagedTokenSource>::skip_after_end_attribute ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+
+  while (t->get_id () != RIGHT_SQUARE)
+    {
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  // Don't skip the RIGHT_SQUARE token
+}
+
+/* Pratt parser impl of parse_expr. FIXME: this is only provisional and
+ * probably will be changed.
+ * FIXME: this may only parse expressions without blocks as they are the only
+ * expressions to have precedence? */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Expr>
+Parser<ManagedTokenSource>::parse_expr (int right_binding_power,
+					AST::AttrVec outer_attrs,
+					ParseRestrictions restrictions)
+{
+  const_TokenPtr current_token = lexer.peek_token ();
+  // Special hack because we are allowed to return nullptr, in that case we
+  // don't want to skip the token, since we don't actually parse it. But if
+  // null isn't allowed it indicates an error, and we want to skip past that.
+  // So return early if it is one of the tokens that ends an expression
+  // (or at least cannot start a new expression).
+  if (restrictions.expr_can_be_null)
+    {
+      TokenId id = current_token->get_id ();
+      if (id == SEMICOLON || id == RIGHT_PAREN || id == RIGHT_CURLY
+	  || id == RIGHT_SQUARE)
+	return nullptr;
+    }
+  lexer.skip_token ();
+
+  // parse null denotation (unary part of expression)
+  std::unique_ptr<AST::Expr> expr
+    = null_denotation (current_token, {}, restrictions);
+
+  if (expr == nullptr)
+    {
+      // DEBUG
+      rust_debug ("null denotation is null; returning null for parse_expr");
+      return nullptr;
+    }
+
+  // stop parsing if find lower priority token - parse higher priority first
+  while (right_binding_power < left_binding_power (lexer.peek_token ()))
+    {
+      current_token = lexer.peek_token ();
+      lexer.skip_token ();
+
+      expr = left_denotation (current_token, std::move (expr),
+			      std::move (outer_attrs), restrictions);
+
+      if (expr == nullptr)
+	{
+	  // DEBUG
+	  rust_debug ("left denotation is null; returning null for parse_expr");
+
+	  return nullptr;
+	}
+    }
+
+  return expr;
+}
+
+// Parse expression with lowest left binding power.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Expr>
+Parser<ManagedTokenSource>::parse_expr (AST::AttrVec outer_attrs,
+					ParseRestrictions restrictions)
+{
+  return parse_expr (LBP_LOWEST, std::move (outer_attrs), restrictions);
+}
+
+/* Determines action to take when finding token at beginning of expression.
+ * FIXME: this may only apply to precedence-capable expressions (which are all
+ * expressions without blocks), so make return type ExprWithoutBlock? It would
+ * simplify stuff. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Expr>
+Parser<ManagedTokenSource>::null_denotation (const_TokenPtr tok,
+					     AST::AttrVec outer_attrs,
+					     ParseRestrictions restrictions)
+{
+  /* note: tok is previous character in input stream, not current one, as
+   * parse_expr skips it before passing it in */
+
+  /* as a Pratt parser (which works by decomposing expressions into a null
+   * denotation and then a left denotation), null denotations handle primaries
+   * and unary operands (but only prefix unary operands) */
+
+  switch (tok->get_id ())
+    {
+      case IDENTIFIER: {
+	// DEBUG
+	rust_debug ("beginning null denotation identifier 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 ("finished null denotation identifier path parsing - "
+		    "next is branching");
+
+	// 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),
+						   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 ();
+      }
+      /* 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_ANGLE: {
+	// qualified path
+	// HACK: add outer attrs to path
+	AST::QualifiedPathInExpression path
+	  = parse_qualified_path_in_expression (tok->get_locus ());
+	path.set_outer_attrs (std::move (outer_attrs));
+	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?
+    case INT_LITERAL:
+      // we should check the range, but ignore for now
+      // encode as int?
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr (tok->get_str (), AST::Literal::INT,
+			      tok->get_type_hint (), {}, tok->get_locus ()));
+    case FLOAT_LITERAL:
+      // encode as float?
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr (tok->get_str (), AST::Literal::FLOAT,
+			      tok->get_type_hint (), {}, tok->get_locus ()));
+    case STRING_LITERAL:
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr (tok->get_str (), AST::Literal::STRING,
+			      tok->get_type_hint (), {}, tok->get_locus ()));
+    case BYTE_STRING_LITERAL:
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr (tok->get_str (), AST::Literal::BYTE_STRING,
+			      tok->get_type_hint (), {}, tok->get_locus ()));
+    case CHAR_LITERAL:
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr (tok->get_str (), AST::Literal::CHAR,
+			      tok->get_type_hint (), {}, tok->get_locus ()));
+    case BYTE_CHAR_LITERAL:
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr (tok->get_str (), AST::Literal::BYTE,
+			      tok->get_type_hint (), {}, tok->get_locus ()));
+    case TRUE_LITERAL:
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr ("true", AST::Literal::BOOL, tok->get_type_hint (),
+			      {}, tok->get_locus ()));
+    case FALSE_LITERAL:
+      return std::unique_ptr<AST::LiteralExpr> (
+	new AST::LiteralExpr ("false", AST::Literal::BOOL,
+			      tok->get_type_hint (), {}, tok->get_locus ()));
+    case LEFT_PAREN:
+      return parse_grouped_or_tuple_expr (std::move (outer_attrs),
+					  tok->get_locus ());
+
+      /*case PLUS: { // unary plus operator
+	  // invoke parse_expr recursively with appropriate priority, etc. for
+      below AST::Expr* expr = parse_expr(LBP_UNARY_PLUS);
+
+	  if (expr == nullptr)
+	      return nullptr;
+	  // can only apply to integer and float expressions
+	  if (expr->get_type() != integer_type_node || expr->get_type() !=
+      float_type_node) { rust_error_at(tok->get_locus(), "operand of unary
+      plus must be int or float but it is %s", print_type(expr->get_type()));
+      return nullptr;
+	  }
+
+	  return Tree(expr, tok->get_locus());
+      }*/
+      // Rust has no unary plus operator
+      case MINUS: { // unary minus
+	ParseRestrictions entered_from_unary;
+	entered_from_unary.entered_from_unary = true;
+	if (!restrictions.can_be_struct_expr)
+	  entered_from_unary.can_be_struct_expr = false;
+	std::unique_ptr<AST::Expr> expr
+	  = parse_expr (LBP_UNARY_MINUS, {}, entered_from_unary);
+
+	if (expr == nullptr)
+	  return nullptr;
+	// can only apply to integer and float expressions
+	/*if (expr.get_type() != integer_type_node || expr.get_type() !=
+	float_type_node) { rust_error_at(tok->get_locus(), "operand of unary
+	minus must be int or float but it is %s",
+	print_type(expr.get_type())); return Tree::error();
+	}*/
+	/* FIXME: when implemented the "get type" method on expr, ensure it is
+	 * int or float type (except unsigned int). Actually, this would
+	 * probably have to be done in semantic analysis (as type checking).
+	 */
+
+	/* FIXME: allow outer attributes on these expressions by having an
+	 * outer attrs parameter in function*/
+	return std::unique_ptr<AST::NegationExpr> (
+	  new AST::NegationExpr (std::move (expr), NegationOperator::NEGATE,
+				 std::move (outer_attrs), tok->get_locus ()));
+      }
+      case EXCLAM: { // logical or bitwise not
+	ParseRestrictions entered_from_unary;
+	entered_from_unary.entered_from_unary = true;
+	if (!restrictions.can_be_struct_expr)
+	  entered_from_unary.can_be_struct_expr = false;
+	std::unique_ptr<AST::Expr> expr
+	  = parse_expr (LBP_UNARY_EXCLAM, {}, entered_from_unary);
+
+	if (expr == nullptr)
+	  return nullptr;
+	// can only apply to boolean expressions
+	/*if (expr.get_type() != boolean_type_node) {
+	    rust_error_at(tok->get_locus(),
+	      "operand of logical not must be a boolean but it is %s",
+	      print_type(expr.get_type()));
+	    return Tree::error();
+	}*/
+	/* FIXME: type checking for boolean or integer expressions in semantic
+	 * analysis */
+
+	// FIXME: allow outer attributes on these expressions
+	return std::unique_ptr<AST::NegationExpr> (
+	  new AST::NegationExpr (std::move (expr), NegationOperator::NOT,
+				 std::move (outer_attrs), tok->get_locus ()));
+      }
+      case ASTERISK: {
+	/* pointer dereference only - HACK: as struct expressions should
+	 * always be value expressions, cannot be dereferenced */
+	ParseRestrictions entered_from_unary;
+	entered_from_unary.entered_from_unary = true;
+	entered_from_unary.can_be_struct_expr = false;
+	std::unique_ptr<AST::Expr> expr
+	  = parse_expr (LBP_UNARY_ASTERISK, {}, entered_from_unary);
+	// FIXME: allow outer attributes on expression
+	return std::unique_ptr<AST::DereferenceExpr> (
+	  new AST::DereferenceExpr (std::move (expr), std::move (outer_attrs),
+				    tok->get_locus ()));
+      }
+      case AMP: {
+	// (single) "borrow" expression - shared (mutable) or immutable
+	std::unique_ptr<AST::Expr> expr = nullptr;
+	bool is_mut_borrow = false;
+
+	/* HACK: as struct expressions should always be value expressions,
+	 * cannot be referenced */
+	ParseRestrictions entered_from_unary;
+	entered_from_unary.entered_from_unary = true;
+	entered_from_unary.can_be_struct_expr = false;
+
+	if (lexer.peek_token ()->get_id () == MUT)
+	  {
+	    lexer.skip_token ();
+	    expr = parse_expr (LBP_UNARY_AMP_MUT, {}, entered_from_unary);
+	    is_mut_borrow = true;
+	  }
+	else
+	  {
+	    expr = parse_expr (LBP_UNARY_AMP, {}, entered_from_unary);
+	  }
+
+	// FIXME: allow outer attributes on expression
+	return std::unique_ptr<AST::BorrowExpr> (
+	  new AST::BorrowExpr (std::move (expr), is_mut_borrow, false,
+			       std::move (outer_attrs), tok->get_locus ()));
+      }
+      case LOGICAL_AND: {
+	// (double) "borrow" expression - shared (mutable) or immutable
+	std::unique_ptr<AST::Expr> expr = nullptr;
+	bool is_mut_borrow = false;
+
+	ParseRestrictions entered_from_unary;
+	entered_from_unary.entered_from_unary = true;
+
+	if (lexer.peek_token ()->get_id () == MUT)
+	  {
+	    lexer.skip_token ();
+	    expr = parse_expr (LBP_UNARY_AMP_MUT, {}, entered_from_unary);
+	    is_mut_borrow = true;
+	  }
+	else
+	  {
+	    expr = parse_expr (LBP_UNARY_AMP, {}, entered_from_unary);
+	  }
+
+	// FIXME: allow outer attributes on expression
+	return std::unique_ptr<AST::BorrowExpr> (
+	  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:
+      // closure expression
+      return parse_closure_expr_pratt (tok, std::move (outer_attrs));
+    case DOT_DOT:
+      // either "range to" or "range full" expressions
+      return parse_nud_range_exclusive_expr (tok, std::move (outer_attrs));
+    case DOT_DOT_EQ:
+      // range to inclusive expr
+      return parse_range_to_inclusive_expr (tok, std::move (outer_attrs));
+    case RETURN_TOK:
+      // FIXME: is this really a null denotation expression?
+      return parse_return_expr (std::move (outer_attrs), tok->get_locus ());
+    case BREAK:
+      // FIXME: is this really a null denotation expression?
+      return parse_break_expr (std::move (outer_attrs), tok->get_locus ());
+    case CONTINUE:
+      return parse_continue_expr (std::move (outer_attrs), tok->get_locus ());
+    case LEFT_CURLY:
+      // ok - this is an expression with block for once.
+      return parse_block_expr (std::move (outer_attrs), tok->get_locus ());
+    case IF:
+      // if or if let, so more lookahead to find out
+      if (lexer.peek_token (1)->get_id () == LET)
+	{
+	  // if let expr
+	  return parse_if_let_expr (std::move (outer_attrs), tok->get_locus ());
+	}
+      else
+	{
+	  // if expr
+	  return parse_if_expr (std::move (outer_attrs), tok->get_locus ());
+	}
+    case LOOP:
+      return parse_loop_expr (std::move (outer_attrs), AST::LoopLabel::error (),
+			      tok->get_locus ());
+    case WHILE:
+      return parse_while_loop_expr (std::move (outer_attrs),
+				    AST::LoopLabel::error (),
+				    tok->get_locus ());
+    case MATCH_TOK:
+      // also an expression with block
+      return parse_match_expr (std::move (outer_attrs), tok->get_locus ());
+    case LEFT_SQUARE:
+      // array definition expr (not indexing)
+      return parse_array_expr (std::move (outer_attrs), tok->get_locus ());
+    case UNSAFE:
+      return parse_unsafe_block_expr (std::move (outer_attrs),
+				      tok->get_locus ());
+    default:
+      if (!restrictions.expr_can_be_null)
+	add_error (Error (tok->get_locus (),
+			  "found unexpected token %qs in null denotation",
+			  tok->get_token_description ()));
+      return nullptr;
+    }
+}
+
+/* Called for each token that can appear in infix (between) position. Can be
+ * operators or other punctuation. Returns a function pointer to member
+ * function that implements the left denotation for the token given. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::Expr>
+Parser<ManagedTokenSource>::left_denotation (const_TokenPtr tok,
+					     std::unique_ptr<AST::Expr> left,
+					     AST::AttrVec outer_attrs,
+					     ParseRestrictions restrictions)
+{
+  // Token passed in has already been skipped, so peek gives "next" token
+  switch (tok->get_id ())
+    {
+      // FIXME: allow for outer attributes to be applied
+      case QUESTION_MARK: {
+	Location left_locus = left->get_locus ();
+	// error propagation expression - unary postfix
+	return std::unique_ptr<AST::ErrorPropagationExpr> (
+	  new AST::ErrorPropagationExpr (std::move (left),
+					 std::move (outer_attrs), left_locus));
+      }
+    case PLUS:
+      // sum expression - binary infix
+      /*return parse_binary_plus_expr (tok, std::move (left),
+				     std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (tok, std::move (left),
+					       std::move (outer_attrs),
+					       ArithmeticOrLogicalOperator::ADD,
+					       restrictions);
+    case MINUS:
+      // difference expression - binary infix
+      /*return parse_binary_minus_expr (tok, std::move (left),
+				      std::move (outer_attrs),
+	 restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::SUBTRACT, restrictions);
+    case ASTERISK:
+      // product expression - binary infix
+      /*return parse_binary_mult_expr (tok, std::move (left),
+				     std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::MULTIPLY, restrictions);
+    case DIV:
+      // quotient expression - binary infix
+      /*return parse_binary_div_expr (tok, std::move (left),
+				    std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::DIVIDE, restrictions);
+    case PERCENT:
+      // modulo expression - binary infix
+      /*return parse_binary_mod_expr (tok, std::move (left),
+				    std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::MODULUS, restrictions);
+    case AMP:
+      // logical or bitwise and expression - binary infix
+      /*return parse_bitwise_and_expr (tok, std::move (left),
+				     std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::BITWISE_AND, restrictions);
+    case PIPE:
+      // logical or bitwise or expression - binary infix
+      /*return parse_bitwise_or_expr (tok, std::move (left),
+				    std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::BITWISE_OR, restrictions);
+    case CARET:
+      // logical or bitwise xor expression - binary infix
+      /*return parse_bitwise_xor_expr (tok, std::move (left),
+				     std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::BITWISE_XOR, restrictions);
+    case LEFT_SHIFT:
+      // left shift expression - binary infix
+      /*return parse_left_shift_expr (tok, std::move (left),
+				    std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::LEFT_SHIFT, restrictions);
+    case RIGHT_SHIFT:
+      // right shift expression - binary infix
+      /*return parse_right_shift_expr (tok, std::move (left),
+				     std::move (outer_attrs), restrictions);*/
+      return parse_arithmetic_or_logical_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	ArithmeticOrLogicalOperator::RIGHT_SHIFT, restrictions);
+    case EQUAL_EQUAL:
+      // equal to expression - binary infix (no associativity)
+      /*return parse_binary_equal_expr (tok, std::move (left),
+				      std::move (outer_attrs),
+	 restrictions);*/
+      return parse_comparison_expr (tok, std::move (left),
+				    std::move (outer_attrs),
+				    ComparisonOperator::EQUAL, restrictions);
+    case NOT_EQUAL:
+      // not equal to expression - binary infix (no associativity)
+      /*return parse_binary_not_equal_expr (tok, std::move (left),
+					  std::move (outer_attrs),
+					  restrictions);*/
+      return parse_comparison_expr (tok, std::move (left),
+				    std::move (outer_attrs),
+				    ComparisonOperator::NOT_EQUAL,
+				    restrictions);
+    case RIGHT_ANGLE:
+      // greater than expression - binary infix (no associativity)
+      /*return parse_binary_greater_than_expr (tok, std::move (left),
+					     std::move (outer_attrs),
+					     restrictions);*/
+      return parse_comparison_expr (tok, std::move (left),
+				    std::move (outer_attrs),
+				    ComparisonOperator::GREATER_THAN,
+				    restrictions);
+    case LEFT_ANGLE:
+      // less than expression - binary infix (no associativity)
+      /*return parse_binary_less_than_expr (tok, std::move (left),
+					  std::move (outer_attrs),
+					  restrictions);*/
+      return parse_comparison_expr (tok, std::move (left),
+				    std::move (outer_attrs),
+				    ComparisonOperator::LESS_THAN,
+				    restrictions);
+    case GREATER_OR_EQUAL:
+      // greater than or equal to expression - binary infix (no associativity)
+      /*return parse_binary_greater_equal_expr (tok, std::move (left),
+					      std::move (outer_attrs),
+					      restrictions);*/
+      return parse_comparison_expr (tok, std::move (left),
+				    std::move (outer_attrs),
+				    ComparisonOperator::GREATER_OR_EQUAL,
+				    restrictions);
+    case LESS_OR_EQUAL:
+      // less than or equal to expression - binary infix (no associativity)
+      /*return parse_binary_less_equal_expr (tok, std::move (left),
+					   std::move (outer_attrs),
+					   restrictions);*/
+      return parse_comparison_expr (tok, std::move (left),
+				    std::move (outer_attrs),
+				    ComparisonOperator::LESS_OR_EQUAL,
+				    restrictions);
+    case OR:
+      // lazy logical or expression - binary infix
+      return parse_lazy_or_expr (tok, std::move (left), std::move (outer_attrs),
+				 restrictions);
+    case LOGICAL_AND:
+      // lazy logical and expression - binary infix
+      return parse_lazy_and_expr (tok, std::move (left),
+				  std::move (outer_attrs), restrictions);
+    case AS:
+      /* type cast expression - kind of binary infix (RHS is actually a
+       * TypeNoBounds) */
+      return parse_type_cast_expr (tok, std::move (left),
+				   std::move (outer_attrs), restrictions);
+    case EQUAL:
+      // assignment expression - binary infix (note right-to-left
+      // associativity)
+      return parse_assig_expr (tok, std::move (left), std::move (outer_attrs),
+			       restrictions);
+    case PLUS_EQ:
+      /* plus-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_plus_assig_expr (tok, std::move (left),
+				    std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (tok, std::move (left),
+					     std::move (outer_attrs),
+					     CompoundAssignmentOperator::ADD,
+					     restrictions);
+    case MINUS_EQ:
+      /* minus-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_minus_assig_expr (tok, std::move (left),
+				     std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::SUBTRACT, restrictions);
+    case ASTERISK_EQ:
+      /* multiply-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_mult_assig_expr (tok, std::move (left),
+				    std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::MULTIPLY, restrictions);
+    case DIV_EQ:
+      /* division-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_div_assig_expr (tok, std::move (left),
+				   std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (tok, std::move (left),
+					     std::move (outer_attrs),
+					     CompoundAssignmentOperator::DIVIDE,
+					     restrictions);
+    case PERCENT_EQ:
+      /* modulo-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_mod_assig_expr (tok, std::move (left),
+				   std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::MODULUS, restrictions);
+    case AMP_EQ:
+      /* bitwise and-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_and_assig_expr (tok, std::move (left),
+				   std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::BITWISE_AND, restrictions);
+    case PIPE_EQ:
+      /* bitwise or-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_or_assig_expr (tok, std::move (left),
+				  std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::BITWISE_OR, restrictions);
+    case CARET_EQ:
+      /* bitwise xor-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_xor_assig_expr (tok, std::move (left),
+				   std::move (outer_attrs), restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::BITWISE_XOR, restrictions);
+    case LEFT_SHIFT_EQ:
+      /* left shift-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_left_shift_assig_expr (tok, std::move (left),
+					  std::move (outer_attrs),
+					  restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::LEFT_SHIFT, restrictions);
+    case RIGHT_SHIFT_EQ:
+      /* right shift-assignment expression - binary infix (note right-to-left
+       * associativity) */
+      /*return parse_right_shift_assig_expr (tok, std::move (left),
+					   std::move (outer_attrs),
+					   restrictions);*/
+      return parse_compound_assignment_expr (
+	tok, std::move (left), std::move (outer_attrs),
+	CompoundAssignmentOperator::RIGHT_SHIFT, restrictions);
+    case DOT_DOT:
+      /* range exclusive expression - binary infix (no associativity)
+       * either "range" or "range from" */
+      return parse_led_range_exclusive_expr (tok, std::move (left),
+					     std::move (outer_attrs),
+					     restrictions);
+    case DOT_DOT_EQ:
+      /* range inclusive expression - binary infix (no associativity)
+       * unambiguously RangeInclusiveExpr */
+      return parse_range_inclusive_expr (tok, std::move (left),
+					 std::move (outer_attrs), restrictions);
+    case SCOPE_RESOLUTION:
+      // path expression - binary infix? FIXME should this even be parsed
+      // here?
+      add_error (
+	Error (tok->get_locus (),
+	       "found scope resolution operator in left denotation "
+	       "function - this should probably be handled elsewhere"));
+
+      return nullptr;
+      case DOT: {
+	/* field expression or method call - relies on parentheses after next
+	 * identifier or await if token after is "await" (unary postfix) or
+	 * tuple index if token after is a decimal int literal */
+
+	const_TokenPtr next_tok = lexer.peek_token ();
+	if (next_tok->get_id () == IDENTIFIER
+	    && next_tok->get_str () == "await")
+	  {
+	    // await expression
+	    return parse_await_expr (tok, std::move (left),
+				     std::move (outer_attrs));
+	  }
+	else if (next_tok->get_id () == INT_LITERAL)
+	  {
+	    // tuple index expression - TODO check for decimal int literal
+	    return parse_tuple_index_expr (tok, std::move (left),
+					   std::move (outer_attrs),
+					   restrictions);
+	  }
+	else if (next_tok->get_id () == IDENTIFIER
+		 && lexer.peek_token (1)->get_id () != LEFT_PAREN
+		 && lexer.peek_token (1)->get_id () != SCOPE_RESOLUTION)
+	  {
+	    /* field expression (or should be) - FIXME: scope resolution right
+	     * after identifier should always be method, I'm pretty sure */
+	    return parse_field_access_expr (tok, std::move (left),
+					    std::move (outer_attrs),
+					    restrictions);
+	  }
+	else
+	  {
+	    // method call (probably)
+	    return parse_method_call_expr (tok, std::move (left),
+					   std::move (outer_attrs),
+					   restrictions);
+	  }
+      }
+    case LEFT_PAREN:
+      // function call - method call is based on dot notation first
+      return parse_function_call_expr (tok, std::move (left),
+				       std::move (outer_attrs), restrictions);
+    case LEFT_SQUARE:
+      // array or slice index expression (pseudo binary infix)
+      return parse_index_expr (tok, std::move (left), std::move (outer_attrs),
+			       restrictions);
+    case FLOAT_LITERAL:
+      /* HACK: get around lexer mis-identifying '.0' or '.1' or whatever as a
+       * float literal - TODO does this happen anymore? It shouldn't. */
+      return parse_tuple_index_expr_float (tok, std::move (left),
+					   std::move (outer_attrs),
+					   restrictions);
+    default:
+      add_error (Error (tok->get_locus (),
+			"found unexpected token %qs in left denotation",
+			tok->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+/* Returns the left binding power for the given ArithmeticOrLogicalExpr type.
+ * TODO make constexpr? Would that even do anything useful? */
+inline binding_powers
+get_lbp_for_arithmetic_or_logical_expr (
+  AST::ArithmeticOrLogicalExpr::ExprType expr_type)
+{
+  switch (expr_type)
+    {
+    case ArithmeticOrLogicalOperator::ADD:
+      return LBP_PLUS;
+    case ArithmeticOrLogicalOperator::SUBTRACT:
+      return LBP_MINUS;
+    case ArithmeticOrLogicalOperator::MULTIPLY:
+      return LBP_MUL;
+    case ArithmeticOrLogicalOperator::DIVIDE:
+      return LBP_DIV;
+    case ArithmeticOrLogicalOperator::MODULUS:
+      return LBP_MOD;
+    case ArithmeticOrLogicalOperator::BITWISE_AND:
+      return LBP_AMP;
+    case ArithmeticOrLogicalOperator::BITWISE_OR:
+      return LBP_PIPE;
+    case ArithmeticOrLogicalOperator::BITWISE_XOR:
+      return LBP_CARET;
+    case ArithmeticOrLogicalOperator::LEFT_SHIFT:
+      return LBP_L_SHIFT;
+    case ArithmeticOrLogicalOperator::RIGHT_SHIFT:
+      return LBP_R_SHIFT;
+    default:
+      // WTF? should not happen, this is an error
+      gcc_unreachable ();
+
+      return LBP_PLUS;
+    }
+}
+
+// Parses an arithmetic or logical expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_arithmetic_or_logical_expr (
+  const_TokenPtr, std::unique_ptr<AST::Expr> left, AST::AttrVec,
+  AST::ArithmeticOrLogicalExpr::ExprType expr_type,
+  ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (get_lbp_for_arithmetic_or_logical_expr (expr_type),
+		  AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      expr_type, locus));
+}
+
+// Parses a binary addition expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_binary_plus_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_PLUS, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::ADD, locus));
+}
+
+// Parses a binary subtraction expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_binary_minus_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_MINUS, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::SUBTRACT,
+				      locus));
+}
+
+// Parses a binary multiplication expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_binary_mult_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_MUL, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::MULTIPLY,
+				      locus));
+}
+
+// Parses a binary division expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_binary_div_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_DIV, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::DIVIDE,
+				      locus));
+}
+
+// Parses a binary modulo expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_binary_mod_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_MOD, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::MODULUS,
+				      locus));
+}
+
+/* Parses a binary bitwise (or eager logical) and expression (with Pratt
+ * parsing). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_bitwise_and_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_AMP, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::BITWISE_AND,
+				      locus));
+}
+
+/* Parses a binary bitwise (or eager logical) or expression (with Pratt
+ * parsing). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_bitwise_or_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_PIPE, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::BITWISE_OR,
+				      locus));
+}
+
+/* Parses a binary bitwise (or eager logical) xor expression (with Pratt
+ * parsing). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_bitwise_xor_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_CARET, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::BITWISE_XOR,
+				      locus));
+}
+
+// Parses a binary left shift expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_left_shift_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_L_SHIFT, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::LEFT_SHIFT,
+				      locus));
+}
+
+// Parses a binary right shift expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArithmeticOrLogicalExpr>
+Parser<ManagedTokenSource>::parse_right_shift_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_R_SHIFT, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ArithmeticOrLogicalExpr> (
+    new AST::ArithmeticOrLogicalExpr (std::move (left), std::move (right),
+				      ArithmeticOrLogicalOperator::RIGHT_SHIFT,
+				      locus));
+}
+
+/* Returns the left binding power for the given ComparisonExpr type.
+ * TODO make constexpr? Would that even do anything useful? */
+inline binding_powers
+get_lbp_for_comparison_expr (AST::ComparisonExpr::ExprType expr_type)
+{
+  switch (expr_type)
+    {
+    case ComparisonOperator::EQUAL:
+      return LBP_EQUAL;
+    case ComparisonOperator::NOT_EQUAL:
+      return LBP_NOT_EQUAL;
+    case ComparisonOperator::GREATER_THAN:
+      return LBP_GREATER_THAN;
+    case ComparisonOperator::LESS_THAN:
+      return LBP_SMALLER_THAN;
+    case ComparisonOperator::GREATER_OR_EQUAL:
+      return LBP_GREATER_EQUAL;
+    case ComparisonOperator::LESS_OR_EQUAL:
+      return LBP_SMALLER_EQUAL;
+    default:
+      // WTF? should not happen, this is an error
+      gcc_unreachable ();
+
+      return LBP_EQUAL;
+    }
+}
+
+/* Parses a ComparisonExpr of given type and LBP. TODO find a way to only
+ * specify one and have the other looked up - e.g. specify ExprType and
+ * binding power is looked up? */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ComparisonExpr>
+Parser<ManagedTokenSource>::parse_comparison_expr (
+  const_TokenPtr, std::unique_ptr<AST::Expr> left, AST::AttrVec,
+  AST::ComparisonExpr::ExprType expr_type, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (get_lbp_for_comparison_expr (expr_type), AST::AttrVec (),
+		  restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ComparisonExpr> (
+    new AST::ComparisonExpr (std::move (left), std::move (right), expr_type,
+			     locus));
+}
+
+// Parses a binary equal to expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ComparisonExpr>
+Parser<ManagedTokenSource>::parse_binary_equal_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_EQUAL, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ComparisonExpr> (
+    new AST::ComparisonExpr (std::move (left), std::move (right),
+			     ComparisonOperator::EQUAL, locus));
+}
+
+// Parses a binary not equal to expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ComparisonExpr>
+Parser<ManagedTokenSource>::parse_binary_not_equal_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_NOT_EQUAL, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ComparisonExpr> (
+    new AST::ComparisonExpr (std::move (left), std::move (right),
+			     ComparisonOperator::NOT_EQUAL, locus));
+}
+
+// Parses a binary greater than expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ComparisonExpr>
+Parser<ManagedTokenSource>::parse_binary_greater_than_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_GREATER_THAN, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ComparisonExpr> (
+    new AST::ComparisonExpr (std::move (left), std::move (right),
+			     ComparisonOperator::GREATER_THAN, locus));
+}
+
+// Parses a binary less than expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ComparisonExpr>
+Parser<ManagedTokenSource>::parse_binary_less_than_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_SMALLER_THAN, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ComparisonExpr> (
+    new AST::ComparisonExpr (std::move (left), std::move (right),
+			     ComparisonOperator::LESS_THAN, locus));
+}
+
+// Parses a binary greater than or equal to expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ComparisonExpr>
+Parser<ManagedTokenSource>::parse_binary_greater_equal_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_GREATER_EQUAL, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ComparisonExpr> (
+    new AST::ComparisonExpr (std::move (left), std::move (right),
+			     ComparisonOperator::GREATER_OR_EQUAL, locus));
+}
+
+// Parses a binary less than or equal to expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ComparisonExpr>
+Parser<ManagedTokenSource>::parse_binary_less_equal_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_SMALLER_EQUAL, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::ComparisonExpr> (
+    new AST::ComparisonExpr (std::move (left), std::move (right),
+			     ComparisonOperator::LESS_OR_EQUAL, locus));
+}
+
+// Parses a binary lazy boolean or expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::LazyBooleanExpr>
+Parser<ManagedTokenSource>::parse_lazy_or_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_LOGICAL_OR, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::LazyBooleanExpr> (
+    new AST::LazyBooleanExpr (std::move (left), std::move (right),
+			      LazyBooleanOperator::LOGICAL_OR, locus));
+}
+
+// Parses a binary lazy boolean and expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::LazyBooleanExpr>
+Parser<ManagedTokenSource>::parse_lazy_and_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_LOGICAL_AND, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::LazyBooleanExpr> (
+    new AST::LazyBooleanExpr (std::move (left), std::move (right),
+			      LazyBooleanOperator::LOGICAL_AND, locus));
+}
+
+// Parses a pseudo-binary infix type cast expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::TypeCastExpr>
+Parser<ManagedTokenSource>::parse_type_cast_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> expr_to_cast,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED,
+  ParseRestrictions restrictions ATTRIBUTE_UNUSED)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::TypeNoBounds> type = parse_type_no_bounds ();
+  if (type == nullptr)
+    return nullptr;
+  // FIXME: how do I get precedence put in here?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = expr_to_cast->get_locus ();
+
+  return std::unique_ptr<AST::TypeCastExpr> (
+    new AST::TypeCastExpr (std::move (expr_to_cast), std::move (type), locus));
+}
+
+// Parses a binary assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::AssignmentExpr>
+Parser<ManagedTokenSource>::parse_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::AssignmentExpr> (
+    new AST::AssignmentExpr (std::move (left), std::move (right),
+			     std::move (outer_attrs), locus));
+}
+
+/* Returns the left binding power for the given CompoundAssignmentExpr type.
+ * TODO make constexpr? Would that even do anything useful? */
+inline binding_powers
+get_lbp_for_compound_assignment_expr (
+  AST::CompoundAssignmentExpr::ExprType expr_type)
+{
+  switch (expr_type)
+    {
+    case CompoundAssignmentOperator::ADD:
+      return LBP_PLUS;
+    case CompoundAssignmentOperator::SUBTRACT:
+      return LBP_MINUS;
+    case CompoundAssignmentOperator::MULTIPLY:
+      return LBP_MUL;
+    case CompoundAssignmentOperator::DIVIDE:
+      return LBP_DIV;
+    case CompoundAssignmentOperator::MODULUS:
+      return LBP_MOD;
+    case CompoundAssignmentOperator::BITWISE_AND:
+      return LBP_AMP;
+    case CompoundAssignmentOperator::BITWISE_OR:
+      return LBP_PIPE;
+    case CompoundAssignmentOperator::BITWISE_XOR:
+      return LBP_CARET;
+    case CompoundAssignmentOperator::LEFT_SHIFT:
+      return LBP_L_SHIFT;
+    case CompoundAssignmentOperator::RIGHT_SHIFT:
+      return LBP_R_SHIFT;
+    default:
+      // WTF? should not happen, this is an error
+      gcc_unreachable ();
+
+      return LBP_PLUS;
+    }
+}
+
+// Parses a compound assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_compound_assignment_expr (
+  const_TokenPtr, std::unique_ptr<AST::Expr> left, AST::AttrVec,
+  AST::CompoundAssignmentExpr::ExprType expr_type,
+  ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (get_lbp_for_compound_assignment_expr (expr_type) - 1,
+		  AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     expr_type, locus));
+}
+
+// Parses a binary add-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_plus_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_PLUS_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::ADD, locus));
+}
+
+// Parses a binary minus-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_minus_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_MINUS_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::SUBTRACT,
+				     locus));
+}
+
+// Parses a binary multiplication-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_mult_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_MULT_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::MULTIPLY,
+				     locus));
+}
+
+// Parses a binary division-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_div_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_DIV_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::DIVIDE,
+				     locus));
+}
+
+// Parses a binary modulo-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_mod_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_MOD_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::MODULUS,
+				     locus));
+}
+
+// Parses a binary and-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_and_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_AMP_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::BITWISE_AND,
+				     locus));
+}
+
+// Parses a binary or-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_or_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_PIPE_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::BITWISE_OR,
+				     locus));
+}
+
+// Parses a binary xor-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_xor_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_CARET_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::BITWISE_XOR,
+				     locus));
+}
+
+// Parses a binary left shift-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_left_shift_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_L_SHIFT_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::LEFT_SHIFT,
+				     locus));
+}
+
+// Parses a binary right shift-assignment expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CompoundAssignmentExpr>
+Parser<ManagedTokenSource>::parse_right_shift_assig_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_R_SHIFT_ASSIG - 1, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: ensure right-associativity for this - 'LBP - 1' may do this?
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::CompoundAssignmentExpr> (
+    new AST::CompoundAssignmentExpr (std::move (left), std::move (right),
+				     CompoundAssignmentOperator::RIGHT_SHIFT,
+				     locus));
+}
+
+// Parses a postfix unary await expression (with Pratt parsing).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::AwaitExpr>
+Parser<ManagedTokenSource>::parse_await_expr (
+  const_TokenPtr tok, std::unique_ptr<AST::Expr> expr_to_await,
+  AST::AttrVec outer_attrs)
+{
+  /* skip "await" identifier (as "." has already been consumed in
+   * parse_expression) this assumes that the identifier was already identified
+   * as await */
+  if (!skip_token (IDENTIFIER))
+    {
+      Error error (tok->get_locus (), "failed to skip %<await%> in await expr "
+				      "- this is probably a deep issue");
+      add_error (std::move (error));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // TODO: check inside async block in semantic analysis
+  Location locus = expr_to_await->get_locus ();
+
+  return std::unique_ptr<AST::AwaitExpr> (
+    new AST::AwaitExpr (std::move (expr_to_await), std::move (outer_attrs),
+			locus));
+}
+
+/* Parses an exclusive range ('..') in left denotation position (i.e.
+ * RangeFromExpr or RangeFromToExpr). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::RangeExpr>
+Parser<ManagedTokenSource>::parse_led_range_exclusive_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // FIXME: this probably parses expressions accidently or whatever
+  // try parsing RHS (as tok has already been consumed in parse_expression)
+  // Can be nullptr, in which case it is a RangeFromExpr, otherwise a
+  // RangeFromToExpr.
+  restrictions.expr_can_be_null = true;
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_DOT_DOT, AST::AttrVec (), restrictions);
+
+  Location locus = left->get_locus ();
+
+  if (right == nullptr)
+    {
+      // range from expr
+      return std::unique_ptr<AST::RangeFromExpr> (
+	new AST::RangeFromExpr (std::move (left), locus));
+    }
+  else
+    {
+      return std::unique_ptr<AST::RangeFromToExpr> (
+	new AST::RangeFromToExpr (std::move (left), std::move (right), locus));
+    }
+  // FIXME: make non-associative
+}
+
+/* Parses an exclusive range ('..') in null denotation position (i.e.
+ * RangeToExpr or RangeFullExpr). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::RangeExpr>
+Parser<ManagedTokenSource>::parse_nud_range_exclusive_expr (
+  const_TokenPtr tok, AST::AttrVec outer_attrs ATTRIBUTE_UNUSED)
+{
+  // FIXME: this probably parses expressions accidently or whatever
+  // try parsing RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right = parse_expr (LBP_DOT_DOT, AST::AttrVec ());
+
+  Location locus = tok->get_locus ();
+
+  if (right == nullptr)
+    {
+      // range from expr
+      return std::unique_ptr<AST::RangeFullExpr> (
+	new AST::RangeFullExpr (locus));
+    }
+  else
+    {
+      return std::unique_ptr<AST::RangeToExpr> (
+	new AST::RangeToExpr (std::move (right), locus));
+    }
+  // FIXME: make non-associative
+}
+
+// Parses a full binary range inclusive expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::RangeFromToInclExpr>
+Parser<ManagedTokenSource>::parse_range_inclusive_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> left,
+  AST::AttrVec outer_attrs ATTRIBUTE_UNUSED, ParseRestrictions restrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right
+    = parse_expr (LBP_DOT_DOT_EQ, AST::AttrVec (), restrictions);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: make non-associative
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = left->get_locus ();
+
+  return std::unique_ptr<AST::RangeFromToInclExpr> (
+    new AST::RangeFromToInclExpr (std::move (left), std::move (right), locus));
+}
+
+// Parses an inclusive range-to prefix unary expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::RangeToInclExpr>
+Parser<ManagedTokenSource>::parse_range_to_inclusive_expr (
+  const_TokenPtr tok, AST::AttrVec outer_attrs ATTRIBUTE_UNUSED)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  std::unique_ptr<AST::Expr> right = parse_expr (LBP_DOT_DOT_EQ);
+  if (right == nullptr)
+    return nullptr;
+  // FIXME: make non-associative
+
+  // TODO: check types. actually, do so during semantic analysis
+
+  return std::unique_ptr<AST::RangeToInclExpr> (
+    new AST::RangeToInclExpr (std::move (right), tok->get_locus ()));
+}
+
+// Parses a pseudo-binary infix tuple index expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::TupleIndexExpr>
+Parser<ManagedTokenSource>::parse_tuple_index_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> tuple_expr,
+  AST::AttrVec outer_attrs, ParseRestrictions restrictions ATTRIBUTE_UNUSED)
+{
+  // parse int literal (as token already skipped)
+  const_TokenPtr index_tok = expect_token (INT_LITERAL);
+  if (index_tok == nullptr)
+    {
+      return nullptr;
+    }
+  std::string index = index_tok->get_str ();
+
+  // convert to integer
+  if (!index_tok->is_pure_decimal ())
+    {
+      Error error (index_tok->get_locus (),
+		   "tuple index should be a pure decimal literal");
+      add_error (std::move (error));
+    }
+  int index_int = atoi (index.c_str ());
+
+  Location locus = tuple_expr->get_locus ();
+
+  return std::unique_ptr<AST::TupleIndexExpr> (
+    new AST::TupleIndexExpr (std::move (tuple_expr), index_int,
+			     std::move (outer_attrs), locus));
+}
+
+// Parses a pseudo-binary infix array (or slice) index expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ArrayIndexExpr>
+Parser<ManagedTokenSource>::parse_index_expr (
+  const_TokenPtr, std::unique_ptr<AST::Expr> array_expr,
+  AST::AttrVec outer_attrs, ParseRestrictions)
+{
+  // parse RHS (as tok has already been consumed in parse_expression)
+  /*std::unique_ptr<AST::Expr> index_expr
+    = parse_expr (LBP_ARRAY_REF, AST::AttrVec (),
+    restrictions);*/
+  // TODO: conceptually, should treat [] as brackets, so just parse all expr
+  std::unique_ptr<AST::Expr> index_expr = parse_expr ();
+  if (index_expr == nullptr)
+    return nullptr;
+
+  // skip ']' at end of array
+  if (!skip_token (RIGHT_SQUARE))
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = array_expr->get_locus ();
+
+  return std::unique_ptr<AST::ArrayIndexExpr> (
+    new AST::ArrayIndexExpr (std::move (array_expr), std::move (index_expr),
+			     std::move (outer_attrs), locus));
+}
+
+// Parses a pseudo-binary infix struct field access expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::FieldAccessExpr>
+Parser<ManagedTokenSource>::parse_field_access_expr (
+  const_TokenPtr tok ATTRIBUTE_UNUSED, std::unique_ptr<AST::Expr> struct_expr,
+  AST::AttrVec outer_attrs, ParseRestrictions restrictions ATTRIBUTE_UNUSED)
+{
+  /* get field name identifier (assume that this is a field access expr and
+   * not await, for instance) */
+  const_TokenPtr ident_tok = expect_token (IDENTIFIER);
+  if (ident_tok == nullptr)
+    return nullptr;
+
+  Identifier ident = ident_tok->get_str ();
+
+  Location locus = struct_expr->get_locus ();
+
+  // TODO: check types. actually, do so during semantic analysis
+  return std::unique_ptr<AST::FieldAccessExpr> (
+    new AST::FieldAccessExpr (std::move (struct_expr), std::move (ident),
+			      std::move (outer_attrs), locus));
+}
+
+// Parses a pseudo-binary infix method call expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::MethodCallExpr>
+Parser<ManagedTokenSource>::parse_method_call_expr (
+  const_TokenPtr tok, std::unique_ptr<AST::Expr> receiver_expr,
+  AST::AttrVec outer_attrs, ParseRestrictions)
+{
+  // parse path expr segment
+  AST::PathExprSegment segment = parse_path_expr_segment ();
+  if (segment.is_error ())
+    {
+      Error error (tok->get_locus (),
+		   "failed to parse path expr segment of method call expr");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  // skip left parentheses
+  if (!skip_token (LEFT_PAREN))
+    {
+      return nullptr;
+    }
+
+  // parse method params (if they exist)
+  std::vector<std::unique_ptr<AST::Expr>> params;
+
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () != RIGHT_PAREN)
+    {
+      std::unique_ptr<AST::Expr> param = parse_expr ();
+      if (param == nullptr)
+	{
+	  Error error (t->get_locus (),
+		       "failed to parse method param in method call");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      params.push_back (std::move (param));
+
+      if (lexer.peek_token ()->get_id () != COMMA)
+	break;
+
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  // skip right paren
+  if (!skip_token (RIGHT_PAREN))
+    {
+      return nullptr;
+    }
+
+  // TODO: check types. actually do so in semantic analysis pass.
+  Location locus = receiver_expr->get_locus ();
+
+  return std::unique_ptr<AST::MethodCallExpr> (
+    new AST::MethodCallExpr (std::move (receiver_expr), std::move (segment),
+			     std::move (params), std::move (outer_attrs),
+			     locus));
+}
+
+// Parses a pseudo-binary infix function call expression.
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CallExpr>
+Parser<ManagedTokenSource>::parse_function_call_expr (
+  const_TokenPtr, std::unique_ptr<AST::Expr> function_expr,
+  AST::AttrVec outer_attrs, ParseRestrictions)
+{
+  // parse function params (if they exist)
+  std::vector<std::unique_ptr<AST::Expr>> params;
+
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () != RIGHT_PAREN)
+    {
+      std::unique_ptr<AST::Expr> param = parse_expr ();
+      if (param == nullptr)
+	{
+	  Error error (t->get_locus (),
+		       "failed to parse function param in function call");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      params.push_back (std::move (param));
+
+      if (lexer.peek_token ()->get_id () != COMMA)
+	break;
+
+      lexer.skip_token ();
+      t = lexer.peek_token ();
+    }
+
+  // skip ')' at end of param list
+  if (!skip_token (RIGHT_PAREN))
+    {
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // TODO: check types. actually, do so during semantic analysis
+  Location locus = function_expr->get_locus ();
+
+  return std::unique_ptr<AST::CallExpr> (
+    new AST::CallExpr (std::move (function_expr), std::move (params),
+		       std::move (outer_attrs), locus));
+}
+
+/* Parses a macro invocation with a path in expression already parsed (but not
+ * '!' token). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::MacroInvocation>
+Parser<ManagedTokenSource>::parse_macro_invocation_partial (
+  AST::PathInExpression path, AST::AttrVec outer_attrs,
+  ParseRestrictions restrictions)
+{
+  // macro invocation
+  if (!skip_token (EXCLAM))
+    {
+      return nullptr;
+    }
+
+  // convert PathInExpression to SimplePath - if this isn't possible, error
+  AST::SimplePath converted_path = path.as_simple_path ();
+  if (converted_path.is_empty ())
+    {
+      Error error (lexer.peek_token ()->get_locus (),
+		   "failed to parse simple path in macro invocation");
+      add_error (std::move (error));
+
+      return nullptr;
+    }
+
+  AST::DelimTokenTree tok_tree = parse_delim_token_tree ();
+
+  rust_debug ("successfully parsed macro invocation (via partial)");
+
+  Location macro_locus = converted_path.get_locus ();
+
+  return std::unique_ptr<AST::MacroInvocation> (new AST::MacroInvocation (
+    AST::MacroInvocData (std::move (converted_path), std::move (tok_tree)),
+    std::move (outer_attrs), macro_locus, restrictions.expr_can_be_stmt));
+}
+
+/* Parses a struct expr struct with a path in expression already parsed (but
+ * not
+ * '{' token). */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::StructExprStruct>
+Parser<ManagedTokenSource>::parse_struct_expr_struct_partial (
+  AST::PathInExpression path, AST::AttrVec outer_attrs)
+{
+  // assume struct expr struct (as struct-enum disambiguation requires name
+  // lookup) again, make statement if final ';'
+  if (!skip_token (LEFT_CURLY))
+    {
+      return nullptr;
+    }
+
+  // parse inner attributes
+  AST::AttrVec inner_attrs = parse_inner_attributes ();
+
+  // branch based on next token
+  const_TokenPtr t = lexer.peek_token ();
+  Location path_locus = path.get_locus ();
+  switch (t->get_id ())
+    {
+    case RIGHT_CURLY:
+      // struct with no body
+      lexer.skip_token ();
+
+      return std::unique_ptr<AST::StructExprStruct> (
+	new AST::StructExprStruct (std::move (path), std::move (inner_attrs),
+				   std::move (outer_attrs), path_locus));
+    case DOT_DOT:
+      /* technically this would give a struct base-only struct, but this
+       * algorithm should work too. As such, AST type not happening. */
+    case IDENTIFIER:
+      case INT_LITERAL: {
+	// struct with struct expr fields
+
+	// parse struct expr fields
+	std::vector<std::unique_ptr<AST::StructExprField>> fields;
+
+	while (t->get_id () != RIGHT_CURLY && t->get_id () != DOT_DOT)
+	  {
+	    std::unique_ptr<AST::StructExprField> field
+	      = parse_struct_expr_field ();
+	    if (field == nullptr)
+	      {
+		Error error (t->get_locus (),
+			     "failed to parse struct (or enum) expr field");
+		add_error (std::move (error));
+
+		return nullptr;
+	      }
+
+	    // DEBUG:
+	    rust_debug ("struct/enum expr field validated to not be null");
+
+	    fields.push_back (std::move (field));
+
+	    // DEBUG:
+	    rust_debug ("struct/enum expr field pushed back");
+
+	    if (lexer.peek_token ()->get_id () != COMMA)
+	      {
+		// DEBUG:
+		rust_debug ("lack of comma detected in struct/enum expr "
+			    "fields - break");
+		break;
+	      }
+	    lexer.skip_token ();
+
+	    // DEBUG:
+	    rust_debug ("struct/enum expr fields comma skipped ");
+
+	    t = lexer.peek_token ();
+	  }
+
+	// DEBUG:
+	rust_debug ("struct/enum expr about to parse struct base ");
+
+	// parse struct base if it exists
+	AST::StructBase struct_base = AST::StructBase::error ();
+	if (lexer.peek_token ()->get_id () == DOT_DOT)
+	  {
+	    Location dot_dot_location = lexer.peek_token ()->get_locus ();
+	    lexer.skip_token ();
+
+	    // parse required struct base expr
+	    std::unique_ptr<AST::Expr> base_expr = parse_expr ();
+	    if (base_expr == nullptr)
+	      {
+		Error error (lexer.peek_token ()->get_locus (),
+			     "failed to parse struct base expression in struct "
+			     "expression");
+		add_error (std::move (error));
+
+		return nullptr;
+	      }
+
+	    // DEBUG:
+	    rust_debug ("struct/enum expr - parsed and validated base expr");
+
+	    struct_base
+	      = AST::StructBase (std::move (base_expr), dot_dot_location);
+
+	    // DEBUG:
+	    rust_debug ("assigned struct base to new struct base ");
+	  }
+
+	if (!skip_token (RIGHT_CURLY))
+	  {
+	    return nullptr;
+	  }
+
+	// DEBUG:
+	rust_debug (
+	  "struct/enum expr skipped right curly - done and ready to return");
+
+	return std::unique_ptr<AST::StructExprStructFields> (
+	  new AST::StructExprStructFields (std::move (path), std::move (fields),
+					   path_locus, std::move (struct_base),
+					   std::move (inner_attrs),
+					   std::move (outer_attrs)));
+      }
+    default:
+      add_error (
+	Error (t->get_locus (),
+	       "unrecognised token %qs in struct (or enum) expression - "
+	       "expected %<}%>, identifier, integer literal, or %<..%>",
+	       t->get_token_description ()));
+
+      return nullptr;
+    }
+}
+
+/* Parses a struct expr tuple with a path in expression already parsed (but
+ * not
+ * '(' token).
+ * FIXME: this currently outputs a call expr, as they cannot be disambiguated.
+ * A better solution would be to just get this to call that function directly.
+ * */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::CallExpr>
+Parser<ManagedTokenSource>::parse_struct_expr_tuple_partial (
+  AST::PathInExpression path, AST::AttrVec outer_attrs)
+{
+  if (!skip_token (LEFT_PAREN))
+    {
+      return nullptr;
+    }
+
+  AST::AttrVec inner_attrs = parse_inner_attributes ();
+
+  std::vector<std::unique_ptr<AST::Expr>> exprs;
+
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () != RIGHT_PAREN)
+    {
+      // parse expression (required)
+      std::unique_ptr<AST::Expr> expr = parse_expr ();
+      if (expr == nullptr)
+	{
+	  Error error (t->get_locus (), "failed to parse expression in "
+					"struct (or enum) expression tuple");
+	  add_error (std::move (error));
+
+	  return nullptr;
+	}
+      exprs.push_back (std::move (expr));
+
+      if (lexer.peek_token ()->get_id () != COMMA)
+	break;
+
+      lexer.skip_token ();
+
+      t = lexer.peek_token ();
+    }
+
+  if (!skip_token (RIGHT_PAREN))
+    {
+      return nullptr;
+    }
+
+  Location path_locus = path.get_locus ();
+
+  auto pathExpr = std::unique_ptr<AST::PathInExpression> (
+    new AST::PathInExpression (std::move (path)));
+
+  return std::unique_ptr<AST::CallExpr> (
+    new AST::CallExpr (std::move (pathExpr), std::move (exprs),
+		       std::move (outer_attrs), path_locus));
+}
+
+/* Parses a path in expression with the first token passed as a parameter (as
+ * it is skipped in token stream). Note that this only parses segment-first
+ * paths, not global ones. */
+template <typename ManagedTokenSource>
+AST::PathInExpression
+Parser<ManagedTokenSource>::parse_path_in_expression_pratt (const_TokenPtr tok)
+{
+  // HACK-y way of making up for pratt-parsing consuming first token
+
+  // DEBUG
+  rust_debug ("current peek token when starting path pratt parse: '%s'",
+	      lexer.peek_token ()->get_token_description ());
+
+  // create segment vector
+  std::vector<AST::PathExprSegment> segments;
+
+  std::string initial_str;
+
+  switch (tok->get_id ())
+    {
+    case IDENTIFIER:
+      initial_str = tok->get_str ();
+      break;
+    case SUPER:
+      initial_str = "super";
+      break;
+    case SELF:
+      initial_str = "self";
+      break;
+    case SELF_ALIAS:
+      initial_str = "Self";
+      break;
+    case CRATE:
+      initial_str = "crate";
+      break;
+    case DOLLAR_SIGN:
+      if (lexer.peek_token ()->get_id () == CRATE)
+	{
+	  initial_str = "$crate";
+	  break;
+	}
+      gcc_fallthrough ();
+    default:
+      add_error (Error (tok->get_locus (),
+			"unrecognised token %qs in path in expression",
+			tok->get_token_description ()));
+
+      return AST::PathInExpression::create_error ();
+    }
+
+  // parse required initial segment
+  AST::PathExprSegment initial_segment (initial_str, tok->get_locus ());
+  // parse generic args (and turbofish), if they exist
+  /* use lookahead to determine if they actually exist (don't want to
+   * accidently parse over next ident segment) */
+  if (lexer.peek_token ()->get_id () == SCOPE_RESOLUTION
+      && lexer.peek_token (1)->get_id () == LEFT_ANGLE)
+    {
+      // skip scope resolution
+      lexer.skip_token ();
+
+      AST::GenericArgs generic_args = parse_path_generic_args ();
+
+      initial_segment
+	= AST::PathExprSegment (AST::PathIdentSegment (initial_str,
+						       tok->get_locus ()),
+				tok->get_locus (), std::move (generic_args));
+    }
+  if (initial_segment.is_error ())
+    {
+      // skip after somewhere?
+      // don't necessarily throw error but yeah
+
+      // DEBUG
+      rust_debug ("initial segment is error - returning null");
+
+      return AST::PathInExpression::create_error ();
+    }
+  segments.push_back (std::move (initial_segment));
+
+  // parse optional segments (as long as scope resolution operator exists)
+  const_TokenPtr t = lexer.peek_token ();
+  while (t->get_id () == SCOPE_RESOLUTION)
+    {
+      // skip scope resolution operator
+      lexer.skip_token ();
+
+      // parse the actual segment - it is an error if it doesn't exist now
+      AST::PathExprSegment segment = parse_path_expr_segment ();
+      if (segment.is_error ())
+	{
+	  // skip after somewhere?
+	  Error error (t->get_locus (),
+		       "could not parse path expression segment");
+	  add_error (std::move (error));
+
+	  return AST::PathInExpression::create_error ();
+	}
+
+      segments.push_back (std::move (segment));
+
+      t = lexer.peek_token ();
+    }
+
+  // DEBUG:
+  rust_debug (
+    "current token (just about to return path to null denotation): '%s'",
+    lexer.peek_token ()->get_token_description ());
+
+  return AST::PathInExpression (std::move (segments), {}, tok->get_locus (),
+				false);
+}
+
+// Parses a closure expression with pratt parsing (from null denotation).
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::ClosureExpr>
+Parser<ManagedTokenSource>::parse_closure_expr_pratt (const_TokenPtr tok,
+						      AST::AttrVec outer_attrs)
+{
+  // TODO: does this need pratt parsing (for precedence)? probably not, but
+  // idk
+  Location locus = tok->get_locus ();
+  bool has_move = false;
+  if (tok->get_id () == MOVE)
+    {
+      has_move = true;
+      tok = lexer.peek_token ();
+      lexer.skip_token ();
+      // skip token and reassign
+    }
+
+  // handle parameter list
+  std::vector<AST::ClosureParam> params;
+
+  switch (tok->get_id ())
+    {
+    case OR:
+      // no parameters, don't skip token
+      break;
+      case PIPE: {
+	// actually may have parameters
+	// don't skip token
+	const_TokenPtr t = lexer.peek_token ();
+	while (t->get_id () != PIPE)
+	  {
+	    AST::ClosureParam param = parse_closure_param ();
+	    if (param.is_error ())
+	      {
+		// TODO is this really an error?
+		Error error (t->get_locus (), "could not parse closure param");
+		add_error (std::move (error));
+
+		return nullptr;
+	      }
+	    params.push_back (std::move (param));
+
+	    if (lexer.peek_token ()->get_id () != COMMA)
+	      {
+		// not an error but means param list is done
+		break;
+	      }
+	    // skip comma
+	    lexer.skip_token ();
+
+	    t = lexer.peek_token ();
+	  }
+
+	if (!skip_token (PIPE))
+	  {
+	    return nullptr;
+	  }
+	break;
+      }
+    default:
+      add_error (Error (tok->get_locus (),
+			"unexpected token %qs in closure expression - expected "
+			"%<|%> or %<||%>",
+			tok->get_token_description ()));
+
+      // skip somewhere?
+      return nullptr;
+    }
+
+  // again branch based on next token
+  tok = lexer.peek_token ();
+  if (tok->get_id () == RETURN_TYPE)
+    {
+      // must be return type closure with block expr
+
+      // skip "return type" token
+      lexer.skip_token ();
+
+      // parse actual type, which is required
+      std::unique_ptr<AST::TypeNoBounds> type = parse_type_no_bounds ();
+      if (type == nullptr)
+	{
+	  // error
+	  Error error (tok->get_locus (), "failed to parse type for closure");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      // parse block expr, which is required
+      std::unique_ptr<AST::BlockExpr> block = parse_block_expr ();
+      if (block == nullptr)
+	{
+	  // error
+	  Error error (lexer.peek_token ()->get_locus (),
+		       "failed to parse block expr in closure");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      return std::unique_ptr<AST::ClosureExprInnerTyped> (
+	new AST::ClosureExprInnerTyped (std::move (type), std::move (block),
+					std::move (params), locus, has_move,
+					std::move (outer_attrs)));
+    }
+  else
+    {
+      // must be expr-only closure
+
+      // parse expr, which is required
+      std::unique_ptr<AST::Expr> expr = parse_expr ();
+      if (expr == nullptr)
+	{
+	  Error error (tok->get_locus (),
+		       "failed to parse expression in closure");
+	  add_error (std::move (error));
+
+	  // skip somewhere?
+	  return nullptr;
+	}
+
+      return std::unique_ptr<AST::ClosureExprInner> (
+	new AST::ClosureExprInner (std::move (expr), std::move (params), locus,
+				   has_move, std::move (outer_attrs)));
+    }
+}
+
+/* Parses a tuple index expression (pratt-parsed) from a 'float' token as a
+ * result of lexer misidentification. */
+template <typename ManagedTokenSource>
+std::unique_ptr<AST::TupleIndexExpr>
+Parser<ManagedTokenSource>::parse_tuple_index_expr_float (
+  const_TokenPtr tok, std::unique_ptr<AST::Expr> tuple_expr,
+  AST::AttrVec outer_attrs, ParseRestrictions restrictions ATTRIBUTE_UNUSED)
+{
+  // only works on float literals
+  if (tok->get_id () != FLOAT_LITERAL)
+    return nullptr;
+
+  // DEBUG:
+  rust_debug ("exact string form of float: '%s'", tok->get_str ().c_str ());
+
+  // get float string and remove dot and initial 0
+  std::string index_str = tok->get_str ();
+  index_str.erase (index_str.begin ());
+
+  // get int from string
+  int index = atoi (index_str.c_str ());
+
+  Location locus = tuple_expr->get_locus ();
+
+  return std::unique_ptr<AST::TupleIndexExpr> (
+    new AST::TupleIndexExpr (std::move (tuple_expr), index,
+			     std::move (outer_attrs), locus));
+}
+
+// Returns true if the next token is END, ELSE, or EOF;
+template <typename ManagedTokenSource>
+bool
+Parser<ManagedTokenSource>::done_end_or_else ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+  return (t->get_id () == RIGHT_CURLY || t->get_id () == ELSE
+	  || t->get_id () == END_OF_FILE);
+}
+
+// Returns true if the next token is END or EOF.
+template <typename ManagedTokenSource>
+bool
+Parser<ManagedTokenSource>::done_end ()
+{
+  const_TokenPtr t = lexer.peek_token ();
+  return (t->get_id () == RIGHT_CURLY || t->get_id () == END_OF_FILE);
+}
+
+// Dumps lexer output to stderr.
+template <typename ManagedTokenSource>
+void
+Parser<ManagedTokenSource>::debug_dump_lex_output (std::ostream &out)
+{
+  /* TODO: a better implementation of "lexer dump" (as in dump what was
+   * actually tokenised) would actually be to "write" a token to a file every
+   * time skip_token() here was called. This would reflect the parser
+   * modifications to the token stream, such as fixing the template angle
+   * brackets. */
+
+  const_TokenPtr tok = lexer.peek_token ();
+
+  while (true)
+    {
+      if (tok->get_id () == Rust::END_OF_FILE)
+	break;
+
+      bool has_text = tok->get_id () == Rust::IDENTIFIER
+		      || tok->get_id () == Rust::INT_LITERAL
+		      || tok->get_id () == Rust::FLOAT_LITERAL
+		      || tok->get_id () == Rust::STRING_LITERAL
+		      || tok->get_id () == Rust::CHAR_LITERAL
+		      || tok->get_id () == Rust::BYTE_STRING_LITERAL
+		      || tok->get_id () == Rust::BYTE_CHAR_LITERAL;
+
+      Location loc = tok->get_locus ();
+
+      out << "<id=";
+      out << tok->token_id_to_str ();
+      out << has_text ? (std::string (", text=") + tok->get_str ()
+			 + std::string (", typehint=")
+			 + std::string (tok->get_type_hint_str ()))
+		      : "";
+      out << lexer.get_line_map ()->to_string (loc);
+
+      lexer.skip_token ();
+      tok = lexer.peek_token ();
+    }
+}
+
+// Parses crate and dumps AST to stderr, recursively.
+template <typename ManagedTokenSource>
+void
+Parser<ManagedTokenSource>::debug_dump_ast_output (AST::Crate &crate,
+						   std::ostream &out)
+{
+  out << crate.as_string ();
+}
+} // namespace Rust
