public inbox for gcc-rust@gcc.gnu.org
 help / color / mirror / Atom feed
From: herron.philip@googlemail.com
To: gcc-patches@gcc.gnu.org
Cc: gcc-rust@gcc.gnu.org, The Other <simplytheother@gmail.com>,
	Philip Herron <philip.herron@embecosm.com>
Subject: [PATCH Rust front-end v2 08/37] gccrs: Add the Rust front-end AST data structures
Date: Wed, 24 Aug 2022 12:59:27 +0100	[thread overview]
Message-ID: <20220824115956.737931-9-philip.herron@embecosm.com> (raw)
In-Reply-To: <20220824115956.737931-1-philip.herron@embecosm.com>

From: The Other <simplytheother@gmail.com>

This is a full C++11 class hierarchy representing the Rust AST. We do not
allow dynamic_cast and so the main mechanism to work with the AST is by
using the visitor interface. Slowly we are adding TREE_CODE style node
types to the AST which will allow for more ways to work with the AST but
for now this is it.

See: https://doc.rust-lang.org/reference/items.html

Co-authored-by: Philip Herron <philip.herron@embecosm.com>
Co-authored-by: Arthur Cohen <arthur.cohen@embecosm.com
---
 gcc/rust/ast/rust-ast-dump.cc        | 1089 +++++
 gcc/rust/ast/rust-ast-dump.h         |  246 ++
 gcc/rust/ast/rust-ast-full-decls.h   |  273 ++
 gcc/rust/ast/rust-ast-full-test.cc   | 5814 ++++++++++++++++++++++++++
 gcc/rust/ast/rust-ast-full.h         |   31 +
 gcc/rust/ast/rust-ast-visitor.h      |  234 ++
 gcc/rust/ast/rust-ast.h              | 2007 +++++++++
 gcc/rust/ast/rust-cond-compilation.h |  249 ++
 gcc/rust/ast/rust-expr.h             | 4631 ++++++++++++++++++++
 gcc/rust/ast/rust-item.h             | 4382 +++++++++++++++++++
 gcc/rust/ast/rust-macro.h            |  958 +++++
 gcc/rust/ast/rust-path.h             | 1297 ++++++
 gcc/rust/ast/rust-pattern.h          | 1576 +++++++
 gcc/rust/ast/rust-stmt.h             |  358 ++
 gcc/rust/ast/rust-type.h             |  962 +++++
 gcc/rust/operator.h                  |   72 +
 16 files changed, 24179 insertions(+)
 create mode 100644 gcc/rust/ast/rust-ast-dump.cc
 create mode 100644 gcc/rust/ast/rust-ast-dump.h
 create mode 100644 gcc/rust/ast/rust-ast-full-decls.h
 create mode 100644 gcc/rust/ast/rust-ast-full-test.cc
 create mode 100644 gcc/rust/ast/rust-ast-full.h
 create mode 100644 gcc/rust/ast/rust-ast-visitor.h
 create mode 100644 gcc/rust/ast/rust-ast.h
 create mode 100644 gcc/rust/ast/rust-cond-compilation.h
 create mode 100644 gcc/rust/ast/rust-expr.h
 create mode 100644 gcc/rust/ast/rust-item.h
 create mode 100644 gcc/rust/ast/rust-macro.h
 create mode 100644 gcc/rust/ast/rust-path.h
 create mode 100644 gcc/rust/ast/rust-pattern.h
 create mode 100644 gcc/rust/ast/rust-stmt.h
 create mode 100644 gcc/rust/ast/rust-type.h
 create mode 100644 gcc/rust/operator.h

diff --git a/gcc/rust/ast/rust-ast-dump.cc b/gcc/rust/ast/rust-ast-dump.cc
new file mode 100644
index 00000000000..ad9ad0b7de7
--- /dev/null
+++ b/gcc/rust/ast/rust-ast-dump.cc
@@ -0,0 +1,1089 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-ast-dump.h"
+
+namespace Rust {
+namespace AST {
+
+Indent::Indent () : tabs (0) {}
+
+std::ostream &
+operator<< (std::ostream &stream, const Indent &indent)
+{
+  return stream << std::string (indent.tabs, '\t');
+}
+
+void
+Indent::increment ()
+{
+  tabs++;
+}
+
+void
+Indent::decrement ()
+{
+  rust_assert (tabs != 0);
+  tabs--;
+}
+
+Dump::Dump (std::ostream &stream) : stream (stream), indentation (Indent ()) {}
+
+void
+Dump::go (AST::Crate &crate)
+{
+  for (auto &item : crate.items)
+    {
+      stream << indentation;
+      item->accept_vis (*this);
+      stream << "\n";
+    }
+}
+
+void
+Dump::go (AST::Item &item)
+{
+  item.accept_vis (*this);
+}
+
+void
+Dump::format_function_param (FunctionParam &param)
+{
+  param.get_pattern ()->accept_vis (*this);
+  stream << ": ";
+  param.get_type ()->accept_vis (*this);
+}
+
+void
+Dump::emit_attrib (const Attribute &attrib)
+{
+  stream << "#";
+  stream << "[";
+
+  for (size_t i = 0; i < attrib.get_path ().get_segments ().size (); i++)
+    {
+      const auto &seg = attrib.get_path ().get_segments ().at (i);
+      bool has_next = (i + 1) < attrib.get_path ().get_segments ().size ();
+
+      stream << seg.get_segment_name ();
+      if (has_next)
+	stream << "::";
+    }
+
+  if (attrib.has_attr_input ())
+    {
+      stream << " = ";
+
+      bool is_literal = attrib.get_attr_input ().get_attr_input_type ()
+			== AST::AttrInput::AttrInputType::LITERAL;
+      if (is_literal)
+	{
+	  auto &literal
+	    = static_cast<AST::AttrInputLiteral &> (attrib.get_attr_input ());
+	  const auto &value = literal.get_literal ().as_string ();
+
+	  stream << "\"" << value << "\"";
+	}
+      else
+	{
+	  stream << "FIXME";
+	}
+    }
+
+  stream << "]";
+}
+
+void
+Dump::visit (Token &tok)
+{}
+
+void
+Dump::visit (DelimTokenTree &delim_tok_tree)
+{}
+
+void
+Dump::visit (AttrInputMetaItemContainer &input)
+{}
+
+void
+Dump::visit (IdentifierExpr &ident_expr)
+{
+  stream << ident_expr.get_ident ();
+}
+
+void
+Dump::visit (Lifetime &lifetime)
+{}
+
+void
+Dump::visit (LifetimeParam &lifetime_param)
+{}
+
+void
+Dump::visit (ConstGenericParam &lifetime_param)
+{}
+
+// rust-path.h
+void
+Dump::visit (PathInExpression &path)
+{}
+
+void
+Dump::visit (TypePathSegment &segment)
+{}
+
+void
+Dump::visit (TypePathSegmentGeneric &segment)
+{}
+
+void
+Dump::visit (TypePathSegmentFunction &segment)
+{}
+
+void
+Dump::visit (TypePath &path)
+{
+  stream << path.as_string ();
+}
+
+void
+Dump::visit (QualifiedPathInExpression &path)
+{}
+
+void
+Dump::visit (QualifiedPathInType &path)
+{}
+
+// rust-expr.h
+void
+Dump::visit (LiteralExpr &expr)
+{
+  stream << expr.as_string ();
+}
+
+void
+Dump::visit (AttrInputLiteral &attr_input)
+{}
+
+void
+Dump::visit (MetaItemLitExpr &meta_item)
+{}
+
+void
+Dump::visit (MetaItemPathLit &meta_item)
+{}
+
+void
+Dump::visit (BorrowExpr &expr)
+{}
+
+void
+Dump::visit (DereferenceExpr &expr)
+{}
+
+void
+Dump::visit (ErrorPropagationExpr &expr)
+{}
+
+void
+Dump::visit (NegationExpr &expr)
+{}
+
+void
+Dump::visit (ArithmeticOrLogicalExpr &expr)
+{
+  expr.get_left_expr ()->accept_vis (*this);
+  stream << " ";
+
+  switch (expr.get_expr_type ())
+    {
+    case ArithmeticOrLogicalOperator::ADD:
+      stream << "+";
+      break;
+
+    case ArithmeticOrLogicalOperator::SUBTRACT:
+      stream << "-";
+      break;
+
+    case ArithmeticOrLogicalOperator::MULTIPLY:
+      stream << "*";
+      break;
+
+    case ArithmeticOrLogicalOperator::DIVIDE:
+      stream << "/";
+      break;
+
+    case ArithmeticOrLogicalOperator::MODULUS:
+      stream << "%";
+      break;
+
+    case ArithmeticOrLogicalOperator::BITWISE_AND:
+      stream << "&";
+      break;
+
+    case ArithmeticOrLogicalOperator::BITWISE_OR:
+      stream << "|";
+      break;
+
+    case ArithmeticOrLogicalOperator::BITWISE_XOR:
+      stream << "^";
+      break;
+
+    case ArithmeticOrLogicalOperator::LEFT_SHIFT:
+      stream << "<<";
+      break;
+
+    case ArithmeticOrLogicalOperator::RIGHT_SHIFT:
+      stream << ">>";
+      break;
+    }
+
+  stream << " ";
+  expr.get_right_expr ()->accept_vis (*this);
+}
+
+void
+Dump::visit (ComparisonExpr &expr)
+{}
+
+void
+Dump::visit (LazyBooleanExpr &expr)
+{}
+
+void
+Dump::visit (TypeCastExpr &expr)
+{}
+
+void
+Dump::visit (AssignmentExpr &expr)
+{}
+
+void
+Dump::visit (CompoundAssignmentExpr &expr)
+{}
+
+void
+Dump::visit (GroupedExpr &expr)
+{}
+
+void
+Dump::visit (ArrayElemsValues &elems)
+{}
+
+void
+Dump::visit (ArrayElemsCopied &elems)
+{}
+
+void
+Dump::visit (ArrayExpr &expr)
+{}
+
+void
+Dump::visit (ArrayIndexExpr &expr)
+{}
+
+void
+Dump::visit (TupleExpr &expr)
+{}
+
+void
+Dump::visit (TupleIndexExpr &expr)
+{}
+
+void
+Dump::visit (StructExprStruct &expr)
+{}
+
+void
+Dump::visit (StructExprFieldIdentifier &field)
+{}
+
+void
+Dump::visit (StructExprFieldIdentifierValue &field)
+{}
+
+void
+Dump::visit (StructExprFieldIndexValue &field)
+{}
+
+void
+Dump::visit (StructExprStructFields &expr)
+{}
+
+void
+Dump::visit (StructExprStructBase &expr)
+{}
+
+void
+Dump::visit (CallExpr &expr)
+{}
+
+void
+Dump::visit (MethodCallExpr &expr)
+{}
+
+void
+Dump::visit (FieldAccessExpr &expr)
+{}
+
+void
+Dump::visit (ClosureExprInner &expr)
+{}
+
+void
+Dump::visit (BlockExpr &expr)
+{
+  stream << "{\n";
+  indentation.increment ();
+
+  for (auto &stmt : expr.get_statements ())
+    {
+      stream << indentation;
+      stmt->accept_vis (*this);
+      stream << ";\n";
+    }
+
+  if (expr.has_tail_expr ())
+    {
+      stream << indentation;
+      expr.get_tail_expr ()->accept_vis (*this);
+    }
+
+  indentation.decrement ();
+  stream << "\n" << indentation << "}\n";
+}
+
+void
+Dump::visit (ClosureExprInnerTyped &expr)
+{}
+
+void
+Dump::visit (ContinueExpr &expr)
+{}
+
+void
+Dump::visit (BreakExpr &expr)
+{}
+
+void
+Dump::visit (RangeFromToExpr &expr)
+{}
+
+void
+Dump::visit (RangeFromExpr &expr)
+{}
+
+void
+Dump::visit (RangeToExpr &expr)
+{}
+
+void
+Dump::visit (RangeFullExpr &expr)
+{}
+
+void
+Dump::visit (RangeFromToInclExpr &expr)
+{}
+
+void
+Dump::visit (RangeToInclExpr &expr)
+{}
+
+void
+Dump::visit (ReturnExpr &expr)
+{}
+
+void
+Dump::visit (UnsafeBlockExpr &expr)
+{}
+
+void
+Dump::visit (LoopExpr &expr)
+{}
+
+void
+Dump::visit (WhileLoopExpr &expr)
+{}
+
+void
+Dump::visit (WhileLetLoopExpr &expr)
+{}
+
+void
+Dump::visit (ForLoopExpr &expr)
+{}
+
+void
+Dump::visit (IfExpr &expr)
+{}
+
+void
+Dump::visit (IfExprConseqElse &expr)
+{}
+
+void
+Dump::visit (IfExprConseqIf &expr)
+{}
+
+void
+Dump::visit (IfExprConseqIfLet &expr)
+{}
+
+void
+Dump::visit (IfLetExpr &expr)
+{}
+
+void
+Dump::visit (IfLetExprConseqElse &expr)
+{}
+
+void
+Dump::visit (IfLetExprConseqIf &expr)
+{}
+
+void
+Dump::visit (IfLetExprConseqIfLet &expr)
+{}
+
+void
+Dump::visit (MatchExpr &expr)
+{}
+
+void
+Dump::visit (AwaitExpr &expr)
+{}
+
+void
+Dump::visit (AsyncBlockExpr &expr)
+{}
+
+// rust-item.h
+void
+Dump::visit (TypeParam &param)
+{
+  stream << param.get_type_representation ();
+  if (param.has_type ())
+    {
+      stream << " = ";
+      param.get_type ()->accept_vis (*this);
+    }
+}
+
+void
+Dump::visit (LifetimeWhereClauseItem &item)
+{}
+
+void
+Dump::visit (TypeBoundWhereClauseItem &item)
+{}
+
+void
+Dump::visit (Method &method)
+{
+  stream << indentation << "fn " << method.get_method_name () << '(';
+
+  auto &self = method.get_self_param ();
+  stream << self.as_string ();
+
+  auto &params = method.get_function_params ();
+  for (auto &param : params)
+    {
+      stream << ", ";
+      format_function_param (param);
+    }
+
+  stream << ") ";
+
+  if (method.has_return_type ())
+    {
+      stream << "-> ";
+      method.get_return_type ()->accept_vis (*this);
+      stream << " ";
+    }
+
+  auto &block = method.get_definition ();
+  if (!block)
+    stream << ';';
+  else
+    block->accept_vis (*this);
+
+  stream << '\n';
+}
+
+void
+Dump::visit (Module &module)
+{}
+
+void
+Dump::visit (ExternCrate &crate)
+{}
+
+void
+Dump::visit (UseTreeGlob &use_tree)
+{}
+
+void
+Dump::visit (UseTreeList &use_tree)
+{}
+
+void
+Dump::visit (UseTreeRebind &use_tree)
+{}
+
+void
+Dump::visit (UseDeclaration &use_decl)
+{}
+
+void
+Dump::visit (Function &function)
+{
+  stream << "fn " << function.get_function_name ();
+
+  if (function.has_generics ())
+    {
+      stream << "<";
+      for (size_t i = 0; i < function.get_generic_params ().size (); i++)
+	{
+	  auto &param = function.get_generic_params ().at (i);
+	  param->accept_vis (*this);
+
+	  bool has_next = (i + 1) < function.get_generic_params ().size ();
+	  if (has_next)
+	    stream << ", ";
+	}
+      stream << ">";
+    }
+
+  stream << '(';
+  auto &params = function.get_function_params ();
+  if (params.size () >= 1)
+    {
+      format_function_param (params[0]);
+      for (size_t i = 1; i < params.size (); i++)
+	{
+	  stream << ", ";
+	  format_function_param (params[i]);
+	}
+    }
+
+  stream << ") ";
+
+  if (function.has_return_type ())
+    {
+      stream << "-> ";
+      function.get_return_type ()->accept_vis (*this);
+      stream << " ";
+    }
+
+  auto &block = function.get_definition ();
+  if (!block)
+    stream << ';';
+  else
+    block->accept_vis (*this);
+
+  stream << '\n';
+}
+
+void
+Dump::visit (TypeAlias &type_alias)
+{}
+
+void
+Dump::visit (StructStruct &struct_item)
+{}
+
+void
+Dump::visit (TupleStruct &tuple_struct)
+{}
+
+void
+Dump::visit (EnumItem &item)
+{}
+
+void
+Dump::visit (EnumItemTuple &item)
+{}
+
+void
+Dump::visit (EnumItemStruct &item)
+{}
+
+void
+Dump::visit (EnumItemDiscriminant &item)
+{}
+
+void
+Dump::visit (Enum &enum_item)
+{}
+
+void
+Dump::visit (Union &union_item)
+{}
+
+void
+Dump::visit (ConstantItem &const_item)
+{}
+
+void
+Dump::visit (StaticItem &static_item)
+{}
+
+void
+Dump::format_function_common (std::unique_ptr<Type> &return_type,
+			      std::unique_ptr<BlockExpr> &block)
+{
+  if (return_type)
+    {
+      stream << "-> ";
+      return_type->accept_vis (*this);
+    }
+
+  if (block)
+    {
+      if (return_type)
+	stream << ' ';
+      block->accept_vis (*this);
+    }
+  else
+    stream << ";\n";
+}
+
+void
+Dump::visit (TraitItemFunc &item)
+{
+  auto func = item.get_trait_function_decl ();
+  stream << indentation << "fn " << func.get_identifier () << '(';
+
+  auto &params = func.get_function_params ();
+  for (auto &param : params)
+    {
+      stream << ", ";
+      format_function_param (param);
+    }
+
+  stream << ") ";
+
+  format_function_common (func.get_return_type (), item.get_definition ());
+}
+
+void
+Dump::visit (TraitItemMethod &item)
+{
+  auto method = item.get_trait_method_decl ();
+  stream << indentation << "fn " << method.get_identifier () << '(';
+
+  auto &self = method.get_self_param ();
+  stream << self.as_string ();
+
+  auto &params = method.get_function_params ();
+  for (auto &param : params)
+    {
+      stream << ", ";
+      format_function_param (param);
+    }
+
+  stream << ") ";
+
+  format_function_common (method.get_return_type (), item.get_definition ());
+}
+
+void
+Dump::visit (TraitItemConst &item)
+{
+  stream << indentation << "const " << item.get_identifier () << ": ";
+  item.get_type ()->accept_vis (*this);
+  stream << ";\n";
+}
+
+void
+Dump::visit (TraitItemType &item)
+{
+  stream << indentation << "type " << item.get_identifier () << ";\n";
+}
+
+void
+Dump::visit (Trait &trait)
+{
+  for (const auto &attr : trait.get_outer_attrs ())
+    {
+      emit_attrib (attr);
+      stream << "\n" << indentation;
+    }
+
+  stream << "trait " << trait.get_identifier ();
+
+  // Traits actually have an implicit Self thrown at the start so we must expect
+  // the number of generic params to be > 1
+  if (trait.get_generic_params ().size () > 1)
+    {
+      stream << "<";
+      for (size_t i = 1; i < trait.get_generic_params ().size (); i++)
+	{
+	  auto &param = trait.get_generic_params ().at (i);
+	  param->accept_vis (*this);
+
+	  bool has_next = (i + 1) < trait.get_generic_params ().size ();
+	  if (has_next)
+	    stream << ", ";
+	}
+      stream << ">";
+    }
+
+  stream << " {\n";
+
+  indentation.increment ();
+
+  for (auto &item : trait.get_trait_items ())
+    item->accept_vis (*this);
+
+  indentation.decrement ();
+  stream << "\n}\n";
+}
+
+void
+Dump::visit (InherentImpl &impl)
+{
+  stream << "impl ";
+
+  // FIXME: Handle generics
+
+  impl.get_type ()->accept_vis (*this);
+
+  // FIXME: Handle where-clause
+  // FIXME: Handle inner attributes
+
+  stream << " {\n";
+  indentation.increment ();
+
+  for (auto &item : impl.get_impl_items ())
+    item->accept_vis (*this);
+
+  indentation.decrement ();
+  stream << "\n}\n";
+}
+
+void
+Dump::visit (TraitImpl &impl)
+{
+  stream << "impl ";
+  impl.get_trait_path ().accept_vis (*this);
+  stream << " for ";
+  impl.get_type ()->accept_vis (*this);
+
+  stream << " {\n";
+  indentation.increment ();
+
+  for (auto &item : impl.get_impl_items ())
+    item->accept_vis (*this);
+
+  indentation.decrement ();
+  stream << "\n}\n";
+}
+
+void
+Dump::visit (ExternalStaticItem &item)
+{}
+
+void
+Dump::visit (ExternalFunctionItem &function)
+{
+  stream << "fn " << function.get_identifier () << '(';
+
+  for (size_t i = 0; i < function.get_function_params ().size (); i++)
+    {
+      auto &param = function.get_function_params ().at (i);
+      bool has_next = (i + 1) < function.get_function_params ().size ();
+
+      stream << param.get_name () << ": ";
+      param.get_type ()->accept_vis (*this);
+
+      if (has_next)
+	stream << ", ";
+    }
+
+  stream << ')';
+  if (function.has_return_type ())
+    {
+      stream << "-> ";
+      function.get_return_type ()->accept_vis (*this);
+    }
+}
+
+void
+Dump::visit (ExternBlock &block)
+{
+  stream << "extern ";
+
+  if (block.has_abi ())
+    {
+      stream << "\"";
+      stream << block.get_abi ();
+      stream << "\" ";
+    }
+
+  stream << "{\n";
+  indentation.increment ();
+
+  for (auto &item : block.get_extern_items ())
+    {
+      stream << indentation;
+      item->accept_vis (*this);
+      stream << ";\n";
+    }
+
+  indentation.decrement ();
+  stream << "\n" << indentation << "}\n";
+}
+
+// rust-macro.h
+void
+Dump::visit (MacroMatchFragment &match)
+{}
+
+void
+Dump::visit (MacroMatchRepetition &match)
+{}
+
+void
+Dump::visit (MacroMatcher &matcher)
+{}
+
+void
+Dump::visit (MacroRulesDefinition &rules_def)
+{}
+
+void
+Dump::visit (MacroInvocation &macro_invoc)
+{}
+
+void
+Dump::visit (MetaItemPath &meta_item)
+{}
+
+void
+Dump::visit (MetaItemSeq &meta_item)
+{}
+
+void
+Dump::visit (MetaWord &meta_item)
+{}
+
+void
+Dump::visit (MetaNameValueStr &meta_item)
+{}
+
+void
+Dump::visit (MetaListPaths &meta_item)
+{}
+
+void
+Dump::visit (MetaListNameValueStr &meta_item)
+{}
+
+// rust-pattern.h
+void
+Dump::visit (LiteralPattern &pattern)
+{}
+
+void
+Dump::visit (IdentifierPattern &pattern)
+{
+  stream << pattern.get_ident ();
+}
+
+void
+Dump::visit (WildcardPattern &pattern)
+{}
+
+// void Dump::visit(RangePatternBound& bound){}
+
+void
+Dump::visit (RangePatternBoundLiteral &bound)
+{}
+
+void
+Dump::visit (RangePatternBoundPath &bound)
+{}
+
+void
+Dump::visit (RangePatternBoundQualPath &bound)
+{}
+
+void
+Dump::visit (RangePattern &pattern)
+{}
+
+void
+Dump::visit (ReferencePattern &pattern)
+{}
+
+// void Dump::visit(StructPatternField& field){}
+
+void
+Dump::visit (StructPatternFieldTuplePat &field)
+{}
+
+void
+Dump::visit (StructPatternFieldIdentPat &field)
+{}
+
+void
+Dump::visit (StructPatternFieldIdent &field)
+{}
+
+void
+Dump::visit (StructPattern &pattern)
+{}
+
+// void Dump::visit(TupleStructItems& tuple_items){}
+
+void
+Dump::visit (TupleStructItemsNoRange &tuple_items)
+{}
+
+void
+Dump::visit (TupleStructItemsRange &tuple_items)
+{}
+
+void
+Dump::visit (TupleStructPattern &pattern)
+{}
+
+// void Dump::visit(TuplePatternItems& tuple_items){}
+
+void
+Dump::visit (TuplePatternItemsMultiple &tuple_items)
+{}
+
+void
+Dump::visit (TuplePatternItemsRanged &tuple_items)
+{}
+
+void
+Dump::visit (TuplePattern &pattern)
+{}
+
+void
+Dump::visit (GroupedPattern &pattern)
+{}
+
+void
+Dump::visit (SlicePattern &pattern)
+{}
+
+// rust-stmt.h
+void
+Dump::visit (EmptyStmt &stmt)
+{}
+
+void
+Dump::visit (LetStmt &stmt)
+{
+  stream << "let ";
+  auto &pattern = stmt.get_pattern ();
+  if (pattern)
+    pattern->accept_vis (*this);
+
+  if (stmt.has_type ())
+    {
+      stream << ": ";
+      stmt.get_type ()->accept_vis (*this);
+    }
+
+  if (stmt.has_init_expr ())
+    {
+      stream << " = ";
+      stmt.get_init_expr ()->accept_vis (*this);
+    }
+}
+
+void
+Dump::visit (ExprStmtWithoutBlock &stmt)
+{}
+
+void
+Dump::visit (ExprStmtWithBlock &stmt)
+{}
+
+// rust-type.h
+void
+Dump::visit (TraitBound &bound)
+{}
+
+void
+Dump::visit (ImplTraitType &type)
+{}
+
+void
+Dump::visit (TraitObjectType &type)
+{}
+
+void
+Dump::visit (ParenthesisedType &type)
+{}
+
+void
+Dump::visit (ImplTraitTypeOneBound &type)
+{}
+
+void
+Dump::visit (TraitObjectTypeOneBound &type)
+{}
+
+void
+Dump::visit (TupleType &type)
+{}
+
+void
+Dump::visit (NeverType &type)
+{}
+
+void
+Dump::visit (RawPointerType &type)
+{}
+
+void
+Dump::visit (ReferenceType &type)
+{
+  type.get_type_referenced ()->accept_vis (*this);
+}
+
+void
+Dump::visit (ArrayType &type)
+{
+  type.get_elem_type ()->accept_vis (*this);
+}
+
+void
+Dump::visit (SliceType &type)
+{
+  type.get_elem_type ()->accept_vis (*this);
+}
+
+void
+Dump::visit (InferredType &type)
+{
+  stream << "_";
+}
+
+void
+Dump::visit (BareFunctionType &type)
+{}
+
+} // namespace AST
+} // namespace Rust
diff --git a/gcc/rust/ast/rust-ast-dump.h b/gcc/rust/ast/rust-ast-dump.h
new file mode 100644
index 00000000000..c3854e8287d
--- /dev/null
+++ b/gcc/rust/ast/rust-ast-dump.h
@@ -0,0 +1,246 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-ast-visitor.h"
+#include "rust-ast.h"
+#include "rust-ast-full.h"
+
+#ifndef RUST_AST_DUMP_H
+#define RUST_AST_DUMP_H
+
+namespace Rust {
+namespace AST {
+
+// TODO: We might want to reuse this class somewhere else
+class Indent
+{
+public:
+  Indent ();
+
+  friend std::ostream &operator<< (std::ostream &stream, const Indent &indent);
+
+  void increment ();
+  void decrement ();
+
+private:
+  size_t tabs;
+};
+
+class Dump : public ASTVisitor
+{
+public:
+  Dump (std::ostream &stream);
+
+  /**
+   * Run the visitor on an entire crate and its items
+   */
+  void go (AST::Crate &crate);
+  void go (AST::Item &item);
+
+private:
+  std::ostream &stream;
+  Indent indentation;
+
+  // Format together common items of functions: Parameters, return type, block
+  void format_function_common (std::unique_ptr<Type> &return_type,
+			       std::unique_ptr<BlockExpr> &block);
+
+  /**
+   * Format a function's definition parameter
+   */
+  void format_function_param (FunctionParam &param);
+  void emit_attrib (const Attribute &attrib);
+
+  // rust-ast.h
+  void visit (Token &tok);
+  void visit (DelimTokenTree &delim_tok_tree);
+  void visit (AttrInputMetaItemContainer &input);
+  void visit (IdentifierExpr &ident_expr);
+  void visit (Lifetime &lifetime);
+  void visit (LifetimeParam &lifetime_param);
+  void visit (ConstGenericParam &const_param);
+
+  // rust-path.h
+  void visit (PathInExpression &path);
+  void visit (TypePathSegment &segment);
+  void visit (TypePathSegmentGeneric &segment);
+  void visit (TypePathSegmentFunction &segment);
+  void visit (TypePath &path);
+  void visit (QualifiedPathInExpression &path);
+  void visit (QualifiedPathInType &path);
+
+  // rust-expr.h
+  void visit (LiteralExpr &expr);
+  void visit (AttrInputLiteral &attr_input);
+  void visit (MetaItemLitExpr &meta_item);
+  void visit (MetaItemPathLit &meta_item);
+  void visit (BorrowExpr &expr);
+  void visit (DereferenceExpr &expr);
+  void visit (ErrorPropagationExpr &expr);
+  void visit (NegationExpr &expr);
+  void visit (ArithmeticOrLogicalExpr &expr);
+  void visit (ComparisonExpr &expr);
+  void visit (LazyBooleanExpr &expr);
+  void visit (TypeCastExpr &expr);
+  void visit (AssignmentExpr &expr);
+  void visit (CompoundAssignmentExpr &expr);
+  void visit (GroupedExpr &expr);
+  void visit (ArrayElemsValues &elems);
+  void visit (ArrayElemsCopied &elems);
+  void visit (ArrayExpr &expr);
+  void visit (ArrayIndexExpr &expr);
+  void visit (TupleExpr &expr);
+  void visit (TupleIndexExpr &expr);
+  void visit (StructExprStruct &expr);
+  void visit (StructExprFieldIdentifier &field);
+  void visit (StructExprFieldIdentifierValue &field);
+  void visit (StructExprFieldIndexValue &field);
+  void visit (StructExprStructFields &expr);
+  void visit (StructExprStructBase &expr);
+  void visit (CallExpr &expr);
+  void visit (MethodCallExpr &expr);
+  void visit (FieldAccessExpr &expr);
+  void visit (ClosureExprInner &expr);
+  void visit (BlockExpr &expr);
+  void visit (ClosureExprInnerTyped &expr);
+  void visit (ContinueExpr &expr);
+  void visit (BreakExpr &expr);
+  void visit (RangeFromToExpr &expr);
+  void visit (RangeFromExpr &expr);
+  void visit (RangeToExpr &expr);
+  void visit (RangeFullExpr &expr);
+  void visit (RangeFromToInclExpr &expr);
+  void visit (RangeToInclExpr &expr);
+  void visit (ReturnExpr &expr);
+  void visit (UnsafeBlockExpr &expr);
+  void visit (LoopExpr &expr);
+  void visit (WhileLoopExpr &expr);
+  void visit (WhileLetLoopExpr &expr);
+  void visit (ForLoopExpr &expr);
+  void visit (IfExpr &expr);
+  void visit (IfExprConseqElse &expr);
+  void visit (IfExprConseqIf &expr);
+  void visit (IfExprConseqIfLet &expr);
+  void visit (IfLetExpr &expr);
+  void visit (IfLetExprConseqElse &expr);
+  void visit (IfLetExprConseqIf &expr);
+  void visit (IfLetExprConseqIfLet &expr);
+  void visit (MatchExpr &expr);
+  void visit (AwaitExpr &expr);
+  void visit (AsyncBlockExpr &expr);
+
+  // rust-item.h
+  void visit (TypeParam &param);
+  void visit (LifetimeWhereClauseItem &item);
+  void visit (TypeBoundWhereClauseItem &item);
+  void visit (Method &method);
+  void visit (Module &module);
+  void visit (ExternCrate &crate);
+  void visit (UseTreeGlob &use_tree);
+  void visit (UseTreeList &use_tree);
+  void visit (UseTreeRebind &use_tree);
+  void visit (UseDeclaration &use_decl);
+  void visit (Function &function);
+  void visit (TypeAlias &type_alias);
+  void visit (StructStruct &struct_item);
+  void visit (TupleStruct &tuple_struct);
+  void visit (EnumItem &item);
+  void visit (EnumItemTuple &item);
+  void visit (EnumItemStruct &item);
+  void visit (EnumItemDiscriminant &item);
+  void visit (Enum &enum_item);
+  void visit (Union &union_item);
+  void visit (ConstantItem &const_item);
+  void visit (StaticItem &static_item);
+  void visit (TraitItemFunc &item);
+  void visit (TraitItemMethod &item);
+  void visit (TraitItemConst &item);
+  void visit (TraitItemType &item);
+  void visit (Trait &trait);
+  void visit (InherentImpl &impl);
+  void visit (TraitImpl &impl);
+  void visit (ExternalStaticItem &item);
+  void visit (ExternalFunctionItem &item);
+  void visit (ExternBlock &block);
+
+  // rust-macro.h
+  void visit (MacroMatchFragment &match);
+  void visit (MacroMatchRepetition &match);
+  void visit (MacroMatcher &matcher);
+  void visit (MacroRulesDefinition &rules_def);
+  void visit (MacroInvocation &macro_invoc);
+  void visit (MetaItemPath &meta_item);
+  void visit (MetaItemSeq &meta_item);
+  void visit (MetaWord &meta_item);
+  void visit (MetaNameValueStr &meta_item);
+  void visit (MetaListPaths &meta_item);
+  void visit (MetaListNameValueStr &meta_item);
+
+  // rust-pattern.h
+  void visit (LiteralPattern &pattern);
+  void visit (IdentifierPattern &pattern);
+  void visit (WildcardPattern &pattern);
+  // void visit(RangePatternBound& bound);
+  void visit (RangePatternBoundLiteral &bound);
+  void visit (RangePatternBoundPath &bound);
+  void visit (RangePatternBoundQualPath &bound);
+  void visit (RangePattern &pattern);
+  void visit (ReferencePattern &pattern);
+  // void visit(StructPatternField& field);
+  void visit (StructPatternFieldTuplePat &field);
+  void visit (StructPatternFieldIdentPat &field);
+  void visit (StructPatternFieldIdent &field);
+  void visit (StructPattern &pattern);
+  // void visit(TupleStructItems& tuple_items);
+  void visit (TupleStructItemsNoRange &tuple_items);
+  void visit (TupleStructItemsRange &tuple_items);
+  void visit (TupleStructPattern &pattern);
+  // void visit(TuplePatternItems& tuple_items);
+  void visit (TuplePatternItemsMultiple &tuple_items);
+  void visit (TuplePatternItemsRanged &tuple_items);
+  void visit (TuplePattern &pattern);
+  void visit (GroupedPattern &pattern);
+  void visit (SlicePattern &pattern);
+
+  // rust-stmt.h
+  void visit (EmptyStmt &stmt);
+  void visit (LetStmt &stmt);
+  void visit (ExprStmtWithoutBlock &stmt);
+  void visit (ExprStmtWithBlock &stmt);
+
+  // rust-type.h
+  void visit (TraitBound &bound);
+  void visit (ImplTraitType &type);
+  void visit (TraitObjectType &type);
+  void visit (ParenthesisedType &type);
+  void visit (ImplTraitTypeOneBound &type);
+  void visit (TraitObjectTypeOneBound &type);
+  void visit (TupleType &type);
+  void visit (NeverType &type);
+  void visit (RawPointerType &type);
+  void visit (ReferenceType &type);
+  void visit (ArrayType &type);
+  void visit (SliceType &type);
+  void visit (InferredType &type);
+  void visit (BareFunctionType &type);
+};
+
+} // namespace AST
+} // namespace Rust
+
+#endif // !RUST_AST_DUMP_H
diff --git a/gcc/rust/ast/rust-ast-full-decls.h b/gcc/rust/ast/rust-ast-full-decls.h
new file mode 100644
index 00000000000..47f332193cc
--- /dev/null
+++ b/gcc/rust/ast/rust-ast-full-decls.h
@@ -0,0 +1,273 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_FULL_DECLS_H
+#define RUST_AST_FULL_DECLS_H
+
+// Forward declarations for all AST classes. Useful for not having to include
+// all definitions.
+
+namespace Rust {
+namespace AST {
+// rust-ast.h
+class AttrInput;
+class TokenTree;
+class MacroMatch;
+class Token;
+struct Literal;
+class DelimTokenTree;
+class PathSegment;
+class SimplePathSegment;
+class SimplePath;
+struct Attribute;
+class MetaItemInner;
+class AttrInputMetaItemContainer;
+class MetaItem;
+class Stmt;
+class Item;
+class Expr;
+class ExprWithoutBlock;
+class IdentifierExpr;
+class Pattern;
+class Type;
+class TypeNoBounds;
+class TypeParamBound;
+class Lifetime;
+class GenericParam;
+class LifetimeParam;
+class ConstGenericParam;
+class MacroItem;
+class TraitItem;
+class InherentImplItem;
+class TraitImplItem;
+struct Crate;
+class PathExpr;
+
+// rust-path.h
+class PathIdentSegment;
+struct GenericArgsBinding;
+struct GenericArgs;
+class PathExprSegment;
+class PathPattern;
+class PathInExpression;
+class TypePathSegment;
+class TypePathSegmentGeneric;
+struct TypePathFunction;
+class TypePathSegmentFunction;
+class TypePath;
+struct QualifiedPathType;
+class QualifiedPathInExpression;
+class QualifiedPathInType;
+
+// rust-expr.h
+class ExprWithBlock;
+class LiteralExpr;
+class AttrInputLiteral;
+class MetaItemLitExpr;
+class MetaItemPathLit;
+class OperatorExpr;
+class BorrowExpr;
+class DereferenceExpr;
+class ErrorPropagationExpr;
+class NegationExpr;
+class ArithmeticOrLogicalExpr;
+class ComparisonExpr;
+class LazyBooleanExpr;
+class TypeCastExpr;
+class AssignmentExpr;
+class CompoundAssignmentExpr;
+class GroupedExpr;
+class ArrayElems;
+class ArrayElemsValues;
+class ArrayElemsCopied;
+class ArrayExpr;
+class ArrayIndexExpr;
+class TupleExpr;
+class TupleIndexExpr;
+class StructExpr;
+class StructExprStruct;
+struct StructBase;
+class StructExprField;
+class StructExprFieldIdentifier;
+class StructExprFieldWithVal;
+class StructExprFieldIdentifierValue;
+class StructExprFieldIndexValue;
+class StructExprStructFields;
+class StructExprStructBase;
+class CallExpr;
+class MethodCallExpr;
+class FieldAccessExpr;
+struct ClosureParam;
+class ClosureExpr;
+class ClosureExprInner;
+class BlockExpr;
+class ClosureExprInnerTyped;
+class ContinueExpr;
+class BreakExpr;
+class RangeExpr;
+class RangeFromToExpr;
+class RangeFromExpr;
+class RangeToExpr;
+class RangeFullExpr;
+class RangeFromToInclExpr;
+class RangeToInclExpr;
+class ReturnExpr;
+class UnsafeBlockExpr;
+class LoopLabel;
+class BaseLoopExpr;
+class LoopExpr;
+class WhileLoopExpr;
+class WhileLetLoopExpr;
+class ForLoopExpr;
+class IfExpr;
+class IfExprConseqElse;
+class IfExprConseqIf;
+class IfLetExpr;
+class IfExprConseqIfLet;
+class IfLetExprConseqElse;
+class IfLetExprConseqIf;
+class IfLetExprConseqIfLet;
+struct MatchArm;
+// class MatchCase;
+// class MatchCaseBlockExpr;
+// class MatchCaseExpr;
+struct MatchCase;
+class MatchExpr;
+class AwaitExpr;
+class AsyncBlockExpr;
+
+// rust-stmt.h
+class EmptyStmt;
+class LetStmt;
+class ExprStmt;
+class ExprStmtWithoutBlock;
+class ExprStmtWithBlock;
+
+// rust-item.h
+class TypeParam;
+class WhereClauseItem;
+class LifetimeWhereClauseItem;
+class TypeBoundWhereClauseItem;
+struct WhereClause;
+struct SelfParam;
+struct FunctionQualifiers;
+struct FunctionParam;
+struct Visibility;
+class Method;
+class VisItem;
+class Module;
+class ExternCrate;
+class UseTree;
+class UseTreeGlob;
+class UseTreeList;
+class UseTreeRebind;
+class UseDeclaration;
+class Function;
+class TypeAlias;
+class Struct;
+struct StructField;
+class StructStruct;
+struct TupleField;
+class TupleStruct;
+class EnumItem;
+class EnumItemTuple;
+class EnumItemStruct;
+class EnumItemDiscriminant;
+class Enum;
+class Union;
+class ConstantItem;
+class StaticItem;
+struct TraitFunctionDecl;
+class TraitItemFunc;
+struct TraitMethodDecl;
+class TraitItemMethod;
+class TraitItemConst;
+class TraitItemType;
+class Trait;
+class Impl;
+class InherentImpl;
+class TraitImpl;
+class ExternalItem;
+class ExternalStaticItem;
+struct NamedFunctionParam;
+class ExternalFunctionItem;
+class ExternBlock;
+
+// rust-macro.h
+class MacroMatchFragment;
+class MacroMatchRepetition;
+class MacroMatcher;
+struct MacroTranscriber;
+struct MacroRule;
+class MacroRulesDefinition;
+class MacroInvocation;
+class MetaItemPath;
+class MetaItemSeq;
+class MetaWord;
+class MetaNameValueStr;
+class MetaListPaths;
+class MetaListNameValueStr;
+
+// rust-pattern.h
+class LiteralPattern;
+class IdentifierPattern;
+class WildcardPattern;
+class RangePatternBound;
+class RangePatternBoundLiteral;
+class RangePatternBoundPath;
+class RangePatternBoundQualPath;
+class RangePattern;
+class ReferencePattern;
+struct StructPatternEtc;
+class StructPatternField;
+class StructPatternFieldTuplePat;
+class StructPatternFieldIdentPat;
+class StructPatternFieldIdent;
+struct StructPatternElements;
+class StructPattern;
+class TupleStructItems;
+class TupleStructItemsNoRange;
+class TupleStructItemsRange;
+class TupleStructPattern;
+class TuplePatternItems;
+class TuplePatternItemsMultiple;
+class TuplePatternItemsRanged;
+class TuplePattern;
+class GroupedPattern;
+class SlicePattern;
+
+// rust-type.h
+class TraitBound;
+class ImplTraitType;
+class TraitObjectType;
+class ParenthesisedType;
+class ImplTraitTypeOneBound;
+class TraitObjectTypeOneBound;
+class TupleType;
+class NeverType;
+class RawPointerType;
+class ReferenceType;
+class ArrayType;
+class SliceType;
+class InferredType;
+struct MaybeNamedParam;
+class BareFunctionType;
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-ast-full-test.cc b/gcc/rust/ast/rust-ast-full-test.cc
new file mode 100644
index 00000000000..cac816d2545
--- /dev/null
+++ b/gcc/rust/ast/rust-ast-full-test.cc
@@ -0,0 +1,5814 @@
+/* General AST-related method implementations for Rust frontend.
+   Copyright (C) 2009-2022 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+// FIXME: This does not work on Windows
+#include <string>
+#include <unistd.h>
+#include <memory>
+
+#include "rust-ast-full.h"
+#include "rust-diagnostics.h"
+#include "rust-ast-visitor.h"
+#include "rust-macro.h"
+#include "rust-session-manager.h"
+#include "rust-lex.h"
+#include "rust-parse.h"
+#include "operator.h"
+
+/* Compilation unit used for various AST-related functions that would make
+ * the headers too long if they were defined inline and don't receive any
+ * benefits from being defined inline because they are virtual. Also used
+ * for various other stuff. */
+
+namespace Rust {
+namespace AST {
+
+enum indent_mode
+{
+  enter,
+  out,
+  stay
+};
+
+std::string
+indent_spaces (enum indent_mode mode)
+{
+  static int indent = 0;
+  std::string str = "";
+  if (out == mode)
+    indent--;
+  for (int i = 0; i < indent; i++)
+    str += " ";
+  if (enter == mode)
+    indent++;
+
+  return str;
+}
+
+// Gets a string in a certain delim type.
+std::string
+get_string_in_delims (std::string str_input, DelimType delim_type)
+{
+  switch (delim_type)
+    {
+    case PARENS:
+      return "(" + str_input + ")";
+    case SQUARE:
+      return "[" + str_input + "]";
+    case CURLY:
+      return "{" + str_input + "}";
+    default:
+      return "ERROR-MARK-STRING (delims)";
+    }
+  gcc_unreachable ();
+}
+
+enum AttrMode
+{
+  OUTER,
+  INNER
+};
+
+std::string
+get_mode_dump_desc (AttrMode mode)
+{
+  switch (mode)
+    {
+    case OUTER:
+      return "outer attributes";
+    case INNER:
+      return "inner attributes";
+    default:
+      gcc_unreachable ();
+      return "";
+    }
+}
+
+// Adds lines below adding attributes
+std::string
+append_attributes (std::vector<Attribute> attrs, AttrMode mode)
+{
+  indent_spaces (enter);
+
+  std::string str
+    = "\n" + indent_spaces (stay) + get_mode_dump_desc (mode) + ": ";
+  // str += "\n" + indent_spaces (stay) + "inner attributes: ";
+  if (attrs.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      /* note that this does not print them with outer or "inner attribute"
+       * syntax - just prints the body */
+      for (const auto &attr : attrs)
+	str += "\n" + indent_spaces (stay) + attr.as_string ();
+    }
+
+  indent_spaces (out);
+
+  return str;
+}
+
+// Removes the beginning and end quotes of a quoted string.
+std::string
+unquote_string (std::string input)
+{
+  rust_assert (input.front () == '"');
+  rust_assert (input.back () == '"');
+  return input.substr (1, input.length () - 2);
+}
+
+std::string
+Crate::as_string () const
+{
+  rust_debug ("beginning crate recursive as-string");
+
+  std::string str ("Crate: ");
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  // items
+  str += "\n items: ";
+  if (items.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &item : items)
+	{
+	  // DEBUG: null pointer check
+	  if (item == nullptr)
+	    {
+	      rust_debug ("something really terrible has gone wrong - "
+			  "null pointer item in crate.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + item->as_string ();
+	}
+    }
+
+  return str + "\n";
+}
+
+std::string
+Attribute::as_string () const
+{
+  std::string path_str = path.as_string ();
+  if (attr_input == nullptr)
+    return path_str;
+  else
+    return path_str + attr_input->as_string ();
+}
+
+// Copy constructor must deep copy attr_input as unique pointer
+Attribute::Attribute (Attribute const &other)
+  : path (other.path), locus (other.locus)
+{
+  // guard to protect from null pointer dereference
+  if (other.attr_input != nullptr)
+    attr_input = other.attr_input->clone_attr_input ();
+}
+
+// overload assignment operator to use custom clone method
+Attribute &
+Attribute::operator= (Attribute const &other)
+{
+  path = other.path;
+  locus = other.locus;
+  // guard to protect from null pointer dereference
+  if (other.attr_input != nullptr)
+    attr_input = other.attr_input->clone_attr_input ();
+  else
+    attr_input = nullptr;
+
+  return *this;
+}
+
+std::string
+DelimTokenTree::as_string () const
+{
+  std::string start_delim;
+  std::string end_delim;
+  switch (delim_type)
+    {
+    case PARENS:
+      start_delim = "(";
+      end_delim = ")";
+      break;
+    case SQUARE:
+      start_delim = "[";
+      end_delim = "]";
+      break;
+    case CURLY:
+      start_delim = "{";
+      end_delim = "}";
+      break;
+    default:
+      rust_debug ("Invalid delimiter type, "
+		  "Should be PARENS, SQUARE, or CURLY.");
+      return "Invalid delimiter type";
+    }
+  std::string str = start_delim;
+  if (!token_trees.empty ())
+    {
+      for (const auto &tree : token_trees)
+	{
+	  // DEBUG: null pointer check
+	  if (tree == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"token tree in delim token tree.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += tree->as_string ();
+	}
+    }
+  str += end_delim;
+
+  return str;
+}
+
+std::string
+Token::as_string () const
+{
+  if (tok_ref->has_str ())
+    {
+      std::string str = tok_ref->get_str ();
+
+      std::string quote = is_string_lit () ? "\"" : "";
+      return quote + str + quote;
+    }
+  else
+    {
+      return tok_ref->get_token_description ();
+    }
+}
+
+std::string
+SimplePathSegment::as_string () const
+{
+  return segment_name;
+}
+
+std::string
+SimplePath::as_string () const
+{
+  std::string path;
+  if (has_opening_scope_resolution)
+    path = "::";
+
+  // crappy hack because doing proper for loop would be more code
+  bool first_time = true;
+  for (const auto &segment : segments)
+    {
+      if (first_time)
+	{
+	  path += segment.as_string ();
+	  first_time = false;
+	}
+      else
+	{
+	  path += "::" + segment.as_string ();
+	}
+
+      // DEBUG: remove later. Checks for path error.
+      if (segment.is_error ())
+	{
+	  rust_debug ("segment in path is error - this should've been filtered "
+		      "out. first segment "
+		      "was '%s'",
+		      segments.at (0).as_string ().c_str ());
+	}
+    }
+
+  return path;
+}
+
+std::string
+Visibility::as_string () const
+{
+  switch (vis_type)
+    {
+    case PRIV:
+      return std::string ("");
+    case PUB:
+      return std::string ("pub");
+    case PUB_CRATE:
+      return std::string ("pub(crate)");
+    case PUB_SELF:
+      return std::string ("pub(self)");
+    case PUB_SUPER:
+      return std::string ("pub(super)");
+    case PUB_IN_PATH:
+      return std::string ("pub(in ") + in_path.as_string () + std::string (")");
+    default:
+      gcc_unreachable ();
+    }
+}
+
+// Creates a string that reflects the visibility stored.
+std::string
+VisItem::as_string () const
+{
+  // FIXME: can't do formatting on string to make identation occur.
+  std::string str;
+
+  if (!outer_attrs.empty ())
+    {
+      for (const auto &attr : outer_attrs)
+	str += attr.as_string () + "\n";
+    }
+
+  if (has_visibility ())
+    str += visibility.as_string () + " ";
+
+  return str;
+}
+
+std::string
+Module::as_string () const
+{
+  std::string str = VisItem::as_string () + "mod " + module_name;
+
+  // Return early if we're dealing with an unloaded module as their body resides
+  // in a different file
+  if (kind == ModuleKind::UNLOADED)
+    return str + "\n no body (reference to external file)\n";
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  // items
+  str += "\n items: ";
+
+  // This can still happen if the module is loaded but empty, i.e. `mod foo {}`
+  if (items.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &item : items)
+	{
+	  // DEBUG: null pointer check
+	  if (item == nullptr)
+	    {
+	      rust_debug ("something really terrible has gone wrong - "
+			  "null pointer item in crate.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + item->as_string ();
+	}
+    }
+
+  return str + "\n";
+}
+
+std::string
+StaticItem::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += indent_spaces (stay) + "static";
+
+  if (has_mut)
+    str += " mut";
+
+  str += " " + name;
+
+  // DEBUG: null pointer check
+  if (type == nullptr)
+    {
+      rust_debug ("something really terrible has gone wrong - null "
+		  "pointer type in static item.");
+      return "NULL_POINTER_MARK";
+    }
+  str += "\n" + indent_spaces (stay) + "Type: " + type->as_string ();
+
+  // DEBUG: null pointer check
+  if (expr == nullptr)
+    {
+      rust_debug ("something really terrible has gone wrong - null "
+		  "pointer expr in static item.");
+      return "NULL_POINTER_MARK";
+    }
+  str += "\n" + indent_spaces (stay) + "Expression: " + expr->as_string ();
+
+  return str + "\n";
+}
+
+std::string
+ExternCrate::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += "extern crate " + referenced_crate;
+
+  if (has_as_clause ())
+    str += " as " + as_clause_name;
+
+  return str;
+}
+
+std::string
+TupleStruct::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += "struct " + struct_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in enum.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  // tuple fields
+  str += "\n Tuple fields: ";
+  if (fields.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &field : fields)
+	str += "\n  " + field.as_string ();
+    }
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  return str;
+}
+
+std::string
+ConstantItem::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += "const " + identifier;
+
+  // DEBUG: null pointer check
+  if (type == nullptr)
+    {
+      rust_debug ("something really terrible has gone wrong - null "
+		  "pointer type in const item.");
+      return "NULL_POINTER_MARK";
+    }
+  str += "\n  Type: " + type->as_string ();
+
+  // DEBUG: null pointer check
+  if (const_expr == nullptr)
+    {
+      rust_debug ("something really terrible has gone wrong - null "
+		  "pointer expr in const item.");
+      return "NULL_POINTER_MARK";
+    }
+  str += "\n  Expression: " + const_expr->as_string ();
+
+  return str + "\n";
+}
+
+std::string
+InherentImpl::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += "impl ";
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in inherent impl.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Type: " + trait_type->as_string ();
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  // inherent impl items
+  str += "\n Inherent impl items: ";
+  if (!has_impl_items ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &item : impl_items)
+	str += "\n  " + item->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+Method::as_string () const
+{
+  std::string str ("Method: \n ");
+
+  str += vis.as_string () + " " + qualifiers.as_string ();
+
+  str += " fn " + method_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in method.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Self param: " + self_param.as_string ();
+
+  str += "\n Function params: ";
+  if (function_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : function_params)
+	str += "\n  " + param.as_string ();
+    }
+
+  str += "\n Return type: ";
+  if (has_return_type ())
+    str += return_type->as_string ();
+  else
+    str += "none (void)";
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  str += "\n Block expr (body): \n  ";
+  str += function_body->as_string ();
+
+  return str;
+}
+
+std::string
+StructStruct::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += "struct " + struct_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in enum.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  // struct fields
+  str += "\n Struct fields: ";
+  if (is_unit)
+    {
+      str += "none (unit)";
+    }
+  else if (fields.empty ())
+    {
+      str += "none (non-unit)";
+    }
+  else
+    {
+      for (const auto &field : fields)
+	str += "\n  " + field.as_string ();
+    }
+
+  return str;
+}
+
+std::string
+UseDeclaration::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  // DEBUG: null pointer check
+  if (use_tree == nullptr)
+    {
+      rust_debug (
+	"something really terrible has gone wrong - null pointer use tree in "
+	"use declaration.");
+      return "NULL_POINTER_MARK";
+    }
+
+  str += "use " + use_tree->as_string ();
+
+  return str;
+}
+
+std::string
+UseTreeGlob::as_string () const
+{
+  switch (glob_type)
+    {
+    case NO_PATH:
+      return "*";
+    case GLOBAL:
+      return "::*";
+      case PATH_PREFIXED: {
+	std::string path_str = path.as_string ();
+	return path_str + "::*";
+      }
+    default:
+      // some kind of error
+      return "ERROR-PATH";
+    }
+  gcc_unreachable ();
+}
+
+std::string
+UseTreeList::as_string () const
+{
+  std::string path_str;
+  switch (path_type)
+    {
+    case NO_PATH:
+      path_str = "{";
+      break;
+    case GLOBAL:
+      path_str = "::{";
+      break;
+      case PATH_PREFIXED: {
+	path_str = path.as_string () + "::{";
+	break;
+      }
+    default:
+      // some kind of error
+      return "ERROR-PATH-LIST";
+    }
+
+  if (has_trees ())
+    {
+      auto i = trees.begin ();
+      auto e = trees.end ();
+
+      // DEBUG: null pointer check
+      if (*i == nullptr)
+	{
+	  rust_debug ("something really terrible has gone wrong - null pointer "
+		      "tree in use tree list.");
+	  return "NULL_POINTER_MARK";
+	}
+
+      for (; i != e; i++)
+	{
+	  path_str += (*i)->as_string ();
+	  if (e != i + 1)
+	    path_str += ", ";
+	}
+    }
+  else
+    {
+      path_str += "none";
+    }
+
+  return path_str + "}";
+}
+
+std::string
+UseTreeRebind::as_string () const
+{
+  std::string path_str = path.as_string ();
+
+  switch (bind_type)
+    {
+    case NONE:
+      // nothing to add, just path
+      break;
+    case IDENTIFIER:
+      path_str += " as " + identifier;
+      break;
+    case WILDCARD:
+      path_str += " as _";
+      break;
+    default:
+      // error
+      return "ERROR-PATH-REBIND";
+    }
+
+  return path_str;
+}
+
+std::string
+Enum::as_string () const
+{
+  std::string str = VisItem::as_string ();
+  str += enum_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in enum.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  // items
+  str += "\n Items: ";
+  if (items.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &item : items)
+	{
+	  // DEBUG: null pointer check
+	  if (item == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"enum item in enum.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + item->as_string ();
+	}
+    }
+
+  return str;
+}
+
+std::string
+Trait::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  if (has_unsafe)
+    str += "unsafe ";
+
+  str += "trait " + name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in trait.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Type param bounds: ";
+  if (!has_type_param_bounds ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &bound : type_param_bounds)
+	{
+	  // DEBUG: null pointer check
+	  if (bound == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"type param bound in trait.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + bound->as_string ();
+	}
+    }
+
+  str += "\n Where clause: ";
+  if (!has_where_clause ())
+    str += "none";
+  else
+    str += where_clause.as_string ();
+
+  str += "\n Trait items: ";
+  if (!has_trait_items ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &item : trait_items)
+	{
+	  // DEBUG: null pointer check
+	  if (item == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"trait item in trait.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + item->as_string ();
+	}
+    }
+
+  return str;
+}
+
+std::string
+Union::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += "union " + union_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in union.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  // struct fields
+  str += "\n Struct fields (variants): ";
+  if (variants.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &field : variants)
+	str += "\n  " + field.as_string ();
+    }
+
+  return str;
+}
+
+std::string
+Function::as_string () const
+{
+  std::string str = VisItem::as_string () + "\n";
+  std::string qstr = qualifiers.as_string ();
+  if ("" != qstr)
+    str += qstr + " ";
+
+  if (has_return_type ())
+    {
+      // DEBUG: null pointer check
+      if (return_type == nullptr)
+	{
+	  rust_debug (
+	    "something really terrible has gone wrong - null pointer return "
+	    "type in function.");
+	  return "NULL_POINTER_MARK";
+	}
+
+      str += return_type->as_string () + " ";
+    }
+  else
+    {
+      str += "void ";
+    }
+
+  str += function_name;
+
+  if (has_generics ())
+    {
+      str += "<";
+
+      auto i = generic_params.begin ();
+      auto e = generic_params.end ();
+
+      // DEBUG: null pointer check
+      if (i == e)
+	{
+	  rust_debug ("something really terrible has gone wrong - null pointer "
+		      "generic param in function item.");
+	  return "NULL_POINTER_MARK";
+	}
+
+      for (; i != e; i++)
+	{
+	  str += (*i)->as_string ();
+	  if (e != i + 1)
+	    str += ", ";
+	}
+      str += ">";
+    }
+
+  if (has_function_params ())
+    {
+      auto i = function_params.begin ();
+      auto e = function_params.end ();
+      str += "(";
+      for (; i != e; i++)
+	{
+	  str += (*i).as_string ();
+	  if (e != i + 1)
+	    str += ", ";
+	}
+      str += ")";
+    }
+  else
+    {
+      str += "()";
+    }
+
+  if (has_where_clause ())
+    str += " where " + where_clause.as_string ();
+
+  str += "\n";
+
+  // DEBUG: null pointer check
+  if (function_body == nullptr)
+    {
+      rust_debug (
+	"something really terrible has gone wrong - null pointer function "
+	"body in function.");
+      return "NULL_POINTER_MARK";
+    }
+  str += function_body->as_string () + "\n";
+
+  return str;
+}
+
+std::string
+WhereClause::as_string () const
+{
+  // just print where clause items, don't mention "where" or "where clause"
+  std::string str;
+
+  if (where_clause_items.empty ())
+    {
+      str = "none";
+    }
+  else
+    {
+      for (const auto &item : where_clause_items)
+	str += "\n  " + item->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+BlockExpr::as_string () const
+{
+  std::string istr = indent_spaces (enter);
+  std::string str = istr + "BlockExpr:\n" + istr;
+
+  // get outer attributes
+  str += append_attributes (outer_attrs, OUTER);
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  // statements
+  str += "\n" + indent_spaces (stay) + "statements: ";
+  if (statements.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &stmt : statements)
+	{
+	  // DEBUG: null pointer check
+	  if (stmt == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"stmt in block expr.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n" + indent_spaces (stay) + stmt->as_string ();
+	}
+    }
+
+  // final expression
+  str += "\n" + indent_spaces (stay) + "final expression: ";
+  if (expr == nullptr)
+    str += "none";
+  else
+    str += "\n" + expr->as_string ();
+
+  str += "\n" + indent_spaces (out);
+  return str;
+}
+
+std::string
+TraitImpl::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  if (has_unsafe)
+    str += "unsafe ";
+
+  str += "impl ";
+
+  // generic params
+  str += "\n Generic params: ";
+  if (!has_generics ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	str += "\n  " + param->as_string ();
+    }
+
+  str += "\n Has exclam: ";
+  if (has_exclam)
+    str += "true";
+  else
+    str += "false";
+
+  str += "\n TypePath (to trait): " + trait_path.as_string ();
+
+  str += "\n Type (struct to impl on): " + trait_type->as_string ();
+
+  str += "\n Where clause: ";
+  if (!has_where_clause ())
+    str += "none";
+  else
+    str += where_clause.as_string ();
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  str += "\n trait impl items: ";
+  if (!has_impl_items ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &item : impl_items)
+	str += "\n  " + item->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+TypeAlias::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += " " + new_type_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (!has_generics ())
+    {
+      str += "none";
+    }
+  else
+    {
+      auto i = generic_params.begin ();
+      auto e = generic_params.end ();
+
+      for (; i != e; i++)
+	{
+	  str += (*i)->as_string ();
+	  if (e != i + 1)
+	    str += ", ";
+	}
+    }
+
+  str += "\n Where clause: ";
+  if (!has_where_clause ())
+    str += "none";
+  else
+    str += where_clause.as_string ();
+
+  str += "\n Type: " + existing_type->as_string ();
+
+  return str;
+}
+
+std::string
+ExternBlock::as_string () const
+{
+  std::string str = VisItem::as_string ();
+
+  str += "extern ";
+  if (has_abi ())
+    str += "\"" + abi + "\" ";
+
+  str += append_attributes (inner_attrs, INNER);
+
+  str += "\n external items: ";
+  if (!has_extern_items ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &item : extern_items)
+	str += "\n  " + item->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+MacroRule::as_string () const
+{
+  std::string str ("Macro rule: ");
+
+  str += "\n Matcher: \n  ";
+  str += matcher.as_string ();
+
+  str += "\n Transcriber: \n  ";
+  str += transcriber.as_string ();
+
+  return str;
+}
+
+std::string
+MacroRulesDefinition::as_string () const
+{
+  std::string str;
+
+  // get outer attrs
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "macro_rules!";
+
+  str += rule_name;
+
+  str += "\n Macro rules: ";
+  if (rules.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &rule : rules)
+	str += "\n  " + rule.as_string ();
+    }
+
+  str += "\n Delim type: ";
+  switch (delim_type)
+    {
+    case PARENS:
+      str += "parentheses";
+      break;
+    case SQUARE:
+      str += "square";
+      break;
+    case CURLY:
+      str += "curly";
+      break;
+    default:
+      return "ERROR_MARK_STRING - delim type in macro invocation";
+    }
+
+  return str;
+}
+
+std::string
+MacroInvocation::as_string () const
+{
+  std::string str = "MacroInvocation: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n " + invoc_data.as_string ();
+
+  str += "\n has semicolon: ";
+  str += has_semicolon () ? "true" : "false";
+
+  return str;
+}
+
+std::string
+MacroInvocData::as_string () const
+{
+  return path.as_string () + "!" + token_tree.as_string ();
+}
+
+std::string
+PathInExpression::as_string () const
+{
+  std::string str;
+
+  if (has_opening_scope_resolution)
+    str = "::";
+
+  return str + PathPattern::as_string ();
+}
+
+std::string
+ExprStmtWithBlock::as_string () const
+{
+  std::string str = indent_spaces (enter) + "ExprStmtWithBlock: \n";
+
+  if (expr == nullptr)
+    {
+      str += "none (this should not happen and is an error)";
+    }
+  else
+    {
+      indent_spaces (enter);
+      str += expr->as_string ();
+      indent_spaces (out);
+    }
+
+  indent_spaces (out);
+  return str;
+}
+
+std::string
+ClosureParam::as_string () const
+{
+  std::string str (pattern->as_string ());
+
+  if (has_type_given ())
+    str += " : " + type->as_string ();
+
+  return str;
+}
+
+std::string
+ClosureExpr::as_string () const
+{
+  std::string str = "ClosureExpr:";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Has move: ";
+  if (has_move)
+    str += "true";
+  else
+    str += "false";
+
+  str += "\n Params: ";
+  if (params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : params)
+	str += "\n  " + param.as_string ();
+    }
+
+  return str;
+}
+
+std::string
+ClosureExprInnerTyped::as_string () const
+{
+  std::string str = ClosureExpr::as_string ();
+
+  str += "\n Return type: " + return_type->as_string ();
+
+  str += "\n Body: " + expr->as_string ();
+
+  return str;
+}
+
+std::string
+PathPattern::as_string () const
+{
+  std::string str;
+
+  for (const auto &segment : segments)
+    str += segment.as_string () + "::";
+
+  // basically a hack - remove last two characters of string (remove final ::)
+  str.erase (str.length () - 2);
+
+  return str;
+}
+
+std::string
+QualifiedPathType::as_string () const
+{
+  std::string str ("<");
+  str += type_to_invoke_on->as_string ();
+
+  if (has_as_clause ())
+    str += " as " + trait_path.as_string ();
+
+  return str + ">";
+}
+
+std::string
+QualifiedPathInExpression::as_string () const
+{
+  return path_type.as_string () + "::" + PathPattern::as_string ();
+}
+
+std::string
+BorrowExpr::as_string () const
+{
+  /* TODO: find way to incorporate outer attrs - may have to represent in
+   * different style (i.e. something more like BorrowExpr: \n outer attrs) */
+
+  std::string str ("&");
+
+  if (double_borrow)
+    str += "&";
+
+  if (is_mut)
+    str += "mut ";
+
+  str += main_or_left_expr->as_string ();
+
+  return str;
+}
+
+std::string
+ReturnExpr::as_string () const
+{
+  /* TODO: find way to incorporate outer attrs - may have to represent in
+   * different style (i.e. something more like BorrowExpr: \n outer attrs) */
+
+  std::string str ("return ");
+
+  if (has_returned_expr ())
+    str += return_expr->as_string ();
+
+  return str;
+}
+
+std::string
+GroupedExpr::as_string () const
+{
+  std::string str ("Grouped expr:");
+
+  // outer attrs
+  str += append_attributes (outer_attrs, OUTER);
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  str += "\n Expr in parens: " + expr_in_parens->as_string ();
+
+  return str;
+}
+
+std::string
+RangeToExpr::as_string () const
+{
+  return ".." + to->as_string ();
+}
+
+std::string
+ContinueExpr::as_string () const
+{
+  // TODO: rewrite format to allow outer attributes
+  std::string str ("continue ");
+
+  if (has_label ())
+    str += label.as_string ();
+
+  return str;
+}
+
+std::string
+NegationExpr::as_string () const
+{
+  // TODO: rewrite formula to allow outer attributes
+  std::string str;
+
+  switch (expr_type)
+    {
+    case NegationOperator::NEGATE:
+      str = "-";
+      break;
+    case NegationOperator::NOT:
+      str = "!";
+      break;
+    default:
+      return "ERROR_MARK_STRING - negation expr";
+    }
+
+  str += main_or_left_expr->as_string ();
+
+  return str;
+}
+
+std::string
+RangeFromExpr::as_string () const
+{
+  return from->as_string () + "..";
+}
+
+std::string
+RangeFullExpr::as_string () const
+{
+  return "..";
+}
+
+std::string
+ArrayIndexExpr::as_string () const
+{
+  // TODO: rewrite formula to allow outer attributes
+  return array_expr->as_string () + "[" + index_expr->as_string () + "]";
+}
+
+std::string
+AssignmentExpr::as_string () const
+{
+  std::string str ("AssignmentExpr: ");
+
+  if (main_or_left_expr == nullptr || right_expr == nullptr)
+    {
+      str += "error (either or both expressions are null)";
+    }
+  else
+    {
+      // left expr
+      str += "\n left: " + main_or_left_expr->as_string ();
+
+      // right expr
+      str += "\n right: " + right_expr->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+AsyncBlockExpr::as_string () const
+{
+  std::string str = "AsyncBlockExpr: ";
+
+  // get outer attributes
+  // str += "\n " + Expr::as_string ();
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Has move: ";
+  str += has_move ? "true" : "false";
+
+  return str + "\n" + block_expr->as_string ();
+}
+
+std::string
+ComparisonExpr::as_string () const
+{
+  // TODO: rewrite to better reflect non-literal expressions
+  std::string str (main_or_left_expr->as_string ());
+
+  switch (expr_type)
+    {
+    case ComparisonOperator::EQUAL:
+      str += " == ";
+      break;
+    case ComparisonOperator::NOT_EQUAL:
+      str += " != ";
+      break;
+    case ComparisonOperator::GREATER_THAN:
+      str += " > ";
+      break;
+    case ComparisonOperator::LESS_THAN:
+      str += " < ";
+      break;
+    case ComparisonOperator::GREATER_OR_EQUAL:
+      str += " >= ";
+      break;
+    case ComparisonOperator::LESS_OR_EQUAL:
+      str += " <= ";
+      break;
+    default:
+      return "ERROR_MARK_STRING - comparison expr";
+    }
+
+  str += right_expr->as_string ();
+
+  return str;
+}
+
+std::string
+MethodCallExpr::as_string () const
+{
+  std::string str = "MethodCallExpr: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Object (receiver) expr: \n";
+  str += receiver->as_string ();
+
+  str += "\n Method path segment: \n";
+  str += method_name.as_string ();
+
+  str += "\n Call params:";
+  if (params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : params)
+	{
+	  if (param == nullptr)
+	    return "ERROR_MARK_STRING - method call expr param is null";
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  return str;
+}
+
+std::string
+TupleIndexExpr::as_string () const
+{
+  // TODO: rewrite dump to better reflect non-literal exprs
+  return tuple_expr->as_string () + "." + std::to_string (tuple_index);
+}
+
+std::string
+DereferenceExpr::as_string () const
+{
+  // TODO: rewrite dump to better reflect non-literal exprs
+  return "*" + main_or_left_expr->as_string ();
+}
+
+std::string
+FieldAccessExpr::as_string () const
+{
+  // TODO: rewrite dump to better reflect non-literal exprs
+  return receiver->as_string () + "." + field;
+}
+
+std::string
+LazyBooleanExpr::as_string () const
+{
+  // TODO: rewrite dump to better reflect non-literal exprs
+  std::string str (main_or_left_expr->as_string ());
+
+  switch (expr_type)
+    {
+    case LazyBooleanOperator::LOGICAL_OR:
+      str += " || ";
+      break;
+    case LazyBooleanOperator::LOGICAL_AND:
+      str += " && ";
+      break;
+    default:
+      return "ERROR_MARK_STRING - lazy boolean expr out of bounds";
+    }
+
+  str += right_expr->as_string ();
+
+  return str;
+}
+
+std::string
+RangeFromToExpr::as_string () const
+{
+  // TODO: rewrite dump to better reflect non-literal exprs
+  return from->as_string () + ".." + to->as_string ();
+}
+
+std::string
+RangeToInclExpr::as_string () const
+{
+  // TODO: rewrite dump to better reflect non-literal exprs
+  return "..=" + to->as_string ();
+}
+
+std::string
+UnsafeBlockExpr::as_string () const
+{
+  std::string str = "UnsafeBlockExpr:" + indent_spaces (enter);
+
+  // get outer attributes
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += indent_spaces (stay) + expr->as_string () + "\n" + indent_spaces (out);
+
+  return str;
+}
+
+std::string
+ClosureExprInner::as_string () const
+{
+  std::string str = ClosureExpr::as_string ();
+
+  str += "\n Expression: " + closure_inner->as_string ();
+
+  return str;
+}
+
+std::string
+IfExpr::as_string () const
+{
+  std::string str = "IfExpr: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Condition expr: " + condition->as_string ();
+
+  str += "\n If block expr: " + if_block->as_string ();
+
+  return str;
+}
+
+std::string
+IfExprConseqElse::as_string () const
+{
+  std::string str = IfExpr::as_string ();
+
+  str += "\n Else block expr: " + else_block->as_string ();
+
+  return str;
+}
+
+std::string
+IfExprConseqIf::as_string () const
+{
+  std::string str = IfExpr::as_string ();
+
+  str += "\n Else if expr: \n  " + conseq_if_expr->as_string ();
+
+  return str;
+}
+
+std::string
+IfExprConseqIfLet::as_string () const
+{
+  std::string str = IfExpr::as_string ();
+
+  str += "\n Else if let expr: \n  " + if_let_expr->as_string ();
+
+  return str;
+}
+
+std::string
+IfLetExpr::as_string () const
+{
+  std::string str = "IfLetExpr: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Condition match arm patterns: ";
+  if (match_arm_patterns.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &pattern : match_arm_patterns)
+	str += "\n  " + pattern->as_string ();
+    }
+
+  str += "\n Scrutinee expr: " + value->as_string ();
+
+  str += "\n If let block expr: " + if_block->as_string ();
+
+  return str;
+}
+
+std::string
+IfLetExprConseqElse::as_string () const
+{
+  std::string str = IfLetExpr::as_string ();
+
+  str += "\n Else block expr: " + else_block->as_string ();
+
+  return str;
+}
+
+std::string
+IfLetExprConseqIf::as_string () const
+{
+  std::string str = IfLetExpr::as_string ();
+
+  str += "\n Else if expr: \n  " + if_expr->as_string ();
+
+  return str;
+}
+
+std::string
+IfLetExprConseqIfLet::as_string () const
+{
+  std::string str = IfLetExpr::as_string ();
+
+  str += "\n Else if let expr: \n  " + if_let_expr->as_string ();
+
+  return str;
+}
+
+std::string
+RangeFromToInclExpr::as_string () const
+{
+  // TODO: rewrite to allow dumps with non-literal exprs
+  return from->as_string () + "..=" + to->as_string ();
+}
+
+std::string
+ErrorPropagationExpr::as_string () const
+{
+  // TODO: rewrite to allow dumps with non-literal exprs
+  return main_or_left_expr->as_string () + "?";
+}
+
+std::string
+CompoundAssignmentExpr::as_string () const
+{
+  std::string operator_str;
+  operator_str.reserve (1);
+
+  // get operator string
+  switch (expr_type)
+    {
+    case CompoundAssignmentOperator::ADD:
+      operator_str = "+";
+      break;
+    case CompoundAssignmentOperator::SUBTRACT:
+      operator_str = "-";
+      break;
+    case CompoundAssignmentOperator::MULTIPLY:
+      operator_str = "*";
+      break;
+    case CompoundAssignmentOperator::DIVIDE:
+      operator_str = "/";
+      break;
+    case CompoundAssignmentOperator::MODULUS:
+      operator_str = "%";
+      break;
+    case CompoundAssignmentOperator::BITWISE_AND:
+      operator_str = "&";
+      break;
+    case CompoundAssignmentOperator::BITWISE_OR:
+      operator_str = "|";
+      break;
+    case CompoundAssignmentOperator::BITWISE_XOR:
+      operator_str = "^";
+      break;
+    case CompoundAssignmentOperator::LEFT_SHIFT:
+      operator_str = "<<";
+      break;
+    case CompoundAssignmentOperator::RIGHT_SHIFT:
+      operator_str = ">>";
+      break;
+    default:
+      operator_str = "invalid operator. wtf";
+      break;
+    }
+
+  operator_str += "=";
+
+  std::string str ("CompoundAssignmentExpr: ");
+  if (main_or_left_expr == nullptr || right_expr == nullptr)
+    {
+      str += "error. this is probably a parsing failure.";
+    }
+  else
+    {
+      str += "\n left: " + main_or_left_expr->as_string ();
+      str += "\n right: " + right_expr->as_string ();
+      str += "\n operator: " + operator_str;
+    }
+
+  return str;
+}
+
+std::string
+ArithmeticOrLogicalExpr::as_string () const
+{
+  std::string operator_str;
+  operator_str.reserve (1);
+
+  // get operator string
+  switch (expr_type)
+    {
+    case ArithmeticOrLogicalOperator::ADD:
+      operator_str = "+";
+      break;
+    case ArithmeticOrLogicalOperator::SUBTRACT:
+      operator_str = "-";
+      break;
+    case ArithmeticOrLogicalOperator::MULTIPLY:
+      operator_str = "*";
+      break;
+    case ArithmeticOrLogicalOperator::DIVIDE:
+      operator_str = "/";
+      break;
+    case ArithmeticOrLogicalOperator::MODULUS:
+      operator_str = "%";
+      break;
+    case ArithmeticOrLogicalOperator::BITWISE_AND:
+      operator_str = "&";
+      break;
+    case ArithmeticOrLogicalOperator::BITWISE_OR:
+      operator_str = "|";
+      break;
+    case ArithmeticOrLogicalOperator::BITWISE_XOR:
+      operator_str = "^";
+      break;
+    case ArithmeticOrLogicalOperator::LEFT_SHIFT:
+      operator_str = "<<";
+      break;
+    case ArithmeticOrLogicalOperator::RIGHT_SHIFT:
+      operator_str = ">>";
+      break;
+    default:
+      operator_str = "invalid operator. wtf";
+      break;
+    }
+
+  std::string str ("ArithmeticOrLogicalExpr: ");
+  if (main_or_left_expr == nullptr || right_expr == nullptr)
+    {
+      str += "error. this is probably a parsing failure.";
+    }
+  else
+    {
+      str += main_or_left_expr->as_string () + " ";
+      str += operator_str + " ";
+      str += right_expr->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+CallExpr::as_string () const
+{
+  std::string str = "CallExpr: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Function expr: ";
+  str += function->as_string ();
+
+  str += "\n Call params:";
+  if (!has_params ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : params)
+	{
+	  if (param == nullptr)
+	    return "ERROR_MARK_STRING - call expr param is null";
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  return str;
+}
+
+std::string
+WhileLoopExpr::as_string () const
+{
+  std::string str = "WhileLoopExpr: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Label: ";
+  if (!has_loop_label ())
+    str += "none";
+  else
+    str += loop_label.as_string ();
+
+  str += "\n Conditional expr: " + condition->as_string ();
+
+  str += "\n Loop block: " + loop_block->as_string ();
+
+  return str;
+}
+
+std::string
+WhileLetLoopExpr::as_string () const
+{
+  std::string str = "WhileLetLoopExpr: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Label: ";
+  if (!has_loop_label ())
+    str += "none";
+  else
+    str += loop_label.as_string ();
+
+  str += "\n Match arm patterns: ";
+  if (match_arm_patterns.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &pattern : match_arm_patterns)
+	str += "\n  " + pattern->as_string ();
+    }
+
+  str += "\n Scrutinee expr: " + scrutinee->as_string ();
+
+  str += "\n Loop block: " + loop_block->as_string ();
+
+  return str;
+}
+
+std::string
+LoopExpr::as_string () const
+{
+  std::string str = "LoopExpr: (infinite loop)";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Label: ";
+  if (!has_loop_label ())
+    str += "none";
+  else
+    str += loop_label.as_string ();
+
+  str += "\n Loop block: " + loop_block->as_string ();
+
+  return str;
+}
+
+std::string
+ArrayExpr::as_string () const
+{
+  std::string str = "ArrayExpr:";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  str += "\n Array elems: ";
+  str += internal_elements->as_string ();
+
+  return str;
+}
+
+std::string
+AwaitExpr::as_string () const
+{
+  // TODO: rewrite dump to allow non-literal exprs
+  return awaited_expr->as_string () + ".await";
+}
+
+std::string
+BreakExpr::as_string () const
+{
+  // TODO: rewrite dump to allow outer attrs, non-literal exprs
+  std::string str ("break ");
+
+  if (has_label ())
+    str += label.as_string () + " ";
+
+  if (has_break_expr ())
+    str += break_expr->as_string ();
+
+  return str;
+}
+
+std::string
+LoopLabel::as_string () const
+{
+  return label.as_string () + ": (label) ";
+}
+
+std::string
+MatchArm::as_string () const
+{
+  // outer attributes
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  str += "\nPatterns: ";
+  if (match_arm_patterns.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &pattern : match_arm_patterns)
+	str += "\n " + pattern->as_string ();
+    }
+
+  str += "\nGuard expr: ";
+  if (!has_match_arm_guard ())
+    str += "none";
+  else
+    str += guard_expr->as_string ();
+
+  return str;
+}
+
+std::string
+MatchCase::as_string () const
+{
+  std::string str ("MatchCase: (match arm) ");
+
+  str += "\n Match arm matcher: \n" + arm.as_string ();
+  str += "\n Expr: " + expr->as_string ();
+
+  return str;
+}
+
+std::string
+MatchExpr::as_string () const
+{
+  std::string str ("MatchExpr:");
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Scrutinee expr: " + branch_value->as_string ();
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  // match arms
+  str += "\n Match arms: ";
+  if (match_arms.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &arm : match_arms)
+	str += "\n  " + arm.as_string ();
+    }
+
+  return str;
+}
+
+std::string
+TupleExpr::as_string () const
+{
+  std::string str ("TupleExpr:");
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  str += "\n Tuple elements: ";
+  if (tuple_elems.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &elem : tuple_elems)
+	str += "\n  " + elem->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+ExprStmtWithoutBlock::as_string () const
+{
+  std::string str ("ExprStmtWithoutBlock:\n");
+  indent_spaces (enter);
+  str += indent_spaces (stay);
+
+  if (expr == nullptr)
+    str += "none (this shouldn't happen and is probably an error)";
+  else
+    str += expr->as_string ();
+  indent_spaces (out);
+
+  return str;
+}
+
+std::string
+FunctionParam::as_string () const
+{
+  // TODO: rewrite dump to allow non-literal types
+  return param_name->as_string () + " : " + type->as_string ();
+}
+
+std::string
+FunctionQualifiers::as_string () const
+{
+  std::string str;
+
+  switch (const_status)
+    {
+    case NONE:
+      // do nothing
+      break;
+    case CONST_FN:
+      str += "const ";
+      break;
+    case ASYNC_FN:
+      str += "async ";
+      break;
+    default:
+      return "ERROR_MARK_STRING: async-const status failure";
+    }
+
+  if (has_unsafe)
+    str += "unsafe ";
+
+  if (has_extern)
+    {
+      str += "extern";
+      if (extern_abi != "")
+	str += " \"" + extern_abi + "\"";
+    }
+
+  return str;
+}
+
+std::string
+TraitBound::as_string () const
+{
+  std::string str ("TraitBound:");
+
+  str += "\n Has opening question mark: ";
+  if (opening_question_mark)
+    str += "true";
+  else
+    str += "false";
+
+  str += "\n For lifetimes: ";
+  if (!has_for_lifetimes ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &lifetime : for_lifetimes)
+	str += "\n  " + lifetime.as_string ();
+    }
+
+  str += "\n Type path: " + type_path.as_string ();
+
+  return str;
+}
+
+std::string
+MacroMatcher::as_string () const
+{
+  std::string str ("Macro matcher: ");
+
+  str += "\n Delim type: ";
+
+  switch (delim_type)
+    {
+    case PARENS:
+      str += "parentheses";
+      break;
+    case SQUARE:
+      str += "square";
+      break;
+    case CURLY:
+      str += "curly";
+      break;
+    default:
+      return "ERROR_MARK_STRING - macro matcher delim";
+    }
+
+  str += "\n Matches: ";
+
+  if (matches.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &match : matches)
+	str += "\n  " + match->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+LifetimeParam::as_string () const
+{
+  std::string str ("LifetimeParam: ");
+
+  str += "\n Outer attribute: ";
+  if (!has_outer_attribute ())
+    str += "none";
+  else
+    str += outer_attr.as_string ();
+
+  str += "\n Lifetime: " + lifetime.as_string ();
+
+  str += "\n Lifetime bounds: ";
+  if (!has_lifetime_bounds ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &bound : lifetime_bounds)
+	str += "\n  " + bound.as_string ();
+    }
+
+  return str;
+}
+
+std::string
+ConstGenericParam::as_string () const
+{
+  std::string str ("ConstGenericParam: ");
+  str += "const " + name + ": " + type->as_string ();
+
+  if (has_default_value ())
+    str += " = " + get_default_value ().as_string ();
+
+  return str;
+}
+
+std::string
+MacroMatchFragment::as_string () const
+{
+  return "$" + ident + ": " + frag_spec.as_string ();
+}
+
+std::string
+QualifiedPathInType::as_string () const
+{
+  /* TODO: this may need adjusting if segments (e.g. with functions) can't be
+   * literalised */
+  std::string str = path_type.as_string ();
+
+  for (const auto &segment : segments)
+    str += "::" + segment->as_string ();
+
+  return str;
+}
+
+std::string
+MacroMatchRepetition::as_string () const
+{
+  std::string str ("Macro match repetition: ");
+
+  str += "\n Matches: ";
+  if (matches.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &match : matches)
+	str += "\n  " + match->as_string ();
+    }
+
+  str += "\n Sep: ";
+  if (!has_sep ())
+    str += "none";
+  else
+    str += sep->as_string ();
+
+  str += "\n Op: ";
+  switch (op)
+    {
+    case ANY:
+      str += "*";
+      break;
+    case ONE_OR_MORE:
+      str += "+";
+      break;
+    case ZERO_OR_ONE:
+      str += "?";
+      break;
+    case NONE:
+      str += "no op? shouldn't be allowed";
+      break;
+    default:
+      return "ERROR_MARK_STRING - unknown op in macro match repetition";
+    }
+
+  return str;
+}
+
+std::string
+Lifetime::as_string () const
+{
+  if (is_error ())
+    return "error lifetime";
+
+  switch (lifetime_type)
+    {
+    case NAMED:
+      return "'" + lifetime_name;
+    case STATIC:
+      return "'static";
+    case WILDCARD:
+      return "'_";
+    default:
+      return "ERROR-MARK-STRING: lifetime type failure";
+    }
+}
+
+std::string
+TypePath::as_string () const
+{
+  /* TODO: this may need to be rewritten if a segment (e.g. function) can't be
+   * literalised */
+  std::string str;
+
+  if (has_opening_scope_resolution)
+    str = "::";
+
+  for (const auto &segment : segments)
+    str += segment->as_string () + "::";
+
+  // kinda hack - remove last 2 '::' characters
+  str.erase (str.length () - 2);
+
+  return str;
+}
+
+std::string
+TypeParam::as_string () const
+{
+  std::string str ("TypeParam: ");
+
+  str += "\n Outer attribute: ";
+  if (!has_outer_attribute ())
+    str += "none";
+  else
+    str += outer_attr.as_string ();
+
+  str += "\n Identifier: " + type_representation;
+
+  str += "\n Type param bounds: ";
+  if (!has_type_param_bounds ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &bound : type_param_bounds)
+	str += "\n  " + bound->as_string ();
+    }
+
+  str += "\n Type: ";
+  if (!has_type ())
+    str += "none";
+  else
+    str += type->as_string ();
+
+  return str;
+}
+
+SimplePath
+PathPattern::convert_to_simple_path (bool with_opening_scope_resolution) const
+{
+  if (!has_segments ())
+    return SimplePath::create_empty ();
+
+  // create vector of reserved size (to minimise reallocations)
+  std::vector<SimplePathSegment> simple_segments;
+  simple_segments.reserve (segments.size ());
+
+  for (const auto &segment : segments)
+    {
+      // return empty path if doesn't meet simple path segment requirements
+      if (segment.is_error () || segment.has_generic_args ()
+	  || segment.as_string () == "Self")
+	return SimplePath::create_empty ();
+
+      // create segment and add to vector
+      std::string segment_str = segment.as_string ();
+      simple_segments.push_back (
+	SimplePathSegment (std::move (segment_str), segment.get_locus ()));
+    }
+
+  // kind of a HACK to get locus depending on opening scope resolution
+  Location locus = Linemap::unknown_location ();
+  if (with_opening_scope_resolution)
+    locus = simple_segments[0].get_locus () - 2; // minus 2 chars for ::
+  else
+    locus = simple_segments[0].get_locus ();
+  // FIXME: this hack probably doesn't actually work
+
+  return SimplePath (std::move (simple_segments), with_opening_scope_resolution,
+		     locus);
+}
+
+SimplePath
+TypePath::as_simple_path () const
+{
+  if (segments.empty ())
+    return SimplePath::create_empty ();
+
+  // create vector of reserved size (to minimise reallocations)
+  std::vector<SimplePathSegment> simple_segments;
+  simple_segments.reserve (segments.size ());
+
+  for (const auto &segment : segments)
+    {
+      // return empty path if doesn't meet simple path segment requirements
+      if (segment == nullptr || segment->is_error ()
+	  || !segment->is_ident_only () || segment->as_string () == "Self")
+	return SimplePath::create_empty ();
+
+      // create segment and add to vector
+      std::string segment_str = segment->as_string ();
+      simple_segments.push_back (
+	SimplePathSegment (std::move (segment_str), segment->get_locus ()));
+    }
+
+  return SimplePath (std::move (simple_segments), has_opening_scope_resolution,
+		     locus);
+}
+
+std::string
+PathExprSegment::as_string () const
+{
+  // TODO: rewrite dump to work with non-literalisable types
+  std::string ident_str = segment_name.as_string ();
+  if (has_generic_args ())
+    ident_str += "::<" + generic_args.as_string () + ">";
+
+  return ident_str;
+}
+
+std::string
+GenericArgs::as_string () const
+{
+  std::string args;
+
+  // lifetime args
+  if (!lifetime_args.empty ())
+    {
+      auto i = lifetime_args.begin ();
+      auto e = lifetime_args.end ();
+
+      for (; i != e; i++)
+	{
+	  args += (*i).as_string ();
+	  if (e != i + 1)
+	    args += ", ";
+	}
+    }
+
+  // type args
+  if (!generic_args.empty ())
+    {
+      auto i = generic_args.begin ();
+      auto e = generic_args.end ();
+
+      for (; i != e; i++)
+	{
+	  args += (*i).as_string ();
+	  if (e != i + 1)
+	    args += ", ";
+	}
+    }
+
+  // binding args
+  if (!binding_args.empty ())
+    {
+      auto i = binding_args.begin ();
+      auto e = binding_args.end ();
+
+      for (; i != e; i++)
+	{
+	  args += (*i).as_string ();
+	  if (e != i + 1)
+	    args += ", ";
+	}
+    }
+
+  return args;
+}
+
+std::string
+GenericArgsBinding::as_string () const
+{
+  // TODO: rewrite to work with non-literalisable types
+  return identifier + " = " + type->as_string ();
+}
+
+std::string
+ForLoopExpr::as_string () const
+{
+  std::string str = "ForLoopExpr: ";
+
+  str += append_attributes (outer_attrs, OUTER);
+
+  str += "\n Label: ";
+  if (!has_loop_label ())
+    str += "none";
+  else
+    str += loop_label.as_string ();
+
+  str += "\n Pattern: " + pattern->as_string ();
+
+  str += "\n Iterator expr: " + iterator_expr->as_string ();
+
+  str += "\n Loop block: " + loop_block->as_string ();
+
+  return str;
+}
+
+std::string
+RangePattern::as_string () const
+{
+  // TODO: maybe rewrite to work with non-linearisable bounds
+  if (has_ellipsis_syntax)
+    return lower->as_string () + "..." + upper->as_string ();
+  else
+    return lower->as_string () + "..=" + upper->as_string ();
+}
+
+std::string
+RangePatternBoundLiteral::as_string () const
+{
+  std::string str;
+
+  if (has_minus)
+    str += "-";
+
+  str += literal.as_string ();
+
+  return str;
+}
+
+std::string
+SlicePattern::as_string () const
+{
+  std::string str ("SlicePattern: ");
+
+  for (const auto &pattern : items)
+    str += "\n " + pattern->as_string ();
+
+  return str;
+}
+
+std::string
+TuplePatternItemsMultiple::as_string () const
+{
+  std::string str;
+
+  for (const auto &pattern : patterns)
+    str += "\n " + pattern->as_string ();
+
+  return str;
+}
+
+std::string
+TuplePatternItemsRanged::as_string () const
+{
+  std::string str;
+
+  str += "\n Lower patterns: ";
+  if (lower_patterns.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &lower : lower_patterns)
+	str += "\n  " + lower->as_string ();
+    }
+
+  str += "\n Upper patterns: ";
+  if (upper_patterns.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &upper : upper_patterns)
+	str += "\n  " + upper->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+TuplePattern::as_string () const
+{
+  return "TuplePattern: " + items->as_string ();
+}
+
+std::string
+StructPatternField::as_string () const
+{
+  // outer attributes
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  return str;
+}
+
+std::string
+StructPatternFieldIdent::as_string () const
+{
+  std::string str = StructPatternField::as_string ();
+
+  str += "\n";
+
+  if (has_ref)
+    str += "ref ";
+
+  if (has_mut)
+    str += "mut ";
+
+  str += ident;
+
+  return str;
+}
+
+std::string
+StructPatternFieldTuplePat::as_string () const
+{
+  // TODO: maybe rewrite to work with non-linearisable patterns
+  std::string str = StructPatternField::as_string ();
+
+  str += "\n";
+
+  str += std::to_string (index) + " : " + tuple_pattern->as_string ();
+
+  return str;
+}
+
+std::string
+StructPatternFieldIdentPat::as_string () const
+{
+  // TODO: maybe rewrite to work with non-linearisable patterns
+  std::string str = StructPatternField::as_string ();
+
+  str += "\n";
+
+  str += ident + " : " + ident_pattern->as_string ();
+
+  return str;
+}
+
+std::string
+StructPatternElements::as_string () const
+{
+  std::string str ("\n  Fields: ");
+
+  if (!has_struct_pattern_fields ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &field : fields)
+	str += "\n   " + field->as_string ();
+    }
+
+  str += "\n  Etc: ";
+  if (has_struct_pattern_etc)
+    str += "true";
+  else
+    str += "false";
+
+  return str;
+}
+
+std::string
+StructPattern::as_string () const
+{
+  std::string str ("StructPattern: \n Path: ");
+
+  str += path.as_string ();
+
+  str += "\n Struct pattern elems: ";
+  if (!has_struct_pattern_elems ())
+    str += "none";
+  else
+    str += elems.as_string ();
+
+  return str;
+}
+
+std::string
+LiteralPattern::as_string () const
+{
+  return lit.as_string ();
+}
+
+std::string
+ReferencePattern::as_string () const
+{
+  // TODO: maybe rewrite to work with non-linearisable patterns
+  std::string str ("&");
+
+  if (has_two_amps)
+    str += "&";
+
+  if (is_mut)
+    str += "mut ";
+
+  str += pattern->as_string ();
+
+  return str;
+}
+
+std::string
+IdentifierPattern::as_string () const
+{
+  // TODO: maybe rewrite to work with non-linearisable patterns
+  std::string str;
+
+  if (is_ref)
+    str += "ref ";
+
+  if (is_mut)
+    str += "mut ";
+
+  str += variable_ident;
+
+  if (has_pattern_to_bind ())
+    str += " @ " + to_bind->as_string ();
+
+  return str;
+}
+
+std::string
+TupleStructItemsNoRange::as_string () const
+{
+  std::string str;
+
+  for (const auto &pattern : patterns)
+    str += "\n  " + pattern->as_string ();
+
+  return str;
+}
+
+std::string
+TupleStructItemsRange::as_string () const
+{
+  std::string str ("\n  Lower patterns: ");
+
+  if (lower_patterns.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &lower : lower_patterns)
+	str += "\n   " + lower->as_string ();
+    }
+
+  str += "\n  Upper patterns: ";
+  if (upper_patterns.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &upper : upper_patterns)
+	str += "\n   " + upper->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+TupleStructPattern::as_string () const
+{
+  std::string str ("TupleStructPattern: \n Path: ");
+
+  str += path.as_string ();
+
+  str += "\n Tuple struct items: " + items->as_string ();
+
+  return str;
+}
+
+std::string
+LetStmt::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types and exprs
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  str += "\n" + indent_spaces (stay) + "let " + variables_pattern->as_string ();
+
+  if (has_type ())
+    str += " : " + type->as_string ();
+
+  if (has_init_expr ())
+    str += " = " + init_expr->as_string ();
+
+  return str;
+}
+
+// hopefully definition here will prevent circular dependency issue
+TraitBound *
+TypePath::to_trait_bound (bool in_parens) const
+{
+  return new TraitBound (TypePath (*this), get_locus (), in_parens);
+}
+
+std::string
+InferredType::as_string () const
+{
+  return "_ (inferred)";
+}
+
+std::string
+TypeCastExpr::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable exprs and types
+  return main_or_left_expr->as_string () + " as "
+	 + type_to_convert_to->as_string ();
+}
+
+std::string
+ImplTraitType::as_string () const
+{
+  std::string str ("ImplTraitType: \n TypeParamBounds: ");
+
+  if (type_param_bounds.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &bound : type_param_bounds)
+	str += "\n  " + bound->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+ReferenceType::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types
+  std::string str ("&");
+
+  if (has_lifetime ())
+    str += lifetime.as_string () + " ";
+
+  if (has_mut)
+    str += "mut ";
+
+  str += type->as_string ();
+
+  return str;
+}
+
+std::string
+RawPointerType::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types
+  std::string str ("*");
+
+  switch (pointer_type)
+    {
+    case MUT:
+      str += "mut ";
+      break;
+    case CONST:
+      str += "const ";
+      break;
+    default:
+      return "ERROR_MARK_STRING - unknown pointer type in raw pointer type";
+    }
+
+  str += type->as_string ();
+
+  return str;
+}
+
+std::string
+TraitObjectType::as_string () const
+{
+  std::string str ("TraitObjectType: \n Has dyn dispatch: ");
+
+  if (has_dyn)
+    str += "true";
+  else
+    str += "false";
+
+  str += "\n TypeParamBounds: ";
+  if (type_param_bounds.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &bound : type_param_bounds)
+	str += "\n  " + bound->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+BareFunctionType::as_string () const
+{
+  std::string str ("BareFunctionType: \n For lifetimes: ");
+
+  if (!has_for_lifetimes ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &for_lifetime : for_lifetimes)
+	str += "\n  " + for_lifetime.as_string ();
+    }
+
+  str += "\n Qualifiers: " + function_qualifiers.as_string ();
+
+  str += "\n Params: ";
+  if (params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : params)
+	str += "\n  " + param.as_string ();
+    }
+
+  str += "\n Is variadic: ";
+  if (is_variadic)
+    str += "true";
+  else
+    str += "false";
+
+  str += "\n Return type: ";
+  if (!has_return_type ())
+    str += "none (void)";
+  else
+    str += return_type->as_string ();
+
+  return str;
+}
+
+std::string
+ImplTraitTypeOneBound::as_string () const
+{
+  std::string str ("ImplTraitTypeOneBound: \n TraitBound: ");
+
+  return str + trait_bound.as_string ();
+}
+
+std::string
+TypePathSegmentGeneric::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types
+  return TypePathSegment::as_string () + "<" + generic_args.as_string () + ">";
+}
+
+std::string
+TraitObjectTypeOneBound::as_string () const
+{
+  std::string str ("TraitObjectTypeOneBound: \n Has dyn dispatch: ");
+
+  if (has_dyn)
+    str += "true";
+  else
+    str += "false";
+
+  str += "\n TraitBound: " + trait_bound.as_string ();
+
+  return str;
+}
+
+std::string
+TypePathFunction::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types
+  std::string str ("(");
+
+  if (has_inputs ())
+    {
+      auto i = inputs.begin ();
+      auto e = inputs.end ();
+
+      for (; i != e; i++)
+	{
+	  str += (*i)->as_string ();
+	  if (e != i + 1)
+	    str += ", ";
+	}
+    }
+
+  str += ")";
+
+  if (has_return_type ())
+    str += " -> " + return_type->as_string ();
+
+  return str;
+}
+
+std::string
+TypePathSegmentFunction::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types
+  return TypePathSegment::as_string () + function_path.as_string ();
+}
+
+std::string
+ArrayType::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types and exprs
+  return "[" + elem_type->as_string () + "; " + size->as_string () + "]";
+}
+
+std::string
+SliceType::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types
+  return "[" + elem_type->as_string () + "]";
+}
+
+std::string
+TupleType::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable types
+  std::string str ("(");
+
+  if (!is_unit_type ())
+    {
+      auto i = elems.begin ();
+      auto e = elems.end ();
+
+      for (; i != e; i++)
+	{
+	  str += (*i)->as_string ();
+	  if (e != i + 1)
+	    str += ", ";
+	}
+    }
+
+  str += ")";
+
+  return str;
+}
+
+std::string
+StructExpr::as_string () const
+{
+  std::string str = append_attributes (outer_attrs, OUTER);
+  indent_spaces (enter);
+  str += "\n" + indent_spaces (stay) + "StructExpr:";
+  indent_spaces (enter);
+  str += "\n" + indent_spaces (stay) + "PathInExpr:\n";
+  str += indent_spaces (stay) + struct_name.as_string ();
+  indent_spaces (out);
+  indent_spaces (out);
+  return str;
+}
+
+std::string
+StructExprStruct::as_string () const
+{
+  // TODO: doesn't this require data from StructExpr?
+  std::string str ("StructExprStruct (or subclass): ");
+
+  str += "\n Path: " + get_struct_name ().as_string ();
+
+  // inner attributes
+  str += append_attributes (inner_attrs, INNER);
+
+  return str;
+}
+
+std::string
+StructBase::as_string () const
+{
+  if (base_struct != nullptr)
+    return base_struct->as_string ();
+  else
+    return "ERROR_MARK_STRING - invalid struct base had as string applied";
+}
+
+std::string
+StructExprFieldWithVal::as_string () const
+{
+  // used to get value string
+  return value->as_string ();
+}
+
+std::string
+StructExprFieldIdentifierValue::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable exprs
+  return field_name + " : " + StructExprFieldWithVal::as_string ();
+}
+
+std::string
+StructExprFieldIndexValue::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable exprs
+  return std::to_string (index) + " : " + StructExprFieldWithVal::as_string ();
+}
+
+std::string
+StructExprStructFields::as_string () const
+{
+  std::string str = StructExprStruct::as_string ();
+
+  str += "\n Fields: ";
+  if (fields.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &field : fields)
+	str += "\n  " + field->as_string ();
+    }
+
+  str += "\n Struct base: ";
+  if (!has_struct_base ())
+    str += "none";
+  else
+    str += struct_base.as_string ();
+
+  return str;
+}
+
+std::string
+EnumItem::as_string () const
+{
+  std::string str = VisItem::as_string ();
+  str += variant_name;
+
+  return str;
+}
+
+std::string
+EnumItemTuple::as_string () const
+{
+  std::string str = EnumItem::as_string ();
+
+  // add tuple opening parens
+  str += "(";
+
+  // tuple fields
+  if (has_tuple_fields ())
+    {
+      auto i = tuple_fields.begin ();
+      auto e = tuple_fields.end ();
+
+      for (; i != e; i++)
+	{
+	  str += (*i).as_string ();
+	  if (e != i + 1)
+	    str += ", ";
+	}
+    }
+
+  // add tuple closing parens
+  str += ")";
+
+  return str;
+}
+
+std::string
+TupleField::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable exprs
+
+  // outer attributes
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  if (has_visibility ())
+    str += "\n" + visibility.as_string ();
+
+  str += " " + field_type->as_string ();
+
+  return str;
+}
+
+std::string
+EnumItemStruct::as_string () const
+{
+  std::string str = EnumItem::as_string ();
+
+  // add struct opening parens
+  str += "{";
+
+  // tuple fields
+  if (has_struct_fields ())
+    {
+      auto i = struct_fields.begin ();
+      auto e = struct_fields.end ();
+
+      for (; i != e; i++)
+	{
+	  str += (*i).as_string ();
+	  if (e != i + 1)
+	    str += ", ";
+	}
+    }
+
+  // add struct closing parens
+  str += "}";
+
+  return str;
+}
+
+std::string
+StructField::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable exprs
+  // outer attributes
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  if (has_visibility ())
+    str += "\n" + visibility.as_string ();
+
+  str += " " + field_name + " : " + field_type->as_string ();
+
+  return str;
+}
+
+std::string
+EnumItemDiscriminant::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable exprs
+  std::string str = EnumItem::as_string ();
+
+  // add equal and expression
+  str += " = " + expression->as_string ();
+
+  return str;
+}
+
+std::string
+ExternalStaticItem::as_string () const
+{
+  // outer attributes
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  // start visibility on new line and with a space
+  str += "\n" + visibility.as_string () + " ";
+
+  str += "static ";
+
+  if (has_mut)
+    str += "mut ";
+
+  // add name
+  str += item_name;
+
+  // add type on new line
+  str += "\n Type: " + item_type->as_string ();
+
+  return str;
+}
+
+std::string
+ExternalFunctionItem::as_string () const
+{
+  // outer attributes
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  // start visibility on new line and with a space
+  str += "\n" + visibility.as_string () + " ";
+
+  str += "fn ";
+
+  // add name
+  str += item_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in external function item.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  // function params
+  str += "\n Function params: ";
+  if (function_params.empty () && !has_variadics)
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : function_params)
+	str += "\n  " + param.as_string ();
+
+      if (has_variadics)
+	{
+	  str += "\n  variadic outer attrs: ";
+	  if (has_variadic_outer_attrs ())
+	    {
+	      for (const auto &attr : variadic_outer_attrs)
+		str += "\n   " + attr.as_string ();
+	    }
+	  else
+	    {
+	      str += "none";
+	    }
+	  str += "\n  ... (variadic)";
+	}
+    }
+
+  // add type on new line
+  str += "\n (return) Type: "
+	 + (has_return_type () ? return_type->as_string () : "()");
+
+  // where clause
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  return str;
+}
+
+std::string
+NamedFunctionParam::as_string () const
+{
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  str += "\n" + name;
+
+  str += "\n Type: " + param_type->as_string ();
+
+  return str;
+}
+
+std::string
+TraitItemFunc::as_string () const
+{
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  str += "\n" + decl.as_string ();
+
+  str += "\n Definition (block expr): ";
+  if (has_definition ())
+    str += block_expr->as_string ();
+  else
+    str += "none";
+
+  return str;
+}
+
+std::string
+TraitFunctionDecl::as_string () const
+{
+  std::string str = qualifiers.as_string () + "fn " + function_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in trait function decl.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Function params: ";
+  if (has_params ())
+    {
+      for (const auto &param : function_params)
+	str += "\n  " + param.as_string ();
+    }
+  else
+    {
+      str += "none";
+    }
+
+  str += "\n Return type: ";
+  if (has_return_type ())
+    str += return_type->as_string ();
+  else
+    str += "none (void)";
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  return str;
+}
+
+std::string
+TraitItemMethod::as_string () const
+{
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  str += "\n" + decl.as_string ();
+
+  str += "\n Definition (block expr): ";
+  if (has_definition ())
+    str += block_expr->as_string ();
+  else
+    str += "none";
+
+  return str;
+}
+
+std::string
+TraitMethodDecl::as_string () const
+{
+  std::string str = qualifiers.as_string () + "fn " + function_name;
+
+  // generic params
+  str += "\n Generic params: ";
+  if (generic_params.empty ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &param : generic_params)
+	{
+	  // DEBUG: null pointer check
+	  if (param == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"generic param in trait function decl.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + param->as_string ();
+	}
+    }
+
+  str += "\n Self param: " + self_param.as_string ();
+
+  str += "\n Function params: ";
+  if (has_params ())
+    {
+      for (const auto &param : function_params)
+	str += "\n  " + param.as_string ();
+    }
+  else
+    {
+      str += "none";
+    }
+
+  str += "\n Return type: ";
+  if (has_return_type ())
+    str += return_type->as_string ();
+  else
+    str += "none (void)";
+
+  str += "\n Where clause: ";
+  if (has_where_clause ())
+    str += where_clause.as_string ();
+  else
+    str += "none";
+
+  return str;
+}
+
+std::string
+TraitItemConst::as_string () const
+{
+  // TODO: rewrite to work with non-linearisable exprs
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  str += "\nconst " + name + " : " + type->as_string ();
+
+  if (has_expression ())
+    str += " = " + expr->as_string ();
+
+  return str;
+}
+
+std::string
+TraitItemType::as_string () const
+{
+  std::string str = append_attributes (outer_attrs, OUTER);
+
+  str += "\ntype " + name;
+
+  str += "\n Type param bounds: ";
+  if (!has_type_param_bounds ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &bound : type_param_bounds)
+	{
+	  // DEBUG: null pointer check
+	  if (bound == nullptr)
+	    {
+	      rust_debug (
+		"something really terrible has gone wrong - null pointer "
+		"type param bound in trait item type.");
+	      return "NULL_POINTER_MARK";
+	    }
+
+	  str += "\n  " + bound->as_string ();
+	}
+    }
+
+  return str;
+}
+
+std::string
+SelfParam::as_string () const
+{
+  // TODO: rewrite to allow non-linearisable types
+  if (is_error ())
+    {
+      return "error";
+    }
+  else
+    {
+      if (has_type ())
+	{
+	  // type (i.e. not ref, no lifetime)
+	  std::string str;
+
+	  if (is_mut)
+	    str += "mut ";
+
+	  str += "self : ";
+
+	  str += type->as_string ();
+
+	  return str;
+	}
+      else if (has_lifetime ())
+	{
+	  // ref and lifetime
+	  std::string str = "&" + lifetime.as_string () + " ";
+
+	  if (is_mut)
+	    str += "mut ";
+
+	  str += "self";
+
+	  return str;
+	}
+      else if (has_ref)
+	{
+	  // ref with no lifetime
+	  std::string str = "&";
+
+	  if (is_mut)
+	    str += " mut ";
+
+	  str += "self";
+
+	  return str;
+	}
+      else
+	{
+	  // no ref, no type
+	  std::string str;
+
+	  if (is_mut)
+	    str += "mut ";
+
+	  str += "self";
+
+	  return str;
+	}
+    }
+}
+
+std::string
+ArrayElemsCopied::as_string () const
+{
+  // TODO: rewrite to allow non-linearisable exprs
+  return elem_to_copy->as_string () + "; " + num_copies->as_string ();
+}
+
+std::string
+LifetimeWhereClauseItem::as_string () const
+{
+  std::string str ("Lifetime: ");
+
+  str += lifetime.as_string ();
+
+  str += "\nLifetime bounds: ";
+
+  for (const auto &bound : lifetime_bounds)
+    str += "\n " + bound.as_string ();
+
+  return str;
+}
+
+std::string
+TypeBoundWhereClauseItem::as_string () const
+{
+  std::string str ("For lifetimes: ");
+
+  if (!has_for_lifetimes ())
+    {
+      str += "none";
+    }
+  else
+    {
+      for (const auto &for_lifetime : for_lifetimes)
+	str += "\n " + for_lifetime.as_string ();
+    }
+
+  str += "\nType: " + bound_type->as_string ();
+
+  str += "\nType param bounds bounds: ";
+
+  for (const auto &bound : type_param_bounds)
+    {
+      // debug null pointer check
+      if (bound == nullptr)
+	return "NULL_POINTER_MARK - type param bounds";
+
+      str += "\n " + bound->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+ArrayElemsValues::as_string () const
+{
+  std::string str;
+
+  for (const auto &expr : values)
+    {
+      // DEBUG: null pointer check
+      if (expr == nullptr)
+	{
+	  rust_debug ("something really terrible has gone wrong - null pointer "
+		      "expr in array elems values.");
+	  return "NULL_POINTER_MARK";
+	}
+
+      str += "\n  " + expr->as_string ();
+    }
+
+  return str;
+}
+
+std::string
+MaybeNamedParam::as_string () const
+{
+  // TODO: rewrite to allow using non-linearisable types in dump
+  std::string str;
+
+  switch (param_kind)
+    {
+    case UNNAMED:
+      break;
+    case IDENTIFIER:
+      str = name + " : ";
+      break;
+    case WILDCARD:
+      str = "_ : ";
+      break;
+    default:
+      return "ERROR_MARK_STRING - maybe named param unrecognised param kind";
+    }
+
+  str += param_type->as_string ();
+
+  return str;
+}
+
+MetaItemInner::~MetaItemInner () = default;
+
+std::unique_ptr<MetaNameValueStr>
+MetaItemInner::to_meta_name_value_str () const
+{
+  if (is_key_value_pair ())
+    {
+      auto converted_item = static_cast<const MetaNameValueStr *> (this);
+      return converted_item->to_meta_name_value_str ();
+    }
+  // TODO actually parse foo = bar
+  return nullptr;
+}
+
+std::string
+MetaItemSeq::as_string () const
+{
+  std::string path_str = path.as_string () + "(";
+
+  auto i = seq.begin ();
+  auto e = seq.end ();
+
+  for (; i != e; i++)
+    {
+      path_str += (*i)->as_string ();
+      if (e != i + 1)
+	path_str += ", ";
+    }
+
+  return path_str + ")";
+}
+
+std::string
+MetaListPaths::as_string () const
+{
+  std::string str = ident + "(";
+
+  auto i = paths.begin ();
+  auto e = paths.end ();
+
+  for (; i != e; i++)
+    {
+      str += (*i).as_string ();
+      if (e != i + 1)
+	str += ", ";
+    }
+
+  return str + ")";
+}
+
+std::string
+MetaListNameValueStr::as_string () const
+{
+  std::string str = ident + "(";
+
+  auto i = strs.begin ();
+  auto e = strs.end ();
+
+  for (; i != e; i++)
+    {
+      str += (*i).as_string ();
+      if (e != i + 1)
+	str += ", ";
+    }
+
+  return str + ")";
+}
+
+std::string
+AttrInputMetaItemContainer::as_string () const
+{
+  std::string str = "(";
+
+  auto i = items.begin ();
+  auto e = items.end ();
+
+  for (; i != e; i++)
+    {
+      str += (*i)->as_string ();
+      if (e != i + 1)
+	str += ", ";
+    }
+
+  return str + ")";
+}
+
+/* Override that calls the function recursively on all items contained within
+ * the module. */
+void
+Module::add_crate_name (std::vector<std::string> &names) const
+{
+  /* TODO: test whether module has been 'cfg'-ed out to determine whether to
+   * exclude it from search */
+
+  for (const auto &item : items)
+    item->add_crate_name (names);
+}
+
+static bool
+file_exists (const std::string path)
+{
+  // Simply check if the file exists
+  // FIXME: This does not work on Windows
+  return access (path.c_str (), F_OK) != -1;
+}
+
+static std::string
+filename_from_path_attribute (std::vector<Attribute> &outer_attrs)
+{
+  // An out-of-line module cannot have inner attributes. Additionally, the
+  // default name is specified as `""` so that the caller can detect the case
+  // of "no path given" and use the default path logic (`name.rs` or
+  // `name/mod.rs`).
+  return extract_module_path ({}, outer_attrs, "");
+}
+
+void
+Module::process_file_path ()
+{
+  rust_assert (kind == Module::ModuleKind::UNLOADED);
+  rust_assert (module_file.empty ());
+
+  // This corresponds to the path of the file 'including' the module. So the
+  // file that contains the 'mod <file>;' directive
+  std::string including_fname (outer_filename);
+
+  std::string expected_file_path = module_name + ".rs";
+  std::string expected_dir_path = "mod.rs";
+
+  auto dir_slash_pos = including_fname.rfind (file_separator);
+  std::string current_directory_name;
+
+  // If we haven't found a file_separator, then we have to look for files in the
+  // current directory ('.')
+  if (dir_slash_pos == std::string::npos)
+    current_directory_name = std::string (".") + file_separator;
+  else
+    current_directory_name
+      = including_fname.substr (0, dir_slash_pos) + file_separator;
+
+  // Handle inline module declarations adding path components.
+  for (auto const &name : module_scope)
+    {
+      current_directory_name.append (name);
+      current_directory_name.append (file_separator);
+    }
+
+  auto path_string = filename_from_path_attribute (get_outer_attrs ());
+  if (!path_string.empty ())
+    {
+      module_file = current_directory_name + path_string;
+      return;
+    }
+
+  // FIXME: We also have to search for
+  // <directory>/<including_fname>/<module_name>.rs In rustc, this is done via
+  // the concept of `DirOwnernship`, which is based on whether or not the
+  // current file is titled `mod.rs`.
+
+  // First, we search for <directory>/<module_name>.rs
+  std::string file_mod_path = current_directory_name + expected_file_path;
+  bool file_mod_found = file_exists (file_mod_path);
+
+  // Then, search for <directory>/<module_name>/mod.rs
+  std::string dir_mod_path
+    = current_directory_name + module_name + file_separator + expected_dir_path;
+  bool dir_mod_found = file_exists (dir_mod_path);
+
+  bool multiple_candidates_found = file_mod_found && dir_mod_found;
+  bool no_candidates_found = !file_mod_found && !dir_mod_found;
+
+  if (multiple_candidates_found)
+    rust_error_at (locus,
+		   "two candidates found for module %s: %s.rs and %s%smod.rs",
+		   module_name.c_str (), module_name.c_str (),
+		   module_name.c_str (), file_separator);
+
+  if (no_candidates_found)
+    rust_error_at (locus, "no candidate found for module %s",
+		   module_name.c_str ());
+
+  if (no_candidates_found || multiple_candidates_found)
+    return;
+
+  module_file = std::move (file_mod_found ? file_mod_path : dir_mod_path);
+}
+
+void
+Module::load_items ()
+{
+  process_file_path ();
+
+  // We will already have errored out appropriately in the process_file_path ()
+  // method
+  if (module_file.empty ())
+    return;
+
+  RAIIFile file_wrap (module_file.c_str ());
+  Linemap *linemap = Session::get_instance ().linemap;
+  if (!file_wrap.ok ())
+    {
+      rust_error_at (get_locus (), "cannot open module file %s: %m",
+		     module_file.c_str ());
+      return;
+    }
+
+  rust_debug ("Attempting to parse file %s", module_file.c_str ());
+
+  Lexer lex (module_file.c_str (), std::move (file_wrap), linemap);
+  Parser<Lexer> parser (lex);
+
+  // we need to parse any possible inner attributes for this module
+  inner_attrs = parser.parse_inner_attributes ();
+  auto parsed_items = parser.parse_items ();
+  for (const auto &error : parser.get_errors ())
+    error.emit_error ();
+
+  items = std::move (parsed_items);
+  kind = ModuleKind::LOADED;
+}
+
+void
+Attribute::parse_attr_to_meta_item ()
+{
+  // only parse if has attribute input and not already parsed
+  if (!has_attr_input () || is_parsed_to_meta_item ())
+    return;
+
+  auto res = attr_input->parse_to_meta_item ();
+  std::unique_ptr<AttrInput> converted_input (res);
+
+  if (converted_input != nullptr)
+    attr_input = std::move (converted_input);
+}
+
+AttrInputMetaItemContainer *
+DelimTokenTree::parse_to_meta_item () const
+{
+  // must have token trees
+  if (token_trees.empty ())
+    return nullptr;
+
+  /* assume top-level delim token tree in attribute - convert all nested ones
+   * to token stream */
+  std::vector<std::unique_ptr<Token>> token_stream = to_token_stream ();
+
+  AttributeParser parser (std::move (token_stream));
+  std::vector<std::unique_ptr<MetaItemInner>> meta_items (
+    parser.parse_meta_item_seq ());
+
+  return new AttrInputMetaItemContainer (std::move (meta_items));
+}
+
+std::unique_ptr<MetaItemInner>
+AttributeParser::parse_meta_item_inner ()
+{
+  // if first tok not identifier, not a "special" case one
+  if (peek_token ()->get_id () != IDENTIFIER)
+    {
+      switch (peek_token ()->get_id ())
+	{
+	case CHAR_LITERAL:
+	case STRING_LITERAL:
+	case BYTE_CHAR_LITERAL:
+	case BYTE_STRING_LITERAL:
+	case INT_LITERAL:
+	case FLOAT_LITERAL:
+	case TRUE_LITERAL:
+	case FALSE_LITERAL:
+	  return parse_meta_item_lit ();
+
+	case SUPER:
+	case SELF:
+	case CRATE:
+	case DOLLAR_SIGN:
+	case SCOPE_RESOLUTION:
+	  return parse_path_meta_item ();
+
+	default:
+	  rust_error_at (peek_token ()->get_locus (),
+			 "unrecognised token '%s' in meta item",
+			 get_token_description (peek_token ()->get_id ()));
+	  return nullptr;
+	}
+    }
+
+  // else, check for path
+  if (peek_token (1)->get_id () == SCOPE_RESOLUTION)
+    {
+      // path
+      return parse_path_meta_item ();
+    }
+
+  auto ident = peek_token ()->as_string ();
+  auto ident_locus = peek_token ()->get_locus ();
+
+  if (is_end_meta_item_tok (peek_token (1)->get_id ()))
+    {
+      // meta word syntax
+      skip_token ();
+      return std::unique_ptr<MetaWord> (new MetaWord (ident, ident_locus));
+    }
+
+  if (peek_token (1)->get_id () == EQUAL)
+    {
+      // maybe meta name value str syntax - check next 2 tokens
+      if (peek_token (2)->get_id () == STRING_LITERAL
+	  && is_end_meta_item_tok (peek_token (3)->get_id ()))
+	{
+	  // meta name value str syntax
+	  auto &value_tok = peek_token (2);
+	  auto value = value_tok->as_string ();
+	  auto locus = value_tok->get_locus ();
+
+	  skip_token (2);
+
+	  // remove the quotes from the string value
+	  std::string raw_value = unquote_string (std::move (value));
+
+	  return std::unique_ptr<MetaNameValueStr> (
+	    new MetaNameValueStr (ident, ident_locus, std::move (raw_value),
+				  locus));
+	}
+      else
+	{
+	  // just interpret as path-based meta item
+	  return parse_path_meta_item ();
+	}
+    }
+
+  if (peek_token (1)->get_id () != LEFT_PAREN)
+    {
+      rust_error_at (peek_token (1)->get_locus (),
+		     "unexpected token '%s' after identifier in attribute",
+		     get_token_description (peek_token (1)->get_id ()));
+      return nullptr;
+    }
+
+  // is it one of those special cases like not?
+  if (peek_token ()->get_id () == IDENTIFIER)
+    {
+      return parse_path_meta_item ();
+    }
+
+  auto meta_items = parse_meta_item_seq ();
+
+  // pass for meta name value str
+  std::vector<MetaNameValueStr> meta_name_value_str_items;
+  for (const auto &item : meta_items)
+    {
+      std::unique_ptr<MetaNameValueStr> converted_item
+	= item->to_meta_name_value_str ();
+      if (converted_item == nullptr)
+	{
+	  meta_name_value_str_items.clear ();
+	  break;
+	}
+      meta_name_value_str_items.push_back (std::move (*converted_item));
+    }
+  // if valid, return this
+  if (!meta_name_value_str_items.empty ())
+    {
+      return std::unique_ptr<MetaListNameValueStr> (
+	new MetaListNameValueStr (ident, ident_locus,
+				  std::move (meta_name_value_str_items)));
+    }
+
+  // // pass for meta list idents
+  // std::vector<Identifier> ident_items;
+  // for (const auto &item : meta_items)
+  //   {
+  //     std::unique_ptr<Identifier> converted_ident (item->to_ident_item ());
+  //     if (converted_ident == nullptr)
+  //       {
+  //         ident_items.clear ();
+  //         break;
+  //       }
+  //     ident_items.push_back (std::move (*converted_ident));
+  //   }
+  // // if valid return this
+  // if (!ident_items.empty ())
+  //   {
+  //     return std::unique_ptr<MetaListIdents> (
+  //       new MetaListIdents (std::move (ident), std::move (ident_items)));
+  //   }
+  // // as currently no meta list ident, currently no path. may change in future
+
+  // pass for meta list paths
+  std::vector<SimplePath> path_items;
+  for (const auto &item : meta_items)
+    {
+      SimplePath converted_path (item->to_path_item ());
+      if (converted_path.is_empty ())
+	{
+	  path_items.clear ();
+	  break;
+	}
+      path_items.push_back (std::move (converted_path));
+    }
+  if (!path_items.empty ())
+    {
+      return std::unique_ptr<MetaListPaths> (
+	new MetaListPaths (ident, ident_locus, std::move (path_items)));
+    }
+
+  rust_error_at (Linemap::unknown_location (),
+		 "failed to parse any meta item inner");
+  return nullptr;
+}
+
+bool
+AttributeParser::is_end_meta_item_tok (TokenId id) const
+{
+  return id == COMMA || id == RIGHT_PAREN;
+}
+
+std::unique_ptr<MetaItem>
+AttributeParser::parse_path_meta_item ()
+{
+  SimplePath path = parse_simple_path ();
+  if (path.is_empty ())
+    {
+      rust_error_at (peek_token ()->get_locus (),
+		     "failed to parse simple path in attribute");
+      return nullptr;
+    }
+
+  switch (peek_token ()->get_id ())
+    {
+      case LEFT_PAREN: {
+	std::vector<std::unique_ptr<MetaItemInner>> meta_items
+	  = parse_meta_item_seq ();
+
+	return std::unique_ptr<MetaItemSeq> (
+	  new MetaItemSeq (std::move (path), std::move (meta_items)));
+      }
+      case EQUAL: {
+	skip_token ();
+
+	Location locus = peek_token ()->get_locus ();
+	Literal lit = parse_literal ();
+	if (lit.is_error ())
+	  {
+	    rust_error_at (peek_token ()->get_locus (),
+			   "failed to parse literal in attribute");
+	    return nullptr;
+	  }
+	LiteralExpr expr (std::move (lit), {}, locus);
+	// stream_pos++;
+	/* shouldn't be required anymore due to parsing literal actually
+	 * skipping the token */
+	return std::unique_ptr<MetaItemPathLit> (
+	  new MetaItemPathLit (std::move (path), std::move (expr)));
+      }
+    case COMMA:
+      // just simple path
+      return std::unique_ptr<MetaItemPath> (
+	new MetaItemPath (std::move (path)));
+    default:
+      rust_error_at (peek_token ()->get_locus (),
+		     "unrecognised token '%s' in meta item",
+		     get_token_description (peek_token ()->get_id ()));
+      return nullptr;
+    }
+}
+
+/* Parses a parenthesised sequence of meta item inners. Parentheses are
+ * required here. */
+std::vector<std::unique_ptr<MetaItemInner>>
+AttributeParser::parse_meta_item_seq ()
+{
+  int vec_length = token_stream.size ();
+  std::vector<std::unique_ptr<MetaItemInner>> meta_items;
+
+  if (peek_token ()->get_id () != LEFT_PAREN)
+    {
+      rust_error_at (peek_token ()->get_locus (),
+		     "missing left paren in delim token tree");
+      return {};
+    }
+  skip_token ();
+
+  while (stream_pos < vec_length && peek_token ()->get_id () != RIGHT_PAREN)
+    {
+      std::unique_ptr<MetaItemInner> inner = parse_meta_item_inner ();
+      if (inner == nullptr)
+	{
+	  rust_error_at (peek_token ()->get_locus (),
+			 "failed to parse inner meta item in attribute");
+	  return {};
+	}
+      meta_items.push_back (std::move (inner));
+
+      if (peek_token ()->get_id () != COMMA)
+	break;
+
+      skip_token ();
+    }
+
+  if (peek_token ()->get_id () != RIGHT_PAREN)
+    {
+      rust_error_at (peek_token ()->get_locus (),
+		     "missing right paren in delim token tree");
+      return {};
+    }
+  skip_token ();
+
+  return meta_items;
+}
+
+/* Collects any nested token trees into a flat token stream, suitable for
+ * parsing. */
+std::vector<std::unique_ptr<Token>>
+DelimTokenTree::to_token_stream () const
+{
+  std::vector<std::unique_ptr<Token>> tokens;
+  for (const auto &tree : token_trees)
+    {
+      std::vector<std::unique_ptr<Token>> stream = tree->to_token_stream ();
+
+      tokens.insert (tokens.end (), std::make_move_iterator (stream.begin ()),
+		     std::make_move_iterator (stream.end ()));
+    }
+
+  tokens.shrink_to_fit ();
+  return tokens;
+}
+
+Literal
+AttributeParser::parse_literal ()
+{
+  const std::unique_ptr<Token> &tok = peek_token ();
+  switch (tok->get_id ())
+    {
+    case CHAR_LITERAL:
+      skip_token ();
+      return Literal (tok->as_string (), Literal::CHAR, tok->get_type_hint ());
+    case STRING_LITERAL:
+      skip_token ();
+      return Literal (tok->as_string (), Literal::STRING,
+		      tok->get_type_hint ());
+    case BYTE_CHAR_LITERAL:
+      skip_token ();
+      return Literal (tok->as_string (), Literal::BYTE, tok->get_type_hint ());
+    case BYTE_STRING_LITERAL:
+      skip_token ();
+      return Literal (tok->as_string (), Literal::BYTE_STRING,
+		      tok->get_type_hint ());
+    case INT_LITERAL:
+      skip_token ();
+      return Literal (tok->as_string (), Literal::INT, tok->get_type_hint ());
+    case FLOAT_LITERAL:
+      skip_token ();
+      return Literal (tok->as_string (), Literal::FLOAT, tok->get_type_hint ());
+    case TRUE_LITERAL:
+      skip_token ();
+      return Literal ("true", Literal::BOOL, tok->get_type_hint ());
+    case FALSE_LITERAL:
+      skip_token ();
+      return Literal ("false", Literal::BOOL, tok->get_type_hint ());
+    default:
+      rust_error_at (tok->get_locus (), "expected literal - found '%s'",
+		     get_token_description (tok->get_id ()));
+      return Literal::create_error ();
+    }
+}
+
+SimplePath
+AttributeParser::parse_simple_path ()
+{
+  bool has_opening_scope_res = false;
+  if (peek_token ()->get_id () == SCOPE_RESOLUTION)
+    {
+      has_opening_scope_res = true;
+      skip_token ();
+    }
+
+  std::vector<SimplePathSegment> segments;
+
+  SimplePathSegment segment = parse_simple_path_segment ();
+  if (segment.is_error ())
+    {
+      rust_error_at (
+	peek_token ()->get_locus (),
+	"failed to parse simple path segment in attribute simple path");
+      return SimplePath::create_empty ();
+    }
+  segments.push_back (std::move (segment));
+
+  while (peek_token ()->get_id () == SCOPE_RESOLUTION)
+    {
+      skip_token ();
+
+      SimplePathSegment segment = parse_simple_path_segment ();
+      if (segment.is_error ())
+	{
+	  rust_error_at (
+	    peek_token ()->get_locus (),
+	    "failed to parse simple path segment in attribute simple path");
+	  return SimplePath::create_empty ();
+	}
+      segments.push_back (std::move (segment));
+    }
+  segments.shrink_to_fit ();
+
+  return SimplePath (std::move (segments), has_opening_scope_res);
+}
+
+SimplePathSegment
+AttributeParser::parse_simple_path_segment ()
+{
+  const std::unique_ptr<Token> &tok = peek_token ();
+  switch (tok->get_id ())
+    {
+    case IDENTIFIER:
+      skip_token ();
+      return SimplePathSegment (tok->as_string (), tok->get_locus ());
+    case SUPER:
+      skip_token ();
+      return SimplePathSegment ("super", tok->get_locus ());
+    case SELF:
+      skip_token ();
+      return SimplePathSegment ("self", tok->get_locus ());
+    case CRATE:
+      skip_token ();
+      return SimplePathSegment ("crate", tok->get_locus ());
+    case DOLLAR_SIGN:
+      if (peek_token (1)->get_id () == CRATE)
+	{
+	  skip_token (1);
+	  return SimplePathSegment ("$crate", tok->get_locus ());
+	}
+      gcc_fallthrough ();
+    default:
+      rust_error_at (tok->get_locus (),
+		     "unexpected token '%s' in simple path segment",
+		     get_token_description (tok->get_id ()));
+      return SimplePathSegment::create_error ();
+    }
+}
+
+std::unique_ptr<MetaItemLitExpr>
+AttributeParser::parse_meta_item_lit ()
+{
+  Location locus = peek_token ()->get_locus ();
+  LiteralExpr lit_expr (parse_literal (), {}, locus);
+  return std::unique_ptr<MetaItemLitExpr> (
+    new MetaItemLitExpr (std::move (lit_expr)));
+}
+
+bool
+AttrInputMetaItemContainer::check_cfg_predicate (const Session &session) const
+{
+  if (items.empty ())
+    return false;
+
+  for (const auto &inner_item : items)
+    {
+      if (!inner_item->check_cfg_predicate (session))
+	return false;
+    }
+
+  return true;
+}
+
+bool
+MetaItemLitExpr::check_cfg_predicate (const Session &) const
+{
+  /* as far as I can tell, a literal expr can never be a valid cfg body, so
+   * false */
+  return false;
+}
+
+bool
+MetaListNameValueStr::check_cfg_predicate (const Session &session) const
+{
+  if (ident == "all")
+    {
+      for (const auto &str : strs)
+	{
+	  if (!str.check_cfg_predicate (session))
+	    return false;
+	}
+      return true;
+    }
+  else if (ident == "any")
+    {
+      for (const auto &str : strs)
+	{
+	  if (str.check_cfg_predicate (session))
+	    return true;
+	}
+      return false;
+    }
+  else if (ident == "not")
+    {
+      if (strs.size () != 1)
+	{
+	  /* HACK: convert vector platform-dependent size_type to string to
+	   * use in printf */
+	  rust_error_at (Linemap::unknown_location (),
+			 "cfg predicate could not be checked for "
+			 "MetaListNameValueStr with ident of "
+			 "'not' because there are '%s' elements, not '1'",
+			 std::to_string (strs.size ()).c_str ());
+	  return false;
+	}
+
+      return !strs[0].check_cfg_predicate (session);
+    }
+  else
+    {
+      rust_error_at (Linemap::unknown_location (),
+		     "cfg predicate could not be checked for "
+		     "MetaListNameValueStr with ident of "
+		     "'%s' - ident must be 'all' or 'any'",
+		     ident.c_str ());
+      return false;
+    }
+}
+
+bool
+MetaListPaths::check_cfg_predicate (const Session &session) const
+{
+  if (ident == "all")
+    {
+      for (const auto &path : paths)
+	{
+	  if (!check_path_exists_in_cfg (session, path))
+	    return false;
+	}
+      return true;
+    }
+  else if (ident == "any")
+    {
+      for (const auto &path : paths)
+	{
+	  if (check_path_exists_in_cfg (session, path))
+	    return true;
+	}
+      return false;
+    }
+  else if (ident == "not")
+    {
+      if (paths.size () != 1)
+	{
+	  // HACK: convert vector platform-dependent size_type to string to
+	  // use in printf
+	  rust_error_at (Linemap::unknown_location (),
+			 "cfg predicate could not be checked for MetaListPaths "
+			 "with ident of 'not' "
+			 "because there are '%s' elements, not '1'",
+			 std::to_string (paths.size ()).c_str ());
+	  return false;
+	}
+
+      return !check_path_exists_in_cfg (session, paths[0]);
+    }
+  else
+    {
+      rust_error_at (Linemap::unknown_location (),
+		     "cfg predicate could not be checked for "
+		     "MetaListNameValueStr with ident of "
+		     "'%s' - ident must be 'all' or 'any'",
+		     ident.c_str ());
+      return false;
+    }
+}
+
+bool
+MetaListPaths::check_path_exists_in_cfg (const Session &session,
+					 const SimplePath &path) const
+{
+  return session.options.target_data.has_key (path.as_string ());
+}
+
+bool
+MetaItemSeq::check_cfg_predicate (const Session &session) const
+{
+  if (path.as_string () == "all")
+    {
+      for (const auto &item : seq)
+	{
+	  if (!item->check_cfg_predicate (session))
+	    return false;
+	}
+      return true;
+    }
+  else if (path.as_string () == "any")
+    {
+      for (const auto &item : seq)
+	{
+	  if (item->check_cfg_predicate (session))
+	    return true;
+	}
+      return false;
+    }
+  else if (path.as_string () == "not")
+    {
+      if (seq.size () != 1)
+	{
+	  /* HACK: convert vector platform-dependent size_type to string to
+	   * use in printf */
+	  rust_error_at (Linemap::unknown_location (),
+			 "cfg predicate could not be checked for MetaItemSeq "
+			 "with ident of 'not' "
+			 "because there are '%s' elements, not '1'",
+			 std::to_string (seq.size ()).c_str ());
+	  return false;
+	}
+
+      return !seq[0]->check_cfg_predicate (session);
+    }
+  else
+    {
+      rust_error_at (
+	Linemap::unknown_location (),
+	"cfg predicate could not be checked for MetaItemSeq with path of "
+	"'%s' - path must be 'all' or 'any'",
+	path.as_string ().c_str ());
+      return false;
+    }
+}
+
+bool
+MetaWord::check_cfg_predicate (const Session &session) const
+{
+  return session.options.target_data.has_key (ident);
+}
+
+bool
+MetaItemPath::check_cfg_predicate (const Session &session) const
+{
+  /* Strictly speaking, this should always be false, but maybe do check
+   * relating to SimplePath being identifier. Currently, it would return true
+   * if path as identifier existed, and if the path in string form existed
+   * (though this shouldn't occur). */
+  return session.options.target_data.has_key (path.as_string ());
+}
+
+bool
+MetaNameValueStr::check_cfg_predicate (const Session &session) const
+{
+  // DEBUG
+  rust_debug (
+    "checked key-value pair for cfg: '%s', '%s' - is%s in target data",
+    ident.c_str (), str.c_str (),
+    session.options.target_data.has_key_value_pair (ident, str) ? "" : " not");
+
+  return session.options.target_data.has_key_value_pair (ident, str);
+}
+
+bool
+MetaItemPathLit::check_cfg_predicate (const Session &session) const
+{
+  return session.options.target_data.has_key_value_pair (path.as_string (),
+							 lit.as_string ());
+}
+
+std::vector<std::unique_ptr<Token>>
+Token::to_token_stream () const
+{
+  /* initialisation list doesn't work as it needs copy constructor, so have to
+   * do this */
+  std::vector<std::unique_ptr<Token>> dummy_vector;
+  dummy_vector.reserve (1);
+  dummy_vector.push_back (std::unique_ptr<Token> (clone_token_impl ()));
+  return dummy_vector;
+}
+
+Attribute
+MetaNameValueStr::to_attribute () const
+{
+  LiteralExpr lit_expr (str, Literal::LitType::STRING,
+			PrimitiveCoreType::CORETYPE_UNKNOWN, {}, str_locus);
+  // FIXME: What location do we put here? Is the literal above supposed to have
+  // an empty location as well?
+  // Should MetaNameValueStr keep a location?
+  return Attribute (SimplePath::from_str (ident, ident_locus),
+		    std::unique_ptr<AttrInputLiteral> (
+		      new AttrInputLiteral (std::move (lit_expr))));
+}
+
+Attribute
+MetaItemPath::to_attribute () const
+{
+  return Attribute (path, nullptr);
+}
+
+Attribute
+MetaItemSeq::to_attribute () const
+{
+  std::vector<std::unique_ptr<MetaItemInner>> new_seq;
+  new_seq.reserve (seq.size ());
+  for (const auto &e : seq)
+    new_seq.push_back (e->clone_meta_item_inner ());
+
+  std::unique_ptr<AttrInputMetaItemContainer> new_seq_container (
+    new AttrInputMetaItemContainer (std::move (new_seq)));
+  return Attribute (path, std::move (new_seq_container));
+}
+
+Attribute
+MetaWord::to_attribute () const
+{
+  return Attribute (SimplePath::from_str (ident, ident_locus), nullptr);
+}
+
+Attribute
+MetaListPaths::to_attribute () const
+{
+  /* probably one of the most annoying conversions - have to lose specificity by
+   * turning it into just AttrInputMetaItemContainer (i.e. paths-only nature is
+   * no longer known). If conversions back are required, might have to do a
+   * "check all are paths" pass or something. */
+
+  std::vector<std::unique_ptr<MetaItemInner>> new_seq;
+  new_seq.reserve (paths.size ());
+  for (const auto &e : paths)
+    new_seq.push_back (std::unique_ptr<MetaItemPath> (new MetaItemPath (e)));
+
+  std::unique_ptr<AttrInputMetaItemContainer> new_seq_container (
+    new AttrInputMetaItemContainer (std::move (new_seq)));
+  return Attribute (SimplePath::from_str (ident, ident_locus),
+		    std::move (new_seq_container));
+}
+
+Attribute
+MetaListNameValueStr::to_attribute () const
+{
+  std::vector<std::unique_ptr<MetaItemInner>> new_seq;
+  new_seq.reserve (strs.size ());
+  for (const auto &e : strs)
+    new_seq.push_back (
+      std::unique_ptr<MetaNameValueStr> (new MetaNameValueStr (e)));
+
+  std::unique_ptr<AttrInputMetaItemContainer> new_seq_container (
+    new AttrInputMetaItemContainer (std::move (new_seq)));
+  return Attribute (SimplePath::from_str (ident, ident_locus),
+		    std::move (new_seq_container));
+}
+
+Attribute
+MetaItemPathLit::to_attribute () const
+{
+  return Attribute (path, std::unique_ptr<AttrInputLiteral> (
+			    new AttrInputLiteral (lit)));
+}
+
+std::vector<Attribute>
+AttrInputMetaItemContainer::separate_cfg_attrs () const
+{
+  rust_assert (!items.empty ());
+
+  if (items.size () == 1)
+    return {};
+
+  std::vector<Attribute> attrs;
+  attrs.reserve (items.size () - 1);
+
+  for (auto it = items.begin () + 1; it != items.end (); ++it)
+    {
+      Attribute attr = (*it)->to_attribute ();
+      if (attr.is_empty ())
+	{
+	  /* TODO should this be an error that causes us to chuck out
+	   * everything? */
+	  continue;
+	}
+      attrs.push_back (std::move (attr));
+    }
+
+  attrs.shrink_to_fit ();
+  return attrs;
+}
+
+bool
+Attribute::check_cfg_predicate (const Session &session) const
+{
+  /* assume that cfg predicate actually can exist, i.e. attribute has cfg or
+   * cfg_attr path */
+  if (!has_attr_input ()
+      || (path.as_string () != "cfg" && path.as_string () != "cfg_attr"))
+    {
+      // DEBUG message
+      rust_debug (
+	"tried to check cfg predicate on attr that either has no input "
+	"or invalid path. attr: '%s'",
+	as_string ().c_str ());
+
+      return false;
+    }
+
+  // assume that it has already been parsed
+  if (!is_parsed_to_meta_item ())
+    return false;
+
+  return attr_input->check_cfg_predicate (session);
+}
+
+std::vector<Attribute>
+Attribute::separate_cfg_attrs () const
+{
+  if (!has_attr_input () || path.as_string () != "cfg_attr")
+    return {};
+
+  // assume that it has already been parsed
+  if (!is_parsed_to_meta_item ())
+    return {};
+
+  return attr_input->separate_cfg_attrs ();
+}
+
+bool
+Attribute::is_parsed_to_meta_item () const
+{
+  return has_attr_input () && attr_input->is_meta_item ();
+}
+
+/* Visitor implementations - these are short but inlining can't happen anyway
+ * due to virtual functions and I didn't want to make the ast header includes
+ * any longer than they already are. */
+
+void
+Token::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+DelimTokenTree::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IdentifierExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+Lifetime::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+LifetimeParam::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ConstGenericParam::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+PathInExpression::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypePathSegment::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypePathSegmentGeneric::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypePathSegmentFunction::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypePath::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+QualifiedPathInExpression::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+QualifiedPathInType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+LiteralExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+AttrInputLiteral::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaItemLitExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaItemPathLit::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+BorrowExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+DereferenceExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ErrorPropagationExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+NegationExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ArithmeticOrLogicalExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ComparisonExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+LazyBooleanExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypeCastExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+AssignmentExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+CompoundAssignmentExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+GroupedExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ArrayElemsValues::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ArrayElemsCopied::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ArrayExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ArrayIndexExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TupleExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TupleIndexExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructExprStruct::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructExprFieldIdentifier::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructExprFieldIdentifierValue::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructExprFieldIndexValue::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructExprStructFields::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructExprStructBase::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+CallExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MethodCallExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+FieldAccessExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ClosureExprInner::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+BlockExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ClosureExprInnerTyped::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ContinueExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+BreakExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangeFromToExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangeFromExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangeToExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangeFullExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangeFromToInclExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangeToInclExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ReturnExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+UnsafeBlockExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+LoopExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+WhileLoopExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+WhileLetLoopExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ForLoopExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfExprConseqElse::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfExprConseqIf::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfExprConseqIfLet::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfLetExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfLetExprConseqElse::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfLetExprConseqIf::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IfLetExprConseqIfLet::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MatchExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+AwaitExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+AsyncBlockExpr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypeParam::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+LifetimeWhereClauseItem::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypeBoundWhereClauseItem::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+Method::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+Module::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ExternCrate::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+UseTreeGlob::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+UseTreeList::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+UseTreeRebind::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+UseDeclaration::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+Function::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TypeAlias::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructStruct::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TupleStruct::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+EnumItem::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+EnumItemTuple::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+EnumItemStruct::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+EnumItemDiscriminant::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+Enum::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+Union::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ConstantItem::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StaticItem::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitItemFunc::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitItemMethod::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitItemConst::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitItemType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+Trait::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+InherentImpl::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitImpl::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ExternalStaticItem::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ExternalFunctionItem::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ExternBlock::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MacroMatchFragment::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MacroMatchRepetition::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MacroMatcher::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MacroRulesDefinition::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MacroInvocation::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+LiteralPattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+IdentifierPattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+WildcardPattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangePatternBoundLiteral::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangePatternBoundPath::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangePatternBoundQualPath::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RangePattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ReferencePattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructPatternFieldTuplePat::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructPatternFieldIdentPat::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructPatternFieldIdent::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+StructPattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TupleStructItemsNoRange::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TupleStructItemsRange::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TupleStructPattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TuplePatternItemsMultiple::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TuplePatternItemsRanged::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TuplePattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+GroupedPattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+SlicePattern::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+EmptyStmt::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+LetStmt::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ExprStmtWithoutBlock::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ExprStmtWithBlock::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitBound::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ImplTraitType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitObjectType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ParenthesisedType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ImplTraitTypeOneBound::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TraitObjectTypeOneBound::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+TupleType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+NeverType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+RawPointerType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ReferenceType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+ArrayType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+SliceType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+InferredType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+BareFunctionType::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaItemSeq::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaItemPath::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaListPaths::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaNameValueStr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaListNameValueStr::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+AttrInputMetaItemContainer::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+void
+MetaWord::accept_vis (ASTVisitor &vis)
+{
+  vis.visit (*this);
+}
+
+GenericArg
+GenericArg::disambiguate_to_const () const
+{
+  rust_assert (get_kind () == Kind::Either);
+
+  // FIXME: is it fine to have no outer attributes?
+  return GenericArg::create_const (
+    std::unique_ptr<Expr> (new IdentifierExpr (path, {}, locus)));
+}
+
+GenericArg
+GenericArg::disambiguate_to_type () const
+{
+  rust_assert (get_kind () == Kind::Either);
+
+  auto segment = std::unique_ptr<TypePathSegment> (
+    new TypePathSegment (path, false, locus));
+  auto segments = std::vector<std::unique_ptr<TypePathSegment>> ();
+  segments.emplace_back (std::move (segment));
+
+  return GenericArg::create_type (
+    std::unique_ptr<Type> (new TypePath (std::move (segments), locus)));
+}
+
+} // namespace AST
+} // namespace Rust
diff --git a/gcc/rust/ast/rust-ast-full.h b/gcc/rust/ast/rust-ast-full.h
new file mode 100644
index 00000000000..5ab136c61b6
--- /dev/null
+++ b/gcc/rust/ast/rust-ast-full.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_FULL_H
+#define RUST_AST_FULL_H
+// Use as a fast way of including all aspects of the AST (i.e. all headers)
+#include "rust-ast.h"
+#include "rust-expr.h"
+#include "rust-item.h"
+#include "rust-path.h"
+#include "rust-pattern.h"
+#include "rust-stmt.h"
+#include "rust-type.h"
+#include "rust-macro.h"
+
+#endif
diff --git a/gcc/rust/ast/rust-ast-visitor.h b/gcc/rust/ast/rust-ast-visitor.h
new file mode 100644
index 00000000000..bbb04771fea
--- /dev/null
+++ b/gcc/rust/ast/rust-ast-visitor.h
@@ -0,0 +1,234 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_VISITOR_H
+#define RUST_AST_VISITOR_H
+// Visitor base for AST
+
+// full include not required - only forward decls
+#include "rust-ast-full-decls.h"
+
+namespace Rust {
+namespace AST {
+/* Pure abstract class that provides an interface for accessing different
+ * classes of the AST. */
+class ASTVisitor
+{
+public:
+  // only concrete class overloads are required
+
+  // rust-ast.h
+  // virtual void visit(AttrInput& attr_input) = 0;
+  // virtual void visit(TokenTree& token_tree) = 0;
+  // virtual void visit(MacroMatch& macro_match) = 0;
+  virtual void visit (Token &tok) = 0;
+  virtual void visit (DelimTokenTree &delim_tok_tree) = 0;
+  virtual void visit (AttrInputMetaItemContainer &input) = 0;
+  // virtual void visit(MetaItem& meta_item) = 0;
+  // virtual void visit(Stmt& stmt) = 0;
+  // virtual void visit(Expr& expr) = 0;
+  virtual void visit (IdentifierExpr &ident_expr) = 0;
+  // virtual void visit(Pattern& pattern) = 0;
+  // virtual void visit(Type& type) = 0;
+  // virtual void visit(TypeParamBound& type_param_bound) = 0;
+  virtual void visit (Lifetime &lifetime) = 0;
+  // virtual void visit(GenericParam& generic_param) = 0;
+  virtual void visit (LifetimeParam &lifetime_param) = 0;
+  virtual void visit (ConstGenericParam &const_param) = 0;
+  // virtual void visit(TraitItem& trait_item) = 0;
+  // virtual void visit(InherentImplItem& inherent_impl_item) = 0;
+  // virtual void visit(TraitImplItem& trait_impl_item) = 0;
+
+  // rust-path.h
+  virtual void visit (PathInExpression &path) = 0;
+  virtual void visit (TypePathSegment &segment) = 0;
+  virtual void visit (TypePathSegmentGeneric &segment) = 0;
+  virtual void visit (TypePathSegmentFunction &segment) = 0;
+  virtual void visit (TypePath &path) = 0;
+  virtual void visit (QualifiedPathInExpression &path) = 0;
+  virtual void visit (QualifiedPathInType &path) = 0;
+
+  // rust-expr.h
+  virtual void visit (LiteralExpr &expr) = 0;
+  virtual void visit (AttrInputLiteral &attr_input) = 0;
+  virtual void visit (MetaItemLitExpr &meta_item) = 0;
+  virtual void visit (MetaItemPathLit &meta_item) = 0;
+  virtual void visit (BorrowExpr &expr) = 0;
+  virtual void visit (DereferenceExpr &expr) = 0;
+  virtual void visit (ErrorPropagationExpr &expr) = 0;
+  virtual void visit (NegationExpr &expr) = 0;
+  virtual void visit (ArithmeticOrLogicalExpr &expr) = 0;
+  virtual void visit (ComparisonExpr &expr) = 0;
+  virtual void visit (LazyBooleanExpr &expr) = 0;
+  virtual void visit (TypeCastExpr &expr) = 0;
+  virtual void visit (AssignmentExpr &expr) = 0;
+  virtual void visit (CompoundAssignmentExpr &expr) = 0;
+  virtual void visit (GroupedExpr &expr) = 0;
+  // virtual void visit(ArrayElems& elems) = 0;
+  virtual void visit (ArrayElemsValues &elems) = 0;
+  virtual void visit (ArrayElemsCopied &elems) = 0;
+  virtual void visit (ArrayExpr &expr) = 0;
+  virtual void visit (ArrayIndexExpr &expr) = 0;
+  virtual void visit (TupleExpr &expr) = 0;
+  virtual void visit (TupleIndexExpr &expr) = 0;
+  virtual void visit (StructExprStruct &expr) = 0;
+  // virtual void visit(StructExprField& field) = 0;
+  virtual void visit (StructExprFieldIdentifier &field) = 0;
+  virtual void visit (StructExprFieldIdentifierValue &field) = 0;
+  virtual void visit (StructExprFieldIndexValue &field) = 0;
+  virtual void visit (StructExprStructFields &expr) = 0;
+  virtual void visit (StructExprStructBase &expr) = 0;
+  virtual void visit (CallExpr &expr) = 0;
+  virtual void visit (MethodCallExpr &expr) = 0;
+  virtual void visit (FieldAccessExpr &expr) = 0;
+  virtual void visit (ClosureExprInner &expr) = 0;
+  virtual void visit (BlockExpr &expr) = 0;
+  virtual void visit (ClosureExprInnerTyped &expr) = 0;
+  virtual void visit (ContinueExpr &expr) = 0;
+  virtual void visit (BreakExpr &expr) = 0;
+  virtual void visit (RangeFromToExpr &expr) = 0;
+  virtual void visit (RangeFromExpr &expr) = 0;
+  virtual void visit (RangeToExpr &expr) = 0;
+  virtual void visit (RangeFullExpr &expr) = 0;
+  virtual void visit (RangeFromToInclExpr &expr) = 0;
+  virtual void visit (RangeToInclExpr &expr) = 0;
+  virtual void visit (ReturnExpr &expr) = 0;
+  virtual void visit (UnsafeBlockExpr &expr) = 0;
+  virtual void visit (LoopExpr &expr) = 0;
+  virtual void visit (WhileLoopExpr &expr) = 0;
+  virtual void visit (WhileLetLoopExpr &expr) = 0;
+  virtual void visit (ForLoopExpr &expr) = 0;
+  virtual void visit (IfExpr &expr) = 0;
+  virtual void visit (IfExprConseqElse &expr) = 0;
+  virtual void visit (IfExprConseqIf &expr) = 0;
+  virtual void visit (IfExprConseqIfLet &expr) = 0;
+  virtual void visit (IfLetExpr &expr) = 0;
+  virtual void visit (IfLetExprConseqElse &expr) = 0;
+  virtual void visit (IfLetExprConseqIf &expr) = 0;
+  virtual void visit (IfLetExprConseqIfLet &expr) = 0;
+  // virtual void visit(MatchCase& match_case) = 0;
+  // virtual void visit (MatchCaseBlockExpr &match_case) = 0;
+  // virtual void visit (MatchCaseExpr &match_case) = 0;
+  virtual void visit (MatchExpr &expr) = 0;
+  virtual void visit (AwaitExpr &expr) = 0;
+  virtual void visit (AsyncBlockExpr &expr) = 0;
+
+  // rust-item.h
+  virtual void visit (TypeParam &param) = 0;
+  // virtual void visit(WhereClauseItem& item) = 0;
+  virtual void visit (LifetimeWhereClauseItem &item) = 0;
+  virtual void visit (TypeBoundWhereClauseItem &item) = 0;
+  virtual void visit (Method &method) = 0;
+  virtual void visit (Module &module) = 0;
+  virtual void visit (ExternCrate &crate) = 0;
+  // virtual void visit(UseTree& use_tree) = 0;
+  virtual void visit (UseTreeGlob &use_tree) = 0;
+  virtual void visit (UseTreeList &use_tree) = 0;
+  virtual void visit (UseTreeRebind &use_tree) = 0;
+  virtual void visit (UseDeclaration &use_decl) = 0;
+  virtual void visit (Function &function) = 0;
+  virtual void visit (TypeAlias &type_alias) = 0;
+  virtual void visit (StructStruct &struct_item) = 0;
+  virtual void visit (TupleStruct &tuple_struct) = 0;
+  virtual void visit (EnumItem &item) = 0;
+  virtual void visit (EnumItemTuple &item) = 0;
+  virtual void visit (EnumItemStruct &item) = 0;
+  virtual void visit (EnumItemDiscriminant &item) = 0;
+  virtual void visit (Enum &enum_item) = 0;
+  virtual void visit (Union &union_item) = 0;
+  virtual void visit (ConstantItem &const_item) = 0;
+  virtual void visit (StaticItem &static_item) = 0;
+  virtual void visit (TraitItemFunc &item) = 0;
+  virtual void visit (TraitItemMethod &item) = 0;
+  virtual void visit (TraitItemConst &item) = 0;
+  virtual void visit (TraitItemType &item) = 0;
+  virtual void visit (Trait &trait) = 0;
+  virtual void visit (InherentImpl &impl) = 0;
+  virtual void visit (TraitImpl &impl) = 0;
+  // virtual void visit(ExternalItem& item) = 0;
+  virtual void visit (ExternalStaticItem &item) = 0;
+  virtual void visit (ExternalFunctionItem &item) = 0;
+  virtual void visit (ExternBlock &block) = 0;
+
+  // rust-macro.h
+  virtual void visit (MacroMatchFragment &match) = 0;
+  virtual void visit (MacroMatchRepetition &match) = 0;
+  virtual void visit (MacroMatcher &matcher) = 0;
+  virtual void visit (MacroRulesDefinition &rules_def) = 0;
+  virtual void visit (MacroInvocation &macro_invoc) = 0;
+  virtual void visit (MetaItemPath &meta_item) = 0;
+  virtual void visit (MetaItemSeq &meta_item) = 0;
+  virtual void visit (MetaWord &meta_item) = 0;
+  virtual void visit (MetaNameValueStr &meta_item) = 0;
+  virtual void visit (MetaListPaths &meta_item) = 0;
+  virtual void visit (MetaListNameValueStr &meta_item) = 0;
+
+  // rust-pattern.h
+  virtual void visit (LiteralPattern &pattern) = 0;
+  virtual void visit (IdentifierPattern &pattern) = 0;
+  virtual void visit (WildcardPattern &pattern) = 0;
+  // virtual void visit(RangePatternBound& bound) = 0;
+  virtual void visit (RangePatternBoundLiteral &bound) = 0;
+  virtual void visit (RangePatternBoundPath &bound) = 0;
+  virtual void visit (RangePatternBoundQualPath &bound) = 0;
+  virtual void visit (RangePattern &pattern) = 0;
+  virtual void visit (ReferencePattern &pattern) = 0;
+  // virtual void visit(StructPatternField& field) = 0;
+  virtual void visit (StructPatternFieldTuplePat &field) = 0;
+  virtual void visit (StructPatternFieldIdentPat &field) = 0;
+  virtual void visit (StructPatternFieldIdent &field) = 0;
+  virtual void visit (StructPattern &pattern) = 0;
+  // virtual void visit(TupleStructItems& tuple_items) = 0;
+  virtual void visit (TupleStructItemsNoRange &tuple_items) = 0;
+  virtual void visit (TupleStructItemsRange &tuple_items) = 0;
+  virtual void visit (TupleStructPattern &pattern) = 0;
+  // virtual void visit(TuplePatternItems& tuple_items) = 0;
+  virtual void visit (TuplePatternItemsMultiple &tuple_items) = 0;
+  virtual void visit (TuplePatternItemsRanged &tuple_items) = 0;
+  virtual void visit (TuplePattern &pattern) = 0;
+  virtual void visit (GroupedPattern &pattern) = 0;
+  virtual void visit (SlicePattern &pattern) = 0;
+
+  // rust-stmt.h
+  virtual void visit (EmptyStmt &stmt) = 0;
+  virtual void visit (LetStmt &stmt) = 0;
+  virtual void visit (ExprStmtWithoutBlock &stmt) = 0;
+  virtual void visit (ExprStmtWithBlock &stmt) = 0;
+
+  // rust-type.h
+  virtual void visit (TraitBound &bound) = 0;
+  virtual void visit (ImplTraitType &type) = 0;
+  virtual void visit (TraitObjectType &type) = 0;
+  virtual void visit (ParenthesisedType &type) = 0;
+  virtual void visit (ImplTraitTypeOneBound &type) = 0;
+  virtual void visit (TraitObjectTypeOneBound &type) = 0;
+  virtual void visit (TupleType &type) = 0;
+  virtual void visit (NeverType &type) = 0;
+  virtual void visit (RawPointerType &type) = 0;
+  virtual void visit (ReferenceType &type) = 0;
+  virtual void visit (ArrayType &type) = 0;
+  virtual void visit (SliceType &type) = 0;
+  virtual void visit (InferredType &type) = 0;
+  virtual void visit (BareFunctionType &type) = 0;
+
+  // TODO: rust-cond-compilation.h visiting? not currently used
+};
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-ast.h b/gcc/rust/ast/rust-ast.h
new file mode 100644
index 00000000000..461a2460f8f
--- /dev/null
+++ b/gcc/rust/ast/rust-ast.h
@@ -0,0 +1,2007 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_BASE_H
+#define RUST_AST_BASE_H
+// Base for AST used in gccrs, basically required by all specific ast things
+
+#include "rust-system.h"
+#include "rust-hir-map.h"
+#include "rust-token.h"
+#include "rust-location.h"
+
+namespace Rust {
+// TODO: remove typedefs and make actual types for these
+typedef std::string Identifier;
+typedef int TupleIndex;
+struct Session;
+
+namespace AST {
+// foward decl: ast visitor
+class ASTVisitor;
+using AttrVec = std::vector<Attribute>;
+
+// The available kinds of AST Nodes
+enum Kind
+{
+  UNKNOWN,
+  MACRO_RULES_DEFINITION,
+  MACRO_INVOCATION,
+};
+
+// Abstract base class for all AST elements
+class Node
+{
+public:
+  /**
+   * Get the kind of Node this is. This is used to differentiate various AST
+   * elements with very little overhead when extracting the derived type through
+   * static casting is not necessary.
+   */
+  // FIXME: Mark this as `= 0` in the future to make sure every node implements
+  // it
+  virtual Kind get_ast_kind () const { return Kind::UNKNOWN; }
+};
+
+// Delimiter types - used in macros and whatever.
+enum DelimType
+{
+  PARENS,
+  SQUARE,
+  CURLY
+};
+
+// forward decl for use in token tree method
+class Token;
+
+// A tree of tokens (or a single token) - abstract base class
+class TokenTree
+{
+public:
+  virtual ~TokenTree () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<TokenTree> clone_token_tree () const
+  {
+    return std::unique_ptr<TokenTree> (clone_token_tree_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  /* Converts token tree to a flat token stream. Tokens must be pointer to avoid
+   * mutual dependency with Token. */
+  virtual std::vector<std::unique_ptr<Token> > to_token_stream () const = 0;
+
+protected:
+  // pure virtual clone implementation
+  virtual TokenTree *clone_token_tree_impl () const = 0;
+};
+
+// Abstract base class for a macro match
+class MacroMatch
+{
+public:
+  enum MacroMatchType
+  {
+    Fragment,
+    Repetition,
+    Matcher,
+    Tok
+  };
+
+  virtual ~MacroMatch () {}
+
+  virtual std::string as_string () const = 0;
+  virtual Location get_match_locus () const = 0;
+
+  // Unique pointer custom clone function
+  std::unique_ptr<MacroMatch> clone_macro_match () const
+  {
+    return std::unique_ptr<MacroMatch> (clone_macro_match_impl ());
+  }
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual MacroMatchType get_macro_match_type () const = 0;
+
+protected:
+  // pure virtual clone implementation
+  virtual MacroMatch *clone_macro_match_impl () const = 0;
+};
+
+// A token is a kind of token tree (except delimiter tokens)
+class Token : public TokenTree, public MacroMatch
+{
+  // A token is a kind of token tree (except delimiter tokens)
+  // A token is a kind of MacroMatch (except $ and delimiter tokens)
+#if 0
+  // TODO: improve member variables - current ones are the same as lexer token
+  // Token kind.
+  TokenId token_id;
+  // Token location.
+  Location locus;
+  // Associated text (if any) of token.
+  std::string str;
+  // Token type hint (if any).
+  PrimitiveCoreType type_hint;
+#endif
+
+  const_TokenPtr tok_ref;
+
+  /* new idea: wrapper around const_TokenPtr used for heterogeneuous storage in
+   * token trees. rather than convert back and forth when parsing macros, just
+   * wrap it. */
+
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<Token> clone_token () const
+  {
+    return std::unique_ptr<Token> (clone_token_impl ());
+  }
+
+#if 0
+  /* constructor from general text - avoid using if lexer const_TokenPtr is
+   * available */
+  Token (TokenId token_id, Location locus, std::string str,
+	 PrimitiveCoreType type_hint)
+    : token_id (token_id), locus (locus), str (std::move (str)),
+      type_hint (type_hint)
+  {}
+#endif
+  // not doable with new implementation - will have to make a const_TokenPtr
+
+  // Constructor from lexer const_TokenPtr
+#if 0
+  /* TODO: find workaround for std::string being nullptr - probably have to
+   * introduce new method in lexer Token, or maybe make conversion method
+   * there */
+  Token (const_TokenPtr lexer_token_ptr)
+    : token_id (lexer_token_ptr->get_id ()),
+      locus (lexer_token_ptr->get_locus ()), str (""),
+      type_hint (lexer_token_ptr->get_type_hint ())
+  {
+    // FIXME: change to "should have str" later?
+    if (lexer_token_ptr->has_str ())
+      {
+	str = lexer_token_ptr->get_str ();
+
+	// DEBUG
+	rust_debug ("ast token created with str '%s'", str.c_str ());
+      }
+    else
+      {
+	// FIXME: is this returning correct thing?
+	str = lexer_token_ptr->get_token_description ();
+
+	// DEBUG
+	rust_debug ("ast token created with string '%s'", str.c_str ());
+      }
+
+    // DEBUG
+    if (lexer_token_ptr->should_have_str () && !lexer_token_ptr->has_str ())
+      {
+	rust_debug (
+		 "BAD: for token '%s', should have string but does not!",
+		 lexer_token_ptr->get_token_description ());
+      }
+  }
+#endif
+  Token (const_TokenPtr lexer_tok_ptr) : tok_ref (std::move (lexer_tok_ptr)) {}
+
+  bool is_string_lit () const
+  {
+    switch (get_id ())
+      {
+      case STRING_LITERAL:
+      case BYTE_STRING_LITERAL:
+	return true;
+      default:
+	return false;
+      }
+  }
+
+  std::string as_string () const override;
+  Location get_match_locus () const override { return tok_ref->get_locus (); };
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Return copy of itself but in token stream form.
+  std::vector<std::unique_ptr<Token> > to_token_stream () const override;
+
+  TokenId get_id () const { return tok_ref->get_id (); }
+  const std::string &get_str () const { return tok_ref->get_str (); }
+
+  Location get_locus () const { return tok_ref->get_locus (); }
+
+  PrimitiveCoreType get_type_hint () const { return tok_ref->get_type_hint (); }
+
+  // Get a new token pointer copy.
+  const_TokenPtr get_tok_ptr () const { return tok_ref; }
+
+  MacroMatchType get_macro_match_type () const override
+  {
+    return MacroMatchType::Tok;
+  }
+
+protected:
+  // No virtual for now as not polymorphic but can be in future
+  /*virtual*/ Token *clone_token_impl () const { return new Token (*this); }
+
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  Token *clone_token_tree_impl () const final override
+  {
+    return clone_token_impl ();
+  }
+
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  Token *clone_macro_match_impl () const final override
+  {
+    return clone_token_impl ();
+  }
+};
+
+// A literal - value with a type. Used in LiteralExpr and LiteralPattern.
+struct Literal
+{
+public:
+  enum LitType
+  {
+    CHAR,
+    STRING,
+    BYTE,
+    BYTE_STRING,
+    INT,
+    FLOAT,
+    BOOL,
+    ERROR
+  };
+
+private:
+  /* TODO: maybe make subclasses of each type of literal with their typed values
+   * (or generics) */
+  std::string value_as_string;
+  LitType type;
+  PrimitiveCoreType type_hint;
+
+public:
+  std::string as_string () const { return value_as_string; }
+
+  LitType get_lit_type () const { return type; }
+
+  PrimitiveCoreType get_type_hint () const { return type_hint; }
+
+  Literal (std::string value_as_string, LitType type,
+	   PrimitiveCoreType type_hint)
+    : value_as_string (std::move (value_as_string)), type (type),
+      type_hint (type_hint)
+  {}
+
+  static Literal create_error ()
+  {
+    return Literal ("", ERROR, PrimitiveCoreType::CORETYPE_UNKNOWN);
+  }
+
+  // Returns whether literal is in an invalid state.
+  bool is_error () const { return type == ERROR; }
+};
+
+/* Forward decl - definition moved to rust-expr.h as it requires LiteralExpr to
+ * be defined */
+class AttrInputLiteral;
+
+/* TODO: move applicable stuff into here or just don't include it because
+ * nothing uses it A segment of a path (maybe) */
+class PathSegment
+{
+public:
+  virtual ~PathSegment () {}
+
+  virtual std::string as_string () const = 0;
+
+  // TODO: add visitor here?
+};
+
+// A segment of a simple path without generic or type arguments
+class SimplePathSegment : public PathSegment
+{
+  std::string segment_name;
+  Location locus;
+  NodeId node_id;
+
+  // only allow identifiers, "super", "self", "crate", or "$crate"
+public:
+  // TODO: put checks in constructor to enforce this rule?
+  SimplePathSegment (std::string segment_name, Location locus)
+    : segment_name (std::move (segment_name)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  /* Returns whether simple path segment is in an invalid state (currently, if
+   * empty). */
+  bool is_error () const { return segment_name.empty (); }
+
+  // Creates an error SimplePathSegment
+  static SimplePathSegment create_error ()
+  {
+    return SimplePathSegment (std::string (""), Location ());
+  }
+
+  std::string as_string () const override;
+
+  Location get_locus () const { return locus; }
+  NodeId get_node_id () const { return node_id; }
+  const std::string &get_segment_name () const { return segment_name; }
+  bool is_super_path_seg () const
+  {
+    return as_string ().compare ("super") == 0;
+  }
+  bool is_crate_path_seg () const
+  {
+    return as_string ().compare ("crate") == 0;
+  }
+  bool is_lower_self () const { return as_string ().compare ("self") == 0; }
+  bool is_big_self () const { return as_string ().compare ("Self") == 0; }
+};
+
+// A simple path without generic or type arguments
+class SimplePath
+{
+  bool has_opening_scope_resolution;
+  std::vector<SimplePathSegment> segments;
+  Location locus;
+  NodeId node_id;
+
+public:
+  // Constructor
+  SimplePath (std::vector<SimplePathSegment> path_segments,
+	      bool has_opening_scope_resolution = false,
+	      Location locus = Location ())
+    : has_opening_scope_resolution (has_opening_scope_resolution),
+      segments (std::move (path_segments)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Creates an empty SimplePath.
+  static SimplePath create_empty ()
+  {
+    return SimplePath (std::vector<SimplePathSegment> ());
+  }
+
+  // Returns whether the SimplePath is empty, i.e. has path segments.
+  bool is_empty () const { return segments.empty (); }
+
+  std::string as_string () const;
+
+  Location get_locus () const { return locus; }
+  NodeId get_node_id () const { return node_id; }
+
+  // does this need visitor if not polymorphic? probably not
+
+  // path-to-string comparison operator
+  bool operator== (const std::string &rhs) const
+  {
+    return !has_opening_scope_resolution && segments.size () == 1
+	   && segments[0].as_string () == rhs;
+  }
+
+  /* Creates a single-segment SimplePath from a string. This will not check to
+   * ensure that this is a valid identifier in path, so be careful. Also, this
+   * will have no location data.
+   * TODO have checks? */
+  static SimplePath from_str (std::string str, Location locus)
+  {
+    std::vector<AST::SimplePathSegment> single_segments
+      = {AST::SimplePathSegment (std::move (str), locus)};
+    return SimplePath (std::move (single_segments));
+  }
+
+  const std::vector<SimplePathSegment> &get_segments () const
+  {
+    return segments;
+  }
+
+  std::vector<SimplePathSegment> &get_segments () { return segments; }
+};
+
+// path-to-string inverse comparison operator
+inline bool
+operator!= (const SimplePath &lhs, const std::string &rhs)
+{
+  return !(lhs == rhs);
+}
+
+// forward decl for Attribute
+class AttrInput;
+
+// aka Attr
+// Attribute AST representation
+struct Attribute
+{
+private:
+  SimplePath path;
+
+  // bool has_attr_input;
+  std::unique_ptr<AttrInput> attr_input;
+
+  Location locus;
+
+  // TODO: maybe a variable storing whether attr input is parsed or not
+
+public:
+  // Returns whether Attribute has AttrInput
+  bool has_attr_input () const { return attr_input != nullptr; }
+
+  // Constructor has pointer AttrInput for polymorphism reasons
+  Attribute (SimplePath path, std::unique_ptr<AttrInput> input,
+	     Location locus = Location ())
+    : path (std::move (path)), attr_input (std::move (input)), locus (locus)
+  {}
+
+  // default destructor
+  ~Attribute () = default;
+
+  // no point in being defined inline as requires virtual call anyway
+  Attribute (const Attribute &other);
+
+  // no point in being defined inline as requires virtual call anyway
+  Attribute &operator= (const Attribute &other);
+
+  // default move semantics
+  Attribute (Attribute &&other) = default;
+  Attribute &operator= (Attribute &&other) = default;
+
+  // Unique pointer custom clone function
+  std::unique_ptr<Attribute> clone_attribute () const
+  {
+    return std::unique_ptr<Attribute> (clone_attribute_impl ());
+  }
+
+  // Creates an empty attribute (which is invalid)
+  static Attribute create_empty ()
+  {
+    return Attribute (SimplePath::create_empty (), nullptr);
+  }
+
+  // Returns whether the attribute is considered an "empty" attribute.
+  bool is_empty () const { return attr_input == nullptr && path.is_empty (); }
+
+  Location get_locus () const { return locus; }
+
+  AttrInput &get_attr_input () const { return *attr_input; }
+
+  /* e.g.:
+      #![crate_type = "lib"]
+      #[test]
+      #[cfg(target_os = "linux")]
+      #[allow(non_camel_case_types)]
+      #![allow(unused_variables)]
+  */
+
+  // Full built-in attribute list:
+  /*   cfg
+   *   cfg_attr
+   *   test
+   *   ignore
+   *   should_panic
+   *   derive
+   *   macro_export
+   *   macro_use
+   *   proc_macro
+   *   proc_macro_derive
+   *   proc_macro_attribute
+   *   allow
+   *   warn
+   *   deny
+   *   forbid
+   *   deprecated
+   *   must_use
+   *   link
+   *   link_name
+   *   no_link
+   *   repr
+   *   crate_type
+   *   no_main
+   *   export_name
+   *   link_section
+   *   no_mangle
+   *   used
+   *   crate_name
+   *   inline
+   *   cold
+   *   no_builtins
+   *   target_feature
+   *   doc
+   *   no_std
+   *   no_implicit_prelude
+   *   path
+   *   recursion_limit
+   *   type_length_limit
+   *   panic_handler
+   *   global_allocator
+   *   windows_subsystem
+   *   feature     */
+
+  std::string as_string () const;
+
+  // no visitor pattern as not currently polymorphic
+
+  const SimplePath &get_path () const { return path; }
+  SimplePath &get_path () { return path; }
+
+  // Call to parse attribute body to meta item syntax.
+  void parse_attr_to_meta_item ();
+
+  /* Determines whether cfg predicate is true and item with attribute should not
+   * be stripped. Attribute body must already be parsed to meta item. */
+  bool check_cfg_predicate (const Session &session) const;
+
+  // Returns whether body has been parsed to meta item form or not.
+  bool is_parsed_to_meta_item () const;
+
+  /* Returns any attributes generated from cfg_attr attributes. Attribute body
+   * must already be parsed to meta item. */
+  std::vector<Attribute> separate_cfg_attrs () const;
+
+protected:
+  // not virtual as currently no subclasses of Attribute, but could be in future
+  /*virtual*/ Attribute *clone_attribute_impl () const
+  {
+    return new Attribute (*this);
+  }
+};
+
+// Attribute body - abstract base class
+class AttrInput
+{
+public:
+  enum AttrInputType
+  {
+    LITERAL,
+    META_ITEM,
+    TOKEN_TREE,
+  };
+
+  virtual ~AttrInput () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<AttrInput> clone_attr_input () const
+  {
+    return std::unique_ptr<AttrInput> (clone_attr_input_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual bool check_cfg_predicate (const Session &session) const = 0;
+
+  // Parse attribute input to meta item, if possible
+  virtual AttrInput *parse_to_meta_item () const { return nullptr; }
+
+  virtual std::vector<Attribute> separate_cfg_attrs () const { return {}; }
+
+  // Returns whether attr input has been parsed to meta item syntax.
+  virtual bool is_meta_item () const = 0;
+
+  virtual AttrInputType get_attr_input_type () const = 0;
+
+protected:
+  // pure virtual clone implementation
+  virtual AttrInput *clone_attr_input_impl () const = 0;
+};
+
+// Forward decl - defined in rust-macro.h
+class MetaNameValueStr;
+
+// abstract base meta item inner class
+class MetaItemInner
+{
+protected:
+  // pure virtual as MetaItemInner
+  virtual MetaItemInner *clone_meta_item_inner_impl () const = 0;
+
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<MetaItemInner> clone_meta_item_inner () const
+  {
+    return std::unique_ptr<MetaItemInner> (clone_meta_item_inner_impl ());
+  }
+
+  virtual ~MetaItemInner ();
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  /* HACK: used to simplify parsing - creates a copy of that type, or returns
+   * null */
+  virtual std::unique_ptr<MetaNameValueStr> to_meta_name_value_str () const;
+
+  // HACK: used to simplify parsing - same thing
+  virtual SimplePath to_path_item () const
+  {
+    return SimplePath::create_empty ();
+  }
+
+  virtual Attribute to_attribute () const { return Attribute::create_empty (); }
+
+  virtual bool check_cfg_predicate (const Session &session) const = 0;
+
+  virtual bool is_key_value_pair () const { return false; }
+};
+
+// Container used to store MetaItems as AttrInput (bridge-ish kinda thing)
+class AttrInputMetaItemContainer : public AttrInput
+{
+  std::vector<std::unique_ptr<MetaItemInner> > items;
+
+public:
+  AttrInputMetaItemContainer (
+    std::vector<std::unique_ptr<MetaItemInner> > items)
+    : items (std::move (items))
+  {}
+
+  // copy constructor with vector clone
+  AttrInputMetaItemContainer (const AttrInputMetaItemContainer &other)
+  {
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_meta_item_inner ());
+  }
+
+  // copy assignment operator with vector clone
+  AttrInputMetaItemContainer &
+  operator= (const AttrInputMetaItemContainer &other)
+  {
+    AttrInput::operator= (other);
+
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_meta_item_inner ());
+
+    return *this;
+  }
+
+  // default move constructors
+  AttrInputMetaItemContainer (AttrInputMetaItemContainer &&other) = default;
+  AttrInputMetaItemContainer &operator= (AttrInputMetaItemContainer &&other)
+    = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+  AttrInputType get_attr_input_type () const final override
+  {
+    return AttrInput::AttrInputType::META_ITEM;
+  }
+
+  // Clones this object.
+  std::unique_ptr<AttrInputMetaItemContainer>
+  clone_attr_input_meta_item_container () const
+  {
+    return std::unique_ptr<AttrInputMetaItemContainer> (
+      clone_attr_input_meta_item_container_impl ());
+  }
+
+  std::vector<Attribute> separate_cfg_attrs () const override;
+
+  bool is_meta_item () const override { return true; }
+
+  // TODO: this mutable getter seems dodgy
+  std::vector<std::unique_ptr<MetaItemInner> > &get_items () { return items; }
+  const std::vector<std::unique_ptr<MetaItemInner> > &get_items () const
+  {
+    return items;
+  }
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  AttrInputMetaItemContainer *clone_attr_input_impl () const final override
+  {
+    return clone_attr_input_meta_item_container_impl ();
+  }
+
+  AttrInputMetaItemContainer *clone_attr_input_meta_item_container_impl () const
+  {
+    return new AttrInputMetaItemContainer (*this);
+  }
+};
+
+// A token tree with delimiters
+class DelimTokenTree : public TokenTree, public AttrInput
+{
+  DelimType delim_type;
+  std::vector<std::unique_ptr<TokenTree> > token_trees;
+  Location locus;
+
+protected:
+  DelimTokenTree *clone_delim_tok_tree_impl () const
+  {
+    return new DelimTokenTree (*this);
+  }
+
+  /* Use covariance to implement clone function as returning a DelimTokenTree
+   * object */
+  DelimTokenTree *clone_attr_input_impl () const final override
+  {
+    return clone_delim_tok_tree_impl ();
+  }
+
+  /* Use covariance to implement clone function as returning a DelimTokenTree
+   * object */
+  DelimTokenTree *clone_token_tree_impl () const final override
+  {
+    return clone_delim_tok_tree_impl ();
+  }
+
+public:
+  DelimTokenTree (DelimType delim_type,
+		  std::vector<std::unique_ptr<TokenTree> > token_trees
+		  = std::vector<std::unique_ptr<TokenTree> > (),
+		  Location locus = Location ())
+    : delim_type (delim_type), token_trees (std::move (token_trees)),
+      locus (locus)
+  {}
+
+  // Copy constructor with vector clone
+  DelimTokenTree (DelimTokenTree const &other)
+    : delim_type (other.delim_type), locus (other.locus)
+  {
+    token_trees.reserve (other.token_trees.size ());
+    for (const auto &e : other.token_trees)
+      token_trees.push_back (e->clone_token_tree ());
+  }
+
+  // overloaded assignment operator with vector clone
+  DelimTokenTree &operator= (DelimTokenTree const &other)
+  {
+    delim_type = other.delim_type;
+    locus = other.locus;
+
+    token_trees.reserve (other.token_trees.size ());
+    for (const auto &e : other.token_trees)
+      token_trees.push_back (e->clone_token_tree ());
+
+    return *this;
+  }
+
+  // move constructors
+  DelimTokenTree (DelimTokenTree &&other) = default;
+  DelimTokenTree &operator= (DelimTokenTree &&other) = default;
+
+  static DelimTokenTree create_empty () { return DelimTokenTree (PARENS); }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &) const override
+  {
+    // this should never be called - should be converted first
+    rust_assert (false);
+    return false;
+  }
+
+  AttrInputMetaItemContainer *parse_to_meta_item () const override;
+
+  std::vector<std::unique_ptr<Token> > to_token_stream () const override;
+
+  std::unique_ptr<DelimTokenTree> clone_delim_token_tree () const
+  {
+    return std::unique_ptr<DelimTokenTree> (clone_delim_tok_tree_impl ());
+  }
+
+  bool is_meta_item () const override { return false; }
+
+  AttrInputType get_attr_input_type () const final override
+  {
+    return AttrInput::AttrInputType::TOKEN_TREE;
+  }
+
+  std::vector<std::unique_ptr<TokenTree> > &get_token_trees ()
+  {
+    return token_trees;
+  }
+
+  DelimType get_delim_type () const { return delim_type; }
+};
+
+/* Forward decl - definition moved to rust-expr.h as it requires LiteralExpr to
+ * be defined */
+class AttrInputLiteral;
+
+// abstract base meta item class
+class MetaItem : public MetaItemInner
+{
+};
+
+// Forward decl - defined in rust-expr.h
+class MetaItemLitExpr;
+
+// Forward decl - defined in rust-expr.h
+class MetaItemPathLit;
+
+// Forward decl - defined in rust-macro.h
+class MetaItemPath;
+
+// Forward decl - defined in rust-macro.h
+class MetaItemSeq;
+
+// Forward decl - defined in rust-macro.h
+class MetaWord;
+
+// Forward decl - defined in rust-macro.h
+class MetaListPaths;
+
+// Forward decl - defined in rust-macro.h
+class MetaListNameValueStr;
+
+/* Base statement abstract class. Note that most "statements" are not allowed in
+ * top-level module scope - only a subclass of statements called "items" are. */
+class Stmt : public Node
+{
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<Stmt> clone_stmt () const
+  {
+    return std::unique_ptr<Stmt> (clone_stmt_impl ());
+  }
+
+  virtual ~Stmt () {}
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual Location get_locus () const = 0;
+
+  virtual void mark_for_strip () = 0;
+  virtual bool is_marked_for_strip () const = 0;
+  NodeId get_node_id () const { return node_id; }
+
+  virtual bool is_item () const = 0;
+
+protected:
+  Stmt () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
+
+  // Clone function implementation as pure virtual method
+  virtual Stmt *clone_stmt_impl () const = 0;
+
+  NodeId node_id;
+};
+
+// Rust "item" AST node (declaration of top-level/module-level allowed stuff)
+class Item : public Stmt
+{
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<Item> clone_item () const
+  {
+    return std::unique_ptr<Item> (clone_item_impl ());
+  }
+
+  /* Adds crate names to the vector passed by reference, if it can
+   * (polymorphism). TODO: remove, unused. */
+  virtual void
+  add_crate_name (std::vector<std::string> &names ATTRIBUTE_UNUSED) const
+  {}
+
+  // FIXME: ARTHUR: Is it okay to have removed that final? Is it *required*
+  // behavior that we have items that can also be expressions?
+  bool is_item () const override { return true; }
+
+protected:
+  // Clone function implementation as pure virtual method
+  virtual Item *clone_item_impl () const = 0;
+
+  /* Save having to specify two clone methods in derived classes by making
+   * statement clone return item clone. Hopefully won't affect performance too
+   * much. */
+  Item *clone_stmt_impl () const final override { return clone_item_impl (); }
+};
+
+// forward decl of ExprWithoutBlock
+class ExprWithoutBlock;
+
+// Base expression AST node - abstract
+class Expr : public Node
+{
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<Expr> clone_expr () const
+  {
+    return std::unique_ptr<Expr> (clone_expr_impl ());
+  }
+
+  /* TODO: public methods that could be useful:
+   *  - get_type() - returns type of expression. set_type() may also be useful
+   * for some?
+   *  - evaluate() - evaluates expression if constant? can_evaluate()? */
+
+  /* HACK: downcasting without dynamic_cast (if possible) via polymorphism -
+   * overrided in subclasses of ExprWithoutBlock */
+  virtual ExprWithoutBlock *as_expr_without_block () const { return nullptr; }
+
+  virtual std::string as_string () const = 0;
+
+  virtual ~Expr () {}
+
+  virtual Location get_locus () const = 0;
+
+  // HACK: strictly not needed, but faster than full downcast clone
+  virtual bool is_expr_without_block () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual void mark_for_strip () = 0;
+  virtual bool is_marked_for_strip () const = 0;
+
+  virtual NodeId get_node_id () const { return node_id; }
+
+  virtual void set_node_id (NodeId id) { node_id = id; }
+
+protected:
+  // Constructor
+  Expr () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
+
+  // Clone function implementation as pure virtual method
+  virtual Expr *clone_expr_impl () const = 0;
+
+  // TODO: think of less hacky way to implement this kind of thing
+  // Sets outer attributes.
+  virtual void set_outer_attrs (std::vector<Attribute>) = 0;
+
+  NodeId node_id;
+};
+
+// AST node for an expression without an accompanying block - abstract
+class ExprWithoutBlock : public Expr
+{
+protected:
+  // pure virtual clone implementation
+  virtual ExprWithoutBlock *clone_expr_without_block_impl () const = 0;
+
+  /* Save having to specify two clone methods in derived classes by making expr
+   * clone return exprwithoutblock clone. Hopefully won't affect performance too
+   * much. */
+  ExprWithoutBlock *clone_expr_impl () const final override
+  {
+    return clone_expr_without_block_impl ();
+  }
+
+  bool is_expr_without_block () const final override { return true; };
+
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<ExprWithoutBlock> clone_expr_without_block () const
+  {
+    return std::unique_ptr<ExprWithoutBlock> (clone_expr_without_block_impl ());
+  }
+
+  /* downcasting hack from expr to use pratt parsing with
+   * parse_expr_without_block */
+  ExprWithoutBlock *as_expr_without_block () const final override
+  {
+    return clone_expr_without_block_impl ();
+  }
+
+  virtual ExprWithoutBlock *to_stmt () const { return clone_expr_impl (); }
+};
+
+/* HACK: IdentifierExpr, delete when figure out identifier vs expr problem in
+ * Pratt parser */
+/* Alternatively, identifiers could just be represented as single-segment paths
+ */
+class IdentifierExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  Identifier ident;
+  Location locus;
+
+public:
+  IdentifierExpr (Identifier ident, std::vector<Attribute> outer_attrs,
+		  Location locus)
+    : outer_attrs (std::move (outer_attrs)), ident (std::move (ident)),
+      locus (locus)
+  {}
+
+  std::string as_string () const override { return ident; }
+
+  Location get_locus () const override final { return locus; }
+
+  Identifier get_ident () const { return ident; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Clones this object.
+  std::unique_ptr<IdentifierExpr> clone_identifier_expr () const
+  {
+    return std::unique_ptr<IdentifierExpr> (clone_identifier_expr_impl ());
+  }
+
+  // "Error state" if ident is empty, so base stripping on this.
+  void mark_for_strip () override { ident = {}; }
+  bool is_marked_for_strip () const override { return ident.empty (); }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  // Clone method implementation
+  IdentifierExpr *clone_expr_without_block_impl () const final override
+  {
+    return clone_identifier_expr_impl ();
+  }
+
+  IdentifierExpr *clone_identifier_expr_impl () const
+  {
+    return new IdentifierExpr (*this);
+  }
+};
+
+// Pattern base AST node
+class Pattern
+{
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<Pattern> clone_pattern () const
+  {
+    return std::unique_ptr<Pattern> (clone_pattern_impl ());
+  }
+
+  // possible virtual methods: is_refutable()
+
+  virtual ~Pattern () {}
+
+  virtual std::string as_string () const = 0;
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  // as only one kind of pattern can be stripped, have default of nothing
+  virtual void mark_for_strip () {}
+  virtual bool is_marked_for_strip () const { return false; }
+
+  virtual Location get_locus () const = 0;
+  virtual NodeId get_pattern_node_id () const = 0;
+
+protected:
+  // Clone pattern implementation as pure virtual method
+  virtual Pattern *clone_pattern_impl () const = 0;
+};
+
+// forward decl for Type
+class TraitBound;
+
+// Base class for types as represented in AST - abstract
+class Type : public Node
+{
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<Type> clone_type () const
+  {
+    return std::unique_ptr<Type> (clone_type_impl ());
+  }
+
+  // virtual destructor
+  virtual ~Type () {}
+
+  virtual std::string as_string () const = 0;
+
+  /* HACK: convert to trait bound. Virtual method overriden by classes that
+   * enable this. */
+  virtual TraitBound *to_trait_bound (bool) const { return nullptr; }
+  /* as pointer, shouldn't require definition beforehand, only forward
+   * declaration. */
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  // as only two kinds of types can be stripped, have default of nothing
+  virtual void mark_for_strip () {}
+  virtual bool is_marked_for_strip () const { return false; }
+
+  virtual Location get_locus () const = 0;
+
+  NodeId get_node_id () const { return node_id; }
+
+protected:
+  Type () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
+
+  // Clone function implementation as pure virtual method
+  virtual Type *clone_type_impl () const = 0;
+
+  NodeId node_id;
+};
+
+// A type without parentheses? - abstract
+class TypeNoBounds : public Type
+{
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<TypeNoBounds> clone_type_no_bounds () const
+  {
+    return std::unique_ptr<TypeNoBounds> (clone_type_no_bounds_impl ());
+  }
+
+protected:
+  // Clone function implementation as pure virtual method
+  virtual TypeNoBounds *clone_type_no_bounds_impl () const = 0;
+
+  /* Save having to specify two clone methods in derived classes by making type
+   * clone return typenobounds clone. Hopefully won't affect performance too
+   * much. */
+  TypeNoBounds *clone_type_impl () const final override
+  {
+    return clone_type_no_bounds_impl ();
+  }
+
+  TypeNoBounds () : Type () {}
+};
+
+/* Abstract base class representing a type param bound - Lifetime and TraitBound
+ * extends it */
+class TypeParamBound
+{
+public:
+  virtual ~TypeParamBound () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<TypeParamBound> clone_type_param_bound () const
+  {
+    return std::unique_ptr<TypeParamBound> (clone_type_param_bound_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  NodeId get_node_id () const { return node_id; }
+
+  virtual Location get_locus () const = 0;
+
+protected:
+  // Clone function implementation as pure virtual method
+  virtual TypeParamBound *clone_type_param_bound_impl () const = 0;
+
+  TypeParamBound (NodeId node_id) : node_id (node_id) {}
+
+  NodeId node_id;
+};
+
+// Represents a lifetime (and is also a kind of type param bound)
+class Lifetime : public TypeParamBound
+{
+public:
+  enum LifetimeType
+  {
+    NAMED,   // corresponds to LIFETIME_OR_LABEL
+    STATIC,  // corresponds to 'static
+    WILDCARD // corresponds to '_
+  };
+
+private:
+  LifetimeType lifetime_type;
+  std::string lifetime_name;
+  Location locus;
+  NodeId node_id;
+
+public:
+  // Constructor
+  Lifetime (LifetimeType type, std::string name = std::string (),
+	    Location locus = Location ())
+    : TypeParamBound (Analysis::Mappings::get ()->get_next_node_id ()),
+      lifetime_type (type), lifetime_name (std::move (name)), locus (locus)
+  {}
+
+  Lifetime (NodeId id, LifetimeType type, std::string name = std::string (),
+	    Location locus = Location ())
+    : TypeParamBound (id), lifetime_type (type),
+      lifetime_name (std::move (name)), locus (locus)
+  {}
+
+  // Creates an "error" lifetime.
+  static Lifetime error () { return Lifetime (NAMED, ""); }
+
+  // Returns true if the lifetime is in an error state.
+  bool is_error () const
+  {
+    return lifetime_type == NAMED && lifetime_name.empty ();
+  }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  LifetimeType get_lifetime_type () { return lifetime_type; }
+
+  Location get_locus () const override final { return locus; }
+
+  std::string get_lifetime_name () const { return lifetime_name; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  Lifetime *clone_type_param_bound_impl () const override
+  {
+    return new Lifetime (node_id, lifetime_type, lifetime_name, locus);
+  }
+};
+
+/* Base generic parameter in AST. Abstract - can be represented by a Lifetime or
+ * Type param */
+class GenericParam
+{
+public:
+  enum class Kind
+  {
+    Lifetime,
+    Type,
+    Const,
+  };
+
+  virtual ~GenericParam () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<GenericParam> clone_generic_param () const
+  {
+    return std::unique_ptr<GenericParam> (clone_generic_param_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual Location get_locus () const = 0;
+
+  virtual Kind get_kind () const = 0;
+
+  NodeId get_node_id () { return node_id; }
+
+protected:
+  GenericParam () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
+  GenericParam (NodeId node_id) : node_id (node_id) {}
+
+  // Clone function implementation as pure virtual method
+  virtual GenericParam *clone_generic_param_impl () const = 0;
+
+  NodeId node_id;
+};
+
+// A lifetime generic parameter (as opposed to a type generic parameter)
+class LifetimeParam : public GenericParam
+{
+  Lifetime lifetime;
+  std::vector<Lifetime> lifetime_bounds;
+  Attribute outer_attr;
+  Location locus;
+
+public:
+  Lifetime get_lifetime () const { return lifetime; }
+
+  // Returns whether the lifetime param has any lifetime bounds.
+  bool has_lifetime_bounds () const { return !lifetime_bounds.empty (); }
+
+  // Returns whether the lifetime param has an outer attribute.
+  bool has_outer_attribute () const { return !outer_attr.is_empty (); }
+
+  // Creates an error state lifetime param.
+  static LifetimeParam create_error ()
+  {
+    return LifetimeParam (Lifetime::error (), {}, Attribute::create_empty (),
+			  Location ());
+  }
+
+  // Returns whether the lifetime param is in an error state.
+  bool is_error () const { return lifetime.is_error (); }
+
+  // Constructor
+  LifetimeParam (Lifetime lifetime, std::vector<Lifetime> lifetime_bounds,
+		 Attribute outer_attr, Location locus)
+    : lifetime (std::move (lifetime)),
+      lifetime_bounds (std::move (lifetime_bounds)),
+      outer_attr (std::move (outer_attr)), locus (locus)
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  Location get_locus () const override final { return locus; }
+
+  Kind get_kind () const override final { return Kind::Lifetime; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  LifetimeParam *clone_generic_param_impl () const override
+  {
+    return new LifetimeParam (*this);
+  }
+};
+
+// A macro item AST node - abstract base class
+class MacroItem : public Item
+{
+};
+
+// Item used in trait declarations - abstract base class
+class TraitItem
+{
+protected:
+  TraitItem () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
+
+  // Clone function implementation as pure virtual method
+  virtual TraitItem *clone_trait_item_impl () const = 0;
+
+  NodeId node_id;
+
+public:
+  virtual ~TraitItem () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<TraitItem> clone_trait_item () const
+  {
+    return std::unique_ptr<TraitItem> (clone_trait_item_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual void mark_for_strip () = 0;
+  virtual bool is_marked_for_strip () const = 0;
+
+  NodeId get_node_id () const { return node_id; }
+};
+
+/* Abstract base class for items used within an inherent impl block (the impl
+ * name {} one) */
+class InherentImplItem
+{
+protected:
+  // Clone function implementation as pure virtual method
+  virtual InherentImplItem *clone_inherent_impl_item_impl () const = 0;
+
+public:
+  virtual ~InherentImplItem () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<InherentImplItem> clone_inherent_impl_item () const
+  {
+    return std::unique_ptr<InherentImplItem> (clone_inherent_impl_item_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual void mark_for_strip () = 0;
+  virtual bool is_marked_for_strip () const = 0;
+
+  virtual Location get_locus () const = 0;
+};
+
+// Abstract base class for items used in a trait impl
+class TraitImplItem
+{
+protected:
+  virtual TraitImplItem *clone_trait_impl_item_impl () const = 0;
+
+public:
+  virtual ~TraitImplItem (){};
+
+  // Unique pointer custom clone function
+  std::unique_ptr<TraitImplItem> clone_trait_impl_item () const
+  {
+    return std::unique_ptr<TraitImplItem> (clone_trait_impl_item_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual void mark_for_strip () = 0;
+  virtual bool is_marked_for_strip () const = 0;
+};
+
+// Abstract base class for an item used inside an extern block
+class ExternalItem
+{
+public:
+  ExternalItem () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
+
+  virtual ~ExternalItem () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<ExternalItem> clone_external_item () const
+  {
+    return std::unique_ptr<ExternalItem> (clone_external_item_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual void mark_for_strip () = 0;
+  virtual bool is_marked_for_strip () const = 0;
+
+  NodeId get_node_id () const { return node_id; }
+
+protected:
+  // Clone function implementation as pure virtual method
+  virtual ExternalItem *clone_external_item_impl () const = 0;
+
+  NodeId node_id;
+};
+
+/* Data structure to store the data used in macro invocations and macro
+ * invocations with semicolons. */
+struct MacroInvocData
+{
+private:
+  SimplePath path;
+  DelimTokenTree token_tree;
+
+  // One way of parsing the macro. Probably not applicable for all macros.
+  std::vector<std::unique_ptr<MetaItemInner> > parsed_items;
+  bool parsed_to_meta_item = false;
+
+public:
+  std::string as_string () const;
+
+  MacroInvocData (SimplePath path, DelimTokenTree token_tree)
+    : path (std::move (path)), token_tree (std::move (token_tree))
+  {}
+
+  // Copy constructor with vector clone
+  MacroInvocData (const MacroInvocData &other)
+    : path (other.path), token_tree (other.token_tree),
+      parsed_to_meta_item (other.parsed_to_meta_item)
+  {
+    parsed_items.reserve (other.parsed_items.size ());
+    for (const auto &e : other.parsed_items)
+      parsed_items.push_back (e->clone_meta_item_inner ());
+  }
+
+  // Copy assignment operator with vector clone
+  MacroInvocData &operator= (const MacroInvocData &other)
+  {
+    path = other.path;
+    token_tree = other.token_tree;
+    parsed_to_meta_item = other.parsed_to_meta_item;
+
+    parsed_items.reserve (other.parsed_items.size ());
+    for (const auto &e : other.parsed_items)
+      parsed_items.push_back (e->clone_meta_item_inner ());
+
+    return *this;
+  }
+
+  // Move constructors
+  MacroInvocData (MacroInvocData &&other) = default;
+  MacroInvocData &operator= (MacroInvocData &&other) = default;
+
+  // Invalid if path is empty, so base stripping on that.
+  void mark_for_strip () { path = SimplePath::create_empty (); }
+  bool is_marked_for_strip () const { return path.is_empty (); }
+
+  // Returns whether the macro has been parsed already.
+  bool is_parsed () const { return parsed_to_meta_item; }
+  // TODO: update on other ways of parsing it
+
+  // TODO: this mutable getter seems kinda dodgy
+  DelimTokenTree &get_delim_tok_tree () { return token_tree; }
+  const DelimTokenTree &get_delim_tok_tree () const { return token_tree; }
+
+  // TODO: this mutable getter seems kinda dodgy
+  SimplePath &get_path () { return path; }
+  const SimplePath &get_path () const { return path; }
+
+  void
+  set_meta_item_output (std::vector<std::unique_ptr<MetaItemInner> > new_items)
+  {
+    parsed_items = std::move (new_items);
+  }
+  // TODO: mutable getter seems kinda dodgy
+  std::vector<std::unique_ptr<MetaItemInner> > &get_meta_items ()
+  {
+    return parsed_items;
+  }
+  const std::vector<std::unique_ptr<MetaItemInner> > &get_meta_items () const
+  {
+    return parsed_items;
+  }
+};
+
+class SingleASTNode
+{
+public:
+  enum NodeType
+  {
+    EXPRESSION,
+    ITEM,
+    STMT,
+    EXTERN,
+    TRAIT,
+    IMPL,
+    TRAIT_IMPL,
+    TYPE,
+  };
+
+private:
+  NodeType kind;
+
+  // FIXME make this a union
+  std::unique_ptr<Expr> expr;
+  std::unique_ptr<Item> item;
+  std::unique_ptr<Stmt> stmt;
+  std::unique_ptr<ExternalItem> external_item;
+  std::unique_ptr<TraitItem> trait_item;
+  std::unique_ptr<InherentImplItem> impl_item;
+  std::unique_ptr<TraitImplItem> trait_impl_item;
+  std::unique_ptr<Type> type;
+
+public:
+  SingleASTNode (std::unique_ptr<Expr> expr)
+    : kind (EXPRESSION), expr (std::move (expr))
+  {}
+
+  SingleASTNode (std::unique_ptr<Item> item)
+    : kind (ITEM), item (std::move (item))
+  {}
+
+  SingleASTNode (std::unique_ptr<Stmt> stmt)
+    : kind (STMT), stmt (std::move (stmt))
+  {}
+
+  SingleASTNode (std::unique_ptr<ExternalItem> item)
+    : kind (EXTERN), external_item (std::move (item))
+  {}
+
+  SingleASTNode (std::unique_ptr<TraitItem> item)
+    : kind (TRAIT), trait_item (std::move (item))
+  {}
+
+  SingleASTNode (std::unique_ptr<InherentImplItem> item)
+    : kind (IMPL), impl_item (std::move (item))
+  {}
+
+  SingleASTNode (std::unique_ptr<TraitImplItem> trait_impl_item)
+    : kind (TRAIT_IMPL), trait_impl_item (std::move (trait_impl_item))
+  {}
+
+  SingleASTNode (std::unique_ptr<Type> type)
+    : kind (TYPE), type (std::move (type))
+  {}
+
+  SingleASTNode (SingleASTNode const &other)
+  {
+    kind = other.kind;
+    switch (kind)
+      {
+      case EXPRESSION:
+	expr = other.expr->clone_expr ();
+	break;
+
+      case ITEM:
+	item = other.item->clone_item ();
+	break;
+
+      case STMT:
+	stmt = other.stmt->clone_stmt ();
+	break;
+
+      case EXTERN:
+	external_item = other.external_item->clone_external_item ();
+	break;
+
+      case TRAIT:
+	trait_item = other.trait_item->clone_trait_item ();
+	break;
+
+      case IMPL:
+	impl_item = other.impl_item->clone_inherent_impl_item ();
+	break;
+
+      case TRAIT_IMPL:
+	trait_impl_item = other.trait_impl_item->clone_trait_impl_item ();
+	break;
+
+      case TYPE:
+	type = other.type->clone_type ();
+	break;
+      }
+  }
+
+  SingleASTNode operator= (SingleASTNode const &other)
+  {
+    kind = other.kind;
+    switch (kind)
+      {
+      case EXPRESSION:
+	expr = other.expr->clone_expr ();
+	break;
+
+      case ITEM:
+	item = other.item->clone_item ();
+	break;
+
+      case STMT:
+	stmt = other.stmt->clone_stmt ();
+	break;
+
+      case EXTERN:
+	external_item = other.external_item->clone_external_item ();
+	break;
+
+      case TRAIT:
+	trait_item = other.trait_item->clone_trait_item ();
+	break;
+
+      case IMPL:
+	impl_item = other.impl_item->clone_inherent_impl_item ();
+	break;
+
+      case TRAIT_IMPL:
+	trait_impl_item = other.trait_impl_item->clone_trait_impl_item ();
+	break;
+
+      case TYPE:
+	type = other.type->clone_type ();
+	break;
+      }
+    return *this;
+  }
+
+  SingleASTNode (SingleASTNode &&other) = default;
+  SingleASTNode &operator= (SingleASTNode &&other) = default;
+
+  NodeType get_kind () const { return kind; }
+
+  std::unique_ptr<Expr> &get_expr ()
+  {
+    rust_assert (kind == EXPRESSION);
+    return expr;
+  }
+
+  std::unique_ptr<Item> &get_item ()
+  {
+    rust_assert (kind == ITEM);
+    return item;
+  }
+
+  std::unique_ptr<Stmt> &get_stmt ()
+  {
+    rust_assert (kind == STMT);
+    return stmt;
+  }
+
+  /**
+   * Access the inner nodes and take ownership of them.
+   * You can only call these functions once per node
+   */
+
+  std::unique_ptr<Stmt> take_stmt ()
+  {
+    rust_assert (!is_error ());
+    return std::move (stmt);
+  }
+
+  std::unique_ptr<Expr> take_expr ()
+  {
+    rust_assert (!is_error ());
+    return std::move (expr);
+  }
+
+  std::unique_ptr<Item> take_item ()
+  {
+    rust_assert (!is_error ());
+    return std::move (item);
+  }
+
+  std::unique_ptr<TraitItem> take_trait_item ()
+  {
+    rust_assert (!is_error ());
+    return std::move (trait_item);
+  }
+
+  std::unique_ptr<ExternalItem> take_external_item ()
+  {
+    rust_assert (!is_error ());
+    return std::move (external_item);
+  }
+
+  std::unique_ptr<InherentImplItem> take_impl_item ()
+  {
+    rust_assert (!is_error ());
+    return std::move (impl_item);
+  }
+
+  std::unique_ptr<TraitImplItem> take_trait_impl_item ()
+  {
+    rust_assert (!is_error ());
+    return std::move (trait_impl_item);
+  }
+
+  std::unique_ptr<Type> take_type ()
+  {
+    rust_assert (!is_error ());
+    return std::move (type);
+  }
+
+  void accept_vis (ASTVisitor &vis)
+  {
+    switch (kind)
+      {
+      case EXPRESSION:
+	expr->accept_vis (vis);
+	break;
+
+      case ITEM:
+	item->accept_vis (vis);
+	break;
+
+      case STMT:
+	stmt->accept_vis (vis);
+	break;
+
+      case EXTERN:
+	external_item->accept_vis (vis);
+	break;
+
+      case TRAIT:
+	trait_item->accept_vis (vis);
+	break;
+
+      case IMPL:
+	impl_item->accept_vis (vis);
+	break;
+
+      case TRAIT_IMPL:
+	trait_impl_item->accept_vis (vis);
+	break;
+
+      case TYPE:
+	type->accept_vis (vis);
+	break;
+      }
+  }
+
+  bool is_error ()
+  {
+    switch (kind)
+      {
+      case EXPRESSION:
+	return expr == nullptr;
+      case ITEM:
+	return item == nullptr;
+      case STMT:
+	return stmt == nullptr;
+      case EXTERN:
+	return external_item == nullptr;
+      case TRAIT:
+	return trait_item == nullptr;
+      case IMPL:
+	return impl_item == nullptr;
+      case TRAIT_IMPL:
+	return trait_impl_item == nullptr;
+      case TYPE:
+	return type == nullptr;
+      }
+
+    gcc_unreachable ();
+    return true;
+  }
+
+  std::string as_string ()
+  {
+    switch (kind)
+      {
+      case EXPRESSION:
+	return "Expr: " + expr->as_string ();
+      case ITEM:
+	return "Item: " + item->as_string ();
+      case STMT:
+	return "Stmt: " + stmt->as_string ();
+      case EXTERN:
+	return "External Item: " + external_item->as_string ();
+      case TRAIT:
+	return "Trait Item: " + trait_item->as_string ();
+      case IMPL:
+	return "Impl Item: " + impl_item->as_string ();
+      case TRAIT_IMPL:
+	return "Trait Impl Item: " + trait_impl_item->as_string ();
+      case TYPE:
+	return "Type: " + type->as_string ();
+      }
+
+    gcc_unreachable ();
+    return "";
+  }
+};
+
+/* Basically, a "fragment" that can be incorporated into the AST, created as
+ * a result of macro expansion. Really annoying to work with due to the fact
+ * that macros can really expand to anything. As such, horrible representation
+ * at the moment. */
+class ASTFragment
+{
+private:
+  /* basic idea: essentially, a vector of tagged unions of different AST node
+   * types. Now, this could actually be stored without a tagged union if the
+   * different AST node types had a unified parent, but that would create
+   * issues with the diamond problem or significant performance penalties. So
+   * a tagged union had to be used instead. A vector is used to represent the
+   * ability for a macro to expand to two statements, for instance. */
+
+  std::vector<SingleASTNode> nodes;
+  bool fragment_is_error;
+
+  /**
+   * We need to make a special case for Expression and Type fragments as only
+   * one Node will be extracted from the `nodes` vector
+   */
+
+  bool is_single_fragment () const { return nodes.size () == 1; }
+
+  bool is_single_fragment_kind (SingleASTNode::NodeType kind) const
+  {
+    return is_single_fragment () && nodes[0].get_kind () == kind;
+  }
+
+public:
+  ASTFragment (std::vector<SingleASTNode> nodes, bool fragment_is_error = false)
+    : nodes (std::move (nodes)), fragment_is_error (fragment_is_error)
+  {
+    if (fragment_is_error)
+      rust_assert (nodes.empty ());
+  }
+
+  ASTFragment (ASTFragment const &other)
+    : fragment_is_error (other.fragment_is_error)
+  {
+    nodes.clear ();
+    nodes.reserve (other.nodes.size ());
+    for (auto &n : other.nodes)
+      {
+	nodes.push_back (n);
+      }
+  }
+
+  ASTFragment &operator= (ASTFragment const &other)
+  {
+    fragment_is_error = other.fragment_is_error;
+    nodes.clear ();
+    nodes.reserve (other.nodes.size ());
+    for (auto &n : other.nodes)
+      {
+	nodes.push_back (n);
+      }
+
+    return *this;
+  }
+
+  static ASTFragment create_error () { return ASTFragment ({}, true); }
+
+  std::vector<SingleASTNode> &get_nodes () { return nodes; }
+  bool is_error () const { return fragment_is_error; }
+
+  bool should_expand () const { return !is_error (); }
+
+  std::unique_ptr<Expr> take_expression_fragment ()
+  {
+    rust_assert (is_single_fragment_kind (SingleASTNode::NodeType::EXPRESSION));
+    return nodes[0].take_expr ();
+  }
+
+  std::unique_ptr<Type> take_type_fragment ()
+  {
+    rust_assert (is_single_fragment_kind (SingleASTNode::NodeType::TYPE));
+    return nodes[0].take_type ();
+  }
+
+  void accept_vis (ASTVisitor &vis)
+  {
+    for (auto &node : nodes)
+      node.accept_vis (vis);
+  }
+};
+
+// A crate AST object - holds all the data for a single compilation unit
+struct Crate
+{
+  std::vector<Attribute> inner_attrs;
+  // dodgy spacing required here
+  /* TODO: is it better to have a vector of items here or a module (implicit
+   * top-level one)? */
+  std::vector<std::unique_ptr<Item> > items;
+
+  NodeId node_id;
+
+public:
+  // Constructor
+  Crate (std::vector<std::unique_ptr<Item> > items,
+	 std::vector<Attribute> inner_attrs)
+    : inner_attrs (std::move (inner_attrs)), items (std::move (items)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor with vector clone
+  Crate (Crate const &other)
+    : inner_attrs (other.inner_attrs), node_id (other.node_id)
+  {
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_item ());
+  }
+
+  ~Crate () = default;
+
+  // Overloaded assignment operator with vector clone
+  Crate &operator= (Crate const &other)
+  {
+    inner_attrs = other.inner_attrs;
+    node_id = other.node_id;
+
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_item ());
+
+    return *this;
+  }
+
+  // Move constructors
+  Crate (Crate &&other) = default;
+  Crate &operator= (Crate &&other) = default;
+
+  // Get crate representation as string (e.g. for debugging).
+  std::string as_string () const;
+
+  // Delete all crate information, e.g. if fails cfg.
+  void strip_crate ()
+  {
+    inner_attrs.clear ();
+    inner_attrs.shrink_to_fit ();
+
+    items.clear ();
+    items.shrink_to_fit ();
+    // TODO: is this the best way to do this?
+  }
+
+  NodeId get_node_id () const { return node_id; }
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+};
+
+// Base path expression AST node - abstract
+class PathExpr : public ExprWithoutBlock
+{
+};
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-cond-compilation.h b/gcc/rust/ast/rust-cond-compilation.h
new file mode 100644
index 00000000000..71188ef3b4b
--- /dev/null
+++ b/gcc/rust/ast/rust-cond-compilation.h
@@ -0,0 +1,249 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_CONDCOMPILATION
+#define RUST_AST_CONDCOMPILATION
+// Conditional compilation-related AST stuff
+
+#include "rust-ast.h"
+
+namespace Rust {
+namespace AST {
+// Base conditional compilation configuration predicate thing - abstract
+class ConfigurationPredicate
+{
+public:
+  virtual ~ConfigurationPredicate () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<ConfigurationPredicate> clone_configuration_predicate () const
+  {
+    return std::unique_ptr<ConfigurationPredicate> (
+      clone_configuration_predicate_impl ());
+  }
+
+  // not sure if I'll use this but here anyway
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+protected:
+  // Clone function impl to be overriden in base classes
+  virtual ConfigurationPredicate *
+  clone_configuration_predicate_impl () const = 0;
+};
+
+// A configuration option - true if option is set, false if option is not set.
+class ConfigurationOption : public ConfigurationPredicate
+{
+  Identifier option_name;
+
+  // bool has_string_literal_option_body;
+  std::string option_value; // technically a string or raw string literal
+
+public:
+  /* Returns whether the configuration option has a "value" part of the
+   * key-value pair. */
+  bool has_option_value () const { return !option_value.empty (); }
+
+  // Key-value pair constructor
+  ConfigurationOption (Identifier option_name, std::string option_value)
+    : option_name (option_name), option_value (option_value)
+  {}
+
+  // Name-only constructor
+  ConfigurationOption (Identifier option_name) : option_name (option_name) {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ConfigurationOption *clone_configuration_predicate_impl () const override
+  {
+    return new ConfigurationOption (*this);
+  }
+};
+
+// TODO: inline
+struct ConfigurationPredicateList
+{
+  std::vector<std::unique_ptr<ConfigurationPredicate>> predicate_list;
+};
+
+// Predicate that returns true if all of the supplied predicates return true.
+class ConfigurationAll : public ConfigurationPredicate
+{
+  std::vector<std::unique_ptr<ConfigurationPredicate>>
+    predicate_list; // inlined form
+
+public:
+  ConfigurationAll (
+    std::vector<std::unique_ptr<ConfigurationPredicate>> predicate_list)
+    : predicate_list (predicate_list)
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ConfigurationAll *clone_configuration_predicate_impl () const override
+  {
+    return new ConfigurationAll (*this);
+  }
+};
+
+// Predicate that returns true if any of the supplied predicates are true.
+class ConfigurationAny : public ConfigurationPredicate
+{
+  std::vector<std::unique_ptr<ConfigurationPredicate>>
+    predicate_list; // inlined form
+
+public:
+  ConfigurationAny (
+    std::vector<std::unique_ptr<ConfigurationPredicate>> predicate_list)
+    : predicate_list (predicate_list)
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ConfigurationAny *clone_configuration_predicate_impl () const override
+  {
+    return new ConfigurationAny (*this);
+  }
+};
+
+/* Predicate that produces the negation of a supplied other configuration
+ * predicate. */
+class ConfigurationNot : public ConfigurationPredicate
+{
+  std::unique_ptr<ConfigurationPredicate> config_to_negate;
+
+public:
+  ConfigurationNot (ConfigurationPredicate *config_to_negate)
+    : config_to_negate (config_to_negate)
+  {}
+
+  // Copy constructor with clone
+  ConfigurationNot (ConfigurationNot const &other)
+    : config_to_negate (
+      other.config_to_negate->clone_configuration_predicate ())
+  {}
+
+  // Overloaded assignment operator to clone
+  ConfigurationNot &operator= (ConfigurationNot const &other)
+  {
+    config_to_negate = other.config_to_negate->clone_configuration_predicate ();
+
+    return *this;
+  }
+
+  // move constructors
+  ConfigurationNot (ConfigurationNot &&other) = default;
+  ConfigurationNot &operator= (ConfigurationNot &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ConfigurationNot *clone_configuration_predicate_impl () const override
+  {
+    return new ConfigurationNot (*this);
+  }
+};
+
+// TODO: relationship to other attributes?
+class CfgAttribute
+{
+  std::unique_ptr<ConfigurationPredicate> config_to_include;
+
+public:
+  CfgAttribute (ConfigurationPredicate *config_to_include)
+    : config_to_include (config_to_include)
+  {}
+
+  // Copy constructor with clone
+  CfgAttribute (CfgAttribute const &other)
+    : config_to_include (
+      other.config_to_include->clone_configuration_predicate ())
+  {}
+
+  // Overloaded assignment operator to clone
+  CfgAttribute &operator= (CfgAttribute const &other)
+  {
+    config_to_include
+      = other.config_to_include->clone_configuration_predicate ();
+
+    return *this;
+  }
+
+  // move constructors
+  CfgAttribute (CfgAttribute &&other) = default;
+  CfgAttribute &operator= (CfgAttribute &&other) = default;
+};
+/* TODO: ok, best thing to do would be eliminating this class, making Attribute
+ * has a "is_cfg()" method, and having attribute path as "cfg" and AttrInput as
+ * ConfigurationPredicate (so make ConfigurationPredicate a subclass of
+ * AttrInput?). Would need special handling in parser, however. */
+
+// TODO: inline
+struct CfgAttrs
+{
+  std::vector<Attribute> cfg_attrs;
+};
+
+// TODO: relationship to other attributes?
+class CfgAttrAttribute
+{
+  std::unique_ptr<ConfigurationPredicate> config_to_include;
+  std::vector<Attribute> cfg_attrs;
+
+public:
+  CfgAttrAttribute (ConfigurationPredicate *config_to_include,
+		    std::vector<Attribute> cfg_attrs)
+    : config_to_include (config_to_include), cfg_attrs (cfg_attrs)
+  {}
+
+  // Copy constructor with clone
+  CfgAttrAttribute (CfgAttrAttribute const &other)
+    : config_to_include (
+      other.config_to_include->clone_configuration_predicate ()),
+      cfg_attrs (cfg_attrs)
+  {}
+
+  // Overloaded assignment operator to clone
+  CfgAttrAttribute &operator= (CfgAttrAttribute const &other)
+  {
+    config_to_include
+      = other.config_to_include->clone_configuration_predicate ();
+    cfg_attrs = other.cfg_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  CfgAttrAttribute (CfgAttrAttribute &&other) = default;
+  CfgAttrAttribute &operator= (CfgAttrAttribute &&other) = default;
+};
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-expr.h b/gcc/rust/ast/rust-expr.h
new file mode 100644
index 00000000000..1966a590c94
--- /dev/null
+++ b/gcc/rust/ast/rust-expr.h
@@ -0,0 +1,4631 @@
+#ifndef RUST_AST_EXPR_H
+#define RUST_AST_EXPR_H
+
+#include "rust-ast.h"
+#include "rust-path.h"
+#include "operator.h"
+
+namespace Rust {
+namespace AST {
+/* TODO: if GCC moves to C++17 or allows boost, replace some boolean
+ * "has_whatever" pairs with
+ * optional types (std::optional or boost::optional)? */
+
+// AST node for an expression with an accompanying block - abstract
+class ExprWithBlock : public Expr
+{
+protected:
+  // pure virtual clone implementation
+  virtual ExprWithBlock *clone_expr_with_block_impl () const = 0;
+
+  // prevent having to define multiple clone expressions
+  ExprWithBlock *clone_expr_impl () const final override
+  {
+    return clone_expr_with_block_impl ();
+  }
+
+  bool is_expr_without_block () const final override { return false; };
+
+public:
+  // Unique pointer custom clone function
+  std::unique_ptr<ExprWithBlock> clone_expr_with_block () const
+  {
+    return std::unique_ptr<ExprWithBlock> (clone_expr_with_block_impl ());
+  }
+};
+
+// Literals? Or literal base?
+class LiteralExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  Literal literal;
+  Location locus;
+
+public:
+  std::string as_string () const override { return literal.as_string (); }
+
+  Literal::LitType get_lit_type () const { return literal.get_lit_type (); }
+
+  LiteralExpr (std::string value_as_string, Literal::LitType type,
+	       PrimitiveCoreType type_hint, std::vector<Attribute> outer_attrs,
+	       Location locus)
+    : outer_attrs (std::move (outer_attrs)),
+      literal (std::move (value_as_string), type, type_hint), locus (locus)
+  {}
+
+  LiteralExpr (Literal literal, std::vector<Attribute> outer_attrs,
+	       Location locus)
+    : outer_attrs (std::move (outer_attrs)), literal (std::move (literal)),
+      locus (locus)
+  {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<LiteralExpr> clone_literal_expr () const
+  {
+    return std::unique_ptr<LiteralExpr> (clone_literal_expr_impl ());
+  }
+
+  Location get_locus () const override final { return locus; }
+
+  Literal get_literal () const { return literal; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if literal is in error state, so base stripping on that.
+  void mark_for_strip () override { literal = Literal::create_error (); }
+  bool is_marked_for_strip () const override { return literal.is_error (); }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  LiteralExpr *clone_expr_without_block_impl () const final override
+  {
+    return clone_literal_expr_impl ();
+  }
+
+  /* not virtual as currently no subclasses of LiteralExpr, but could be in
+   * future */
+  /*virtual*/ LiteralExpr *clone_literal_expr_impl () const
+  {
+    return new LiteralExpr (*this);
+  }
+};
+
+// Literal expression attribute body (non-macro attribute)
+class AttrInputLiteral : public AttrInput
+{
+  LiteralExpr literal_expr;
+
+public:
+  AttrInputLiteral (LiteralExpr lit_expr) : literal_expr (std::move (lit_expr))
+  {}
+
+  std::string as_string () const override
+  {
+    return " = " + literal_expr.as_string ();
+  }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  /* this can never be a cfg predicate - cfg and cfg_attr require a token-tree
+   * cfg */
+  bool check_cfg_predicate (const Session &) const override { return false; }
+
+  bool is_meta_item () const override { return false; }
+
+  LiteralExpr &get_literal () { return literal_expr; }
+
+  AttrInputType get_attr_input_type () const final override
+  {
+    return AttrInput::AttrInputType::LITERAL;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  AttrInputLiteral *clone_attr_input_impl () const override
+  {
+    return new AttrInputLiteral (*this);
+  }
+};
+
+/* literal expr only meta item inner - TODO possibly replace with inheritance of
+ * LiteralExpr itself? */
+class MetaItemLitExpr : public MetaItemInner
+{
+  LiteralExpr lit_expr;
+
+public:
+  MetaItemLitExpr (LiteralExpr lit_expr) : lit_expr (std::move (lit_expr)) {}
+
+  std::string as_string () const override { return lit_expr.as_string (); }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaItemLitExpr *clone_meta_item_inner_impl () const override
+  {
+    return new MetaItemLitExpr (*this);
+  }
+};
+
+// more generic meta item "path = lit" form
+class MetaItemPathLit : public MetaItem
+{
+  SimplePath path;
+  LiteralExpr lit;
+
+public:
+  MetaItemPathLit (SimplePath path, LiteralExpr lit_expr)
+    : path (std::move (path)), lit (std::move (lit_expr))
+  {}
+
+  std::string as_string () const override
+  {
+    return path.as_string () + " = " + lit.as_string ();
+  }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &session) const override;
+  /* TODO: return true if "ident" is defined and value of it is "lit", return
+   * false otherwise */
+
+  Attribute to_attribute () const override;
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaItemPathLit *clone_meta_item_inner_impl () const override
+  {
+    return new MetaItemPathLit (*this);
+  }
+};
+
+/* Represents an expression using unary or binary operators as AST node. Can be
+ * overloaded. */
+class OperatorExpr : public ExprWithoutBlock
+{
+  // TODO: create binary and unary operator subclasses?
+public:
+  Location locus;
+
+protected:
+  /* Variables must be protected to allow derived classes to use them as first
+   * class citizens */
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> main_or_left_expr;
+
+  // Constructor (only for initialisation of expr purposes)
+  OperatorExpr (std::unique_ptr<Expr> main_or_left_expr,
+		std::vector<Attribute> outer_attribs, Location locus)
+    : locus (locus), outer_attrs (std::move (outer_attribs)),
+      main_or_left_expr (std::move (main_or_left_expr))
+  {}
+
+  // Copy constructor (only for initialisation of expr purposes)
+  OperatorExpr (OperatorExpr const &other)
+    : locus (other.locus), outer_attrs (other.outer_attrs)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.main_or_left_expr != nullptr)
+      main_or_left_expr = other.main_or_left_expr->clone_expr ();
+  }
+
+  // Overload assignment operator to deep copy expr
+  OperatorExpr &operator= (OperatorExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.main_or_left_expr != nullptr)
+      main_or_left_expr = other.main_or_left_expr->clone_expr ();
+    else
+      main_or_left_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  OperatorExpr (OperatorExpr &&other) = default;
+  OperatorExpr &operator= (OperatorExpr &&other) = default;
+
+public:
+  Location get_locus () const override final { return locus; }
+
+  // Invalid if expr is null, so base stripping on that.
+  void mark_for_strip () override { main_or_left_expr = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return main_or_left_expr == nullptr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+};
+
+/* Unary prefix & or &mut (or && and &&mut) borrow operator. Cannot be
+ * overloaded. */
+class BorrowExpr : public OperatorExpr
+{
+  bool is_mut;
+  bool double_borrow;
+
+public:
+  std::string as_string () const override;
+
+  BorrowExpr (std::unique_ptr<Expr> borrow_lvalue, bool is_mut_borrow,
+	      bool is_double_borrow, std::vector<Attribute> outer_attribs,
+	      Location locus)
+    : OperatorExpr (std::move (borrow_lvalue), std::move (outer_attribs),
+		    locus),
+      is_mut (is_mut_borrow), double_borrow (is_double_borrow)
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_borrowed_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+  bool get_is_mut () const { return is_mut; }
+
+  bool get_is_double_borrow () const { return double_borrow; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  BorrowExpr *clone_expr_without_block_impl () const override
+  {
+    return new BorrowExpr (*this);
+  }
+};
+
+// Unary prefix * deference operator
+class DereferenceExpr : public OperatorExpr
+{
+public:
+  std::string as_string () const override;
+
+  // Constructor calls OperatorExpr's protected constructor
+  DereferenceExpr (std::unique_ptr<Expr> deref_lvalue,
+		   std::vector<Attribute> outer_attribs, Location locus)
+    : OperatorExpr (std::move (deref_lvalue), std::move (outer_attribs), locus)
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_dereferenced_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  DereferenceExpr *clone_expr_without_block_impl () const override
+  {
+    return new DereferenceExpr (*this);
+  }
+};
+
+// Unary postfix ? error propogation operator. Cannot be overloaded.
+class ErrorPropagationExpr : public OperatorExpr
+{
+public:
+  std::string as_string () const override;
+
+  // Constructor calls OperatorExpr's protected constructor
+  ErrorPropagationExpr (std::unique_ptr<Expr> potential_error_value,
+			std::vector<Attribute> outer_attribs, Location locus)
+    : OperatorExpr (std::move (potential_error_value),
+		    std::move (outer_attribs), locus)
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_propagating_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ErrorPropagationExpr *clone_expr_without_block_impl () const override
+  {
+    return new ErrorPropagationExpr (*this);
+  }
+};
+
+// Unary prefix - or ! negation or NOT operators.
+class NegationExpr : public OperatorExpr
+{
+public:
+  using ExprType = NegationOperator;
+
+private:
+  /* Note: overload negation via std::ops::Neg and not via std::ops::Not
+   * Negation only works for signed integer and floating-point types, NOT only
+   * works for boolean and integer types (via bitwise NOT) */
+  ExprType expr_type;
+
+public:
+  std::string as_string () const override;
+
+  ExprType get_expr_type () const { return expr_type; }
+
+  // Constructor calls OperatorExpr's protected constructor
+  NegationExpr (std::unique_ptr<Expr> negated_value, ExprType expr_kind,
+		std::vector<Attribute> outer_attribs, Location locus)
+    : OperatorExpr (std::move (negated_value), std::move (outer_attribs),
+		    locus),
+      expr_type (expr_kind)
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_negated_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  NegationExpr *clone_expr_without_block_impl () const override
+  {
+    return new NegationExpr (*this);
+  }
+};
+
+// Infix binary operators. +, -, *, /, %, &, |, ^, <<, >>
+class ArithmeticOrLogicalExpr : public OperatorExpr
+{
+public:
+  using ExprType = ArithmeticOrLogicalOperator;
+
+private:
+  // Note: overloading trait specified in comments
+  ExprType expr_type;
+
+  std::unique_ptr<Expr> right_expr;
+
+public:
+  std::string as_string () const override;
+
+  ExprType get_expr_type () const { return expr_type; }
+
+  // Constructor calls OperatorExpr's protected constructor
+  ArithmeticOrLogicalExpr (std::unique_ptr<Expr> left_value,
+			   std::unique_ptr<Expr> right_value,
+			   ExprType expr_kind, Location locus)
+    : OperatorExpr (std::move (left_value), std::vector<Attribute> (), locus),
+      expr_type (expr_kind), right_expr (std::move (right_value))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor - probably required due to unique pointer
+  ArithmeticOrLogicalExpr (ArithmeticOrLogicalExpr const &other)
+    : OperatorExpr (other), expr_type (other.expr_type),
+      right_expr (other.right_expr->clone_expr ())
+  {}
+
+  // Overload assignment operator
+  ArithmeticOrLogicalExpr &operator= (ArithmeticOrLogicalExpr const &other)
+  {
+    OperatorExpr::operator= (other);
+    // main_or_left_expr = other.main_or_left_expr->clone_expr();
+    right_expr = other.right_expr->clone_expr ();
+    expr_type = other.expr_type;
+
+    return *this;
+  }
+
+  // move constructors
+  ArithmeticOrLogicalExpr (ArithmeticOrLogicalExpr &&other) = default;
+  ArithmeticOrLogicalExpr &operator= (ArithmeticOrLogicalExpr &&other)
+    = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_left_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_right_expr ()
+  {
+    rust_assert (right_expr != nullptr);
+    return right_expr;
+  }
+
+  void visit_lhs (ASTVisitor &vis) { main_or_left_expr->accept_vis (vis); }
+  void visit_rhs (ASTVisitor &vis) { right_expr->accept_vis (vis); }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ArithmeticOrLogicalExpr *clone_expr_without_block_impl () const override
+  {
+    return new ArithmeticOrLogicalExpr (*this);
+  }
+};
+
+// Infix binary comparison operators. ==, !=, <, <=, >, >=
+class ComparisonExpr : public OperatorExpr
+{
+public:
+  using ExprType = ComparisonOperator;
+
+private:
+  // Note: overloading trait specified in comments
+  ExprType expr_type;
+
+  std::unique_ptr<Expr> right_expr;
+
+public:
+  std::string as_string () const override;
+
+  ExprType get_expr_type () const { return expr_type; }
+
+  // Constructor requires pointers for polymorphism
+  ComparisonExpr (std::unique_ptr<Expr> left_value,
+		  std::unique_ptr<Expr> right_value, ExprType comparison_kind,
+		  Location locus)
+    : OperatorExpr (std::move (left_value), std::vector<Attribute> (), locus),
+      expr_type (comparison_kind), right_expr (std::move (right_value))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor also calls OperatorExpr's protected constructor
+  ComparisonExpr (ComparisonExpr const &other)
+    : OperatorExpr (other), expr_type (other.expr_type),
+      right_expr (other.right_expr->clone_expr ())
+  {}
+
+  // Overload assignment operator to deep copy
+  ComparisonExpr &operator= (ComparisonExpr const &other)
+  {
+    OperatorExpr::operator= (other);
+    // main_or_left_expr = other.main_or_left_expr->clone_expr();
+    right_expr = other.right_expr->clone_expr ();
+    expr_type = other.expr_type;
+    // outer_attrs = other.outer_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  ComparisonExpr (ComparisonExpr &&other) = default;
+  ComparisonExpr &operator= (ComparisonExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_left_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_right_expr ()
+  {
+    rust_assert (right_expr != nullptr);
+    return right_expr;
+  }
+
+  ExprType get_kind () { return expr_type; }
+
+  /* TODO: implement via a function call to std::cmp::PartialEq::eq(&op1, &op2)
+   * maybe? */
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ComparisonExpr *clone_expr_without_block_impl () const override
+  {
+    return new ComparisonExpr (*this);
+  }
+};
+
+// Infix binary lazy boolean logical operators && and ||.
+class LazyBooleanExpr : public OperatorExpr
+{
+public:
+  using ExprType = LazyBooleanOperator;
+
+private:
+  ExprType expr_type;
+
+  std::unique_ptr<Expr> right_expr;
+
+public:
+  // Constructor calls OperatorExpr's protected constructor
+  LazyBooleanExpr (std::unique_ptr<Expr> left_bool_expr,
+		   std::unique_ptr<Expr> right_bool_expr, ExprType expr_kind,
+		   Location locus)
+    : OperatorExpr (std::move (left_bool_expr), std::vector<Attribute> (),
+		    locus),
+      expr_type (expr_kind), right_expr (std::move (right_bool_expr))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor also calls OperatorExpr's protected constructor
+  LazyBooleanExpr (LazyBooleanExpr const &other)
+    : OperatorExpr (other), expr_type (other.expr_type),
+      right_expr (other.right_expr->clone_expr ())
+  {}
+
+  // Overload assignment operator to deep copy
+  LazyBooleanExpr &operator= (LazyBooleanExpr const &other)
+  {
+    OperatorExpr::operator= (other);
+    // main_or_left_expr = other.main_or_left_expr->clone_expr();
+    right_expr = other.right_expr->clone_expr ();
+    expr_type = other.expr_type;
+
+    return *this;
+  }
+
+  // move constructors
+  LazyBooleanExpr (LazyBooleanExpr &&other) = default;
+  LazyBooleanExpr &operator= (LazyBooleanExpr &&other) = default;
+
+  std::string as_string () const override;
+
+  ExprType get_expr_type () const { return expr_type; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_left_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_right_expr ()
+  {
+    rust_assert (right_expr != nullptr);
+    return right_expr;
+  }
+
+  ExprType get_kind () { return expr_type; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  LazyBooleanExpr *clone_expr_without_block_impl () const override
+  {
+    return new LazyBooleanExpr (*this);
+  }
+};
+
+// Binary infix "as" cast expression.
+class TypeCastExpr : public OperatorExpr
+{
+  std::unique_ptr<TypeNoBounds> type_to_convert_to;
+
+  // Note: only certain type casts allowed, outlined in reference
+public:
+  std::string as_string () const override;
+
+  // Constructor requires calling protected constructor of OperatorExpr
+  TypeCastExpr (std::unique_ptr<Expr> expr_to_cast,
+		std::unique_ptr<TypeNoBounds> type_to_cast_to, Location locus)
+    : OperatorExpr (std::move (expr_to_cast), std::vector<Attribute> (), locus),
+      type_to_convert_to (std::move (type_to_cast_to))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor also requires calling protected constructor
+  TypeCastExpr (TypeCastExpr const &other)
+    : OperatorExpr (other),
+      type_to_convert_to (other.type_to_convert_to->clone_type_no_bounds ())
+  {}
+
+  // Overload assignment operator to deep copy
+  TypeCastExpr &operator= (TypeCastExpr const &other)
+  {
+    OperatorExpr::operator= (other);
+    // main_or_left_expr = other.main_or_left_expr->clone_expr();
+    type_to_convert_to = other.type_to_convert_to->clone_type_no_bounds ();
+
+    return *this;
+  }
+
+  // move constructors
+  TypeCastExpr (TypeCastExpr &&other) = default;
+  TypeCastExpr &operator= (TypeCastExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_casted_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<TypeNoBounds> &get_type_to_cast_to ()
+  {
+    rust_assert (type_to_convert_to != nullptr);
+    return type_to_convert_to;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TypeCastExpr *clone_expr_without_block_impl () const override
+  {
+    return new TypeCastExpr (*this);
+  }
+};
+
+// Binary assignment expression.
+class AssignmentExpr : public OperatorExpr
+{
+  std::unique_ptr<Expr> right_expr;
+
+public:
+  std::string as_string () const override;
+
+  // Call OperatorExpr constructor to initialise left_expr
+  AssignmentExpr (std::unique_ptr<Expr> value_to_assign_to,
+		  std::unique_ptr<Expr> value_to_assign,
+		  std::vector<Attribute> outer_attribs, Location locus)
+    : OperatorExpr (std::move (value_to_assign_to), std::move (outer_attribs),
+		    locus),
+      right_expr (std::move (value_to_assign))
+  {}
+  // outer attributes not allowed
+
+  // Call OperatorExpr constructor in copy constructor, as well as clone
+  AssignmentExpr (AssignmentExpr const &other)
+    : OperatorExpr (other), right_expr (other.right_expr->clone_expr ())
+  {}
+
+  // Overload assignment operator to clone unique_ptr right_expr
+  AssignmentExpr &operator= (AssignmentExpr const &other)
+  {
+    OperatorExpr::operator= (other);
+    // main_or_left_expr = other.main_or_left_expr->clone_expr();
+    right_expr = other.right_expr->clone_expr ();
+    // outer_attrs = other.outer_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  AssignmentExpr (AssignmentExpr &&other) = default;
+  AssignmentExpr &operator= (AssignmentExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  void visit_lhs (ASTVisitor &vis) { main_or_left_expr->accept_vis (vis); }
+  void visit_rhs (ASTVisitor &vis) { right_expr->accept_vis (vis); }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_left_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_right_expr ()
+  {
+    rust_assert (right_expr != nullptr);
+    return right_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  AssignmentExpr *clone_expr_without_block_impl () const override
+  {
+    return new AssignmentExpr (*this);
+  }
+};
+
+/* Binary infix compound assignment (arithmetic or logic then assignment)
+ * expressions. */
+class CompoundAssignmentExpr : public OperatorExpr
+{
+public:
+  using ExprType = CompoundAssignmentOperator;
+
+private:
+  // Note: overloading trait specified in comments
+  ExprType expr_type;
+  std::unique_ptr<Expr> right_expr;
+
+public:
+  std::string as_string () const override;
+
+  ExprType get_expr_type () const { return expr_type; }
+
+  // Use pointers in constructor to enable polymorphism
+  CompoundAssignmentExpr (std::unique_ptr<Expr> value_to_assign_to,
+			  std::unique_ptr<Expr> value_to_assign,
+			  ExprType expr_kind, Location locus)
+    : OperatorExpr (std::move (value_to_assign_to), std::vector<Attribute> (),
+		    locus),
+      expr_type (expr_kind), right_expr (std::move (value_to_assign))
+  {}
+  // outer attributes not allowed
+
+  // Have clone in copy constructor
+  CompoundAssignmentExpr (CompoundAssignmentExpr const &other)
+    : OperatorExpr (other), expr_type (other.expr_type),
+      right_expr (other.right_expr->clone_expr ())
+  {}
+
+  // Overload assignment operator to clone
+  CompoundAssignmentExpr &operator= (CompoundAssignmentExpr const &other)
+  {
+    OperatorExpr::operator= (other);
+    // main_or_left_expr = other.main_or_left_expr->clone_expr();
+    right_expr = other.right_expr->clone_expr ();
+    expr_type = other.expr_type;
+    // outer_attrs = other.outer_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  CompoundAssignmentExpr (CompoundAssignmentExpr &&other) = default;
+  CompoundAssignmentExpr &operator= (CompoundAssignmentExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_left_expr ()
+  {
+    rust_assert (main_or_left_expr != nullptr);
+    return main_or_left_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_right_expr ()
+  {
+    rust_assert (right_expr != nullptr);
+    return right_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  CompoundAssignmentExpr *clone_expr_without_block_impl () const override
+  {
+    return new CompoundAssignmentExpr (*this);
+  }
+};
+
+// Expression in parentheses (i.e. like literally just any 3 + (2 * 6))
+class GroupedExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::vector<Attribute> inner_attrs;
+  std::unique_ptr<Expr> expr_in_parens;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  GroupedExpr (std::unique_ptr<Expr> parenthesised_expr,
+	       std::vector<Attribute> inner_attribs,
+	       std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      inner_attrs (std::move (inner_attribs)),
+      expr_in_parens (std::move (parenthesised_expr)), locus (locus)
+  {}
+
+  // Copy constructor includes clone for expr_in_parens
+  GroupedExpr (GroupedExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      inner_attrs (other.inner_attrs), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr_in_parens != nullptr)
+      expr_in_parens = other.expr_in_parens->clone_expr ();
+  }
+
+  // Overloaded assignment operator to clone expr_in_parens
+  GroupedExpr &operator= (GroupedExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    inner_attrs = other.inner_attrs;
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr_in_parens != nullptr)
+      expr_in_parens = other.expr_in_parens->clone_expr ();
+    else
+      expr_in_parens = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  GroupedExpr (GroupedExpr &&other) = default;
+  GroupedExpr &operator= (GroupedExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if inner expr is null, so base stripping on that.
+  void mark_for_strip () override { expr_in_parens = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return expr_in_parens == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_expr_in_parens ()
+  {
+    rust_assert (expr_in_parens != nullptr);
+    return expr_in_parens;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  GroupedExpr *clone_expr_without_block_impl () const override
+  {
+    return new GroupedExpr (*this);
+  }
+};
+
+// Base array initialisation internal element representation thing (abstract)
+// aka ArrayElements
+class ArrayElems
+{
+public:
+  virtual ~ArrayElems () {}
+
+  // Unique pointer custom clone ArrayElems function
+  std::unique_ptr<ArrayElems> clone_array_elems () const
+  {
+    return std::unique_ptr<ArrayElems> (clone_array_elems_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  NodeId get_node_id () const { return node_id; }
+
+protected:
+  ArrayElems () : node_id (Analysis::Mappings::get ()->get_next_node_id ()) {}
+
+  // pure virtual clone implementation
+  virtual ArrayElems *clone_array_elems_impl () const = 0;
+
+  NodeId node_id;
+};
+
+// Value array elements
+class ArrayElemsValues : public ArrayElems
+{
+  std::vector<std::unique_ptr<Expr> > values;
+  Location locus;
+
+public:
+  ArrayElemsValues (std::vector<std::unique_ptr<Expr> > elems, Location locus)
+    : ArrayElems (), values (std::move (elems)), locus (locus)
+  {}
+
+  // copy constructor with vector clone
+  ArrayElemsValues (ArrayElemsValues const &other)
+  {
+    values.reserve (other.values.size ());
+    for (const auto &e : other.values)
+      values.push_back (e->clone_expr ());
+  }
+
+  // overloaded assignment operator with vector clone
+  ArrayElemsValues &operator= (ArrayElemsValues const &other)
+  {
+    values.reserve (other.values.size ());
+    for (const auto &e : other.values)
+      values.push_back (e->clone_expr ());
+
+    return *this;
+  }
+
+  // move constructors
+  ArrayElemsValues (ArrayElemsValues &&other) = default;
+  ArrayElemsValues &operator= (ArrayElemsValues &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<std::unique_ptr<Expr> > &get_values () const
+  {
+    return values;
+  }
+  std::vector<std::unique_ptr<Expr> > &get_values () { return values; }
+
+  size_t get_num_values () const { return values.size (); }
+
+protected:
+  ArrayElemsValues *clone_array_elems_impl () const override
+  {
+    return new ArrayElemsValues (*this);
+  }
+};
+
+// Copied array element and number of copies
+class ArrayElemsCopied : public ArrayElems
+{
+  std::unique_ptr<Expr> elem_to_copy;
+  std::unique_ptr<Expr> num_copies;
+  Location locus;
+
+public:
+  // Constructor requires pointers for polymorphism
+  ArrayElemsCopied (std::unique_ptr<Expr> copied_elem,
+		    std::unique_ptr<Expr> copy_amount, Location locus)
+    : ArrayElems (), elem_to_copy (std::move (copied_elem)),
+      num_copies (std::move (copy_amount)), locus (locus)
+  {}
+
+  // Copy constructor required due to unique_ptr - uses custom clone
+  ArrayElemsCopied (ArrayElemsCopied const &other)
+    : elem_to_copy (other.elem_to_copy->clone_expr ()),
+      num_copies (other.num_copies->clone_expr ())
+  {}
+
+  // Overloaded assignment operator for deep copying
+  ArrayElemsCopied &operator= (ArrayElemsCopied const &other)
+  {
+    elem_to_copy = other.elem_to_copy->clone_expr ();
+    num_copies = other.num_copies->clone_expr ();
+
+    return *this;
+  }
+
+  // move constructors
+  ArrayElemsCopied (ArrayElemsCopied &&other) = default;
+  ArrayElemsCopied &operator= (ArrayElemsCopied &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_elem_to_copy ()
+  {
+    rust_assert (elem_to_copy != nullptr);
+    return elem_to_copy;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_num_copies ()
+  {
+    rust_assert (num_copies != nullptr);
+    return num_copies;
+  }
+
+protected:
+  ArrayElemsCopied *clone_array_elems_impl () const override
+  {
+    return new ArrayElemsCopied (*this);
+  }
+};
+
+// Array definition-ish expression
+class ArrayExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::vector<Attribute> inner_attrs;
+  std::unique_ptr<ArrayElems> internal_elements;
+  Location locus;
+
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  // Constructor requires ArrayElems pointer
+  ArrayExpr (std::unique_ptr<ArrayElems> array_elems,
+	     std::vector<Attribute> inner_attribs,
+	     std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      inner_attrs (std::move (inner_attribs)),
+      internal_elements (std::move (array_elems)), locus (locus)
+  {
+    rust_assert (internal_elements != nullptr);
+  }
+
+  // Copy constructor requires cloning ArrayElems for polymorphism to hold
+  ArrayExpr (ArrayExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      inner_attrs (other.inner_attrs), locus (other.locus),
+      marked_for_strip (other.marked_for_strip)
+  {
+    internal_elements = other.internal_elements->clone_array_elems ();
+    rust_assert (internal_elements != nullptr);
+  }
+
+  // Overload assignment operator to clone internal_elements
+  ArrayExpr &operator= (ArrayExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    inner_attrs = other.inner_attrs;
+    locus = other.locus;
+    marked_for_strip = other.marked_for_strip;
+    outer_attrs = other.outer_attrs;
+
+    internal_elements = other.internal_elements->clone_array_elems ();
+
+    rust_assert (internal_elements != nullptr);
+    return *this;
+  }
+
+  // move constructors
+  ArrayExpr (ArrayExpr &&other) = default;
+  ArrayExpr &operator= (ArrayExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<ArrayElems> &get_array_elems ()
+  {
+    rust_assert (internal_elements != nullptr);
+    return internal_elements;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ArrayExpr *clone_expr_without_block_impl () const override
+  {
+    return new ArrayExpr (*this);
+  }
+};
+
+// Aka IndexExpr (also applies to slices)
+/* Apparently a[b] is equivalent to *std::ops::Index::index(&a, b) or
+ * *std::ops::Index::index_mut(&mut a, b) */
+/* Also apparently deref operations on a will be repeatedly applied to find an
+ * implementation */
+class ArrayIndexExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> array_expr;
+  std::unique_ptr<Expr> index_expr;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  ArrayIndexExpr (std::unique_ptr<Expr> array_expr,
+		  std::unique_ptr<Expr> array_index_expr,
+		  std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      array_expr (std::move (array_expr)),
+      index_expr (std::move (array_index_expr)), locus (locus)
+  {}
+
+  // Copy constructor requires special cloning due to unique_ptr
+  ArrayIndexExpr (ArrayIndexExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.array_expr != nullptr)
+      array_expr = other.array_expr->clone_expr ();
+    if (other.index_expr != nullptr)
+      index_expr = other.index_expr->clone_expr ();
+  }
+
+  // Overload assignment operator to clone unique_ptrs
+  ArrayIndexExpr &operator= (ArrayIndexExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.array_expr != nullptr)
+      array_expr = other.array_expr->clone_expr ();
+    else
+      array_expr = nullptr;
+    if (other.index_expr != nullptr)
+      index_expr = other.index_expr->clone_expr ();
+    else
+      index_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ArrayIndexExpr (ArrayIndexExpr &&other) = default;
+  ArrayIndexExpr &operator= (ArrayIndexExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if either expr is null, so base stripping on that.
+  void mark_for_strip () override
+  {
+    array_expr = nullptr;
+    index_expr = nullptr;
+  }
+  bool is_marked_for_strip () const override
+  {
+    return array_expr == nullptr && index_expr == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_array_expr ()
+  {
+    rust_assert (array_expr != nullptr);
+    return array_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_index_expr ()
+  {
+    rust_assert (index_expr != nullptr);
+    return index_expr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ArrayIndexExpr *clone_expr_without_block_impl () const override
+  {
+    return new ArrayIndexExpr (*this);
+  }
+};
+
+// AST representation of a tuple
+class TupleExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::vector<Attribute> inner_attrs;
+  std::vector<std::unique_ptr<Expr> > tuple_elems;
+  Location locus;
+
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  TupleExpr (std::vector<std::unique_ptr<Expr> > tuple_elements,
+	     std::vector<Attribute> inner_attribs,
+	     std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      inner_attrs (std::move (inner_attribs)),
+      tuple_elems (std::move (tuple_elements)), locus (locus)
+  {}
+
+  // copy constructor with vector clone
+  TupleExpr (TupleExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      inner_attrs (other.inner_attrs), locus (other.locus),
+      marked_for_strip (other.marked_for_strip)
+  {
+    tuple_elems.reserve (other.tuple_elems.size ());
+    for (const auto &e : other.tuple_elems)
+      tuple_elems.push_back (e->clone_expr ());
+  }
+
+  // overloaded assignment operator to vector clone
+  TupleExpr &operator= (TupleExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    outer_attrs = other.outer_attrs;
+    inner_attrs = other.inner_attrs;
+    locus = other.locus;
+    marked_for_strip = other.marked_for_strip;
+
+    tuple_elems.reserve (other.tuple_elems.size ());
+    for (const auto &e : other.tuple_elems)
+      tuple_elems.push_back (e->clone_expr ());
+
+    return *this;
+  }
+
+  // move constructors
+  TupleExpr (TupleExpr &&other) = default;
+  TupleExpr &operator= (TupleExpr &&other) = default;
+
+  /* Note: syntactically, can disambiguate single-element tuple from parens with
+   * comma, i.e. (0,) rather than (0) */
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<std::unique_ptr<Expr> > &get_tuple_elems () const
+  {
+    return tuple_elems;
+  }
+  std::vector<std::unique_ptr<Expr> > &get_tuple_elems ()
+  {
+    return tuple_elems;
+  }
+
+  bool is_unit () const { return tuple_elems.size () == 0; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TupleExpr *clone_expr_without_block_impl () const override
+  {
+    return new TupleExpr (*this);
+  }
+};
+
+// aka TupleIndexingExpr
+// AST representation of a tuple indexing expression
+class TupleIndexExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> tuple_expr;
+  // TupleIndex is a decimal int literal with no underscores or suffix
+  TupleIndex tuple_index;
+
+  Location locus;
+
+  // i.e. pair.0
+
+public:
+  std::string as_string () const override;
+
+  TupleIndex get_tuple_index () const { return tuple_index; }
+
+  TupleIndexExpr (std::unique_ptr<Expr> tuple_expr, TupleIndex index,
+		  std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      tuple_expr (std::move (tuple_expr)), tuple_index (index), locus (locus)
+  {}
+
+  // Copy constructor requires a clone for tuple_expr
+  TupleIndexExpr (TupleIndexExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      tuple_index (other.tuple_index), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.tuple_expr != nullptr)
+      tuple_expr = other.tuple_expr->clone_expr ();
+  }
+
+  // Overload assignment operator in order to clone
+  TupleIndexExpr &operator= (TupleIndexExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    tuple_index = other.tuple_index;
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.tuple_expr != nullptr)
+      tuple_expr = other.tuple_expr->clone_expr ();
+    else
+      tuple_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  TupleIndexExpr (TupleIndexExpr &&other) = default;
+  TupleIndexExpr &operator= (TupleIndexExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if tuple expr is null, so base stripping on that.
+  void mark_for_strip () override { tuple_expr = nullptr; }
+  bool is_marked_for_strip () const override { return tuple_expr == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_tuple_expr ()
+  {
+    rust_assert (tuple_expr != nullptr);
+    return tuple_expr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TupleIndexExpr *clone_expr_without_block_impl () const override
+  {
+    return new TupleIndexExpr (*this);
+  }
+};
+
+// Base struct/tuple/union value creator AST node (abstract)
+class StructExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  PathInExpression struct_name;
+
+protected:
+  // Protected constructor to allow initialising struct_name
+  StructExpr (PathInExpression struct_path,
+	      std::vector<Attribute> outer_attribs)
+    : outer_attrs (std::move (outer_attribs)),
+      struct_name (std::move (struct_path))
+  {}
+
+public:
+  const PathInExpression &get_struct_name () const { return struct_name; }
+  PathInExpression &get_struct_name () { return struct_name; }
+
+  std::string as_string () const override;
+
+  // Invalid if path is empty, so base stripping on that.
+  void mark_for_strip () override
+  {
+    struct_name = PathInExpression::create_error ();
+  }
+  bool is_marked_for_strip () const override { return struct_name.is_error (); }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+};
+
+// Actual AST node of the struct creator (with no fields). Not abstract!
+class StructExprStruct : public StructExpr
+{
+  std::vector<Attribute> inner_attrs;
+
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  // Constructor has to call protected constructor of base class
+  StructExprStruct (PathInExpression struct_path,
+		    std::vector<Attribute> inner_attribs,
+		    std::vector<Attribute> outer_attribs, Location locus)
+    : StructExpr (std::move (struct_path), std::move (outer_attribs)),
+      inner_attrs (std::move (inner_attribs)), locus (locus)
+  {}
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructExprStruct *clone_expr_without_block_impl () const override
+  {
+    return new StructExprStruct (*this);
+  }
+};
+
+/* AST node representing expression used to fill a struct's fields from another
+ * struct */
+struct StructBase
+{
+private:
+  std::unique_ptr<Expr> base_struct;
+  Location locus;
+
+public:
+  StructBase (std::unique_ptr<Expr> base_struct_ptr, Location locus)
+    : base_struct (std::move (base_struct_ptr)), locus (locus)
+  {}
+
+  // Copy constructor requires clone
+  StructBase (StructBase const &other)
+  {
+    /* HACK: gets around base_struct pointer being null (e.g. if no struct base
+     * exists) */
+    if (other.base_struct != nullptr)
+      base_struct = other.base_struct->clone_expr ();
+  }
+
+  // Destructor
+  ~StructBase () = default;
+
+  // Overload assignment operator to clone base_struct
+  StructBase &operator= (StructBase const &other)
+  {
+    // prevent null pointer dereference
+    if (other.base_struct != nullptr)
+      base_struct = other.base_struct->clone_expr ();
+    else
+      base_struct = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  StructBase (StructBase &&other) = default;
+  StructBase &operator= (StructBase &&other) = default;
+
+  // Returns a null expr-ed StructBase - error state
+  static StructBase error () { return StructBase (nullptr, Location ()); }
+
+  // Returns whether StructBase is in error state
+  bool is_invalid () const { return base_struct == nullptr; }
+
+  std::string as_string () const;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_base_struct ()
+  {
+    rust_assert (base_struct != nullptr);
+    return base_struct;
+  }
+};
+
+/* Base AST node for a single struct expression field (in struct instance
+ * creation) - abstract */
+class StructExprField
+{
+public:
+  virtual ~StructExprField () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<StructExprField> clone_struct_expr_field () const
+  {
+    return std::unique_ptr<StructExprField> (clone_struct_expr_field_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual Location get_locus () const = 0;
+
+  NodeId get_node_id () const { return node_id; }
+
+protected:
+  // pure virtual clone implementation
+  virtual StructExprField *clone_struct_expr_field_impl () const = 0;
+
+  StructExprField () : node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  NodeId node_id;
+};
+
+// Identifier-only variant of StructExprField AST node
+class StructExprFieldIdentifier : public StructExprField
+{
+  Identifier field_name;
+  Location locus;
+
+public:
+  StructExprFieldIdentifier (Identifier field_identifier, Location locus)
+    : StructExprField (), field_name (std::move (field_identifier)),
+      locus (locus)
+  {}
+
+  std::string as_string () const override { return field_name; }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  Identifier get_field_name () const { return field_name; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructExprFieldIdentifier *clone_struct_expr_field_impl () const override
+  {
+    return new StructExprFieldIdentifier (*this);
+  }
+};
+
+/* Base AST node for a single struct expression field with an assigned value -
+ * abstract */
+class StructExprFieldWithVal : public StructExprField
+{
+  std::unique_ptr<Expr> value;
+
+protected:
+  StructExprFieldWithVal (std::unique_ptr<Expr> field_value)
+    : StructExprField (), value (std::move (field_value))
+  {}
+
+  // Copy constructor requires clone
+  StructExprFieldWithVal (StructExprFieldWithVal const &other)
+    : value (other.value->clone_expr ())
+  {}
+
+  // Overload assignment operator to clone unique_ptr
+  StructExprFieldWithVal &operator= (StructExprFieldWithVal const &other)
+  {
+    value = other.value->clone_expr ();
+
+    return *this;
+  }
+
+  // move constructors
+  StructExprFieldWithVal (StructExprFieldWithVal &&other) = default;
+  StructExprFieldWithVal &operator= (StructExprFieldWithVal &&other) = default;
+
+public:
+  std::string as_string () const override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_value ()
+  {
+    rust_assert (value != nullptr);
+    return value;
+  }
+};
+
+// Identifier and value variant of StructExprField AST node
+class StructExprFieldIdentifierValue : public StructExprFieldWithVal
+{
+  Identifier field_name;
+  Location locus;
+
+public:
+  StructExprFieldIdentifierValue (Identifier field_identifier,
+				  std::unique_ptr<Expr> field_value,
+				  Location locus)
+    : StructExprFieldWithVal (std::move (field_value)),
+      field_name (std::move (field_identifier)), locus (locus)
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  std::string get_field_name () const { return field_name; }
+
+  Location get_locus () const override final { return locus; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructExprFieldIdentifierValue *clone_struct_expr_field_impl () const override
+  {
+    return new StructExprFieldIdentifierValue (*this);
+  }
+};
+
+// Tuple index and value variant of StructExprField AST node
+class StructExprFieldIndexValue : public StructExprFieldWithVal
+{
+  TupleIndex index;
+  Location locus;
+
+public:
+  StructExprFieldIndexValue (TupleIndex tuple_index,
+			     std::unique_ptr<Expr> field_value, Location locus)
+    : StructExprFieldWithVal (std::move (field_value)), index (tuple_index),
+      locus (locus)
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  TupleIndex get_index () const { return index; }
+
+  Location get_locus () const override final { return locus; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructExprFieldIndexValue *clone_struct_expr_field_impl () const override
+  {
+    return new StructExprFieldIndexValue (*this);
+  }
+};
+
+// AST node of a struct creator with fields
+class StructExprStructFields : public StructExprStruct
+{
+  // std::vector<StructExprField> fields;
+  std::vector<std::unique_ptr<StructExprField> > fields;
+
+  // bool has_struct_base;
+  StructBase struct_base;
+
+public:
+  std::string as_string () const override;
+
+  bool has_struct_base () const { return !struct_base.is_invalid (); }
+
+  // Constructor for StructExprStructFields when no struct base is used
+  StructExprStructFields (
+    PathInExpression struct_path,
+    std::vector<std::unique_ptr<StructExprField> > expr_fields, Location locus,
+    StructBase base_struct = StructBase::error (),
+    std::vector<Attribute> inner_attribs = std::vector<Attribute> (),
+    std::vector<Attribute> outer_attribs = std::vector<Attribute> ())
+    : StructExprStruct (std::move (struct_path), std::move (inner_attribs),
+			std::move (outer_attribs), locus),
+      fields (std::move (expr_fields)), struct_base (std::move (base_struct))
+  {}
+
+  // copy constructor with vector clone
+  StructExprStructFields (StructExprStructFields const &other)
+    : StructExprStruct (other), struct_base (other.struct_base)
+  {
+    fields.reserve (other.fields.size ());
+    for (const auto &e : other.fields)
+      fields.push_back (e->clone_struct_expr_field ());
+  }
+
+  // overloaded assignment operator with vector clone
+  StructExprStructFields &operator= (StructExprStructFields const &other)
+  {
+    StructExprStruct::operator= (other);
+    struct_base = other.struct_base;
+
+    fields.reserve (other.fields.size ());
+    for (const auto &e : other.fields)
+      fields.push_back (e->clone_struct_expr_field ());
+
+    return *this;
+  }
+
+  // move constructors
+  StructExprStructFields (StructExprStructFields &&other) = default;
+  StructExprStructFields &operator= (StructExprStructFields &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<std::unique_ptr<StructExprField> > &get_fields ()
+  {
+    return fields;
+  }
+  const std::vector<std::unique_ptr<StructExprField> > &get_fields () const
+  {
+    return fields;
+  }
+
+  StructBase &get_struct_base () { return struct_base; }
+  const StructBase &get_struct_base () const { return struct_base; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructExprStructFields *clone_expr_without_block_impl () const override
+  {
+    return new StructExprStructFields (*this);
+  }
+};
+
+// AST node of the functional update struct creator
+/* TODO: remove and replace with StructExprStructFields, except with empty
+ * vector of fields? */
+class StructExprStructBase : public StructExprStruct
+{
+  StructBase struct_base;
+
+public:
+  std::string as_string () const override;
+
+  StructExprStructBase (PathInExpression struct_path, StructBase base_struct,
+			std::vector<Attribute> inner_attribs,
+			std::vector<Attribute> outer_attribs, Location locus)
+    : StructExprStruct (std::move (struct_path), std::move (inner_attribs),
+			std::move (outer_attribs), locus),
+      struct_base (std::move (base_struct))
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  StructBase &get_struct_base () { return struct_base; }
+  const StructBase &get_struct_base () const { return struct_base; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructExprStructBase *clone_expr_without_block_impl () const override
+  {
+    return new StructExprStructBase (*this);
+  }
+};
+
+// Forward decl for Function - used in CallExpr
+class Function;
+
+// Function call expression AST node
+class CallExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> function;
+  std::vector<std::unique_ptr<Expr> > params;
+  Location locus;
+
+public:
+  Function *fndeclRef;
+
+  std::string as_string () const override;
+
+  CallExpr (std::unique_ptr<Expr> function_expr,
+	    std::vector<std::unique_ptr<Expr> > function_params,
+	    std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      function (std::move (function_expr)),
+      params (std::move (function_params)), locus (locus)
+  {}
+
+  // copy constructor requires clone
+  CallExpr (CallExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.function != nullptr)
+      function = other.function->clone_expr ();
+
+    params.reserve (other.params.size ());
+    for (const auto &e : other.params)
+      params.push_back (e->clone_expr ());
+  }
+
+  // Overload assignment operator to clone
+  CallExpr &operator= (CallExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.function != nullptr)
+      function = other.function->clone_expr ();
+    else
+      function = nullptr;
+
+    params.reserve (other.params.size ());
+    for (const auto &e : other.params)
+      params.push_back (e->clone_expr ());
+
+    return *this;
+  }
+
+  // move constructors
+  CallExpr (CallExpr &&other) = default;
+  CallExpr &operator= (CallExpr &&other) = default;
+
+  // Returns whether function call has parameters.
+  bool has_params () const { return !params.empty (); }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if function expr is null, so base stripping on that.
+  void mark_for_strip () override { function = nullptr; }
+  bool is_marked_for_strip () const override { return function == nullptr; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<std::unique_ptr<Expr> > &get_params () const
+  {
+    return params;
+  }
+  std::vector<std::unique_ptr<Expr> > &get_params () { return params; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_function_expr ()
+  {
+    rust_assert (function != nullptr);
+    return function;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  CallExpr *clone_expr_without_block_impl () const override
+  {
+    return new CallExpr (*this);
+  }
+};
+
+// Method call expression AST node
+class MethodCallExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> receiver;
+  PathExprSegment method_name;
+  std::vector<std::unique_ptr<Expr> > params;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  MethodCallExpr (std::unique_ptr<Expr> call_receiver,
+		  PathExprSegment method_path,
+		  std::vector<std::unique_ptr<Expr> > method_params,
+		  std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      receiver (std::move (call_receiver)),
+      method_name (std::move (method_path)), params (std::move (method_params)),
+      locus (locus)
+  {}
+
+  // copy constructor required due to cloning
+  MethodCallExpr (MethodCallExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      method_name (other.method_name), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.receiver != nullptr)
+      receiver = other.receiver->clone_expr ();
+
+    params.reserve (other.params.size ());
+    for (const auto &e : other.params)
+      params.push_back (e->clone_expr ());
+  }
+
+  // Overload assignment operator to clone receiver object
+  MethodCallExpr &operator= (MethodCallExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    method_name = other.method_name;
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.receiver != nullptr)
+      receiver = other.receiver->clone_expr ();
+    else
+      receiver = nullptr;
+
+    params.reserve (other.params.size ());
+    for (const auto &e : other.params)
+      params.push_back (e->clone_expr ());
+
+    return *this;
+  }
+
+  // move constructors
+  MethodCallExpr (MethodCallExpr &&other) = default;
+  MethodCallExpr &operator= (MethodCallExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if receiver expr is null, so base stripping on that.
+  void mark_for_strip () override { receiver = nullptr; }
+  bool is_marked_for_strip () const override { return receiver == nullptr; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<std::unique_ptr<Expr> > &get_params () const
+  {
+    return params;
+  }
+  std::vector<std::unique_ptr<Expr> > &get_params () { return params; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_receiver_expr ()
+  {
+    rust_assert (receiver != nullptr);
+    return receiver;
+  }
+
+  const PathExprSegment &get_method_name () const { return method_name; }
+  PathExprSegment &get_method_name () { return method_name; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MethodCallExpr *clone_expr_without_block_impl () const override
+  {
+    return new MethodCallExpr (*this);
+  }
+};
+
+// aka FieldExpression
+// Struct or union field access expression AST node
+class FieldAccessExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> receiver;
+  Identifier field;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  FieldAccessExpr (std::unique_ptr<Expr> field_access_receiver,
+		   Identifier field_name, std::vector<Attribute> outer_attribs,
+		   Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      receiver (std::move (field_access_receiver)),
+      field (std::move (field_name)), locus (locus)
+  {}
+
+  // Copy constructor required due to unique_ptr cloning
+  FieldAccessExpr (FieldAccessExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      field (other.field), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.receiver != nullptr)
+      receiver = other.receiver->clone_expr ();
+  }
+
+  // Overload assignment operator to clone unique_ptr
+  FieldAccessExpr &operator= (FieldAccessExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    field = other.field;
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.receiver != nullptr)
+      receiver = other.receiver->clone_expr ();
+    else
+      receiver = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  FieldAccessExpr (FieldAccessExpr &&other) = default;
+  FieldAccessExpr &operator= (FieldAccessExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if receiver expr is null, so base stripping on that.
+  void mark_for_strip () override { receiver = nullptr; }
+  bool is_marked_for_strip () const override { return receiver == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_receiver_expr ()
+  {
+    rust_assert (receiver != nullptr);
+    return receiver;
+  }
+
+  Identifier get_field_name () const { return field; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  FieldAccessExpr *clone_expr_without_block_impl () const override
+  {
+    return new FieldAccessExpr (*this);
+  }
+};
+
+// Closure parameter data structure
+struct ClosureParam
+{
+private:
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Pattern> pattern;
+
+  // bool has_type_given;
+  std::unique_ptr<Type> type;
+  Location locus;
+
+public:
+  // Returns whether the type of the parameter has been given.
+  bool has_type_given () const { return type != nullptr; }
+
+  bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+  // Constructor for closure parameter
+  ClosureParam (std::unique_ptr<Pattern> param_pattern, Location locus,
+		std::unique_ptr<Type> param_type = nullptr,
+		std::vector<Attribute> outer_attrs = {})
+    : outer_attrs (std::move (outer_attrs)),
+      pattern (std::move (param_pattern)), type (std::move (param_type)),
+      locus (locus)
+  {}
+
+  // Copy constructor required due to cloning as a result of unique_ptrs
+  ClosureParam (ClosureParam const &other) : outer_attrs (other.outer_attrs)
+  {
+    // guard to protect from null pointer dereference
+    if (other.pattern != nullptr)
+      pattern = other.pattern->clone_pattern ();
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+  }
+
+  ~ClosureParam () = default;
+
+  // Assignment operator must be overloaded to clone as well
+  ClosureParam &operator= (ClosureParam const &other)
+  {
+    outer_attrs = other.outer_attrs;
+
+    // guard to protect from null pointer dereference
+    if (other.pattern != nullptr)
+      pattern = other.pattern->clone_pattern ();
+    else
+      pattern = nullptr;
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ClosureParam (ClosureParam &&other) = default;
+  ClosureParam &operator= (ClosureParam &&other) = default;
+
+  // Returns whether closure parameter is in an error state.
+  bool is_error () const { return pattern == nullptr; }
+
+  // Creates an error state closure parameter.
+  static ClosureParam create_error ()
+  {
+    return ClosureParam (nullptr, Location ());
+  }
+
+  std::string as_string () const;
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Pattern> &get_pattern ()
+  {
+    rust_assert (pattern != nullptr);
+    return pattern;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (has_type_given ());
+    return type;
+  }
+};
+
+// Base closure definition expression AST node - abstract
+class ClosureExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  bool has_move;
+  std::vector<ClosureParam> params; // may be empty
+  Location locus;
+
+protected:
+  ClosureExpr (std::vector<ClosureParam> closure_params, bool has_move,
+	       std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)), has_move (has_move),
+      params (std::move (closure_params)), locus (locus)
+  {}
+
+public:
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<ClosureParam> &get_params () const { return params; }
+  std::vector<ClosureParam> &get_params () { return params; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+};
+
+// Represents a non-type-specified closure expression AST node
+class ClosureExprInner : public ClosureExpr
+{
+  std::unique_ptr<Expr> closure_inner;
+
+public:
+  std::string as_string () const override;
+
+  // Constructor for a ClosureExprInner
+  ClosureExprInner (std::unique_ptr<Expr> closure_inner_expr,
+		    std::vector<ClosureParam> closure_params, Location locus,
+		    bool is_move = false,
+		    std::vector<Attribute> outer_attribs
+		    = std::vector<Attribute> ())
+    : ClosureExpr (std::move (closure_params), is_move,
+		   std::move (outer_attribs), locus),
+      closure_inner (std::move (closure_inner_expr))
+  {}
+
+  // Copy constructor must be defined to allow copying via cloning of unique_ptr
+  ClosureExprInner (ClosureExprInner const &other) : ClosureExpr (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.closure_inner != nullptr)
+      closure_inner = other.closure_inner->clone_expr ();
+  }
+
+  // Overload assignment operator to clone closure_inner
+  ClosureExprInner &operator= (ClosureExprInner const &other)
+  {
+    ClosureExpr::operator= (other);
+    // params = other.params;
+    // has_move = other.has_move;
+    // outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.closure_inner != nullptr)
+      closure_inner = other.closure_inner->clone_expr ();
+    else
+      closure_inner = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ClosureExprInner (ClosureExprInner &&other) = default;
+  ClosureExprInner &operator= (ClosureExprInner &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if inner expr is null, so base stripping on that.
+  void mark_for_strip () override { closure_inner = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return closure_inner == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_definition_expr ()
+  {
+    rust_assert (closure_inner != nullptr);
+    return closure_inner;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ClosureExprInner *clone_expr_without_block_impl () const override
+  {
+    return new ClosureExprInner (*this);
+  }
+};
+
+// A block AST node
+class BlockExpr : public ExprWithBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::vector<Attribute> inner_attrs;
+  std::vector<std::unique_ptr<Stmt> > statements;
+  std::unique_ptr<Expr> expr;
+  Location start_locus;
+  Location end_locus;
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether the block contains statements.
+  bool has_statements () const { return !statements.empty (); }
+
+  // Returns whether the block contains a final expression.
+  bool has_tail_expr () const { return expr != nullptr; }
+
+  BlockExpr (std::vector<std::unique_ptr<Stmt> > block_statements,
+	     std::unique_ptr<Expr> block_expr,
+	     std::vector<Attribute> inner_attribs,
+	     std::vector<Attribute> outer_attribs, Location start_locus,
+	     Location end_locus)
+    : outer_attrs (std::move (outer_attribs)),
+      inner_attrs (std::move (inner_attribs)),
+      statements (std::move (block_statements)), expr (std::move (block_expr)),
+      start_locus (start_locus), end_locus (end_locus)
+  {}
+
+  // Copy constructor with clone
+  BlockExpr (BlockExpr const &other)
+    : ExprWithBlock (other), outer_attrs (other.outer_attrs),
+      inner_attrs (other.inner_attrs), start_locus (other.start_locus),
+      end_locus (other.end_locus), marked_for_strip (other.marked_for_strip)
+  {
+    // guard to protect from null pointer dereference
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr ();
+
+    statements.reserve (other.statements.size ());
+    for (const auto &e : other.statements)
+      statements.push_back (e->clone_stmt ());
+  }
+
+  // Overloaded assignment operator to clone pointer
+  BlockExpr &operator= (BlockExpr const &other)
+  {
+    ExprWithBlock::operator= (other);
+    inner_attrs = other.inner_attrs;
+    start_locus = other.start_locus;
+    end_locus = other.end_locus;
+    marked_for_strip = other.marked_for_strip;
+    outer_attrs = other.outer_attrs;
+
+    // guard to protect from null pointer dereference
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr ();
+    else
+      expr = nullptr;
+
+    statements.reserve (other.statements.size ());
+    for (const auto &e : other.statements)
+      statements.push_back (e->clone_stmt ());
+
+    return *this;
+  }
+
+  // move constructors
+  BlockExpr (BlockExpr &&other) = default;
+  BlockExpr &operator= (BlockExpr &&other) = default;
+
+  // Unique pointer custom clone function
+  std::unique_ptr<BlockExpr> clone_block_expr () const
+  {
+    return std::unique_ptr<BlockExpr> (clone_block_expr_impl ());
+  }
+
+  Location get_locus () const override final { return start_locus; }
+
+  Location get_start_locus () const { return start_locus; }
+  Location get_end_locus () const { return end_locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can be completely empty, so have to have a separate flag.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  size_t num_statements () const { return statements.size (); }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  const std::vector<std::unique_ptr<Stmt> > &get_statements () const
+  {
+    return statements;
+  }
+  std::vector<std::unique_ptr<Stmt> > &get_statements () { return statements; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_tail_expr ()
+  {
+    rust_assert (has_tail_expr ());
+    return expr;
+  }
+
+  // Removes the tail expression from the block.
+  void strip_tail_expr () { expr = nullptr; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  BlockExpr *clone_expr_with_block_impl () const final override
+  {
+    return clone_block_expr_impl ();
+  }
+
+  /* This is the base method as not an abstract class - not virtual but could be
+   * in future if required. */
+  /*virtual*/ BlockExpr *clone_block_expr_impl () const
+  {
+    return new BlockExpr (*this);
+  }
+};
+
+// Represents a type-specified closure expression AST node
+class ClosureExprInnerTyped : public ClosureExpr
+{
+  // TODO: spec says typenobounds
+  std::unique_ptr<Type> return_type;
+  std::unique_ptr<BlockExpr>
+    expr; // only used because may be polymorphic in future
+
+public:
+  std::string as_string () const override;
+
+  // Constructor potentially with a move
+  ClosureExprInnerTyped (std::unique_ptr<Type> closure_return_type,
+			 std::unique_ptr<BlockExpr> closure_expr,
+			 std::vector<ClosureParam> closure_params,
+			 Location locus, bool is_move = false,
+			 std::vector<Attribute> outer_attribs
+			 = std::vector<Attribute> ())
+    : ClosureExpr (std::move (closure_params), is_move,
+		   std::move (outer_attribs), locus),
+      return_type (std::move (closure_return_type)),
+      expr (std::move (closure_expr))
+  {}
+
+  // Copy constructor requires cloning
+  ClosureExprInnerTyped (ClosureExprInnerTyped const &other)
+    : ClosureExpr (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_block_expr ();
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+  }
+
+  // Overload assignment operator to clone unique_ptrs
+  ClosureExprInnerTyped &operator= (ClosureExprInnerTyped const &other)
+  {
+    ClosureExpr::operator= (other);
+    // params = other.params;
+    // has_move = other.has_move;
+    // outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_block_expr ();
+    else
+      expr = nullptr;
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+    else
+      return_type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ClosureExprInnerTyped (ClosureExprInnerTyped &&other) = default;
+  ClosureExprInnerTyped &operator= (ClosureExprInnerTyped &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  /* Invalid if inner expr is null, so base stripping on that. Technically,
+   * type should also not be null. */
+  void mark_for_strip () override { expr = nullptr; }
+  bool is_marked_for_strip () const override { return expr == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_definition_block ()
+  {
+    rust_assert (expr != nullptr);
+    return expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_return_type ()
+  {
+    rust_assert (return_type != nullptr);
+    return return_type;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ClosureExprInnerTyped *clone_expr_without_block_impl () const override
+  {
+    return new ClosureExprInnerTyped (*this);
+  }
+};
+
+// AST node representing continue expression within loops
+class ContinueExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  Lifetime label;
+  Location locus;
+
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  // Returns true if the continue expr has a label.
+  bool has_label () const { return !label.is_error (); }
+
+  // Constructor for a ContinueExpr with a label.
+  ContinueExpr (Lifetime label, std::vector<Attribute> outer_attribs,
+		Location locus)
+    : outer_attrs (std::move (outer_attribs)), label (std::move (label)),
+      locus (locus)
+  {}
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  Lifetime &get_label () { return label; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ContinueExpr *clone_expr_without_block_impl () const override
+  {
+    return new ContinueExpr (*this);
+  }
+};
+// TODO: merge "break" and "continue"? Or even merge in "return"?
+
+// AST node representing break expression within loops
+class BreakExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  Lifetime label;
+  std::unique_ptr<Expr> break_expr;
+  Location locus;
+
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether the break expression has a label or not.
+  bool has_label () const { return !label.is_error (); }
+
+  /* Returns whether the break expression has an expression used in the break or
+   * not. */
+  bool has_break_expr () const { return break_expr != nullptr; }
+
+  // Constructor for a break expression
+  BreakExpr (Lifetime break_label, std::unique_ptr<Expr> expr_in_break,
+	     std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)), label (std::move (break_label)),
+      break_expr (std::move (expr_in_break)), locus (locus)
+  {}
+
+  // Copy constructor defined to use clone for unique pointer
+  BreakExpr (BreakExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      label (other.label), locus (other.locus),
+      marked_for_strip (other.marked_for_strip)
+  {
+    // guard to protect from null pointer dereference
+    if (other.break_expr != nullptr)
+      break_expr = other.break_expr->clone_expr ();
+  }
+
+  // Overload assignment operator to clone unique pointer
+  BreakExpr &operator= (BreakExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    label = other.label;
+    locus = other.locus;
+    marked_for_strip = other.marked_for_strip;
+    outer_attrs = other.outer_attrs;
+
+    // guard to protect from null pointer dereference
+    if (other.break_expr != nullptr)
+      break_expr = other.break_expr->clone_expr ();
+    else
+      break_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  BreakExpr (BreakExpr &&other) = default;
+  BreakExpr &operator= (BreakExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_break_expr ()
+  {
+    rust_assert (has_break_expr ());
+    return break_expr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  Lifetime &get_label () { return label; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  BreakExpr *clone_expr_without_block_impl () const override
+  {
+    return new BreakExpr (*this);
+  }
+};
+
+// Base range expression AST node object - abstract
+class RangeExpr : public ExprWithoutBlock
+{
+  Location locus;
+
+protected:
+  // outer attributes not allowed before range expressions
+  RangeExpr (Location locus) : locus (locus) {}
+
+public:
+  Location get_locus () const override final { return locus; }
+
+  // should never be called - error if called
+  void set_outer_attrs (std::vector<Attribute> /* new_attrs */) override
+  {
+    rust_assert (false);
+  }
+};
+
+// Range from (inclusive) and to (exclusive) expression AST node object
+// aka RangeExpr; constructs a std::ops::Range object
+class RangeFromToExpr : public RangeExpr
+{
+  std::unique_ptr<Expr> from;
+  std::unique_ptr<Expr> to;
+
+public:
+  std::string as_string () const override;
+
+  RangeFromToExpr (std::unique_ptr<Expr> range_from,
+		   std::unique_ptr<Expr> range_to, Location locus)
+    : RangeExpr (locus), from (std::move (range_from)),
+      to (std::move (range_to))
+  {}
+
+  // Copy constructor with cloning
+  RangeFromToExpr (RangeFromToExpr const &other) : RangeExpr (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.from != nullptr)
+      from = other.from->clone_expr ();
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+  }
+
+  // Overload assignment operator to clone unique pointers
+  RangeFromToExpr &operator= (RangeFromToExpr const &other)
+  {
+    RangeExpr::operator= (other);
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.from != nullptr)
+      from = other.from->clone_expr ();
+    else
+      from = nullptr;
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+    else
+      to = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  RangeFromToExpr (RangeFromToExpr &&other) = default;
+  RangeFromToExpr &operator= (RangeFromToExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if either expr is null, so base stripping on that.
+  void mark_for_strip () override
+  {
+    from = nullptr;
+    to = nullptr;
+  }
+  bool is_marked_for_strip () const override
+  {
+    return from == nullptr && to == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_from_expr ()
+  {
+    rust_assert (from != nullptr);
+    return from;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_to_expr ()
+  {
+    rust_assert (to != nullptr);
+    return to;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangeFromToExpr *clone_expr_without_block_impl () const override
+  {
+    return new RangeFromToExpr (*this);
+  }
+};
+
+// Range from (inclusive) expression AST node object
+// constructs a std::ops::RangeFrom object
+class RangeFromExpr : public RangeExpr
+{
+  std::unique_ptr<Expr> from;
+
+public:
+  std::string as_string () const override;
+
+  RangeFromExpr (std::unique_ptr<Expr> range_from, Location locus)
+    : RangeExpr (locus), from (std::move (range_from))
+  {}
+
+  // Copy constructor with clone
+  RangeFromExpr (RangeFromExpr const &other) : RangeExpr (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.from != nullptr)
+      from = other.from->clone_expr ();
+  }
+
+  // Overload assignment operator to clone unique_ptr
+  RangeFromExpr &operator= (RangeFromExpr const &other)
+  {
+    RangeExpr::operator= (other);
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.from != nullptr)
+      from = other.from->clone_expr ();
+    else
+      from = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  RangeFromExpr (RangeFromExpr &&other) = default;
+  RangeFromExpr &operator= (RangeFromExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if expr is null, so base stripping on that.
+  void mark_for_strip () override { from = nullptr; }
+  bool is_marked_for_strip () const override { return from == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_from_expr ()
+  {
+    rust_assert (from != nullptr);
+    return from;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangeFromExpr *clone_expr_without_block_impl () const override
+  {
+    return new RangeFromExpr (*this);
+  }
+};
+
+// Range to (exclusive) expression AST node object
+// constructs a std::ops::RangeTo object
+class RangeToExpr : public RangeExpr
+{
+  std::unique_ptr<Expr> to;
+
+public:
+  std::string as_string () const override;
+
+  // outer attributes not allowed
+  RangeToExpr (std::unique_ptr<Expr> range_to, Location locus)
+    : RangeExpr (locus), to (std::move (range_to))
+  {}
+
+  // Copy constructor with clone
+  RangeToExpr (RangeToExpr const &other) : RangeExpr (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+  }
+
+  // Overload assignment operator to clone unique_ptr
+  RangeToExpr &operator= (RangeToExpr const &other)
+  {
+    RangeExpr::operator= (other);
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+    else
+      to = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  RangeToExpr (RangeToExpr &&other) = default;
+  RangeToExpr &operator= (RangeToExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if expr is null, so base stripping on that.
+  void mark_for_strip () override { to = nullptr; }
+  bool is_marked_for_strip () const override { return to == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_to_expr ()
+  {
+    rust_assert (to != nullptr);
+    return to;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangeToExpr *clone_expr_without_block_impl () const override
+  {
+    return new RangeToExpr (*this);
+  }
+};
+
+// Full range expression AST node object
+// constructs a std::ops::RangeFull object
+class RangeFullExpr : public RangeExpr
+{
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  RangeFullExpr (Location locus) : RangeExpr (locus) {}
+  // outer attributes not allowed
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangeFullExpr *clone_expr_without_block_impl () const override
+  {
+    return new RangeFullExpr (*this);
+  }
+};
+
+// Range from (inclusive) and to (inclusive) expression AST node object
+// aka RangeInclusiveExpr; constructs a std::ops::RangeInclusive object
+class RangeFromToInclExpr : public RangeExpr
+{
+  std::unique_ptr<Expr> from;
+  std::unique_ptr<Expr> to;
+
+public:
+  std::string as_string () const override;
+
+  RangeFromToInclExpr (std::unique_ptr<Expr> range_from,
+		       std::unique_ptr<Expr> range_to, Location locus)
+    : RangeExpr (locus), from (std::move (range_from)),
+      to (std::move (range_to))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor with clone
+  RangeFromToInclExpr (RangeFromToInclExpr const &other) : RangeExpr (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.from != nullptr)
+      from = other.from->clone_expr ();
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+  }
+
+  // Overload assignment operator to use clone
+  RangeFromToInclExpr &operator= (RangeFromToInclExpr const &other)
+  {
+    RangeExpr::operator= (other);
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.from != nullptr)
+      from = other.from->clone_expr ();
+    else
+      from = nullptr;
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+    else
+      to = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  RangeFromToInclExpr (RangeFromToInclExpr &&other) = default;
+  RangeFromToInclExpr &operator= (RangeFromToInclExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if either expr is null, so base stripping on that.
+  void mark_for_strip () override
+  {
+    from = nullptr;
+    to = nullptr;
+  }
+  bool is_marked_for_strip () const override
+  {
+    return from == nullptr && to == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_from_expr ()
+  {
+    rust_assert (from != nullptr);
+    return from;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_to_expr ()
+  {
+    rust_assert (to != nullptr);
+    return to;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangeFromToInclExpr *clone_expr_without_block_impl () const override
+  {
+    return new RangeFromToInclExpr (*this);
+  }
+};
+
+// Range to (inclusive) expression AST node object
+// aka RangeToInclusiveExpr; constructs a std::ops::RangeToInclusive object
+class RangeToInclExpr : public RangeExpr
+{
+  std::unique_ptr<Expr> to;
+
+public:
+  std::string as_string () const override;
+
+  RangeToInclExpr (std::unique_ptr<Expr> range_to, Location locus)
+    : RangeExpr (locus), to (std::move (range_to))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor with clone
+  RangeToInclExpr (RangeToInclExpr const &other) : RangeExpr (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+  }
+
+  // Overload assignment operator to clone pointer
+  RangeToInclExpr &operator= (RangeToInclExpr const &other)
+  {
+    RangeExpr::operator= (other);
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.to != nullptr)
+      to = other.to->clone_expr ();
+    else
+      to = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  RangeToInclExpr (RangeToInclExpr &&other) = default;
+  RangeToInclExpr &operator= (RangeToInclExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if expr is null, so base stripping on that.
+  void mark_for_strip () override { to = nullptr; }
+  bool is_marked_for_strip () const override { return to == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_to_expr ()
+  {
+    rust_assert (to != nullptr);
+    return to;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangeToInclExpr *clone_expr_without_block_impl () const override
+  {
+    return new RangeToInclExpr (*this);
+  }
+};
+
+// Return expression AST node representation
+class ReturnExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> return_expr;
+  Location locus;
+
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  /* Returns whether the object has an expression returned (i.e. not void return
+   * type). */
+  bool has_returned_expr () const { return return_expr != nullptr; }
+
+  // Constructor for ReturnExpr.
+  ReturnExpr (std::unique_ptr<Expr> returned_expr,
+	      std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)),
+      return_expr (std::move (returned_expr)), locus (locus)
+  {}
+
+  // Copy constructor with clone
+  ReturnExpr (ReturnExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      locus (other.locus), marked_for_strip (other.marked_for_strip)
+  {
+    // guard to protect from null pointer dereference
+    if (other.return_expr != nullptr)
+      return_expr = other.return_expr->clone_expr ();
+  }
+
+  // Overloaded assignment operator to clone return_expr pointer
+  ReturnExpr &operator= (ReturnExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    locus = other.locus;
+    marked_for_strip = other.marked_for_strip;
+    outer_attrs = other.outer_attrs;
+
+    // guard to protect from null pointer dereference
+    if (other.return_expr != nullptr)
+      return_expr = other.return_expr->clone_expr ();
+    else
+      return_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ReturnExpr (ReturnExpr &&other) = default;
+  ReturnExpr &operator= (ReturnExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_returned_expr ()
+  {
+    rust_assert (return_expr != nullptr);
+    return return_expr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ReturnExpr *clone_expr_without_block_impl () const override
+  {
+    return new ReturnExpr (*this);
+  }
+};
+
+// Forward decl - defined in rust-macro.h
+class MacroInvocation;
+
+// An unsafe block AST node
+class UnsafeBlockExpr : public ExprWithBlock
+{
+  std::vector<Attribute> outer_attrs;
+  // Or just have it extend BlockExpr
+  std::unique_ptr<BlockExpr> expr;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  UnsafeBlockExpr (std::unique_ptr<BlockExpr> block_expr,
+		   std::vector<Attribute> outer_attribs, Location locus)
+    : outer_attrs (std::move (outer_attribs)), expr (std::move (block_expr)),
+      locus (locus)
+  {}
+
+  // Copy constructor with clone
+  UnsafeBlockExpr (UnsafeBlockExpr const &other)
+    : ExprWithBlock (other), outer_attrs (other.outer_attrs),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_block_expr ();
+  }
+
+  // Overloaded assignment operator to clone
+  UnsafeBlockExpr &operator= (UnsafeBlockExpr const &other)
+  {
+    ExprWithBlock::operator= (other);
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_block_expr ();
+    else
+      expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  UnsafeBlockExpr (UnsafeBlockExpr &&other) = default;
+  UnsafeBlockExpr &operator= (UnsafeBlockExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if block is null, so base stripping on that.
+  void mark_for_strip () override { expr = nullptr; }
+  bool is_marked_for_strip () const override { return expr == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_block_expr ()
+  {
+    rust_assert (expr != nullptr);
+    return expr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  UnsafeBlockExpr *clone_expr_with_block_impl () const override
+  {
+    return new UnsafeBlockExpr (*this);
+  }
+};
+
+// Loop label expression AST node used with break and continue expressions
+// TODO: inline?
+class LoopLabel /*: public Node*/
+{
+  Lifetime label; // or type LIFETIME_OR_LABEL
+  Location locus;
+
+  NodeId node_id;
+
+public:
+  std::string as_string () const;
+
+  LoopLabel (Lifetime loop_label, Location locus = Location ())
+    : label (std::move (loop_label)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Returns whether the LoopLabel is in an error state.
+  bool is_error () const { return label.is_error (); }
+
+  // Creates an error state LoopLabel.
+  static LoopLabel error () { return LoopLabel (Lifetime::error ()); }
+
+  Location get_locus () const { return locus; }
+
+  Lifetime &get_lifetime () { return label; }
+
+  NodeId get_node_id () const { return node_id; }
+};
+
+// Base loop expression AST node - aka LoopExpr
+class BaseLoopExpr : public ExprWithBlock
+{
+protected:
+  // protected to allow subclasses better use of them
+  std::vector<Attribute> outer_attrs;
+  LoopLabel loop_label;
+  std::unique_ptr<BlockExpr> loop_block;
+
+private:
+  Location locus;
+
+protected:
+  // Constructor for BaseLoopExpr
+  BaseLoopExpr (std::unique_ptr<BlockExpr> loop_block, Location locus,
+		LoopLabel loop_label = LoopLabel::error (),
+		std::vector<Attribute> outer_attribs
+		= std::vector<Attribute> ())
+    : outer_attrs (std::move (outer_attribs)),
+      loop_label (std::move (loop_label)), loop_block (std::move (loop_block)),
+      locus (locus)
+  {}
+
+  // Copy constructor for BaseLoopExpr with clone
+  BaseLoopExpr (BaseLoopExpr const &other)
+    : ExprWithBlock (other), outer_attrs (other.outer_attrs),
+      loop_label (other.loop_label), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.loop_block != nullptr)
+      loop_block = other.loop_block->clone_block_expr ();
+  }
+
+  // Overloaded assignment operator to clone
+  BaseLoopExpr &operator= (BaseLoopExpr const &other)
+  {
+    ExprWithBlock::operator= (other);
+    loop_label = other.loop_label;
+    locus = other.locus;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.loop_block != nullptr)
+      loop_block = other.loop_block->clone_block_expr ();
+    else
+      loop_block = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  BaseLoopExpr (BaseLoopExpr &&other) = default;
+  BaseLoopExpr &operator= (BaseLoopExpr &&other) = default;
+
+public:
+  bool has_loop_label () const { return !loop_label.is_error (); }
+
+  LoopLabel &get_loop_label () { return loop_label; }
+
+  Location get_locus () const override final { return locus; }
+
+  // Invalid if loop block is null, so base stripping on that.
+  void mark_for_strip () override { loop_block = nullptr; }
+  bool is_marked_for_strip () const override { return loop_block == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_loop_block ()
+  {
+    rust_assert (loop_block != nullptr);
+    return loop_block;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+};
+
+// 'Loop' expression (i.e. the infinite loop) AST node
+class LoopExpr : public BaseLoopExpr
+{
+public:
+  std::string as_string () const override;
+
+  // Constructor for LoopExpr
+  LoopExpr (std::unique_ptr<BlockExpr> loop_block, Location locus,
+	    LoopLabel loop_label = LoopLabel::error (),
+	    std::vector<Attribute> outer_attribs = std::vector<Attribute> ())
+    : BaseLoopExpr (std::move (loop_block), locus, std::move (loop_label),
+		    std::move (outer_attribs))
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  LoopExpr *clone_expr_with_block_impl () const override
+  {
+    return new LoopExpr (*this);
+  }
+};
+
+// While loop expression AST node (predicate loop)
+class WhileLoopExpr : public BaseLoopExpr
+{
+  std::unique_ptr<Expr> condition;
+
+public:
+  std::string as_string () const override;
+
+  // Constructor for while loop with loop label
+  WhileLoopExpr (std::unique_ptr<Expr> loop_condition,
+		 std::unique_ptr<BlockExpr> loop_block, Location locus,
+		 LoopLabel loop_label = LoopLabel::error (),
+		 std::vector<Attribute> outer_attribs
+		 = std::vector<Attribute> ())
+    : BaseLoopExpr (std::move (loop_block), locus, std::move (loop_label),
+		    std::move (outer_attribs)),
+      condition (std::move (loop_condition))
+  {}
+
+  // Copy constructor with clone
+  WhileLoopExpr (WhileLoopExpr const &other)
+    : BaseLoopExpr (other), condition (other.condition->clone_expr ())
+  {}
+
+  // Overloaded assignment operator to clone
+  WhileLoopExpr &operator= (WhileLoopExpr const &other)
+  {
+    BaseLoopExpr::operator= (other);
+    condition = other.condition->clone_expr ();
+    // loop_block = other.loop_block->clone_block_expr();
+    // loop_label = other.loop_label;
+    // outer_attrs = other.outer_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  WhileLoopExpr (WhileLoopExpr &&other) = default;
+  WhileLoopExpr &operator= (WhileLoopExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_predicate_expr ()
+  {
+    rust_assert (condition != nullptr);
+    return condition;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  WhileLoopExpr *clone_expr_with_block_impl () const override
+  {
+    return new WhileLoopExpr (*this);
+  }
+};
+
+// While let loop expression AST node (predicate pattern loop)
+class WhileLetLoopExpr : public BaseLoopExpr
+{
+  // MatchArmPatterns patterns;
+  std::vector<std::unique_ptr<Pattern> > match_arm_patterns; // inlined
+  std::unique_ptr<Expr> scrutinee;
+
+public:
+  std::string as_string () const override;
+
+  // Constructor with a loop label
+  WhileLetLoopExpr (std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
+		    std::unique_ptr<Expr> scrutinee,
+		    std::unique_ptr<BlockExpr> loop_block, Location locus,
+		    LoopLabel loop_label = LoopLabel::error (),
+		    std::vector<Attribute> outer_attribs
+		    = std::vector<Attribute> ())
+    : BaseLoopExpr (std::move (loop_block), locus, std::move (loop_label),
+		    std::move (outer_attribs)),
+      match_arm_patterns (std::move (match_arm_patterns)),
+      scrutinee (std::move (scrutinee))
+  {}
+
+  // Copy constructor with clone
+  WhileLetLoopExpr (WhileLetLoopExpr const &other)
+    : BaseLoopExpr (other),
+      /*match_arm_patterns(other.match_arm_patterns),*/ scrutinee (
+	other.scrutinee->clone_expr ())
+  {
+    match_arm_patterns.reserve (other.match_arm_patterns.size ());
+    for (const auto &e : other.match_arm_patterns)
+      match_arm_patterns.push_back (e->clone_pattern ());
+  }
+
+  // Overloaded assignment operator to clone pointers
+  WhileLetLoopExpr &operator= (WhileLetLoopExpr const &other)
+  {
+    BaseLoopExpr::operator= (other);
+    // match_arm_patterns = other.match_arm_patterns;
+    scrutinee = other.scrutinee->clone_expr ();
+    // loop_block = other.loop_block->clone_block_expr();
+    // loop_label = other.loop_label;
+    // outer_attrs = other.outer_attrs;
+
+    match_arm_patterns.reserve (other.match_arm_patterns.size ());
+    for (const auto &e : other.match_arm_patterns)
+      match_arm_patterns.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  WhileLetLoopExpr (WhileLetLoopExpr &&other) = default;
+  WhileLetLoopExpr &operator= (WhileLetLoopExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_scrutinee_expr ()
+  {
+    rust_assert (scrutinee != nullptr);
+    return scrutinee;
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<std::unique_ptr<Pattern> > &get_patterns () const
+  {
+    return match_arm_patterns;
+  }
+  std::vector<std::unique_ptr<Pattern> > &get_patterns ()
+  {
+    return match_arm_patterns;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  WhileLetLoopExpr *clone_expr_with_block_impl () const override
+  {
+    return new WhileLetLoopExpr (*this);
+  }
+};
+
+// For loop expression AST node (iterator loop)
+class ForLoopExpr : public BaseLoopExpr
+{
+  std::unique_ptr<Pattern> pattern;
+  std::unique_ptr<Expr> iterator_expr;
+
+public:
+  std::string as_string () const override;
+
+  // Constructor with loop label
+  ForLoopExpr (std::unique_ptr<Pattern> loop_pattern,
+	       std::unique_ptr<Expr> iterator_expr,
+	       std::unique_ptr<BlockExpr> loop_body, Location locus,
+	       LoopLabel loop_label = LoopLabel::error (),
+	       std::vector<Attribute> outer_attribs = std::vector<Attribute> ())
+    : BaseLoopExpr (std::move (loop_body), locus, std::move (loop_label),
+		    std::move (outer_attribs)),
+      pattern (std::move (loop_pattern)),
+      iterator_expr (std::move (iterator_expr))
+  {}
+
+  // Copy constructor with clone
+  ForLoopExpr (ForLoopExpr const &other)
+    : BaseLoopExpr (other), pattern (other.pattern->clone_pattern ()),
+      iterator_expr (other.iterator_expr->clone_expr ())
+  {}
+
+  // Overloaded assignment operator to clone
+  ForLoopExpr &operator= (ForLoopExpr const &other)
+  {
+    BaseLoopExpr::operator= (other);
+    pattern = other.pattern->clone_pattern ();
+    iterator_expr = other.iterator_expr->clone_expr ();
+    /*loop_block = other.loop_block->clone_block_expr();
+    loop_label = other.loop_label;
+    outer_attrs = other.outer_attrs;*/
+
+    return *this;
+  }
+
+  // move constructors
+  ForLoopExpr (ForLoopExpr &&other) = default;
+  ForLoopExpr &operator= (ForLoopExpr &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_iterator_expr ()
+  {
+    rust_assert (iterator_expr != nullptr);
+    return iterator_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Pattern> &get_pattern ()
+  {
+    rust_assert (pattern != nullptr);
+    return pattern;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ForLoopExpr *clone_expr_with_block_impl () const override
+  {
+    return new ForLoopExpr (*this);
+  }
+};
+
+// forward decl for IfExpr
+class IfLetExpr;
+
+// Base if expression with no "else" or "if let" AST node
+class IfExpr : public ExprWithBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> condition;
+  std::unique_ptr<BlockExpr> if_block;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  IfExpr (std::unique_ptr<Expr> condition, std::unique_ptr<BlockExpr> if_block,
+	  std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)), condition (std::move (condition)),
+      if_block (std::move (if_block)), locus (locus)
+  {}
+  // outer attributes are never allowed on IfExprs
+
+  // Copy constructor with clone
+  IfExpr (IfExpr const &other)
+    : ExprWithBlock (other), outer_attrs (other.outer_attrs),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.condition != nullptr)
+      condition = other.condition->clone_expr ();
+    if (other.if_block != nullptr)
+      if_block = other.if_block->clone_block_expr ();
+  }
+
+  // Overloaded assignment operator to clone expressions
+  IfExpr &operator= (IfExpr const &other)
+  {
+    ExprWithBlock::operator= (other);
+    outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.condition != nullptr)
+      condition = other.condition->clone_expr ();
+    else
+      condition = nullptr;
+    if (other.if_block != nullptr)
+      if_block = other.if_block->clone_block_expr ();
+    else
+      if_block = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  IfExpr (IfExpr &&other) = default;
+  IfExpr &operator= (IfExpr &&other) = default;
+
+  // Unique pointer custom clone function
+  std::unique_ptr<IfExpr> clone_if_expr () const
+  {
+    return std::unique_ptr<IfExpr> (clone_if_expr_impl ());
+  }
+
+  /* Note that multiple "else if"s are handled via nested ASTs rather than a
+   * vector of else ifs - i.e. not like a switch statement. TODO - is this a
+   * better approach? or does it not parse correctly and have downsides? */
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  void vis_if_condition (ASTVisitor &vis) { condition->accept_vis (vis); }
+  void vis_if_block (ASTVisitor &vis) { if_block->accept_vis (vis); }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_condition_expr ()
+  {
+    rust_assert (condition != nullptr);
+    return condition;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_if_block ()
+  {
+    rust_assert (if_block != nullptr);
+    return if_block;
+  }
+
+  // Invalid if if block or condition is null, so base stripping on that.
+  void mark_for_strip () override
+  {
+    if_block = nullptr;
+    condition = nullptr;
+  }
+  bool is_marked_for_strip () const override
+  {
+    return if_block == nullptr && condition == nullptr;
+  }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+protected:
+  // Base clone function but still concrete as concrete base class
+  virtual IfExpr *clone_if_expr_impl () const { return new IfExpr (*this); }
+
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IfExpr *clone_expr_with_block_impl () const final override
+  {
+    return clone_if_expr_impl ();
+  }
+};
+
+// If expression with an ending "else" expression AST node (trailing)
+class IfExprConseqElse : public IfExpr
+{
+  std::unique_ptr<BlockExpr> else_block;
+
+public:
+  std::string as_string () const override;
+
+  IfExprConseqElse (std::unique_ptr<Expr> condition,
+		    std::unique_ptr<BlockExpr> if_block,
+		    std::unique_ptr<BlockExpr> else_block,
+		    std::vector<Attribute> outer_attrs, Location locus)
+    : IfExpr (std::move (condition), std::move (if_block),
+	      std::move (outer_attrs), locus),
+      else_block (std::move (else_block))
+  {}
+  // again, outer attributes not allowed
+
+  // Copy constructor with clone
+  IfExprConseqElse (IfExprConseqElse const &other)
+    : IfExpr (other), else_block (other.else_block->clone_block_expr ())
+  {}
+
+  // Overloaded assignment operator with cloning
+  IfExprConseqElse &operator= (IfExprConseqElse const &other)
+  {
+    IfExpr::operator= (other);
+    // condition = other.condition->clone_expr();
+    // if_block = other.if_block->clone_block_expr();
+    else_block = other.else_block->clone_block_expr ();
+
+    return *this;
+  }
+
+  // move constructors
+  IfExprConseqElse (IfExprConseqElse &&other) = default;
+  IfExprConseqElse &operator= (IfExprConseqElse &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  void vis_else_block (ASTVisitor &vis) { else_block->accept_vis (vis); }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_else_block ()
+  {
+    rust_assert (else_block != nullptr);
+    return else_block;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IfExprConseqElse *clone_if_expr_impl () const override
+  {
+    return new IfExprConseqElse (*this);
+  }
+};
+
+// If expression with an ending "else if" expression AST node
+class IfExprConseqIf : public IfExpr
+{
+  std::unique_ptr<IfExpr> conseq_if_expr;
+
+public:
+  std::string as_string () const override;
+
+  IfExprConseqIf (std::unique_ptr<Expr> condition,
+		  std::unique_ptr<BlockExpr> if_block,
+		  std::unique_ptr<IfExpr> conseq_if_expr,
+		  std::vector<Attribute> outer_attrs, Location locus)
+    : IfExpr (std::move (condition), std::move (if_block),
+	      std::move (outer_attrs), locus),
+      conseq_if_expr (std::move (conseq_if_expr))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor with clone
+  IfExprConseqIf (IfExprConseqIf const &other)
+    : IfExpr (other), conseq_if_expr (other.conseq_if_expr->clone_if_expr ())
+  {}
+
+  // Overloaded assignment operator to use clone
+  IfExprConseqIf &operator= (IfExprConseqIf const &other)
+  {
+    IfExpr::operator= (other);
+    // condition = other.condition->clone_expr();
+    // if_block = other.if_block->clone_block_expr();
+    conseq_if_expr = other.conseq_if_expr->clone_if_expr ();
+
+    return *this;
+  }
+
+  // move constructors
+  IfExprConseqIf (IfExprConseqIf &&other) = default;
+  IfExprConseqIf &operator= (IfExprConseqIf &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  void vis_conseq_if_expr (ASTVisitor &vis)
+  {
+    conseq_if_expr->accept_vis (vis);
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<IfExpr> &get_conseq_if_expr ()
+  {
+    rust_assert (conseq_if_expr != nullptr);
+    return conseq_if_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IfExprConseqIf *clone_if_expr_impl () const override
+  {
+    return new IfExprConseqIf (*this);
+  }
+};
+
+// Basic "if let" expression AST node with no else
+class IfLetExpr : public ExprWithBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::vector<std::unique_ptr<Pattern> > match_arm_patterns; // inlined
+  std::unique_ptr<Expr> value;
+  std::unique_ptr<BlockExpr> if_block;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  IfLetExpr (std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
+	     std::unique_ptr<Expr> value, std::unique_ptr<BlockExpr> if_block,
+	     std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)),
+      match_arm_patterns (std::move (match_arm_patterns)),
+      value (std::move (value)), if_block (std::move (if_block)), locus (locus)
+  {}
+
+  // copy constructor with clone
+  IfLetExpr (IfLetExpr const &other)
+    : ExprWithBlock (other), outer_attrs (other.outer_attrs),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.value != nullptr)
+      value = other.value->clone_expr ();
+    if (other.if_block != nullptr)
+      if_block = other.if_block->clone_block_expr ();
+
+    match_arm_patterns.reserve (other.match_arm_patterns.size ());
+    for (const auto &e : other.match_arm_patterns)
+      match_arm_patterns.push_back (e->clone_pattern ());
+  }
+
+  // overload assignment operator to clone
+  IfLetExpr &operator= (IfLetExpr const &other)
+  {
+    ExprWithBlock::operator= (other);
+    outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.value != nullptr)
+      value = other.value->clone_expr ();
+    else
+      value = nullptr;
+    if (other.if_block != nullptr)
+      if_block = other.if_block->clone_block_expr ();
+    else
+      if_block = nullptr;
+
+    match_arm_patterns.reserve (other.match_arm_patterns.size ());
+    for (const auto &e : other.match_arm_patterns)
+      match_arm_patterns.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  IfLetExpr (IfLetExpr &&other) = default;
+  IfLetExpr &operator= (IfLetExpr &&other) = default;
+
+  // Unique pointer custom clone function
+  std::unique_ptr<IfLetExpr> clone_if_let_expr () const
+  {
+    return std::unique_ptr<IfLetExpr> (clone_if_let_expr_impl ());
+  }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if block or value is null, so base stripping on that.
+  void mark_for_strip () override
+  {
+    if_block = nullptr;
+    value = nullptr;
+  }
+  bool is_marked_for_strip () const override
+  {
+    return if_block == nullptr && value == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_value_expr ()
+  {
+    rust_assert (value != nullptr);
+    return value;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_if_block ()
+  {
+    rust_assert (if_block != nullptr);
+    return if_block;
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<std::unique_ptr<Pattern> > &get_patterns () const
+  {
+    return match_arm_patterns;
+  }
+  std::vector<std::unique_ptr<Pattern> > &get_patterns ()
+  {
+    return match_arm_patterns;
+  }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base (or rather this or any derived object) */
+  IfLetExpr *clone_expr_with_block_impl () const final override
+  {
+    return clone_if_let_expr_impl ();
+  }
+
+  // Base clone function but still concrete as concrete base class
+  virtual IfLetExpr *clone_if_let_expr_impl () const
+  {
+    return new IfLetExpr (*this);
+  }
+};
+
+// If expression with an ending "else if let" expression AST node
+class IfExprConseqIfLet : public IfExpr
+{
+  std::unique_ptr<IfLetExpr> if_let_expr;
+
+public:
+  std::string as_string () const override;
+
+  IfExprConseqIfLet (std::unique_ptr<Expr> condition,
+		     std::unique_ptr<BlockExpr> if_block,
+		     std::unique_ptr<IfLetExpr> conseq_if_let_expr,
+		     std::vector<Attribute> outer_attrs, Location locus)
+    : IfExpr (std::move (condition), std::move (if_block),
+	      std::move (outer_attrs), locus),
+      if_let_expr (std::move (conseq_if_let_expr))
+  {}
+  // outer attributes not allowed
+
+  // Copy constructor with clone
+  IfExprConseqIfLet (IfExprConseqIfLet const &other)
+    : IfExpr (other), if_let_expr (other.if_let_expr->clone_if_let_expr ())
+  {}
+
+  // Overloaded assignment operator to use clone
+  IfExprConseqIfLet &operator= (IfExprConseqIfLet const &other)
+  {
+    IfExpr::operator= (other);
+    // condition = other.condition->clone_expr();
+    // if_block = other.if_block->clone_block_expr();
+    if_let_expr = other.if_let_expr->clone_if_let_expr ();
+
+    return *this;
+  }
+
+  // move constructors
+  IfExprConseqIfLet (IfExprConseqIfLet &&other) = default;
+  IfExprConseqIfLet &operator= (IfExprConseqIfLet &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<IfLetExpr> &get_conseq_if_let_expr ()
+  {
+    rust_assert (if_let_expr != nullptr);
+    return if_let_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IfExprConseqIfLet *clone_if_expr_impl () const override
+  {
+    return new IfExprConseqIfLet (*this);
+  }
+};
+
+/* AST node representing "if let" expression with an "else" expression at the
+ * end */
+class IfLetExprConseqElse : public IfLetExpr
+{
+  std::unique_ptr<BlockExpr> else_block;
+
+public:
+  std::string as_string () const override;
+
+  IfLetExprConseqElse (
+    std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
+    std::unique_ptr<Expr> value, std::unique_ptr<BlockExpr> if_block,
+    std::unique_ptr<BlockExpr> else_block, std::vector<Attribute> outer_attrs,
+    Location locus)
+    : IfLetExpr (std::move (match_arm_patterns), std::move (value),
+		 std::move (if_block), std::move (outer_attrs), locus),
+      else_block (std::move (else_block))
+  {}
+  // outer attributes not allowed
+
+  // copy constructor with clone
+  IfLetExprConseqElse (IfLetExprConseqElse const &other)
+    : IfLetExpr (other), else_block (other.else_block->clone_block_expr ())
+  {}
+
+  // overload assignment operator to clone
+  IfLetExprConseqElse &operator= (IfLetExprConseqElse const &other)
+  {
+    IfLetExpr::operator= (other);
+    // match_arm_patterns = other.match_arm_patterns;
+    // value = other.value->clone_expr();
+    // if_block = other.if_block->clone_block_expr();
+    else_block = other.else_block->clone_block_expr ();
+    // outer_attrs = other.outer_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  IfLetExprConseqElse (IfLetExprConseqElse &&other) = default;
+  IfLetExprConseqElse &operator= (IfLetExprConseqElse &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_else_block ()
+  {
+    rust_assert (else_block != nullptr);
+    return else_block;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IfLetExprConseqElse *clone_if_let_expr_impl () const override
+  {
+    return new IfLetExprConseqElse (*this);
+  }
+};
+
+/* AST node representing "if let" expression with an "else if" expression at the
+ * end */
+class IfLetExprConseqIf : public IfLetExpr
+{
+  std::unique_ptr<IfExpr> if_expr;
+
+public:
+  std::string as_string () const override;
+
+  IfLetExprConseqIf (std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
+		     std::unique_ptr<Expr> value,
+		     std::unique_ptr<BlockExpr> if_block,
+		     std::unique_ptr<IfExpr> if_expr,
+		     std::vector<Attribute> outer_attrs, Location locus)
+    : IfLetExpr (std::move (match_arm_patterns), std::move (value),
+		 std::move (if_block), std::move (outer_attrs), locus),
+      if_expr (std::move (if_expr))
+  {}
+  // again, outer attributes not allowed
+
+  // copy constructor with clone
+  IfLetExprConseqIf (IfLetExprConseqIf const &other)
+    : IfLetExpr (other), if_expr (other.if_expr->clone_if_expr ())
+  {}
+
+  // overload assignment operator to clone
+  IfLetExprConseqIf &operator= (IfLetExprConseqIf const &other)
+  {
+    IfLetExpr::operator= (other);
+    // match_arm_patterns = other.match_arm_patterns;
+    // value = other.value->clone_expr();
+    // if_block = other.if_block->clone_block_expr();
+    if_expr = other.if_expr->clone_if_expr ();
+
+    return *this;
+  }
+
+  // move constructors
+  IfLetExprConseqIf (IfLetExprConseqIf &&other) = default;
+  IfLetExprConseqIf &operator= (IfLetExprConseqIf &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<IfExpr> &get_conseq_if_expr ()
+  {
+    rust_assert (if_expr != nullptr);
+    return if_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IfLetExprConseqIf *clone_if_let_expr_impl () const override
+  {
+    return new IfLetExprConseqIf (*this);
+  }
+};
+
+/* AST node representing "if let" expression with an "else if let" expression at
+ * the end */
+class IfLetExprConseqIfLet : public IfLetExpr
+{
+  std::unique_ptr<IfLetExpr> if_let_expr;
+
+public:
+  std::string as_string () const override;
+
+  IfLetExprConseqIfLet (
+    std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
+    std::unique_ptr<Expr> value, std::unique_ptr<BlockExpr> if_block,
+    std::unique_ptr<IfLetExpr> if_let_expr, std::vector<Attribute> outer_attrs,
+    Location locus)
+    : IfLetExpr (std::move (match_arm_patterns), std::move (value),
+		 std::move (if_block), std::move (outer_attrs), locus),
+      if_let_expr (std::move (if_let_expr))
+  {}
+  // outer attributes not allowed
+
+  // copy constructor with clone
+  IfLetExprConseqIfLet (IfLetExprConseqIfLet const &other)
+    : IfLetExpr (other), if_let_expr (other.if_let_expr->clone_if_let_expr ())
+  {}
+
+  // overload assignment operator to clone
+  IfLetExprConseqIfLet &operator= (IfLetExprConseqIfLet const &other)
+  {
+    IfLetExpr::operator= (other);
+    // match_arm_patterns = other.match_arm_patterns;
+    // value = other.value->clone_expr();
+    // if_block = other.if_block->clone_block_expr();
+    if_let_expr = other.if_let_expr->clone_if_let_expr ();
+
+    return *this;
+  }
+
+  // move constructors
+  IfLetExprConseqIfLet (IfLetExprConseqIfLet &&other) = default;
+  IfLetExprConseqIfLet &operator= (IfLetExprConseqIfLet &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<IfLetExpr> &get_conseq_if_let_expr ()
+  {
+    rust_assert (if_let_expr != nullptr);
+    return if_let_expr;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IfLetExprConseqIfLet *clone_if_let_expr_impl () const override
+  {
+    return new IfLetExprConseqIfLet (*this);
+  }
+};
+
+// Match arm expression
+struct MatchArm
+{
+private:
+  std::vector<Attribute> outer_attrs;
+  // MatchArmPatterns patterns;
+  std::vector<std::unique_ptr<Pattern> > match_arm_patterns; // inlined
+
+  // bool has_match_arm_guard;
+  // inlined from MatchArmGuard
+  std::unique_ptr<Expr> guard_expr;
+
+  Location locus;
+
+public:
+  // Returns whether the MatchArm has a match arm guard expression
+  bool has_match_arm_guard () const { return guard_expr != nullptr; }
+
+  // Constructor for match arm with a guard expression
+  MatchArm (std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
+	    Location locus, std::unique_ptr<Expr> guard_expr = nullptr,
+	    std::vector<Attribute> outer_attrs = std::vector<Attribute> ())
+    : outer_attrs (std::move (outer_attrs)),
+      match_arm_patterns (std::move (match_arm_patterns)),
+      guard_expr (std::move (guard_expr)), locus (locus)
+  {}
+
+  // Copy constructor with clone
+  MatchArm (MatchArm const &other) : outer_attrs (other.outer_attrs)
+  {
+    // guard to protect from null pointer dereference
+    if (other.guard_expr != nullptr)
+      guard_expr = other.guard_expr->clone_expr ();
+
+    match_arm_patterns.reserve (other.match_arm_patterns.size ());
+    for (const auto &e : other.match_arm_patterns)
+      match_arm_patterns.push_back (e->clone_pattern ());
+
+    locus = other.locus;
+  }
+
+  ~MatchArm () = default;
+
+  // Overload assignment operator to clone
+  MatchArm &operator= (MatchArm const &other)
+  {
+    outer_attrs = other.outer_attrs;
+
+    if (other.guard_expr != nullptr)
+      guard_expr = other.guard_expr->clone_expr ();
+    else
+      guard_expr = nullptr;
+
+    match_arm_patterns.reserve (other.match_arm_patterns.size ());
+    for (const auto &e : other.match_arm_patterns)
+      match_arm_patterns.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  MatchArm (MatchArm &&other) = default;
+  MatchArm &operator= (MatchArm &&other) = default;
+
+  // Returns whether match arm is in an error state.
+  bool is_error () const { return match_arm_patterns.empty (); }
+
+  // Creates a match arm in an error state.
+  static MatchArm create_error ()
+  {
+    Location locus = Location ();
+    return MatchArm (std::vector<std::unique_ptr<Pattern> > (), locus);
+  }
+
+  std::string as_string () const;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_guard_expr ()
+  {
+    rust_assert (has_match_arm_guard ());
+    return guard_expr;
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  const std::vector<std::unique_ptr<Pattern> > &get_patterns () const
+  {
+    return match_arm_patterns;
+  }
+  std::vector<std::unique_ptr<Pattern> > &get_patterns ()
+  {
+    return match_arm_patterns;
+  }
+
+  Location get_locus () const { return locus; }
+};
+
+/* A "match case" - a correlated match arm and resulting expression. Not
+ * abstract. */
+struct MatchCase
+{
+private:
+  MatchArm arm;
+  std::unique_ptr<Expr> expr;
+  NodeId node_id;
+
+  /* TODO: does whether trailing comma exists need to be stored? currently
+   * assuming it is only syntactical and has no effect on meaning. */
+
+public:
+  MatchCase (MatchArm arm, std::unique_ptr<Expr> expr)
+    : arm (std::move (arm)), expr (std::move (expr)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  MatchCase (const MatchCase &other)
+    : arm (other.arm), expr (other.expr->clone_expr ()), node_id (other.node_id)
+  {}
+
+  MatchCase &operator= (const MatchCase &other)
+  {
+    arm = other.arm;
+    expr = other.expr->clone_expr ();
+    node_id = other.node_id;
+
+    return *this;
+  }
+
+  MatchCase (MatchCase &&other) = default;
+  MatchCase &operator= (MatchCase &&other) = default;
+
+  ~MatchCase () = default;
+
+  std::string as_string () const;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_expr ()
+  {
+    rust_assert (expr != nullptr);
+    return expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  MatchArm &get_arm ()
+  {
+    rust_assert (!arm.is_error ());
+    return arm;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+};
+
+// Match expression AST node
+class MatchExpr : public ExprWithBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> branch_value;
+  std::vector<Attribute> inner_attrs;
+  std::vector<MatchCase> match_arms;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether the match expression has any match arms.
+  bool has_match_arms () const { return !match_arms.empty (); }
+
+  MatchExpr (std::unique_ptr<Expr> branch_value,
+	     std::vector<MatchCase> match_arms,
+	     std::vector<Attribute> inner_attrs,
+	     std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)),
+      branch_value (std::move (branch_value)),
+      inner_attrs (std::move (inner_attrs)),
+      match_arms (std::move (match_arms)), locus (locus)
+  {}
+
+  // Copy constructor requires clone due to unique_ptr
+  MatchExpr (MatchExpr const &other)
+    : ExprWithBlock (other), outer_attrs (other.outer_attrs),
+      inner_attrs (other.inner_attrs), match_arms (other.match_arms),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.branch_value != nullptr)
+      branch_value = other.branch_value->clone_expr ();
+  }
+
+  // Overloaded assignment operator to clone due to unique_ptr
+  MatchExpr &operator= (MatchExpr const &other)
+  {
+    ExprWithBlock::operator= (other);
+    inner_attrs = other.inner_attrs;
+    match_arms = other.match_arms;
+    outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.branch_value != nullptr)
+      branch_value = other.branch_value->clone_expr ();
+    else
+      branch_value = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  MatchExpr (MatchExpr &&other) = default;
+  MatchExpr &operator= (MatchExpr &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if branch value is null, so base stripping on that.
+  void mark_for_strip () override { branch_value = nullptr; }
+  bool is_marked_for_strip () const override { return branch_value == nullptr; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_scrutinee_expr ()
+  {
+    rust_assert (branch_value != nullptr);
+    return branch_value;
+  }
+
+  const std::vector<MatchCase> &get_match_cases () const { return match_arms; }
+  std::vector<MatchCase> &get_match_cases () { return match_arms; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MatchExpr *clone_expr_with_block_impl () const override
+  {
+    return new MatchExpr (*this);
+  }
+};
+
+// Await expression AST node (pseudo-member variable access)
+class AwaitExpr : public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  std::unique_ptr<Expr> awaited_expr;
+  Location locus;
+
+public:
+  // TODO: ensure outer attributes are actually allowed
+  AwaitExpr (std::unique_ptr<Expr> awaited_expr,
+	     std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)),
+      awaited_expr (std::move (awaited_expr)), locus (locus)
+  {}
+
+  // copy constructor with clone
+  AwaitExpr (AwaitExpr const &other)
+    : ExprWithoutBlock (other), outer_attrs (other.outer_attrs),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.awaited_expr != nullptr)
+      awaited_expr = other.awaited_expr->clone_expr ();
+  }
+
+  // overloaded assignment operator with clone
+  AwaitExpr &operator= (AwaitExpr const &other)
+  {
+    ExprWithoutBlock::operator= (other);
+    outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.awaited_expr != nullptr)
+      awaited_expr = other.awaited_expr->clone_expr ();
+    else
+      awaited_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  AwaitExpr (AwaitExpr &&other) = default;
+  AwaitExpr &operator= (AwaitExpr &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if awaited expr is null, so base stripping on that.
+  void mark_for_strip () override { awaited_expr = nullptr; }
+  bool is_marked_for_strip () const override { return awaited_expr == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_awaited_expr ()
+  {
+    rust_assert (awaited_expr != nullptr);
+    return awaited_expr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  AwaitExpr *clone_expr_without_block_impl () const override
+  {
+    return new AwaitExpr (*this);
+  }
+};
+
+// Async block expression AST node (block expr that evaluates to a future)
+class AsyncBlockExpr : public ExprWithBlock
+{
+  // TODO: should this extend BlockExpr rather than be a composite of it?
+  std::vector<Attribute> outer_attrs;
+  bool has_move;
+  std::unique_ptr<BlockExpr> block_expr;
+  Location locus;
+
+public:
+  AsyncBlockExpr (std::unique_ptr<BlockExpr> block_expr, bool has_move,
+		  std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)), has_move (has_move),
+      block_expr (std::move (block_expr)), locus (locus)
+  {}
+
+  // copy constructor with clone
+  AsyncBlockExpr (AsyncBlockExpr const &other)
+    : ExprWithBlock (other), outer_attrs (other.outer_attrs),
+      has_move (other.has_move), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.block_expr != nullptr)
+      block_expr = other.block_expr->clone_block_expr ();
+  }
+
+  // overloaded assignment operator to clone
+  AsyncBlockExpr &operator= (AsyncBlockExpr const &other)
+  {
+    ExprWithBlock::operator= (other);
+    outer_attrs = other.outer_attrs;
+    has_move = other.has_move;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.block_expr != nullptr)
+      block_expr = other.block_expr->clone_block_expr ();
+    else
+      block_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  AsyncBlockExpr (AsyncBlockExpr &&other) = default;
+  AsyncBlockExpr &operator= (AsyncBlockExpr &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if block is null, so base stripping on that.
+  void mark_for_strip () override { block_expr = nullptr; }
+  bool is_marked_for_strip () const override { return block_expr == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_block_expr ()
+  {
+    rust_assert (block_expr != nullptr);
+    return block_expr;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  AsyncBlockExpr *clone_expr_with_block_impl () const override
+  {
+    return new AsyncBlockExpr (*this);
+  }
+};
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-item.h b/gcc/rust/ast/rust-item.h
new file mode 100644
index 00000000000..4987674cba1
--- /dev/null
+++ b/gcc/rust/ast/rust-item.h
@@ -0,0 +1,4382 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_ITEM_H
+#define RUST_AST_ITEM_H
+
+#include "rust-ast.h"
+#include "rust-path.h"
+#include "rust-common.h"
+
+namespace Rust {
+namespace AST {
+// forward decls
+class BlockExpr;
+class TypePath;
+
+// TODO: inline?
+/*struct AbiName {
+    std::string abi_name;
+    // Technically is meant to be STRING_LITERAL
+
+  public:
+    // Returns whether abi name is empty, i.e. doesn't exist.
+    bool is_empty() const {
+	return abi_name.empty();
+    }
+
+    AbiName(std::string name) : abi_name(std::move(name)) {}
+
+    // Empty AbiName constructor
+    AbiName() {}
+};*/
+
+// A type generic parameter (as opposed to a lifetime generic parameter)
+class TypeParam : public GenericParam
+{
+  // bool has_outer_attribute;
+  // std::unique_ptr<Attribute> outer_attr;
+  Attribute outer_attr;
+
+  Identifier type_representation;
+
+  // bool has_type_param_bounds;
+  // TypeParamBounds type_param_bounds;
+  std::vector<std::unique_ptr<TypeParamBound>>
+    type_param_bounds; // inlined form
+
+  // bool has_type;
+  std::unique_ptr<Type> type;
+
+  Location locus;
+
+public:
+  Identifier get_type_representation () const { return type_representation; }
+
+  // Returns whether the type of the type param has been specified.
+  bool has_type () const { return type != nullptr; }
+
+  // Returns whether the type param has type param bounds.
+  bool has_type_param_bounds () const { return !type_param_bounds.empty (); }
+
+  // Returns whether the type param has an outer attribute.
+  bool has_outer_attribute () const { return !outer_attr.is_empty (); }
+
+  TypeParam (Identifier type_representation, Location locus = Location (),
+	     std::vector<std::unique_ptr<TypeParamBound>> type_param_bounds
+	     = std::vector<std::unique_ptr<TypeParamBound>> (),
+	     std::unique_ptr<Type> type = nullptr,
+	     Attribute outer_attr = Attribute::create_empty ())
+    : GenericParam (Analysis::Mappings::get ()->get_next_node_id ()),
+      outer_attr (std::move (outer_attr)),
+      type_representation (std::move (type_representation)),
+      type_param_bounds (std::move (type_param_bounds)),
+      type (std::move (type)), locus (locus)
+  {}
+
+  // Copy constructor uses clone
+  TypeParam (TypeParam const &other)
+    : GenericParam (other.node_id), outer_attr (other.outer_attr),
+      type_representation (other.type_representation), locus (other.locus)
+  {
+    // guard to prevent null pointer dereference
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+  }
+
+  // Overloaded assignment operator to clone
+  TypeParam &operator= (TypeParam const &other)
+  {
+    type_representation = other.type_representation;
+    outer_attr = other.outer_attr;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    // guard to prevent null pointer dereference
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+
+    return *this;
+  }
+
+  // move constructors
+  TypeParam (TypeParam &&other) = default;
+  TypeParam &operator= (TypeParam &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  Kind get_kind () const override final { return Kind::Type; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+
+  // TODO: mutable getter seems kinda dodgy
+  std::vector<std::unique_ptr<TypeParamBound>> &get_type_param_bounds ()
+  {
+    return type_param_bounds;
+  }
+  const std::vector<std::unique_ptr<TypeParamBound>> &
+  get_type_param_bounds () const
+  {
+    return type_param_bounds;
+  }
+
+protected:
+  // Clone function implementation as virtual method
+  TypeParam *clone_generic_param_impl () const override
+  {
+    return new TypeParam (*this);
+  }
+};
+
+/* "where" clause item base. Abstract - use LifetimeWhereClauseItem,
+ * TypeBoundWhereClauseItem */
+class WhereClauseItem
+{
+public:
+  virtual ~WhereClauseItem () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<WhereClauseItem> clone_where_clause_item () const
+  {
+    return std::unique_ptr<WhereClauseItem> (clone_where_clause_item_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual NodeId get_node_id () const = 0;
+
+protected:
+  // Clone function implementation as pure virtual method
+  virtual WhereClauseItem *clone_where_clause_item_impl () const = 0;
+};
+
+// A lifetime where clause item
+class LifetimeWhereClauseItem : public WhereClauseItem
+{
+  Lifetime lifetime;
+  std::vector<Lifetime> lifetime_bounds;
+  Location locus;
+  NodeId node_id;
+
+public:
+  LifetimeWhereClauseItem (Lifetime lifetime,
+			   std::vector<Lifetime> lifetime_bounds,
+			   Location locus)
+    : lifetime (std::move (lifetime)),
+      lifetime_bounds (std::move (lifetime_bounds)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  NodeId get_node_id () const override final { return node_id; }
+
+  Lifetime &get_lifetime () { return lifetime; }
+
+  std::vector<Lifetime> &get_lifetime_bounds () { return lifetime_bounds; }
+
+  Location get_locus () const { return locus; }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  LifetimeWhereClauseItem *clone_where_clause_item_impl () const override
+  {
+    return new LifetimeWhereClauseItem (*this);
+  }
+};
+
+// A type bound where clause item
+class TypeBoundWhereClauseItem : public WhereClauseItem
+{
+  std::vector<LifetimeParam> for_lifetimes;
+  std::unique_ptr<Type> bound_type;
+  std::vector<std::unique_ptr<TypeParamBound>> type_param_bounds;
+  NodeId node_id;
+  Location locus;
+
+public:
+  // Returns whether the item has ForLifetimes
+  bool has_for_lifetimes () const { return !for_lifetimes.empty (); }
+
+  // Returns whether the item has type param bounds
+  bool has_type_param_bounds () const { return !type_param_bounds.empty (); }
+
+  TypeBoundWhereClauseItem (
+    std::vector<LifetimeParam> for_lifetimes, std::unique_ptr<Type> bound_type,
+    std::vector<std::unique_ptr<TypeParamBound>> type_param_bounds,
+    Location locus)
+    : for_lifetimes (std::move (for_lifetimes)),
+      bound_type (std::move (bound_type)),
+      type_param_bounds (std::move (type_param_bounds)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()), locus (locus)
+  {}
+
+  // Copy constructor requires clone
+  TypeBoundWhereClauseItem (TypeBoundWhereClauseItem const &other)
+    : for_lifetimes (other.for_lifetimes),
+      bound_type (other.bound_type->clone_type ())
+  {
+    node_id = other.node_id;
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+  }
+
+  // Overload assignment operator to clone
+  TypeBoundWhereClauseItem &operator= (TypeBoundWhereClauseItem const &other)
+  {
+    node_id = other.node_id;
+    for_lifetimes = other.for_lifetimes;
+    bound_type = other.bound_type->clone_type ();
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+
+    return *this;
+  }
+
+  // move constructors
+  TypeBoundWhereClauseItem (TypeBoundWhereClauseItem &&other) = default;
+  TypeBoundWhereClauseItem &operator= (TypeBoundWhereClauseItem &&other)
+    = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (bound_type != nullptr);
+    return bound_type;
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<std::unique_ptr<TypeParamBound>> &get_type_param_bounds ()
+  {
+    return type_param_bounds;
+  }
+
+  const std::vector<std::unique_ptr<TypeParamBound>> &
+  get_type_param_bounds () const
+  {
+    return type_param_bounds;
+  }
+
+  NodeId get_node_id () const override final { return node_id; }
+
+  Location get_locus () const { return locus; }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  TypeBoundWhereClauseItem *clone_where_clause_item_impl () const override
+  {
+    return new TypeBoundWhereClauseItem (*this);
+  }
+};
+
+// A where clause
+struct WhereClause
+{
+private:
+  std::vector<std::unique_ptr<WhereClauseItem>> where_clause_items;
+  NodeId node_id;
+
+public:
+  WhereClause (std::vector<std::unique_ptr<WhereClauseItem>> where_clause_items)
+    : where_clause_items (std::move (where_clause_items)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // copy constructor with vector clone
+  WhereClause (WhereClause const &other)
+  {
+    node_id = other.node_id;
+    where_clause_items.reserve (other.where_clause_items.size ());
+    for (const auto &e : other.where_clause_items)
+      where_clause_items.push_back (e->clone_where_clause_item ());
+  }
+
+  // overloaded assignment operator with vector clone
+  WhereClause &operator= (WhereClause const &other)
+  {
+    node_id = other.node_id;
+    where_clause_items.reserve (other.where_clause_items.size ());
+    for (const auto &e : other.where_clause_items)
+      where_clause_items.push_back (e->clone_where_clause_item ());
+
+    return *this;
+  }
+
+  // move constructors
+  WhereClause (WhereClause &&other) = default;
+  WhereClause &operator= (WhereClause &&other) = default;
+
+  // Creates a WhereClause with no items.
+  static WhereClause create_empty ()
+  {
+    return WhereClause (std::vector<std::unique_ptr<WhereClauseItem>> ());
+  }
+
+  // Returns whether the WhereClause has no items.
+  bool is_empty () const { return where_clause_items.empty (); }
+
+  std::string as_string () const;
+
+  NodeId get_node_id () const { return node_id; }
+
+  // TODO: this mutable getter seems kinda dodgy
+  std::vector<std::unique_ptr<WhereClauseItem>> &get_items ()
+  {
+    return where_clause_items;
+  }
+  const std::vector<std::unique_ptr<WhereClauseItem>> &get_items () const
+  {
+    return where_clause_items;
+  }
+};
+
+// A self parameter in a method
+struct SelfParam
+{
+private:
+  bool has_ref;
+  bool is_mut;
+  // bool has_lifetime; // only possible if also ref
+  Lifetime lifetime;
+
+  // bool has_type; // only possible if not ref
+  std::unique_ptr<Type> type;
+
+  NodeId node_id;
+
+  Location locus;
+
+  // Unrestricted constructor used for error state
+  SelfParam (Lifetime lifetime, bool has_ref, bool is_mut, Type *type)
+    : has_ref (has_ref), is_mut (is_mut), lifetime (std::move (lifetime)),
+      type (type), node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+  // this is ok as no outside classes can ever call this
+
+  // TODO: self param can have outer attributes
+
+public:
+  // Returns whether the self-param has a type field.
+  bool has_type () const { return type != nullptr; }
+
+  // Returns whether the self-param has a valid lifetime.
+  bool has_lifetime () const { return !lifetime.is_error (); }
+
+  // Returns whether the self-param is in an error state.
+  bool is_error () const
+  {
+    return (has_type () && has_lifetime ()) || (has_lifetime () && !has_ref);
+    // not having either is not an error
+  }
+
+  // Creates an error state self-param.
+  static SelfParam create_error ()
+  {
+    // cannot have no ref but have a lifetime at the same time
+    return SelfParam (Lifetime (Lifetime::STATIC), false, false, nullptr);
+  }
+
+  // Type-based self parameter (not ref, no lifetime)
+  SelfParam (std::unique_ptr<Type> type, bool is_mut, Location locus)
+    : has_ref (false), is_mut (is_mut), lifetime (Lifetime::error ()),
+      type (std::move (type)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()), locus (locus)
+  {}
+
+  // Lifetime-based self parameter (is ref, no type)
+  SelfParam (Lifetime lifetime, bool is_mut, Location locus)
+    : has_ref (true), is_mut (is_mut), lifetime (std::move (lifetime)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()), locus (locus)
+  {}
+
+  // Copy constructor requires clone
+  SelfParam (SelfParam const &other)
+    : has_ref (other.has_ref), is_mut (other.is_mut), lifetime (other.lifetime),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()),
+      locus (other.locus)
+  {
+    node_id = other.node_id;
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+  }
+
+  // Overload assignment operator to use clone
+  SelfParam &operator= (SelfParam const &other)
+  {
+    is_mut = other.is_mut;
+    has_ref = other.has_ref;
+    lifetime = other.lifetime;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  SelfParam (SelfParam &&other) = default;
+  SelfParam &operator= (SelfParam &&other) = default;
+
+  std::string as_string () const;
+
+  Location get_locus () const { return locus; }
+
+  bool get_has_ref () const { return has_ref; };
+  bool get_is_mut () const { return is_mut; }
+
+  Lifetime get_lifetime () const { return lifetime; }
+
+  NodeId get_node_id () const { return node_id; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (has_type ());
+    return type;
+  }
+};
+
+// Qualifiers for function, i.e. const, unsafe, extern etc.
+struct FunctionQualifiers
+{
+private:
+  AsyncConstStatus const_status;
+  bool has_unsafe;
+  bool has_extern;
+  std::string extern_abi;
+  Location locus;
+
+public:
+  FunctionQualifiers (Location locus, AsyncConstStatus const_status,
+		      bool has_unsafe, bool has_extern = false,
+		      std::string extern_abi = std::string ())
+    : const_status (const_status), has_unsafe (has_unsafe),
+      has_extern (has_extern), extern_abi (std::move (extern_abi)),
+      locus (locus)
+  {
+    if (!this->extern_abi.empty ())
+      {
+	// having extern is required; not having it is an implementation error
+	rust_assert (has_extern);
+      }
+  }
+
+  std::string as_string () const;
+
+  AsyncConstStatus get_const_status () const { return const_status; }
+  bool is_unsafe () const { return has_unsafe; }
+  bool is_extern () const { return has_extern; }
+  std::string get_extern_abi () const { return extern_abi; }
+  bool has_abi () const { return !extern_abi.empty (); }
+
+  Location get_locus () const { return locus; }
+};
+
+// A function parameter
+struct FunctionParam
+{
+private:
+  std::vector<Attribute> outer_attrs;
+  Location locus;
+  std::unique_ptr<Pattern> param_name;
+  std::unique_ptr<Type> type;
+
+public:
+  FunctionParam (std::unique_ptr<Pattern> param_name,
+		 std::unique_ptr<Type> param_type,
+		 std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)), locus (locus),
+      param_name (std::move (param_name)), type (std::move (param_type)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor uses clone
+  FunctionParam (FunctionParam const &other)
+    : locus (other.locus), node_id (other.node_id)
+  {
+    // guard to prevent nullptr dereference
+    if (other.param_name != nullptr)
+      param_name = other.param_name->clone_pattern ();
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+  }
+
+  // Overload assignment operator to use clone
+  FunctionParam &operator= (FunctionParam const &other)
+  {
+    locus = other.locus;
+    node_id = other.node_id;
+
+    // guard to prevent nullptr dereference
+    if (other.param_name != nullptr)
+      param_name = other.param_name->clone_pattern ();
+    else
+      param_name = nullptr;
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  FunctionParam (FunctionParam &&other) = default;
+  FunctionParam &operator= (FunctionParam &&other) = default;
+
+  // Returns whether FunctionParam is in an invalid state.
+  bool is_error () const { return param_name == nullptr || type == nullptr; }
+
+  // Creates an error FunctionParam.
+  static FunctionParam create_error ()
+  {
+    return FunctionParam (nullptr, nullptr, {}, Location ());
+  }
+
+  std::string as_string () const;
+
+  Location get_locus () const { return locus; }
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Pattern> &get_pattern ()
+  {
+    rust_assert (param_name != nullptr);
+    return param_name;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+  NodeId get_node_id () const { return node_id; }
+
+protected:
+  NodeId node_id;
+};
+
+// Visibility of item - if the item has it, then it is some form of public
+struct Visibility
+{
+public:
+  enum VisType
+  {
+    PRIV,
+    PUB,
+    PUB_CRATE,
+    PUB_SELF,
+    PUB_SUPER,
+    PUB_IN_PATH
+  };
+
+private:
+  VisType vis_type;
+  // Only assigned if vis_type is IN_PATH
+  SimplePath in_path;
+
+  // should this store location info?
+
+public:
+  // Creates a Visibility - TODO make constructor protected or private?
+  Visibility (VisType vis_type, SimplePath in_path)
+    : vis_type (vis_type), in_path (std::move (in_path))
+  {}
+
+  VisType get_public_vis_type () const { return vis_type; }
+
+  // Returns whether visibility is in an error state.
+  bool is_error () const
+  {
+    return vis_type == PUB_IN_PATH && in_path.is_empty ();
+  }
+
+  // Returns whether a visibility has a path
+  bool has_path () const { return !(is_error ()) && vis_type == PUB_IN_PATH; }
+
+  // Returns whether visibility is public or not.
+  bool is_public () const { return vis_type != PRIV && !is_error (); }
+
+  // Creates an error visibility.
+  static Visibility create_error ()
+  {
+    return Visibility (PUB_IN_PATH, SimplePath::create_empty ());
+  }
+
+  // Unique pointer custom clone function
+  /*std::unique_ptr<Visibility> clone_visibility() const {
+      return std::unique_ptr<Visibility>(clone_visibility_impl());
+  }*/
+
+  /* TODO: think of a way to only allow valid Visibility states - polymorphism
+   * is one idea but may be too resource-intensive. */
+
+  // Creates a public visibility with no further features/arguments.
+  static Visibility create_public ()
+  {
+    return Visibility (PUB, SimplePath::create_empty ());
+  }
+
+  // Creates a public visibility with crate-relative paths
+  static Visibility create_crate (Location crate_tok_location)
+  {
+    return Visibility (PUB_CRATE,
+		       SimplePath::from_str ("crate", crate_tok_location));
+  }
+
+  // Creates a public visibility with self-relative paths
+  static Visibility create_self (Location self_tok_location)
+  {
+    return Visibility (PUB_SELF,
+		       SimplePath::from_str ("self", self_tok_location));
+  }
+
+  // Creates a public visibility with parent module-relative paths
+  static Visibility create_super (Location super_tok_location)
+  {
+    return Visibility (PUB_SUPER,
+		       SimplePath::from_str ("super", super_tok_location));
+  }
+
+  // Creates a private visibility
+  static Visibility create_private ()
+  {
+    return Visibility (PRIV, SimplePath::create_empty ());
+  }
+
+  // Creates a public visibility with a given path or whatever.
+  static Visibility create_in_path (SimplePath in_path)
+  {
+    return Visibility (PUB_IN_PATH, std::move (in_path));
+  }
+
+  std::string as_string () const;
+  const SimplePath &get_path () const { return in_path; }
+  SimplePath &get_path () { return in_path; }
+
+protected:
+  // Clone function implementation - not currently virtual but may be if
+  // polymorphism used
+  /*virtual*/ Visibility *clone_visibility_impl () const
+  {
+    return new Visibility (*this);
+  }
+};
+
+// A method (function belonging to a type)
+class Method : public InherentImplItem, public TraitImplItem
+{
+  std::vector<Attribute> outer_attrs;
+  Visibility vis;
+  FunctionQualifiers qualifiers;
+  Identifier method_name;
+  std::vector<std::unique_ptr<GenericParam>> generic_params;
+  SelfParam self_param;
+  std::vector<FunctionParam> function_params;
+  std::unique_ptr<Type> return_type;
+  WhereClause where_clause;
+  std::unique_ptr<BlockExpr> function_body;
+  Location locus;
+  NodeId node_id;
+
+public:
+  // Returns whether the method is in an error state.
+  bool is_error () const
+  {
+    return function_body == nullptr || method_name.empty ()
+	   || self_param.is_error ();
+  }
+
+  // Creates an error state method.
+  static Method create_error ()
+  {
+    return Method ("", FunctionQualifiers (Location (), NONE, true),
+		   std::vector<std::unique_ptr<GenericParam>> (),
+		   SelfParam::create_error (), std::vector<FunctionParam> (),
+		   nullptr, WhereClause::create_empty (), nullptr,
+		   Visibility::create_error (), std::vector<Attribute> (), {});
+  }
+
+  // Returns whether the method has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether the method has parameters.
+  bool has_params () const { return !function_params.empty (); }
+
+  // Returns whether the method has a return type (void otherwise).
+  bool has_return_type () const { return return_type != nullptr; }
+
+  // Returns whether the where clause exists (i.e. has items)
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  // Returns whether method has a non-default visibility.
+  bool has_visibility () const { return !vis.is_error (); }
+
+  // Mega-constructor with all possible fields
+  Method (Identifier method_name, FunctionQualifiers qualifiers,
+	  std::vector<std::unique_ptr<GenericParam>> generic_params,
+	  SelfParam self_param, std::vector<FunctionParam> function_params,
+	  std::unique_ptr<Type> return_type, WhereClause where_clause,
+	  std::unique_ptr<BlockExpr> function_body, Visibility vis,
+	  std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)), vis (std::move (vis)),
+      qualifiers (std::move (qualifiers)),
+      method_name (std::move (method_name)),
+      generic_params (std::move (generic_params)),
+      self_param (std::move (self_param)),
+      function_params (std::move (function_params)),
+      return_type (std::move (return_type)),
+      where_clause (std::move (where_clause)),
+      function_body (std::move (function_body)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // TODO: add constructor with less fields
+
+  // Copy constructor with clone
+  Method (Method const &other)
+    : outer_attrs (other.outer_attrs), vis (other.vis),
+      qualifiers (other.qualifiers), method_name (other.method_name),
+      self_param (other.self_param), function_params (other.function_params),
+      where_clause (other.where_clause), locus (other.locus)
+  {
+    // guard to prevent null dereference (always required)
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.function_body != nullptr)
+      function_body = other.function_body->clone_block_expr ();
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    node_id = other.node_id;
+  }
+
+  // Overloaded assignment operator to clone
+  Method &operator= (Method const &other)
+  {
+    method_name = other.method_name;
+    outer_attrs = other.outer_attrs;
+    vis = other.vis;
+    qualifiers = other.qualifiers;
+    self_param = other.self_param;
+    function_params = other.function_params;
+    where_clause = other.where_clause;
+    locus = other.locus;
+
+    // guard to prevent null dereference (always required)
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+    else
+      return_type = nullptr;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.function_body != nullptr)
+      function_body = other.function_body->clone_block_expr ();
+    else
+      function_body = nullptr;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    node_id = other.node_id;
+
+    return *this;
+  }
+
+  // move constructors
+  Method (Method &&other) = default;
+  Method &operator= (Method &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if block is null, so base stripping on that.
+  void mark_for_strip () override { function_body = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return function_body == nullptr;
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  std::vector<FunctionParam> &get_function_params () { return function_params; }
+  const std::vector<FunctionParam> &get_function_params () const
+  {
+    return function_params;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_definition ()
+  {
+    rust_assert (function_body != nullptr);
+    return function_body;
+  }
+
+  SelfParam &get_self_param () { return self_param; }
+  const SelfParam &get_self_param () const { return self_param; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_return_type ()
+  {
+    rust_assert (has_return_type ());
+    return return_type;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  Identifier get_method_name () const { return method_name; }
+
+  NodeId get_node_id () const { return node_id; }
+
+  Location get_locus () const override final { return locus; }
+
+  FunctionQualifiers get_qualifiers () { return qualifiers; }
+
+  const Visibility &get_visibility () const { return vis; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Method *clone_inherent_impl_item_impl () const final override
+  {
+    return clone_method_impl ();
+  }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Method *clone_trait_impl_item_impl () const final override
+  {
+    return clone_method_impl ();
+  }
+
+  /*virtual*/ Method *clone_method_impl () const { return new Method (*this); }
+};
+
+// Item that supports visibility - abstract base class
+class VisItem : public Item
+{
+  Visibility visibility;
+  std::vector<Attribute> outer_attrs;
+
+protected:
+  // Visibility constructor
+  VisItem (Visibility visibility,
+	   std::vector<Attribute> outer_attrs = std::vector<Attribute> ())
+    : visibility (std::move (visibility)), outer_attrs (std::move (outer_attrs))
+  {}
+
+  // Visibility copy constructor
+  VisItem (VisItem const &other)
+    : visibility (other.visibility), outer_attrs (other.outer_attrs)
+  {}
+
+  // Overload assignment operator to clone
+  VisItem &operator= (VisItem const &other)
+  {
+    visibility = other.visibility;
+    outer_attrs = other.outer_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  VisItem (VisItem &&other) = default;
+  VisItem &operator= (VisItem &&other) = default;
+
+public:
+  /* Does the item have some kind of public visibility (non-default
+   * visibility)? */
+  bool has_visibility () const { return visibility.is_public (); }
+
+  std::string as_string () const override;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  Visibility &get_visibility () { return visibility; }
+  const Visibility &get_visibility () const { return visibility; }
+
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+};
+
+// Rust module item - abstract base class
+class Module : public VisItem
+{
+public:
+  // Type of the current module. A module can be either loaded or unloaded,
+  // meaning that the items of the module can already be present or not. For
+  // example, the following module would be loaded: `mod foo { fn bar() {} }`.
+  // However, the module would be unloaded if it refers to an external file (i.e
+  // `mod foo;`) and then become loaded upon expansion.
+  enum ModuleKind
+  {
+    LOADED,
+    UNLOADED,
+  };
+
+  Identifier get_name () const { return module_name; }
+
+private:
+  Identifier module_name;
+  Location locus;
+  ModuleKind kind;
+
+  // Name of the file including the module
+  std::string outer_filename;
+  // bool has_inner_attrs;
+  std::vector<Attribute> inner_attrs;
+  // bool has_items;
+  std::vector<std::unique_ptr<Item>> items;
+  // Names of including inline modules (immediate parent is last in the list)
+  std::vector<std::string> module_scope;
+
+  // Filename the module refers to. Empty string on LOADED modules or if an
+  // error occured when dealing with UNLOADED modules
+  std::string module_file;
+
+  void clone_items (const std::vector<std::unique_ptr<Item>> &other_items)
+  {
+    items.reserve (other_items.size ());
+    for (const auto &e : other_items)
+      items.push_back (e->clone_item ());
+  }
+
+public:
+  // Returns whether the module has items in its body.
+  bool has_items () const { return !items.empty (); }
+
+  // Returns whether the module has any inner attributes.
+  bool has_inner_attrs () const { return !inner_attrs.empty (); }
+
+  // Unloaded module constructor
+  Module (Identifier module_name, Visibility visibility,
+	  std::vector<Attribute> outer_attrs, Location locus,
+	  std::string outer_filename, std::vector<std::string> module_scope)
+    : VisItem (std::move (visibility), std::move (outer_attrs)),
+      module_name (module_name), locus (locus), kind (ModuleKind::UNLOADED),
+      outer_filename (outer_filename), inner_attrs (std::vector<Attribute> ()),
+      items (std::vector<std::unique_ptr<Item>> ()),
+      module_scope (std::move (module_scope))
+  {}
+
+  // Loaded module constructor, with items
+  Module (Identifier name, Location locus,
+	  std::vector<std::unique_ptr<Item>> items,
+	  Visibility visibility = Visibility::create_error (),
+	  std::vector<Attribute> inner_attrs = std::vector<Attribute> (),
+	  std::vector<Attribute> outer_attrs = std::vector<Attribute> ())
+    : VisItem (std::move (visibility), std::move (outer_attrs)),
+      module_name (name), locus (locus), kind (ModuleKind::LOADED),
+      outer_filename (std::string ()), inner_attrs (std::move (inner_attrs)),
+      items (std::move (items))
+  {}
+
+  // Copy constructor with vector clone
+  Module (Module const &other)
+    : VisItem (other), module_name (other.module_name), locus (other.locus),
+      kind (other.kind), inner_attrs (other.inner_attrs),
+      module_scope (other.module_scope)
+  {
+    // We need to check whether we are copying a loaded module or an unloaded
+    // one. In the second case, clear the `items` vector.
+    if (other.kind == LOADED)
+      clone_items (other.items);
+    else
+      items.clear ();
+  }
+
+  // Overloaded assignment operator with vector clone
+  Module &operator= (Module const &other)
+  {
+    VisItem::operator= (other);
+
+    module_name = other.module_name;
+    locus = other.locus;
+    kind = other.kind;
+    inner_attrs = other.inner_attrs;
+    module_scope = other.module_scope;
+
+    // Likewise, we need to clear the `items` vector in case the other module is
+    // unloaded
+    if (kind == LOADED)
+      clone_items (other.items);
+    else
+      items.clear ();
+
+    return *this;
+  }
+
+  // Search for the filename associated with an external module, storing it in
+  // module_file
+  void process_file_path ();
+  // Load the items contained in an external module
+  void load_items ();
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  /* Override that runs the function recursively on all items contained within
+   * the module. */
+  void add_crate_name (std::vector<std::string> &names) const override;
+
+  // Returns the kind of the module
+  enum ModuleKind get_kind () const { return kind; }
+
+  // TODO: think of better way to do this - mutable getter seems dodgy
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  const std::vector<std::unique_ptr<Item>> &get_items () const { return items; }
+  std::vector<std::unique_ptr<Item>> &get_items () { return items; }
+
+  // move constructors
+  Module (Module &&other) = default;
+  Module &operator= (Module &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  // Invalid if name is empty, so base stripping on that.
+  void mark_for_strip () override { module_name = ""; }
+  bool is_marked_for_strip () const override { return module_name.empty (); }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Module *clone_item_impl () const override { return new Module (*this); }
+};
+
+// Rust extern crate declaration AST node
+class ExternCrate : public VisItem
+{
+  // this is either an identifier or "self", with self parsed to string
+  std::string referenced_crate;
+  // bool has_as_clause;
+  // AsClause as_clause;
+  // this is either an identifier or "_", with _ parsed to string
+  std::string as_clause_name;
+
+  Location locus;
+
+  /* e.g.
+      "extern crate foo as _"
+      "extern crate foo"
+      "extern crate std as cool_std"  */
+public:
+  std::string as_string () const override;
+
+  // Returns whether extern crate declaration has an as clause.
+  bool has_as_clause () const { return !as_clause_name.empty (); }
+
+  /* Returns whether extern crate declaration references the current crate
+   * (i.e. self). */
+  bool references_self () const { return referenced_crate == "self"; }
+
+  // Constructor
+  ExternCrate (std::string referenced_crate, Visibility visibility,
+	       std::vector<Attribute> outer_attrs, Location locus,
+	       std::string as_clause_name = std::string ())
+    : VisItem (std::move (visibility), std::move (outer_attrs)),
+      referenced_crate (std::move (referenced_crate)),
+      as_clause_name (std::move (as_clause_name)), locus (locus)
+  {}
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  const std::string &get_referenced_crate () const { return referenced_crate; }
+  const std::string &get_as_clause () const { return as_clause_name; }
+
+  // Override that adds extern crate name in decl to passed list of names.
+  void add_crate_name (std::vector<std::string> &names) const override
+  {
+    names.push_back (referenced_crate);
+  }
+
+  // Invalid if crate name is empty, so base stripping on that.
+  void mark_for_strip () override { referenced_crate = ""; }
+  bool is_marked_for_strip () const override
+  {
+    return referenced_crate.empty ();
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  ExternCrate *clone_item_impl () const override
+  {
+    return new ExternCrate (*this);
+  }
+};
+
+// The path-ish thing referred to in a use declaration - abstract base class
+class UseTree
+{
+  Location locus;
+
+public:
+  enum Kind
+  {
+    Glob,
+    Rebind,
+    List,
+  };
+
+  virtual ~UseTree () {}
+
+  // Overload assignment operator to clone
+  UseTree &operator= (UseTree const &other)
+  {
+    locus = other.locus;
+
+    return *this;
+  }
+
+  UseTree (const UseTree &other) = default;
+
+  // move constructors
+  UseTree (UseTree &&other) = default;
+  UseTree &operator= (UseTree &&other) = default;
+
+  // Unique pointer custom clone function
+  std::unique_ptr<UseTree> clone_use_tree () const
+  {
+    return std::unique_ptr<UseTree> (clone_use_tree_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+  virtual Kind get_kind () const = 0;
+
+  Location get_locus () const { return locus; }
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+protected:
+  // Clone function implementation as pure virtual method
+  virtual UseTree *clone_use_tree_impl () const = 0;
+
+  UseTree (Location locus) : locus (locus) {}
+};
+
+// Use tree with a glob (wildcard) operator
+class UseTreeGlob : public UseTree
+{
+public:
+  enum PathType
+  {
+    NO_PATH,
+    GLOBAL,
+    PATH_PREFIXED
+  };
+
+private:
+  PathType glob_type;
+  SimplePath path;
+
+public:
+  UseTreeGlob (PathType glob_type, SimplePath path, Location locus)
+    : UseTree (locus), glob_type (glob_type), path (std::move (path))
+  {
+    if (this->glob_type != PATH_PREFIXED)
+      {
+	// compiler implementation error if there is a path with a
+	// non-path-prefixed use tree glob
+	rust_assert (!has_path ());
+      }
+    // TODO: do path-prefixed paths also have to have a path? If so, have an
+    // assert for that too.
+  }
+
+  /* Returns whether has path. Should be made redundant by PathType
+   * PATH_PREFIXED. */
+  bool has_path () const { return !path.is_empty (); }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  Kind get_kind () const override { return Glob; }
+
+  SimplePath get_path () const
+  {
+    rust_assert (has_path ());
+    return path;
+  }
+
+  /* TODO: find way to ensure only PATH_PREFIXED glob_type has path - factory
+   * methods? */
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  UseTreeGlob *clone_use_tree_impl () const override
+  {
+    return new UseTreeGlob (*this);
+  }
+};
+
+// Use tree with a list of paths with a common prefix
+class UseTreeList : public UseTree
+{
+public:
+  enum PathType
+  {
+    NO_PATH,
+    GLOBAL,
+    PATH_PREFIXED
+  };
+
+private:
+  PathType path_type;
+  SimplePath path;
+
+  std::vector<std::unique_ptr<UseTree>> trees;
+
+public:
+  UseTreeList (PathType path_type, SimplePath path,
+	       std::vector<std::unique_ptr<UseTree>> trees, Location locus)
+    : UseTree (locus), path_type (path_type), path (std::move (path)),
+      trees (std::move (trees))
+  {
+    if (this->path_type != PATH_PREFIXED)
+      {
+	// compiler implementation error if there is a path with a
+	// non-path-prefixed use tree glob
+	rust_assert (!has_path ());
+      }
+    // TODO: do path-prefixed paths also have to have a path? If so, have an
+    // assert for that too.
+  }
+
+  // copy constructor with vector clone
+  UseTreeList (UseTreeList const &other)
+    : UseTree (other), path_type (other.path_type), path (other.path)
+  {
+    trees.reserve (other.trees.size ());
+    for (const auto &e : other.trees)
+      trees.push_back (e->clone_use_tree ());
+  }
+
+  // overloaded assignment operator with vector clone
+  UseTreeList &operator= (UseTreeList const &other)
+  {
+    UseTree::operator= (other);
+    path_type = other.path_type;
+    path = other.path;
+
+    trees.reserve (other.trees.size ());
+    for (const auto &e : other.trees)
+      trees.push_back (e->clone_use_tree ());
+
+    return *this;
+  }
+
+  // move constructors
+  UseTreeList (UseTreeList &&other) = default;
+  UseTreeList &operator= (UseTreeList &&other) = default;
+
+  // Returns whether has path. Should be made redundant by path_type.
+  bool has_path () const { return !path.is_empty (); }
+
+  // Returns whether has inner tree elements.
+  bool has_trees () const { return !trees.empty (); }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  Kind get_kind () const override { return List; }
+  SimplePath get_path () const
+  {
+    rust_assert (has_path ());
+    return path;
+  }
+
+  const std::vector<std::unique_ptr<UseTree>> &get_trees () const
+  {
+    return trees;
+  }
+
+  // TODO: find way to ensure only PATH_PREFIXED path_type has path - factory
+  // methods?
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  UseTreeList *clone_use_tree_impl () const override
+  {
+    return new UseTreeList (*this);
+  }
+};
+
+// Use tree where it rebinds the module name as something else
+class UseTreeRebind : public UseTree
+{
+public:
+  enum NewBindType
+  {
+    NONE,
+    IDENTIFIER,
+    WILDCARD
+  };
+
+private:
+  SimplePath path;
+
+  NewBindType bind_type;
+  Identifier identifier; // only if NewBindType is IDENTIFIER
+
+public:
+  UseTreeRebind (NewBindType bind_type, SimplePath path, Location locus,
+		 Identifier identifier = std::string ())
+    : UseTree (locus), path (std::move (path)), bind_type (bind_type),
+      identifier (std::move (identifier))
+  {}
+
+  // Returns whether has path (this should always be true).
+  bool has_path () const { return !path.is_empty (); }
+
+  // Returns whether has identifier (or, rather, is allowed to).
+  bool has_identifier () const { return bind_type == IDENTIFIER; }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  Kind get_kind () const override { return Rebind; }
+
+  SimplePath get_path () const
+  {
+    rust_assert (has_path ());
+    return path;
+  }
+
+  const Identifier &get_identifier () const
+  {
+    rust_assert (has_identifier ());
+    return identifier;
+  }
+
+  // TODO: find way to ensure only PATH_PREFIXED path_type has path - factory
+  // methods?
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  virtual UseTreeRebind *clone_use_tree_impl () const override
+  {
+    return new UseTreeRebind (*this);
+  }
+};
+
+// Rust use declaration (i.e. for modules) AST node
+class UseDeclaration : public VisItem
+{
+  std::unique_ptr<UseTree> use_tree;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  UseDeclaration (std::unique_ptr<UseTree> use_tree, Visibility visibility,
+		  std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (visibility), std::move (outer_attrs)),
+      use_tree (std::move (use_tree)), locus (locus)
+  {}
+
+  // Copy constructor with clone
+  UseDeclaration (UseDeclaration const &other)
+    : VisItem (other), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.use_tree != nullptr)
+      use_tree = other.use_tree->clone_use_tree ();
+  }
+
+  // Overloaded assignment operator to clone
+  UseDeclaration &operator= (UseDeclaration const &other)
+  {
+    VisItem::operator= (other);
+    // visibility = other.visibility->clone_visibility();
+    // outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.use_tree != nullptr)
+      use_tree = other.use_tree->clone_use_tree ();
+    else
+      use_tree = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  UseDeclaration (UseDeclaration &&other) = default;
+  UseDeclaration &operator= (UseDeclaration &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+  const std::unique_ptr<UseTree> &get_tree () const { return use_tree; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if use tree is null, so base stripping on that.
+  void mark_for_strip () override { use_tree = nullptr; }
+  bool is_marked_for_strip () const override { return use_tree == nullptr; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  UseDeclaration *clone_item_impl () const override
+  {
+    return new UseDeclaration (*this);
+  }
+};
+
+class LetStmt;
+
+// Rust function declaration AST node
+class Function : public VisItem, public InherentImplItem, public TraitImplItem
+{
+  FunctionQualifiers qualifiers;
+  Identifier function_name;
+  std::vector<std::unique_ptr<GenericParam>> generic_params;
+  std::vector<FunctionParam> function_params;
+  std::unique_ptr<Type> return_type;
+  WhereClause where_clause;
+  std::unique_ptr<BlockExpr> function_body;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether function has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether function has regular parameters.
+  bool has_function_params () const { return !function_params.empty (); }
+
+  // Returns whether function has return type - if not, it is void.
+  bool has_return_type () const { return return_type != nullptr; }
+
+  // Returns whether function has a where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  // Mega-constructor with all possible fields
+  Function (Identifier function_name, FunctionQualifiers qualifiers,
+	    std::vector<std::unique_ptr<GenericParam>> generic_params,
+	    std::vector<FunctionParam> function_params,
+	    std::unique_ptr<Type> return_type, WhereClause where_clause,
+	    std::unique_ptr<BlockExpr> function_body, Visibility vis,
+	    std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      qualifiers (std::move (qualifiers)),
+      function_name (std::move (function_name)),
+      generic_params (std::move (generic_params)),
+      function_params (std::move (function_params)),
+      return_type (std::move (return_type)),
+      where_clause (std::move (where_clause)),
+      function_body (std::move (function_body)), locus (locus)
+  {}
+
+  // TODO: add constructor with less fields
+
+  // Copy constructor with clone
+  Function (Function const &other)
+    : VisItem (other), qualifiers (other.qualifiers),
+      function_name (other.function_name),
+      function_params (other.function_params),
+      where_clause (other.where_clause), locus (other.locus)
+  {
+    // guard to prevent null dereference (always required)
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.function_body != nullptr)
+      function_body = other.function_body->clone_block_expr ();
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  // Overloaded assignment operator to clone
+  Function &operator= (Function const &other)
+  {
+    VisItem::operator= (other);
+    function_name = other.function_name;
+    qualifiers = other.qualifiers;
+    function_params = other.function_params;
+    where_clause = other.where_clause;
+    // visibility = other.visibility->clone_visibility();
+    // outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (always required)
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+    else
+      return_type = nullptr;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.function_body != nullptr)
+      function_body = other.function_body->clone_block_expr ();
+    else
+      function_body = nullptr;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  Function (Function &&other) = default;
+  Function &operator= (Function &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if block is null, so base stripping on that.
+  void mark_for_strip () override { function_body = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return function_body == nullptr;
+  }
+
+  std::vector<FunctionParam> &get_function_params () { return function_params; }
+  const std::vector<FunctionParam> &get_function_params () const
+  {
+    return function_params;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_definition ()
+  {
+    rust_assert (function_body != nullptr);
+    return function_body;
+  }
+
+  const FunctionQualifiers &get_qualifiers () const { return qualifiers; }
+
+  Identifier get_function_name () const { return function_name; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_return_type ()
+  {
+    rust_assert (has_return_type ());
+    return return_type;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Function *clone_item_impl () const override { return new Function (*this); }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Function *clone_inherent_impl_item_impl () const override
+  {
+    return new Function (*this);
+  }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Function *clone_trait_impl_item_impl () const override
+  {
+    return new Function (*this);
+  }
+};
+
+// Rust type alias (i.e. typedef) AST node
+class TypeAlias : public VisItem, public TraitImplItem
+{
+  Identifier new_type_name;
+
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+  std::unique_ptr<Type> existing_type;
+
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether type alias has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether type alias has a where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  // Mega-constructor with all possible fields
+  TypeAlias (Identifier new_type_name,
+	     std::vector<std::unique_ptr<GenericParam>> generic_params,
+	     WhereClause where_clause, std::unique_ptr<Type> existing_type,
+	     Visibility vis, std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      new_type_name (std::move (new_type_name)),
+      generic_params (std::move (generic_params)),
+      where_clause (std::move (where_clause)),
+      existing_type (std::move (existing_type)), locus (locus)
+  {}
+
+  // Copy constructor
+  TypeAlias (TypeAlias const &other)
+    : VisItem (other), new_type_name (other.new_type_name),
+      where_clause (other.where_clause), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.existing_type != nullptr)
+      existing_type = other.existing_type->clone_type ();
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  // Overloaded assignment operator to clone
+  TypeAlias &operator= (TypeAlias const &other)
+  {
+    VisItem::operator= (other);
+    new_type_name = other.new_type_name;
+    where_clause = other.where_clause;
+    // visibility = other.visibility->clone_visibility();
+    // outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.existing_type != nullptr)
+      existing_type = other.existing_type->clone_type ();
+    else
+      existing_type = nullptr;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  TypeAlias (TypeAlias &&other) = default;
+  TypeAlias &operator= (TypeAlias &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if existing type is null, so base stripping on that.
+  void mark_for_strip () override { existing_type = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return existing_type == nullptr;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type_aliased ()
+  {
+    rust_assert (existing_type != nullptr);
+    return existing_type;
+  }
+
+  Identifier get_new_type_name () const { return new_type_name; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  TypeAlias *clone_item_impl () const override { return new TypeAlias (*this); }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  TypeAlias *clone_trait_impl_item_impl () const override
+  {
+    return new TypeAlias (*this);
+  }
+};
+
+// Rust base struct declaration AST node - abstract base class
+class Struct : public VisItem
+{
+protected:
+  // protected to enable access by derived classes - allows better as_string
+  Identifier struct_name;
+
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+private:
+  Location locus;
+
+public:
+  // Returns whether struct has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether struct has a where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  Location get_locus () const override final { return locus; }
+
+  // Invalid if name is empty, so base stripping on that.
+  void mark_for_strip () override { struct_name = ""; }
+  bool is_marked_for_strip () const override { return struct_name.empty (); }
+
+  Identifier get_struct_name () const { return struct_name; }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  Identifier get_identifier () const { return struct_name; }
+
+protected:
+  Struct (Identifier struct_name,
+	  std::vector<std::unique_ptr<GenericParam>> generic_params,
+	  WhereClause where_clause, Visibility vis, Location locus,
+	  std::vector<Attribute> outer_attrs = std::vector<Attribute> ())
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      struct_name (std::move (struct_name)),
+      generic_params (std::move (generic_params)),
+      where_clause (std::move (where_clause)), locus (locus)
+  {}
+
+  // Copy constructor with vector clone
+  Struct (Struct const &other)
+    : VisItem (other), struct_name (other.struct_name),
+      where_clause (other.where_clause), locus (other.locus)
+  {
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  Struct &operator= (Struct const &other)
+  {
+    VisItem::operator= (other);
+    struct_name = other.struct_name;
+    where_clause = other.where_clause;
+    locus = other.locus;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  Struct (Struct &&other) = default;
+  Struct &operator= (Struct &&other) = default;
+};
+
+// A single field in a struct
+struct StructField
+{
+private:
+  // bool has_outer_attributes;
+  std::vector<Attribute> outer_attrs;
+
+  // bool has_visibility;
+  Visibility visibility;
+
+  Identifier field_name;
+  std::unique_ptr<Type> field_type;
+
+  NodeId node_id;
+
+  Location locus;
+
+public:
+  // Returns whether struct field has any outer attributes.
+  bool has_outer_attributes () const { return !outer_attrs.empty (); }
+
+  // Returns whether struct field has a non-private (non-default) visibility.
+  bool has_visibility () const { return !visibility.is_error (); }
+
+  StructField (Identifier field_name, std::unique_ptr<Type> field_type,
+	       Visibility vis, Location locus,
+	       std::vector<Attribute> outer_attrs = std::vector<Attribute> ())
+    : outer_attrs (std::move (outer_attrs)), visibility (std::move (vis)),
+      field_name (std::move (field_name)), field_type (std::move (field_type)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()), locus (locus)
+  {}
+
+  // Copy constructor
+  StructField (StructField const &other)
+    : outer_attrs (other.outer_attrs), visibility (other.visibility),
+      field_name (other.field_name), node_id (other.node_id),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference
+    if (other.field_type != nullptr)
+      field_type = other.field_type->clone_type ();
+  }
+
+  ~StructField () = default;
+
+  // Overloaded assignment operator to clone
+  StructField &operator= (StructField const &other)
+  {
+    field_name = other.field_name;
+    visibility = other.visibility;
+    outer_attrs = other.outer_attrs;
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.field_type != nullptr)
+      field_type = other.field_type->clone_type ();
+    else
+      field_type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  StructField (StructField &&other) = default;
+  StructField &operator= (StructField &&other) = default;
+
+  // Returns whether struct field is in an error state.
+  bool is_error () const
+  {
+    return field_name.empty () && field_type == nullptr;
+    // this should really be an or since neither are allowed
+  }
+
+  // Creates an error state struct field.
+  static StructField create_error ()
+  {
+    return StructField (std::string (""), nullptr, Visibility::create_error (),
+			Location ());
+  }
+
+  std::string as_string () const;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  Identifier get_field_name () const { return field_name; }
+
+  Location get_locus () const { return locus; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_field_type ()
+  {
+    rust_assert (field_type != nullptr);
+    return field_type;
+  }
+
+  const Visibility &get_visibility () const { return visibility; }
+
+  NodeId get_node_id () const { return node_id; }
+};
+
+// Rust struct declaration with true struct type AST node
+class StructStruct : public Struct
+{
+  std::vector<StructField> fields;
+  bool is_unit;
+
+public:
+  std::string as_string () const override;
+
+  // Mega-constructor with all possible fields
+  StructStruct (std::vector<StructField> fields, Identifier struct_name,
+		std::vector<std::unique_ptr<GenericParam>> generic_params,
+		WhereClause where_clause, bool is_unit, Visibility vis,
+		std::vector<Attribute> outer_attrs, Location locus)
+    : Struct (std::move (struct_name), std::move (generic_params),
+	      std::move (where_clause), std::move (vis), locus,
+	      std::move (outer_attrs)),
+      fields (std::move (fields)), is_unit (is_unit)
+  {}
+
+  // Unit struct constructor
+  StructStruct (Identifier struct_name,
+		std::vector<std::unique_ptr<GenericParam>> generic_params,
+		WhereClause where_clause, Visibility vis,
+		std::vector<Attribute> outer_attrs, Location locus)
+    : Struct (std::move (struct_name), std::move (generic_params),
+	      std::move (where_clause), std::move (vis), locus,
+	      std::move (outer_attrs)),
+      is_unit (true)
+  {}
+
+  /* Returns whether the struct is a unit struct - struct defined without
+   * fields. This is important because it also means an implicit constant of its
+   * type is defined. */
+  bool is_unit_struct () const { return is_unit; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<StructField> &get_fields () { return fields; }
+  const std::vector<StructField> &get_fields () const { return fields; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  StructStruct *clone_item_impl () const override
+  {
+    return new StructStruct (*this);
+  }
+};
+
+// A single field in a tuple
+struct TupleField
+{
+private:
+  // bool has_outer_attributes;
+  std::vector<Attribute> outer_attrs;
+
+  // bool has_visibility;
+  Visibility visibility;
+
+  std::unique_ptr<Type> field_type;
+
+  NodeId node_id;
+
+  Location locus;
+
+public:
+  // Returns whether tuple field has outer attributes.
+  bool has_outer_attributes () const { return !outer_attrs.empty (); }
+
+  /* Returns whether tuple field has a non-default visibility (i.e. a public
+   * one) */
+  bool has_visibility () const { return !visibility.is_error (); }
+
+  // Complete constructor
+  TupleField (std::unique_ptr<Type> field_type, Visibility vis, Location locus,
+	      std::vector<Attribute> outer_attrs = std::vector<Attribute> ())
+    : outer_attrs (std::move (outer_attrs)), visibility (std::move (vis)),
+      field_type (std::move (field_type)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()), locus (locus)
+  {}
+
+  // Copy constructor with clone
+  TupleField (TupleField const &other)
+    : outer_attrs (other.outer_attrs), visibility (other.visibility),
+      node_id (other.node_id), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error)
+    if (other.field_type != nullptr)
+      field_type = other.field_type->clone_type ();
+  }
+
+  ~TupleField () = default;
+
+  // Overloaded assignment operator to clone
+  TupleField &operator= (TupleField const &other)
+  {
+    visibility = other.visibility;
+    outer_attrs = other.outer_attrs;
+    node_id = other.node_id;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error)
+    if (other.field_type != nullptr)
+      field_type = other.field_type->clone_type ();
+    else
+      field_type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  TupleField (TupleField &&other) = default;
+  TupleField &operator= (TupleField &&other) = default;
+
+  // Returns whether tuple field is in an error state.
+  bool is_error () const { return field_type == nullptr; }
+
+  // Creates an error state tuple field.
+  static TupleField create_error ()
+  {
+    return TupleField (nullptr, Visibility::create_error (), Location ());
+  }
+
+  std::string as_string () const;
+
+  NodeId get_node_id () const { return node_id; }
+
+  const Visibility &get_visibility () const { return visibility; }
+
+  Location get_locus () const { return locus; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_field_type ()
+  {
+    rust_assert (field_type != nullptr);
+    return field_type;
+  }
+};
+
+// Rust tuple declared using struct keyword AST node
+class TupleStruct : public Struct
+{
+  std::vector<TupleField> fields;
+
+public:
+  std::string as_string () const override;
+
+  // Mega-constructor with all possible fields
+  TupleStruct (std::vector<TupleField> fields, Identifier struct_name,
+	       std::vector<std::unique_ptr<GenericParam>> generic_params,
+	       WhereClause where_clause, Visibility vis,
+	       std::vector<Attribute> outer_attrs, Location locus)
+    : Struct (std::move (struct_name), std::move (generic_params),
+	      std::move (where_clause), std::move (vis), locus,
+	      std::move (outer_attrs)),
+      fields (std::move (fields))
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<TupleField> &get_fields () { return fields; }
+  const std::vector<TupleField> &get_fields () const { return fields; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  TupleStruct *clone_item_impl () const override
+  {
+    return new TupleStruct (*this);
+  }
+};
+
+/* An item used in an "enum" tagged union - not abstract: base represents a
+ * name-only enum. EnumItems (variants) syntactically allow a Visibility
+ * annotation. */
+class EnumItem : public VisItem
+{
+  Identifier variant_name;
+
+  Location locus;
+
+public:
+  virtual ~EnumItem () {}
+
+  EnumItem (Identifier variant_name, Visibility vis,
+	    std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      variant_name (std::move (variant_name)), locus (locus)
+  {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<EnumItem> clone_enum_item () const
+  {
+    return std::unique_ptr<EnumItem> (clone_item_impl ());
+  }
+
+  virtual std::string as_string () const;
+
+  // not pure virtual as not abstract
+  virtual void accept_vis (ASTVisitor &vis);
+
+  Location get_locus () const { return locus; }
+
+  Identifier get_identifier () const { return variant_name; }
+
+  // Based on idea that name is never empty.
+  void mark_for_strip () { variant_name = ""; }
+  bool is_marked_for_strip () const { return variant_name.empty (); }
+
+protected:
+  EnumItem *clone_item_impl () const override { return new EnumItem (*this); }
+};
+
+// A tuple item used in an "enum" tagged union
+class EnumItemTuple : public EnumItem
+{
+  // bool has_tuple_fields;
+  std::vector<TupleField> tuple_fields;
+
+public:
+  // Returns whether tuple enum item has tuple fields.
+  bool has_tuple_fields () const { return !tuple_fields.empty (); }
+
+  EnumItemTuple (Identifier variant_name, Visibility vis,
+		 std::vector<TupleField> tuple_fields,
+		 std::vector<Attribute> outer_attrs, Location locus)
+    : EnumItem (std::move (variant_name), std::move (vis),
+		std::move (outer_attrs), locus),
+      tuple_fields (std::move (tuple_fields))
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<TupleField> &get_tuple_fields () { return tuple_fields; }
+  const std::vector<TupleField> &get_tuple_fields () const
+  {
+    return tuple_fields;
+  }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  EnumItemTuple *clone_item_impl () const override
+  {
+    return new EnumItemTuple (*this);
+  }
+};
+
+// A struct item used in an "enum" tagged union
+class EnumItemStruct : public EnumItem
+{
+  // bool has_struct_fields;
+  std::vector<StructField> struct_fields;
+
+public:
+  // Returns whether struct enum item has struct fields.
+  bool has_struct_fields () const { return !struct_fields.empty (); }
+
+  EnumItemStruct (Identifier variant_name, Visibility vis,
+		  std::vector<StructField> struct_fields,
+		  std::vector<Attribute> outer_attrs, Location locus)
+    : EnumItem (std::move (variant_name), std::move (vis),
+		std::move (outer_attrs), locus),
+      struct_fields (std::move (struct_fields))
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<StructField> &get_struct_fields () { return struct_fields; }
+  const std::vector<StructField> &get_struct_fields () const
+  {
+    return struct_fields;
+  }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  EnumItemStruct *clone_item_impl () const override
+  {
+    return new EnumItemStruct (*this);
+  }
+};
+
+// A discriminant (numbered enum) item used in an "enum" tagged union
+class EnumItemDiscriminant : public EnumItem
+{
+  std::unique_ptr<Expr> expression;
+
+public:
+  EnumItemDiscriminant (Identifier variant_name, Visibility vis,
+			std::unique_ptr<Expr> expr,
+			std::vector<Attribute> outer_attrs, Location locus)
+    : EnumItem (std::move (variant_name), std::move (vis),
+		std::move (outer_attrs), locus),
+      expression (std::move (expr))
+  {}
+
+  // Copy constructor with clone
+  EnumItemDiscriminant (EnumItemDiscriminant const &other)
+    : EnumItem (other), expression (other.expression->clone_expr ())
+  {}
+
+  // Overloaded assignment operator to clone
+  EnumItemDiscriminant &operator= (EnumItemDiscriminant const &other)
+  {
+    EnumItem::operator= (other);
+    expression = other.expression->clone_expr ();
+    // variant_name = other.variant_name;
+    // outer_attrs = other.outer_attrs;
+
+    return *this;
+  }
+
+  // move constructors
+  EnumItemDiscriminant (EnumItemDiscriminant &&other) = default;
+  EnumItemDiscriminant &operator= (EnumItemDiscriminant &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_expr ()
+  {
+    rust_assert (expression != nullptr);
+    return expression;
+  }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  EnumItemDiscriminant *clone_item_impl () const override
+  {
+    return new EnumItemDiscriminant (*this);
+  }
+};
+
+// AST node for Rust "enum" - tagged union
+class Enum : public VisItem
+{
+  Identifier enum_name;
+
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+  std::vector<std::unique_ptr<EnumItem>> items;
+
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether "enum" has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether "enum" has a where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  /* Returns whether enum is a "zero-variant" (no possible variant) enum,
+   * which cannot be instantiated. */
+  bool is_zero_variant () const { return items.empty (); }
+
+  // Mega-constructor
+  Enum (Identifier enum_name, Visibility vis,
+	std::vector<std::unique_ptr<GenericParam>> generic_params,
+	WhereClause where_clause, std::vector<std::unique_ptr<EnumItem>> items,
+	std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      enum_name (std::move (enum_name)),
+      generic_params (std::move (generic_params)),
+      where_clause (std::move (where_clause)), items (std::move (items)),
+      locus (locus)
+  {}
+
+  // TODO: constructor with less arguments
+
+  // Copy constructor with vector clone
+  Enum (Enum const &other)
+    : VisItem (other), enum_name (other.enum_name),
+      where_clause (other.where_clause), locus (other.locus)
+  {
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_enum_item ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  Enum &operator= (Enum const &other)
+  {
+    VisItem::operator= (other);
+    enum_name = other.enum_name;
+    where_clause = other.where_clause;
+    locus = other.locus;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_enum_item ());
+
+    return *this;
+  }
+
+  // Move constructors
+  Enum (Enum &&other) = default;
+  Enum &operator= (Enum &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  Identifier get_identifier () const { return enum_name; }
+
+  // Invalid if name is empty, so base stripping on that.
+  void mark_for_strip () override { enum_name = ""; }
+  bool is_marked_for_strip () const override { return enum_name.empty (); }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<std::unique_ptr<EnumItem>> &get_variants () { return items; }
+  const std::vector<std::unique_ptr<EnumItem>> &get_variants () const
+  {
+    return items;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Enum *clone_item_impl () const override { return new Enum (*this); }
+};
+
+// Rust untagged union used for C compat AST node
+class Union : public VisItem
+{
+  Identifier union_name;
+
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+  std::vector<StructField> variants;
+
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether union has generic params.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether union has where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  Union (Identifier union_name, Visibility vis,
+	 std::vector<std::unique_ptr<GenericParam>> generic_params,
+	 WhereClause where_clause, std::vector<StructField> variants,
+	 std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      union_name (std::move (union_name)),
+      generic_params (std::move (generic_params)),
+      where_clause (std::move (where_clause)), variants (std::move (variants)),
+      locus (locus)
+  {}
+
+  // copy constructor with vector clone
+  Union (Union const &other)
+    : VisItem (other), union_name (other.union_name),
+      where_clause (other.where_clause), variants (other.variants),
+      locus (other.locus)
+  {
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  // overloaded assignment operator with vector clone
+  Union &operator= (Union const &other)
+  {
+    VisItem::operator= (other);
+    union_name = other.union_name;
+    where_clause = other.where_clause;
+    variants = other.variants;
+    locus = other.locus;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  Union (Union &&other) = default;
+  Union &operator= (Union &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if name is empty, so base stripping on that.
+  void mark_for_strip () override { union_name = ""; }
+  bool is_marked_for_strip () const override { return union_name.empty (); }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<StructField> &get_variants () { return variants; }
+  const std::vector<StructField> &get_variants () const { return variants; }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  Identifier get_identifier () const { return union_name; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Union *clone_item_impl () const override { return new Union (*this); }
+};
+
+/* "Constant item" AST node - used for constant, compile-time expressions
+ * within module scope (like constexpr) */
+class ConstantItem : public VisItem,
+		     public InherentImplItem,
+		     public TraitImplItem
+{
+  // either has an identifier or "_" - maybe handle in identifier?
+  // bool identifier_is_underscore;
+  // if no identifier declared, identifier will be "_"
+  std::string identifier;
+
+  std::unique_ptr<Type> type;
+  std::unique_ptr<Expr> const_expr;
+
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  ConstantItem (std::string ident, Visibility vis, std::unique_ptr<Type> type,
+		std::unique_ptr<Expr> const_expr,
+		std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      identifier (std::move (ident)), type (std::move (type)),
+      const_expr (std::move (const_expr)), locus (locus)
+  {}
+
+  ConstantItem (ConstantItem const &other)
+    : VisItem (other), identifier (other.identifier), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    if (other.const_expr != nullptr)
+      const_expr = other.const_expr->clone_expr ();
+  }
+
+  // Overload assignment operator to clone
+  ConstantItem &operator= (ConstantItem const &other)
+  {
+    VisItem::operator= (other);
+    identifier = other.identifier;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+    if (other.const_expr != nullptr)
+      const_expr = other.const_expr->clone_expr ();
+    else
+      const_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ConstantItem (ConstantItem &&other) = default;
+  ConstantItem &operator= (ConstantItem &&other) = default;
+
+  /* Returns whether constant item is an "unnamed" (wildcard underscore used
+   * as identifier) constant. */
+  bool is_unnamed () const { return identifier == "_"; }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if type or expression are null, so base stripping on that.
+  void mark_for_strip () override
+  {
+    type = nullptr;
+    const_expr = nullptr;
+  }
+  bool is_marked_for_strip () const override
+  {
+    return type == nullptr && const_expr == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_expr ()
+  {
+    rust_assert (const_expr != nullptr);
+    return const_expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+
+  std::string get_identifier () const { return identifier; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  ConstantItem *clone_item_impl () const override
+  {
+    return new ConstantItem (*this);
+  }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  ConstantItem *clone_inherent_impl_item_impl () const override
+  {
+    return new ConstantItem (*this);
+  }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  ConstantItem *clone_trait_impl_item_impl () const override
+  {
+    return new ConstantItem (*this);
+  }
+};
+
+/* Static item AST node - items within module scope with fixed storage
+ * duration? */
+class StaticItem : public VisItem
+{
+  bool has_mut;
+  Identifier name;
+  std::unique_ptr<Type> type;
+  std::unique_ptr<Expr> expr;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  StaticItem (Identifier name, bool is_mut, std::unique_ptr<Type> type,
+	      std::unique_ptr<Expr> expr, Visibility vis,
+	      std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)), has_mut (is_mut),
+      name (std::move (name)), type (std::move (type)), expr (std::move (expr)),
+      locus (locus)
+  {}
+
+  // Copy constructor with clone
+  StaticItem (StaticItem const &other)
+    : VisItem (other), has_mut (other.has_mut), name (other.name),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr ();
+  }
+
+  // Overloaded assignment operator to clone
+  StaticItem &operator= (StaticItem const &other)
+  {
+    VisItem::operator= (other);
+    name = other.name;
+    has_mut = other.has_mut;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr ();
+    else
+      expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  StaticItem (StaticItem &&other) = default;
+  StaticItem &operator= (StaticItem &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if type or expression are null, so base stripping on that.
+  void mark_for_strip () override
+  {
+    type = nullptr;
+    expr = nullptr;
+  }
+  bool is_marked_for_strip () const override
+  {
+    return type == nullptr && expr == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_expr ()
+  {
+    rust_assert (expr != nullptr);
+    return expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+
+  bool is_mutable () const { return has_mut; }
+
+  Identifier get_identifier () const { return name; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  StaticItem *clone_item_impl () const override
+  {
+    return new StaticItem (*this);
+  }
+};
+
+// Function declaration in traits
+struct TraitFunctionDecl
+{
+private:
+  // TODO: delete and replace with Function decl item? no as no body in this.
+  FunctionQualifiers qualifiers;
+  Identifier function_name;
+
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  // bool has_params;
+  // FunctionParams function_params;
+  std::vector<FunctionParam> function_params; // inlined
+
+  // bool has_return_type;
+  std::unique_ptr<Type> return_type;
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+  // should this store location info?
+
+public:
+  // Returns whether function decl has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether function decl has regular parameters.
+  bool has_params () const { return !function_params.empty (); }
+
+  // Returns whether function has return type (otherwise is void).
+  bool has_return_type () const { return return_type != nullptr; }
+
+  // Returns whether function has a where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  Identifier get_identifier () const { return function_name; }
+
+  // Mega-constructor
+  TraitFunctionDecl (Identifier function_name, FunctionQualifiers qualifiers,
+		     std::vector<std::unique_ptr<GenericParam>> generic_params,
+		     std::vector<FunctionParam> function_params,
+		     std::unique_ptr<Type> return_type,
+		     WhereClause where_clause)
+    : qualifiers (std::move (qualifiers)),
+      function_name (std::move (function_name)),
+      generic_params (std::move (generic_params)),
+      function_params (std::move (function_params)),
+      return_type (std::move (return_type)),
+      where_clause (std::move (where_clause))
+  {}
+
+  // Copy constructor with clone
+  TraitFunctionDecl (TraitFunctionDecl const &other)
+    : qualifiers (other.qualifiers), function_name (other.function_name),
+      function_params (other.function_params), where_clause (other.where_clause)
+  {
+    // guard to prevent nullptr dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  ~TraitFunctionDecl () = default;
+
+  // Overloaded assignment operator with clone
+  TraitFunctionDecl &operator= (TraitFunctionDecl const &other)
+  {
+    function_name = other.function_name;
+    qualifiers = other.qualifiers;
+    function_params = other.function_params;
+    where_clause = other.where_clause;
+
+    // guard to prevent nullptr dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+    else
+      return_type = nullptr;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  TraitFunctionDecl (TraitFunctionDecl &&other) = default;
+  TraitFunctionDecl &operator= (TraitFunctionDecl &&other) = default;
+
+  std::string as_string () const;
+
+  // Invalid if function name is empty, so base stripping on that.
+  void mark_for_strip () { function_name = ""; }
+  bool is_marked_for_strip () const { return function_name.empty (); }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<FunctionParam> &get_function_params () { return function_params; }
+  const std::vector<FunctionParam> &get_function_params () const
+  {
+    return function_params;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_return_type () { return return_type; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  FunctionQualifiers get_qualifiers () { return qualifiers; }
+};
+
+// Actual trait item function declaration within traits
+class TraitItemFunc : public TraitItem
+{
+  std::vector<Attribute> outer_attrs;
+  TraitFunctionDecl decl;
+  std::unique_ptr<BlockExpr> block_expr;
+  Location locus;
+
+public:
+  // Returns whether function has a definition or is just a declaration.
+  bool has_definition () const { return block_expr != nullptr; }
+
+  TraitItemFunc (TraitFunctionDecl decl, std::unique_ptr<BlockExpr> block_expr,
+		 std::vector<Attribute> outer_attrs, Location locus)
+    : TraitItem (), outer_attrs (std::move (outer_attrs)),
+      decl (std::move (decl)), block_expr (std::move (block_expr)),
+      locus (locus)
+  {}
+
+  // Copy constructor with clone
+  TraitItemFunc (TraitItemFunc const &other)
+    : outer_attrs (other.outer_attrs), decl (other.decl), locus (other.locus)
+  {
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.block_expr != nullptr)
+      block_expr = other.block_expr->clone_block_expr ();
+  }
+
+  // Overloaded assignment operator to clone
+  TraitItemFunc &operator= (TraitItemFunc const &other)
+  {
+    TraitItem::operator= (other);
+    outer_attrs = other.outer_attrs;
+    decl = other.decl;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.block_expr != nullptr)
+      block_expr = other.block_expr->clone_block_expr ();
+    else
+      block_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  TraitItemFunc (TraitItemFunc &&other) = default;
+  TraitItemFunc &operator= (TraitItemFunc &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if trait decl is empty, so base stripping on that.
+  void mark_for_strip () override { decl.mark_for_strip (); }
+  bool is_marked_for_strip () const override
+  {
+    return decl.is_marked_for_strip ();
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_definition () { return block_expr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  TraitFunctionDecl &get_trait_function_decl ()
+  {
+    // TODO: maybe only allow access if not marked for strip?
+    return decl;
+  }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  TraitItemFunc *clone_trait_item_impl () const override
+  {
+    return new TraitItemFunc (*this);
+  }
+};
+
+// Method declaration within traits
+struct TraitMethodDecl
+{
+private:
+  // TODO: delete and replace with Function decl item? no as no body.
+  FunctionQualifiers qualifiers;
+  Identifier function_name;
+
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  SelfParam self_param;
+
+  // bool has_params;
+  // FunctionParams function_params;
+  std::vector<FunctionParam> function_params; // inlined
+
+  // bool has_return_type;
+  std::unique_ptr<Type> return_type;
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+  // should this store location info?
+
+public:
+  // Returns whether method decl has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether method decl has regular parameters.
+  bool has_params () const { return !function_params.empty (); }
+
+  // Returns whether method has return type (otherwise is void).
+  bool has_return_type () const { return return_type != nullptr; }
+
+  // Returns whether method has a where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  Identifier get_identifier () const { return function_name; }
+
+  // Mega-constructor
+  TraitMethodDecl (Identifier function_name, FunctionQualifiers qualifiers,
+		   std::vector<std::unique_ptr<GenericParam>> generic_params,
+		   SelfParam self_param,
+		   std::vector<FunctionParam> function_params,
+		   std::unique_ptr<Type> return_type, WhereClause where_clause)
+    : qualifiers (std::move (qualifiers)),
+      function_name (std::move (function_name)),
+      generic_params (std::move (generic_params)),
+      self_param (std::move (self_param)),
+      function_params (std::move (function_params)),
+      return_type (std::move (return_type)),
+      where_clause (std::move (where_clause))
+  {}
+
+  // Copy constructor with clone
+  TraitMethodDecl (TraitMethodDecl const &other)
+    : qualifiers (other.qualifiers), function_name (other.function_name),
+      self_param (other.self_param), function_params (other.function_params),
+      where_clause (other.where_clause)
+  {
+    // guard to prevent nullptr dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  ~TraitMethodDecl () = default;
+
+  // Overloaded assignment operator with clone
+  TraitMethodDecl &operator= (TraitMethodDecl const &other)
+  {
+    function_name = other.function_name;
+    qualifiers = other.qualifiers;
+    self_param = other.self_param;
+    function_params = other.function_params;
+    where_clause = other.where_clause;
+
+    // guard to prevent nullptr dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+    else
+      return_type = nullptr;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  TraitMethodDecl (TraitMethodDecl &&other) = default;
+  TraitMethodDecl &operator= (TraitMethodDecl &&other) = default;
+
+  std::string as_string () const;
+
+  // Invalid if method name is empty, so base stripping on that.
+  void mark_for_strip () { function_name = ""; }
+  bool is_marked_for_strip () const { return function_name.empty (); }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<FunctionParam> &get_function_params () { return function_params; }
+  const std::vector<FunctionParam> &get_function_params () const
+  {
+    return function_params;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_return_type () { return return_type; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  SelfParam &get_self_param () { return self_param; }
+  const SelfParam &get_self_param () const { return self_param; }
+
+  FunctionQualifiers get_qualifiers () { return qualifiers; }
+};
+
+// Actual trait item method declaration within traits
+class TraitItemMethod : public TraitItem
+{
+  std::vector<Attribute> outer_attrs;
+  TraitMethodDecl decl;
+  std::unique_ptr<BlockExpr> block_expr;
+  Location locus;
+
+public:
+  // Returns whether method has a definition or is just a declaration.
+  bool has_definition () const { return block_expr != nullptr; }
+
+  TraitItemMethod (TraitMethodDecl decl, std::unique_ptr<BlockExpr> block_expr,
+		   std::vector<Attribute> outer_attrs, Location locus)
+    : TraitItem (), outer_attrs (std::move (outer_attrs)),
+      decl (std::move (decl)), block_expr (std::move (block_expr)),
+      locus (locus)
+  {}
+
+  // Copy constructor with clone
+  TraitItemMethod (TraitItemMethod const &other)
+    : outer_attrs (other.outer_attrs), decl (other.decl), locus (other.locus)
+  {
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.block_expr != nullptr)
+      block_expr = other.block_expr->clone_block_expr ();
+  }
+
+  // Overloaded assignment operator to clone
+  TraitItemMethod &operator= (TraitItemMethod const &other)
+  {
+    TraitItem::operator= (other);
+    outer_attrs = other.outer_attrs;
+    decl = other.decl;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.block_expr != nullptr)
+      block_expr = other.block_expr->clone_block_expr ();
+    else
+      block_expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  TraitItemMethod (TraitItemMethod &&other) = default;
+  TraitItemMethod &operator= (TraitItemMethod &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if trait decl is empty, so base stripping on that.
+  void mark_for_strip () override { decl.mark_for_strip (); }
+  bool is_marked_for_strip () const override
+  {
+    return decl.is_marked_for_strip ();
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  TraitMethodDecl &get_trait_method_decl ()
+  {
+    // TODO: maybe only allow access if not marked for strip?
+    return decl;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<BlockExpr> &get_definition () { return block_expr; }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  TraitItemMethod *clone_trait_item_impl () const override
+  {
+    return new TraitItemMethod (*this);
+  }
+};
+
+// Constant item within traits
+class TraitItemConst : public TraitItem
+{
+  std::vector<Attribute> outer_attrs;
+  Identifier name;
+  std::unique_ptr<Type> type;
+
+  // bool has_expression;
+  std::unique_ptr<Expr> expr;
+
+  Location locus;
+
+public:
+  // Whether the constant item has an associated expression.
+  bool has_expression () const { return expr != nullptr; }
+
+  TraitItemConst (Identifier name, std::unique_ptr<Type> type,
+		  std::unique_ptr<Expr> expr,
+		  std::vector<Attribute> outer_attrs, Location locus)
+    : TraitItem (), outer_attrs (std::move (outer_attrs)),
+      name (std::move (name)), type (std::move (type)), expr (std::move (expr)),
+      locus (locus)
+  {}
+
+  // Copy constructor with clones
+  TraitItemConst (TraitItemConst const &other)
+    : outer_attrs (other.outer_attrs), name (other.name), locus (other.locus)
+  {
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr ();
+
+    // guard to prevent null dereference (only for error state)
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+  }
+
+  // Overloaded assignment operator to clone
+  TraitItemConst &operator= (TraitItemConst const &other)
+  {
+    TraitItem::operator= (other);
+    outer_attrs = other.outer_attrs;
+    name = other.name;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr ();
+    else
+      expr = nullptr;
+
+    // guard to prevent null dereference (only for error state)
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  TraitItemConst (TraitItemConst &&other) = default;
+  TraitItemConst &operator= (TraitItemConst &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if type is null, so base stripping on that.
+  void mark_for_strip () override { type = nullptr; }
+  bool is_marked_for_strip () const override { return type == nullptr; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  bool has_expr () const { return expr != nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_expr ()
+  {
+    rust_assert (has_expr ());
+    return expr;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+
+  Identifier get_identifier () const { return name; }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  TraitItemConst *clone_trait_item_impl () const override
+  {
+    return new TraitItemConst (*this);
+  }
+};
+
+// Type items within traits
+class TraitItemType : public TraitItem
+{
+  std::vector<Attribute> outer_attrs;
+
+  Identifier name;
+
+  // bool has_type_param_bounds;
+  // TypeParamBounds type_param_bounds;
+  std::vector<std::unique_ptr<TypeParamBound>>
+    type_param_bounds; // inlined form
+
+  Location locus;
+
+public:
+  // Returns whether trait item type has type param bounds.
+  bool has_type_param_bounds () const { return !type_param_bounds.empty (); }
+
+  TraitItemType (Identifier name,
+		 std::vector<std::unique_ptr<TypeParamBound>> type_param_bounds,
+		 std::vector<Attribute> outer_attrs, Location locus)
+    : TraitItem (), outer_attrs (std::move (outer_attrs)),
+      name (std::move (name)),
+      type_param_bounds (std::move (type_param_bounds)), locus (locus)
+  {}
+
+  // Copy constructor with vector clone
+  TraitItemType (TraitItemType const &other)
+    : outer_attrs (other.outer_attrs), name (other.name), locus (other.locus)
+  {
+    node_id = other.node_id;
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  TraitItemType &operator= (TraitItemType const &other)
+  {
+    TraitItem::operator= (other);
+    outer_attrs = other.outer_attrs;
+    name = other.name;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+
+    return *this;
+  }
+
+  // default move constructors
+  TraitItemType (TraitItemType &&other) = default;
+  TraitItemType &operator= (TraitItemType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if name is empty, so base stripping on that.
+  void mark_for_strip () override { name = ""; }
+  bool is_marked_for_strip () const override { return name.empty (); }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: mutable getter seems kinda dodgy
+  std::vector<std::unique_ptr<TypeParamBound>> &get_type_param_bounds ()
+  {
+    return type_param_bounds;
+  }
+  const std::vector<std::unique_ptr<TypeParamBound>> &
+  get_type_param_bounds () const
+  {
+    return type_param_bounds;
+  }
+
+  Identifier get_identifier () const { return name; }
+
+protected:
+  // Clone function implementation as (not pure) virtual method
+  TraitItemType *clone_trait_item_impl () const override
+  {
+    return new TraitItemType (*this);
+  }
+};
+
+// Rust trait item declaration AST node
+class Trait : public VisItem
+{
+  bool has_unsafe;
+  Identifier name;
+  std::vector<std::unique_ptr<GenericParam>> generic_params;
+  std::vector<std::unique_ptr<TypeParamBound>> type_param_bounds;
+  WhereClause where_clause;
+  std::vector<Attribute> inner_attrs;
+  std::vector<std::unique_ptr<TraitItem>> trait_items;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether trait has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether trait has type parameter bounds.
+  bool has_type_param_bounds () const { return !type_param_bounds.empty (); }
+
+  // Returns whether trait has where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  // Returns whether trait has trait items.
+  bool has_trait_items () const { return !trait_items.empty (); }
+
+  // Returns whether trait has inner attributes.
+  bool has_inner_attrs () const { return !inner_attrs.empty (); }
+
+  Identifier get_identifier () const { return name; }
+
+  bool is_unsafe () const { return has_unsafe; }
+
+  // Mega-constructor
+  Trait (Identifier name, bool is_unsafe,
+	 std::vector<std::unique_ptr<GenericParam>> generic_params,
+	 std::vector<std::unique_ptr<TypeParamBound>> type_param_bounds,
+	 WhereClause where_clause,
+	 std::vector<std::unique_ptr<TraitItem>> trait_items, Visibility vis,
+	 std::vector<Attribute> outer_attrs, std::vector<Attribute> inner_attrs,
+	 Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      has_unsafe (is_unsafe), name (std::move (name)),
+      generic_params (std::move (generic_params)),
+      type_param_bounds (std::move (type_param_bounds)),
+      where_clause (std::move (where_clause)),
+      inner_attrs (std::move (inner_attrs)),
+      trait_items (std::move (trait_items)), locus (locus)
+  {}
+
+  // Copy constructor with vector clone
+  Trait (Trait const &other)
+    : VisItem (other), has_unsafe (other.has_unsafe), name (other.name),
+      where_clause (other.where_clause), inner_attrs (other.inner_attrs),
+      locus (other.locus)
+  {
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+
+    trait_items.reserve (other.trait_items.size ());
+    for (const auto &e : other.trait_items)
+      trait_items.push_back (e->clone_trait_item ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  Trait &operator= (Trait const &other)
+  {
+    VisItem::operator= (other);
+    name = other.name;
+    has_unsafe = other.has_unsafe;
+    where_clause = other.where_clause;
+    inner_attrs = other.inner_attrs;
+    locus = other.locus;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+
+    trait_items.reserve (other.trait_items.size ());
+    for (const auto &e : other.trait_items)
+      trait_items.push_back (e->clone_trait_item ());
+
+    return *this;
+  }
+
+  // default move constructors
+  Trait (Trait &&other) = default;
+  Trait &operator= (Trait &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if trait name is empty, so base stripping on that.
+  void mark_for_strip () override { name = ""; }
+  bool is_marked_for_strip () const override { return name.empty (); }
+
+  // TODO: think of better way to do this
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  const std::vector<std::unique_ptr<TraitItem>> &get_trait_items () const
+  {
+    return trait_items;
+  }
+  std::vector<std::unique_ptr<TraitItem>> &get_trait_items ()
+  {
+    return trait_items;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  std::vector<std::unique_ptr<TypeParamBound>> &get_type_param_bounds ()
+  {
+    return type_param_bounds;
+  }
+  const std::vector<std::unique_ptr<TypeParamBound>> &
+  get_type_param_bounds () const
+  {
+    return type_param_bounds;
+  }
+
+  WhereClause &get_where_clause () { return where_clause; }
+
+  void insert_implict_self (std::unique_ptr<AST::GenericParam> &&param)
+  {
+    std::vector<std::unique_ptr<GenericParam>> new_list;
+    new_list.reserve (generic_params.size () + 1);
+
+    new_list.push_back (std::move (param));
+    for (auto &p : generic_params)
+      {
+	new_list.push_back (std::move (p));
+      }
+
+    generic_params = std::move (new_list);
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  Trait *clone_item_impl () const override { return new Trait (*this); }
+};
+
+// Implementation item declaration AST node - abstract base class
+class Impl : public VisItem
+{
+  // must be protected to allow subclasses to access them properly
+protected:
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  std::unique_ptr<Type> trait_type;
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+  // bool has_inner_attrs;
+  std::vector<Attribute> inner_attrs;
+
+private:
+  // doesn't really need to be protected as write access probably not needed
+  Location locus;
+
+public:
+  // Returns whether impl has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether impl has where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  // Returns whether impl has inner attributes.
+  bool has_inner_attrs () const { return !inner_attrs.empty (); }
+
+  Location get_locus () const override final { return locus; }
+
+  // Invalid if trait type is null, so base stripping on that.
+  void mark_for_strip () override { trait_type = nullptr; }
+  bool is_marked_for_strip () const override { return trait_type == nullptr; }
+
+  // TODO: think of better way to do this
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (trait_type != nullptr);
+    return trait_type;
+  }
+
+protected:
+  // Mega-constructor
+  Impl (std::vector<std::unique_ptr<GenericParam>> generic_params,
+	std::unique_ptr<Type> trait_type, WhereClause where_clause,
+	Visibility vis, std::vector<Attribute> inner_attrs,
+	std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)),
+      generic_params (std::move (generic_params)),
+      trait_type (std::move (trait_type)),
+      where_clause (std::move (where_clause)),
+      inner_attrs (std::move (inner_attrs)), locus (locus)
+  {}
+
+  // Copy constructor
+  Impl (Impl const &other)
+    : VisItem (other), where_clause (other.where_clause),
+      inner_attrs (other.inner_attrs), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.trait_type != nullptr)
+      trait_type = other.trait_type->clone_type ();
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  // Assignment operator overload with cloning
+  Impl &operator= (Impl const &other)
+  {
+    VisItem::operator= (other);
+    where_clause = other.where_clause;
+    inner_attrs = other.inner_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.trait_type != nullptr)
+      trait_type = other.trait_type->clone_type ();
+    else
+      trait_type = nullptr;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  Impl (Impl &&other) = default;
+  Impl &operator= (Impl &&other) = default;
+};
+
+// Regular "impl foo" impl block declaration AST node
+class InherentImpl : public Impl
+{
+  // bool has_impl_items;
+  std::vector<std::unique_ptr<InherentImplItem>> impl_items;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether inherent impl block has inherent impl items.
+  bool has_impl_items () const { return !impl_items.empty (); }
+
+  // Mega-constructor
+  InherentImpl (std::vector<std::unique_ptr<InherentImplItem>> impl_items,
+		std::vector<std::unique_ptr<GenericParam>> generic_params,
+		std::unique_ptr<Type> trait_type, WhereClause where_clause,
+		Visibility vis, std::vector<Attribute> inner_attrs,
+		std::vector<Attribute> outer_attrs, Location locus)
+    : Impl (std::move (generic_params), std::move (trait_type),
+	    std::move (where_clause), std::move (vis), std::move (inner_attrs),
+	    std::move (outer_attrs), locus),
+      impl_items (std::move (impl_items))
+  {}
+
+  // Copy constructor with vector clone
+  InherentImpl (InherentImpl const &other) : Impl (other)
+  {
+    impl_items.reserve (other.impl_items.size ());
+    for (const auto &e : other.impl_items)
+      impl_items.push_back (e->clone_inherent_impl_item ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  InherentImpl &operator= (InherentImpl const &other)
+  {
+    Impl::operator= (other);
+
+    impl_items.reserve (other.impl_items.size ());
+    for (const auto &e : other.impl_items)
+      impl_items.push_back (e->clone_inherent_impl_item ());
+
+    return *this;
+  }
+
+  // default move constructors
+  InherentImpl (InherentImpl &&other) = default;
+  InherentImpl &operator= (InherentImpl &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: think of better way to do this
+  const std::vector<std::unique_ptr<InherentImplItem>> &get_impl_items () const
+  {
+    return impl_items;
+  }
+  std::vector<std::unique_ptr<InherentImplItem>> &get_impl_items ()
+  {
+    return impl_items;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  InherentImpl *clone_item_impl () const override
+  {
+    return new InherentImpl (*this);
+  }
+};
+
+// The "impl footrait for foo" impl block declaration AST node
+class TraitImpl : public Impl
+{
+  bool has_unsafe;
+  bool has_exclam;
+  TypePath trait_path;
+
+  // bool has_impl_items;
+  std::vector<std::unique_ptr<TraitImplItem>> impl_items;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether trait impl has impl items.
+  bool has_impl_items () const { return !impl_items.empty (); }
+
+  // Mega-constructor
+  TraitImpl (TypePath trait_path, bool is_unsafe, bool has_exclam,
+	     std::vector<std::unique_ptr<TraitImplItem>> impl_items,
+	     std::vector<std::unique_ptr<GenericParam>> generic_params,
+	     std::unique_ptr<Type> trait_type, WhereClause where_clause,
+	     Visibility vis, std::vector<Attribute> inner_attrs,
+	     std::vector<Attribute> outer_attrs, Location locus)
+    : Impl (std::move (generic_params), std::move (trait_type),
+	    std::move (where_clause), std::move (vis), std::move (inner_attrs),
+	    std::move (outer_attrs), locus),
+      has_unsafe (is_unsafe), has_exclam (has_exclam),
+      trait_path (std::move (trait_path)), impl_items (std::move (impl_items))
+  {}
+
+  // Copy constructor with vector clone
+  TraitImpl (TraitImpl const &other)
+    : Impl (other), has_unsafe (other.has_unsafe),
+      has_exclam (other.has_exclam), trait_path (other.trait_path)
+  {
+    impl_items.reserve (other.impl_items.size ());
+    for (const auto &e : other.impl_items)
+      impl_items.push_back (e->clone_trait_impl_item ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  TraitImpl &operator= (TraitImpl const &other)
+  {
+    Impl::operator= (other);
+    trait_path = other.trait_path;
+    has_unsafe = other.has_unsafe;
+    has_exclam = other.has_exclam;
+
+    impl_items.reserve (other.impl_items.size ());
+    for (const auto &e : other.impl_items)
+      impl_items.push_back (e->clone_trait_impl_item ());
+
+    return *this;
+  }
+
+  // move constructors
+  TraitImpl (TraitImpl &&other) = default;
+  TraitImpl &operator= (TraitImpl &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool is_unsafe () const { return has_unsafe; };
+  bool is_exclam () const { return has_exclam; }
+
+  // TODO: think of better way to do this
+  const std::vector<std::unique_ptr<TraitImplItem>> &get_impl_items () const
+  {
+    return impl_items;
+  }
+  std::vector<std::unique_ptr<TraitImplItem>> &get_impl_items ()
+  {
+    return impl_items;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  TypePath &get_trait_path ()
+  {
+    // TODO: assert that trait path is not empty?
+    return trait_path;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  TraitImpl *clone_item_impl () const override { return new TraitImpl (*this); }
+};
+
+#if 0
+// Abstract base class for an item used inside an extern block
+class ExternalItem
+{
+  // bool has_outer_attrs;
+  std::vector<Attribute> outer_attrs;
+
+  // bool has_visibility;
+  Visibility visibility;
+
+  Identifier item_name;
+  Location locus;
+
+public:
+  virtual ~ExternalItem () {}
+
+  /* TODO: spec syntax rules state that "MacroInvocationSemi" can be used as 
+   * ExternalItem, but text body isn't so clear. Adding MacroInvocationSemi 
+   * support would require a lot of refactoring. */
+
+  // Returns whether item has outer attributes.
+  bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+  // Returns whether item has non-default visibility.
+  bool has_visibility () const { return !visibility.is_error (); }
+
+  // Unique pointer custom clone function
+  std::unique_ptr<ExternalItem> clone_external_item () const
+  {
+    return std::unique_ptr<ExternalItem> (clone_external_item_impl ());
+  }
+
+  virtual std::string as_string () const;
+
+  Location get_locus () const override final { return locus; }
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  // TODO: make virtual? Would be more flexible.
+  // Based on idea that name should never be empty.
+  void mark_for_strip () { item_name = ""; };
+  bool is_marked_for_strip () const { return item_name.empty (); };
+
+protected:
+  ExternalItem (Identifier item_name, Visibility vis,
+		std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)), visibility (std::move (vis)),
+      item_name (std::move (item_name)), locus (locus)
+  {}
+
+  // Copy constructor
+  ExternalItem (ExternalItem const &other)
+    : outer_attrs (other.outer_attrs), visibility (other.visibility),
+      item_name (other.item_name), locus (other.locus)
+  {}
+
+  // Overloaded assignment operator to clone
+  ExternalItem &operator= (ExternalItem const &other)
+  {
+    item_name = other.item_name;
+    visibility = other.visibility;
+    outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    return *this;
+  }
+
+  // move constructors
+  ExternalItem (ExternalItem &&other) = default;
+  ExternalItem &operator= (ExternalItem &&other) = default;
+
+  // Clone function implementation as pure virtual method
+  virtual ExternalItem *clone_external_item_impl () const = 0;
+
+  // possibly make this public if required
+  std::string get_item_name () const { return item_name; }
+};
+#endif
+
+// A static item used in an extern block
+class ExternalStaticItem : public ExternalItem
+{
+  // bool has_outer_attrs;
+  std::vector<Attribute> outer_attrs;
+
+  // bool has_visibility;
+  Visibility visibility;
+
+  Identifier item_name;
+  Location locus;
+
+  bool has_mut;
+  std::unique_ptr<Type> item_type;
+
+public:
+  ExternalStaticItem (Identifier item_name, std::unique_ptr<Type> item_type,
+		      bool is_mut, Visibility vis,
+		      std::vector<Attribute> outer_attrs, Location locus)
+    : ExternalItem (), outer_attrs (std::move (outer_attrs)),
+      visibility (std::move (vis)), item_name (std::move (item_name)),
+      locus (locus), has_mut (is_mut), item_type (std::move (item_type))
+  {}
+
+  // Copy constructor
+  ExternalStaticItem (ExternalStaticItem const &other)
+    : outer_attrs (other.outer_attrs), visibility (other.visibility),
+      item_name (other.item_name), locus (other.locus), has_mut (other.has_mut)
+  {
+    node_id = other.node_id;
+    // guard to prevent null dereference (only required if error state)
+    if (other.item_type != nullptr)
+      item_type = other.item_type->clone_type ();
+  }
+
+  // Overloaded assignment operator to clone
+  ExternalStaticItem &operator= (ExternalStaticItem const &other)
+  {
+    node_id = other.node_id;
+    outer_attrs = other.outer_attrs;
+    visibility = other.visibility;
+    item_name = other.item_name;
+    locus = other.locus;
+    has_mut = other.has_mut;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.item_type != nullptr)
+      item_type = other.item_type->clone_type ();
+    else
+      item_type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ExternalStaticItem (ExternalStaticItem &&other) = default;
+  ExternalStaticItem &operator= (ExternalStaticItem &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Returns whether item has outer attributes.
+  bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+  // Returns whether item has non-default visibility.
+  bool has_visibility () const { return !visibility.is_error (); }
+
+  Location get_locus () const { return locus; }
+
+  // Based on idea that type should never be null.
+  void mark_for_strip () override { item_type = nullptr; };
+  bool is_marked_for_strip () const override { return item_type == nullptr; };
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (item_type != nullptr);
+    return item_type;
+  }
+
+  Identifier get_identifier () const { return item_name; }
+
+  const Visibility &get_visibility () const { return visibility; }
+
+  bool is_mut () const { return has_mut; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  ExternalStaticItem *clone_external_item_impl () const override
+  {
+    return new ExternalStaticItem (*this);
+  }
+};
+
+// A named function parameter used in external functions
+struct NamedFunctionParam
+{
+private:
+  // bool has_name;   // otherwise is _
+  std::string name;
+
+  std::unique_ptr<Type> param_type;
+
+  // seemingly new since writing this node
+  std::vector<Attribute> outer_attrs;
+
+  NodeId node_id;
+  Location locus;
+
+public:
+  /* Returns whether the named function parameter has a name (i.e. name is not
+   * '_'). */
+  bool has_name () const { return name != "_"; }
+
+  bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+  // Returns whether the named function parameter is in an error state.
+  bool is_error () const
+  {
+    // also if identifier is "" but that is probably more costly to compute
+    return param_type == nullptr;
+  }
+
+  std::string get_name () const { return name; }
+
+  // Creates an error state named function parameter.
+  static NamedFunctionParam create_error ()
+  {
+    return NamedFunctionParam ("", nullptr, {}, Location ());
+  }
+
+  NamedFunctionParam (std::string name, std::unique_ptr<Type> param_type,
+		      std::vector<Attribute> outer_attrs, Location locus)
+    : name (std::move (name)), param_type (std::move (param_type)),
+      outer_attrs (std::move (outer_attrs)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()), locus (locus)
+  {}
+
+  // Copy constructor
+  NamedFunctionParam (NamedFunctionParam const &other)
+    : name (other.name), outer_attrs (other.outer_attrs)
+  {
+    node_id = other.node_id;
+    // guard to prevent null dereference (only required if error state)
+    if (other.param_type != nullptr)
+      param_type = other.param_type->clone_type ();
+  }
+
+  ~NamedFunctionParam () = default;
+
+  // Overloaded assignment operator to clone
+  NamedFunctionParam &operator= (NamedFunctionParam const &other)
+  {
+    node_id = other.node_id;
+    name = other.name;
+    // has_name = other.has_name;
+    outer_attrs = other.outer_attrs;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.param_type != nullptr)
+      param_type = other.param_type->clone_type ();
+    else
+      param_type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  NamedFunctionParam (NamedFunctionParam &&other) = default;
+  NamedFunctionParam &operator= (NamedFunctionParam &&other) = default;
+
+  std::string as_string () const;
+
+  // Based on idea that nane should never be empty.
+  void mark_for_strip () { param_type = nullptr; };
+  bool is_marked_for_strip () const { return is_error (); };
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (param_type != nullptr);
+    return param_type;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+};
+
+// A function item used in an extern block
+class ExternalFunctionItem : public ExternalItem
+{
+  // bool has_outer_attrs;
+  std::vector<Attribute> outer_attrs;
+
+  // bool has_visibility;
+  Visibility visibility;
+
+  Identifier item_name;
+  Location locus;
+
+  // bool has_generics;
+  // Generics generic_params;
+  std::vector<std::unique_ptr<GenericParam>> generic_params; // inlined
+
+  // bool has_return_type;
+  // FunctionReturnType return_type;
+  std::unique_ptr<Type> return_type; // inlined
+
+  // bool has_where_clause;
+  WhereClause where_clause;
+
+  std::vector<NamedFunctionParam> function_params;
+  bool has_variadics;
+  std::vector<Attribute> variadic_outer_attrs;
+
+public:
+  // Returns whether item has generic parameters.
+  bool has_generics () const { return !generic_params.empty (); }
+
+  // Returns whether item has a return type (otherwise void).
+  bool has_return_type () const { return return_type != nullptr; }
+
+  // Returns whether item has a where clause.
+  bool has_where_clause () const { return !where_clause.is_empty (); }
+
+  // Returns whether item has outer attributes.
+  bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+  // Returns whether item has non-default visibility.
+  bool has_visibility () const { return !visibility.is_error (); }
+
+  // Returns whether item has variadic parameters.
+  bool is_variadic () const { return has_variadics; }
+
+  // Returns whether item has outer attributes on its variadic parameters.
+  bool has_variadic_outer_attrs () const
+  {
+    return !variadic_outer_attrs.empty ();
+  }
+
+  Location get_locus () const { return locus; }
+
+  const Visibility &get_visibility () const { return visibility; }
+
+  ExternalFunctionItem (
+    Identifier item_name,
+    std::vector<std::unique_ptr<GenericParam>> generic_params,
+    std::unique_ptr<Type> return_type, WhereClause where_clause,
+    std::vector<NamedFunctionParam> function_params, bool has_variadics,
+    std::vector<Attribute> variadic_outer_attrs, Visibility vis,
+    std::vector<Attribute> outer_attrs, Location locus)
+    : ExternalItem (), outer_attrs (std::move (outer_attrs)),
+      visibility (std::move (vis)), item_name (std::move (item_name)),
+      locus (locus), generic_params (std::move (generic_params)),
+      return_type (std::move (return_type)),
+      where_clause (std::move (where_clause)),
+      function_params (std::move (function_params)),
+      has_variadics (has_variadics),
+      variadic_outer_attrs (std::move (variadic_outer_attrs))
+  {
+    // TODO: assert that if has variadic outer attrs, then has_variadics is
+    // true?
+  }
+
+  // Copy constructor with clone
+  ExternalFunctionItem (ExternalFunctionItem const &other)
+    : outer_attrs (other.outer_attrs), visibility (other.visibility),
+      item_name (other.item_name), locus (other.locus),
+      where_clause (other.where_clause),
+      function_params (other.function_params),
+      has_variadics (other.has_variadics),
+      variadic_outer_attrs (other.variadic_outer_attrs)
+  {
+    node_id = other.node_id;
+    // guard to prevent null pointer dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+  }
+
+  // Overloaded assignment operator with clone
+  ExternalFunctionItem &operator= (ExternalFunctionItem const &other)
+  {
+    outer_attrs = other.outer_attrs;
+    visibility = other.visibility;
+    item_name = other.item_name;
+    locus = other.locus;
+    where_clause = other.where_clause;
+    function_params = other.function_params;
+    has_variadics = other.has_variadics;
+    variadic_outer_attrs = other.variadic_outer_attrs;
+    node_id = other.node_id;
+
+    // guard to prevent null pointer dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+    else
+      return_type = nullptr;
+
+    generic_params.reserve (other.generic_params.size ());
+    for (const auto &e : other.generic_params)
+      generic_params.push_back (e->clone_generic_param ());
+
+    return *this;
+  }
+
+  // move constructors
+  ExternalFunctionItem (ExternalFunctionItem &&other) = default;
+  ExternalFunctionItem &operator= (ExternalFunctionItem &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Based on idea that nane should never be empty.
+  void mark_for_strip () override { item_name = ""; };
+  bool is_marked_for_strip () const override { return item_name.empty (); };
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  std::vector<NamedFunctionParam> &get_function_params ()
+  {
+    return function_params;
+  }
+  const std::vector<NamedFunctionParam> &get_function_params () const
+  {
+    return function_params;
+  }
+
+  std::vector<std::unique_ptr<GenericParam>> &get_generic_params ()
+  {
+    return generic_params;
+  }
+  const std::vector<std::unique_ptr<GenericParam>> &get_generic_params () const
+  {
+    return generic_params;
+  }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  WhereClause &get_where_clause () { return where_clause; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Type> &get_return_type ()
+  {
+    rust_assert (has_return_type ());
+    return return_type;
+  }
+
+  Identifier get_identifier () const { return item_name; };
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  ExternalFunctionItem *clone_external_item_impl () const override
+  {
+    return new ExternalFunctionItem (*this);
+  }
+};
+
+// An extern block AST node
+class ExternBlock : public VisItem
+{
+  // bool has_abi;
+  std::string abi;
+
+  // bool has_inner_attrs;
+  std::vector<Attribute> inner_attrs;
+
+  // bool has_extern_items;
+  std::vector<std::unique_ptr<ExternalItem>> extern_items;
+
+  Location locus;
+
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether extern block has inner attributes.
+  bool has_inner_attrs () const { return !inner_attrs.empty (); }
+
+  // Returns whether extern block has extern items.
+  bool has_extern_items () const { return !extern_items.empty (); }
+
+  // Returns whether extern block has ABI name.
+  bool has_abi () const { return !abi.empty (); }
+
+  std::string get_abi () const { return abi; }
+
+  ExternBlock (std::string abi,
+	       std::vector<std::unique_ptr<ExternalItem>> extern_items,
+	       Visibility vis, std::vector<Attribute> inner_attrs,
+	       std::vector<Attribute> outer_attrs, Location locus)
+    : VisItem (std::move (vis), std::move (outer_attrs)), abi (std::move (abi)),
+      inner_attrs (std::move (inner_attrs)),
+      extern_items (std::move (extern_items)), locus (locus)
+  {}
+
+  // Copy constructor with vector clone
+  ExternBlock (ExternBlock const &other)
+    : VisItem (other), abi (other.abi), inner_attrs (other.inner_attrs),
+      locus (other.locus), marked_for_strip (other.marked_for_strip)
+  {
+    extern_items.reserve (other.extern_items.size ());
+    for (const auto &e : other.extern_items)
+      extern_items.push_back (e->clone_external_item ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  ExternBlock &operator= (ExternBlock const &other)
+  {
+    VisItem::operator= (other);
+    abi = other.abi;
+    inner_attrs = other.inner_attrs;
+    locus = other.locus;
+    marked_for_strip = other.marked_for_strip;
+
+    extern_items.reserve (other.extern_items.size ());
+    for (const auto &e : other.extern_items)
+      extern_items.push_back (e->clone_external_item ());
+
+    return *this;
+  }
+
+  // move constructors
+  ExternBlock (ExternBlock &&other) = default;
+  ExternBlock &operator= (ExternBlock &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  // TODO: think of better way to do this
+  const std::vector<std::unique_ptr<ExternalItem>> &get_extern_items () const
+  {
+    return extern_items;
+  }
+  std::vector<std::unique_ptr<ExternalItem>> &get_extern_items ()
+  {
+    return extern_items;
+  }
+
+  // TODO: think of better way to do this
+  const std::vector<Attribute> &get_inner_attrs () const { return inner_attrs; }
+  std::vector<Attribute> &get_inner_attrs () { return inner_attrs; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  ExternBlock *clone_item_impl () const override
+  {
+    return new ExternBlock (*this);
+  }
+};
+
+// Replaced with forward decls - defined in "rust-macro.h"
+class MacroItem;
+class MacroRulesDefinition;
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-macro.h b/gcc/rust/ast/rust-macro.h
new file mode 100644
index 00000000000..ce515db0aad
--- /dev/null
+++ b/gcc/rust/ast/rust-macro.h
@@ -0,0 +1,958 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_MACRO_H
+#define RUST_AST_MACRO_H
+
+#include "rust-ast.h"
+#include "rust-location.h"
+#include <string>
+
+namespace Rust {
+namespace AST {
+
+// Decls as definitions moved to rust-ast.h
+class MacroItem;
+
+class MacroFragSpec
+{
+public:
+  enum Kind
+  {
+    BLOCK,
+    EXPR,
+    IDENT,
+    ITEM,
+    LIFETIME,
+    LITERAL,
+    META,
+    PAT,
+    PATH,
+    STMT,
+    TT,
+    TY,
+    VIS,
+    INVALID // not really a specifier, but used to mark invalid one passed in
+  };
+
+  MacroFragSpec (Kind kind) : kind (kind) {}
+
+  static MacroFragSpec get_frag_spec_from_str (const std::string &str)
+  {
+    if (str == "block")
+      return MacroFragSpec (BLOCK);
+    else if (str == "expr")
+      return MacroFragSpec (EXPR);
+    else if (str == "ident")
+      return MacroFragSpec (IDENT);
+    else if (str == "item")
+      return MacroFragSpec (ITEM);
+    else if (str == "lifetime")
+      return MacroFragSpec (LIFETIME);
+    else if (str == "literal")
+      return MacroFragSpec (LITERAL);
+    else if (str == "meta")
+      return MacroFragSpec (META);
+    else if (str == "pat" || str == "pat_param")
+      return MacroFragSpec (PAT);
+    else if (str == "path")
+      return MacroFragSpec (PATH);
+    else if (str == "stmt")
+      return MacroFragSpec (STMT);
+    else if (str == "tt")
+      return MacroFragSpec (TT);
+    else if (str == "ty")
+      return MacroFragSpec (TY);
+    else if (str == "vis")
+      return MacroFragSpec (VIS);
+    else
+      {
+	// error_at("invalid string '%s' used as fragment specifier",
+	// str->c_str()));
+	return MacroFragSpec (INVALID);
+      }
+  }
+
+  Kind get_kind () const { return kind; }
+  bool is_error () const { return kind == Kind::INVALID; }
+
+  // Converts a frag spec enum item to a string form.
+  std::string as_string () const
+  {
+    switch (kind)
+      {
+      case BLOCK:
+	return "block";
+      case EXPR:
+	return "expr";
+      case IDENT:
+	return "ident";
+      case ITEM:
+	return "item";
+      case LIFETIME:
+	return "lifetime";
+      case LITERAL:
+	return "literal";
+      case META:
+	return "meta";
+      case PAT:
+	return "pat";
+      case PATH:
+	return "path";
+      case STMT:
+	return "stmt";
+      case TT:
+	return "tt";
+      case TY:
+	return "ty";
+      case VIS:
+	return "vis";
+      case INVALID:
+	return "INVALID_FRAG_SPEC";
+      default:
+	return "ERROR_MARK_STRING - unknown frag spec";
+      }
+  }
+
+  bool has_follow_set_restrictions () const
+  {
+    switch (kind)
+      {
+      case EXPR:
+      case STMT:
+      case PAT:
+      case PATH:
+      case TY:
+      case VIS:
+	return true;
+      default:
+	return false;
+      }
+  }
+
+  bool has_follow_set_fragment_restrictions () const
+  {
+    switch (kind)
+      {
+      case PAT:
+      case TY:
+      case VIS:
+	return true;
+      default:
+	return false;
+      }
+  }
+
+private:
+  Kind kind;
+};
+
+// A macro match that has an identifier and fragment spec
+class MacroMatchFragment : public MacroMatch
+{
+  Identifier ident;
+  MacroFragSpec frag_spec;
+  Location locus;
+
+public:
+  MacroMatchFragment (Identifier ident, MacroFragSpec frag_spec, Location locus)
+    : ident (std::move (ident)), frag_spec (frag_spec), locus (locus)
+  {}
+
+  // Returns whether macro match fragment is in an error state.
+  bool is_error () const
+  {
+    return frag_spec.get_kind () == MacroFragSpec::INVALID;
+  }
+
+  // Creates an error state macro match fragment.
+  static MacroMatchFragment create_error (Location locus)
+  {
+    return MacroMatchFragment (std::string (""),
+			       MacroFragSpec (MacroFragSpec::Kind::INVALID),
+			       locus);
+  }
+
+  std::string as_string () const override;
+  Location get_match_locus () const override { return locus; };
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  MacroMatchType get_macro_match_type () const override
+  {
+    return MacroMatchType::Fragment;
+  }
+
+  Identifier get_ident () const { return ident; }
+  const MacroFragSpec &get_frag_spec () const { return frag_spec; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MacroMatchFragment *clone_macro_match_impl () const override
+  {
+    return new MacroMatchFragment (*this);
+  }
+};
+
+// A repetition macro match
+class MacroMatchRepetition : public MacroMatch
+{
+public:
+  enum MacroRepOp
+  {
+    NONE,
+    ANY,
+    ONE_OR_MORE,
+    ZERO_OR_ONE,
+  };
+
+private:
+  std::vector<std::unique_ptr<MacroMatch> > matches;
+  MacroRepOp op;
+
+  // bool has_sep;
+  typedef Token MacroRepSep;
+  // any token except delimiters and repetition operators
+  std::unique_ptr<MacroRepSep> sep;
+  Location locus;
+
+public:
+  // Returns whether macro match repetition has separator token.
+  bool has_sep () const { return sep != nullptr; }
+
+  MacroMatchRepetition (std::vector<std::unique_ptr<MacroMatch> > matches,
+			MacroRepOp op, std::unique_ptr<MacroRepSep> sep,
+			Location locus)
+    : matches (std::move (matches)), op (op), sep (std::move (sep)),
+      locus (locus)
+  {}
+
+  // Copy constructor with clone
+  MacroMatchRepetition (MacroMatchRepetition const &other)
+    : op (other.op), locus (other.locus)
+  {
+    // guard to protect from null pointer dereference
+    if (other.sep != nullptr)
+      sep = other.sep->clone_token ();
+
+    matches.reserve (other.matches.size ());
+    for (const auto &e : other.matches)
+      matches.push_back (e->clone_macro_match ());
+  }
+
+  // Overloaded assignment operator to clone
+  MacroMatchRepetition &operator= (MacroMatchRepetition const &other)
+  {
+    op = other.op;
+    locus = other.locus;
+
+    // guard to protect from null pointer dereference
+    if (other.sep != nullptr)
+      sep = other.sep->clone_token ();
+    else
+      sep = nullptr;
+
+    matches.reserve (other.matches.size ());
+    for (const auto &e : other.matches)
+      matches.push_back (e->clone_macro_match ());
+
+    return *this;
+  }
+
+  // move constructors
+  MacroMatchRepetition (MacroMatchRepetition &&other) = default;
+  MacroMatchRepetition &operator= (MacroMatchRepetition &&other) = default;
+
+  std::string as_string () const override;
+  Location get_match_locus () const override { return locus; };
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  MacroMatchType get_macro_match_type () const override
+  {
+    return MacroMatchType::Repetition;
+  }
+
+  MacroRepOp get_op () const { return op; }
+  const std::unique_ptr<MacroRepSep> &get_sep () const { return sep; }
+  std::vector<std::unique_ptr<MacroMatch> > &get_matches () { return matches; }
+  const std::vector<std::unique_ptr<MacroMatch> > &get_matches () const
+  {
+    return matches;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MacroMatchRepetition *clone_macro_match_impl () const override
+  {
+    return new MacroMatchRepetition (*this);
+  }
+};
+
+// can't inline due to polymorphism
+class MacroMatcher : public MacroMatch
+{
+  DelimType delim_type;
+  std::vector<std::unique_ptr<MacroMatch> > matches;
+  Location locus;
+
+  // TODO: think of way to mark invalid that doesn't take up more space
+  bool is_invalid;
+
+public:
+  MacroMatcher (DelimType delim_type,
+		std::vector<std::unique_ptr<MacroMatch> > matches,
+		Location locus)
+    : delim_type (delim_type), matches (std::move (matches)), locus (locus),
+      is_invalid (false)
+  {}
+
+  // copy constructor with vector clone
+  MacroMatcher (MacroMatcher const &other)
+    : delim_type (other.delim_type), locus (other.locus)
+  {
+    matches.reserve (other.matches.size ());
+    for (const auto &e : other.matches)
+      matches.push_back (e->clone_macro_match ());
+  }
+
+  // overloaded assignment operator with vector clone
+  MacroMatcher &operator= (MacroMatcher const &other)
+  {
+    delim_type = other.delim_type;
+    locus = other.locus;
+
+    matches.reserve (other.matches.size ());
+    for (const auto &e : other.matches)
+      matches.push_back (e->clone_macro_match ());
+
+    return *this;
+  }
+
+  // move constructors
+  MacroMatcher (MacroMatcher &&other) = default;
+  MacroMatcher &operator= (MacroMatcher &&other) = default;
+
+  // Creates an error state macro matcher.
+  static MacroMatcher create_error (Location locus)
+  {
+    return MacroMatcher (true, locus);
+  }
+
+  // Returns whether MacroMatcher is in an error state.
+  bool is_error () const { return is_invalid; }
+  Location get_match_locus () const override { return locus; }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  MacroMatchType get_macro_match_type () const override
+  {
+    return MacroMatchType::Matcher;
+  }
+
+  DelimType get_delim_type () const { return delim_type; }
+  std::vector<std::unique_ptr<MacroMatch> > &get_matches () { return matches; }
+  const std::vector<std::unique_ptr<MacroMatch> > &get_matches () const
+  {
+    return matches;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MacroMatcher *clone_macro_match_impl () const override
+  {
+    return new MacroMatcher (*this);
+  }
+
+  // constructor only used to create error matcher
+  MacroMatcher (bool is_invalid, Location locus)
+    : delim_type (PARENS), locus (locus), is_invalid (is_invalid)
+  {}
+};
+
+// TODO: inline?
+struct MacroTranscriber
+{
+private:
+  DelimTokenTree token_tree;
+  Location locus;
+
+public:
+  MacroTranscriber (DelimTokenTree token_tree, Location locus)
+    : token_tree (std::move (token_tree)), locus (locus)
+  {}
+
+  std::string as_string () const { return token_tree.as_string (); }
+
+  Location get_locus () const { return locus; }
+
+  DelimTokenTree &get_token_tree () { return token_tree; }
+};
+
+// A macro rule? Matcher and transcriber pair?
+struct MacroRule
+{
+private:
+  MacroMatcher matcher;
+  MacroTranscriber transcriber;
+  Location locus;
+
+public:
+  MacroRule (MacroMatcher matcher, MacroTranscriber transcriber, Location locus)
+    : matcher (std::move (matcher)), transcriber (std::move (transcriber)),
+      locus (locus)
+  {}
+
+  // Returns whether macro rule is in error state.
+  bool is_error () const { return matcher.is_error (); }
+
+  // Creates an error state macro rule.
+  static MacroRule create_error (Location locus)
+  {
+    return MacroRule (MacroMatcher::create_error (locus),
+		      MacroTranscriber (DelimTokenTree::create_empty (),
+					Location ()),
+		      locus);
+  }
+
+  Location get_locus () const { return locus; }
+
+  std::string as_string () const;
+
+  MacroMatcher &get_matcher () { return matcher; }
+  MacroTranscriber &get_transcriber () { return transcriber; }
+};
+
+// A macro rules definition item AST node
+class MacroRulesDefinition : public MacroItem
+{
+  std::vector<Attribute> outer_attrs;
+  Identifier rule_name;
+  // MacroRulesDef rules_def;
+  // only curly without required semicolon at end
+  DelimType delim_type;
+  // MacroRules rules;
+  std::vector<MacroRule> rules; // inlined form
+  Location locus;
+
+  std::function<ASTFragment (Location, MacroInvocData &)>
+    associated_transcriber;
+  // Since we can't compare std::functions, we need to use an extra boolean
+  bool is_builtin_rule;
+
+  /**
+   * Default function to use as an associated transcriber. This function should
+   * never be called, hence the gcc_unreachable().
+   * If this function is used, then the macro is not builtin and the compiler
+   * should make use of the actual rules. If the macro is builtin, then another
+   * associated transcriber should be used
+   */
+  static ASTFragment dummy_builtin (Location, MacroInvocData &)
+  {
+    gcc_unreachable ();
+    return ASTFragment::create_error ();
+  }
+
+  /* NOTE: in rustc, macro definitions are considered (and parsed as) a type
+   * of macro, whereas here they are considered part of the language itself.
+   * I am not aware of the implications of this decision. The rustc spec does
+   * mention that using the same parser for macro definitions and invocations
+   * is "extremely self-referential and non-intuitive". */
+
+public:
+  std::string as_string () const override;
+
+  MacroRulesDefinition (Identifier rule_name, DelimType delim_type,
+			std::vector<MacroRule> rules,
+			std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)), rule_name (std::move (rule_name)),
+      delim_type (delim_type), rules (std::move (rules)), locus (locus),
+      associated_transcriber (dummy_builtin), is_builtin_rule (false)
+  {}
+
+  MacroRulesDefinition (Identifier builtin_name, DelimType delim_type,
+			std::function<ASTFragment (Location, MacroInvocData &)>
+			  associated_transcriber)
+    : outer_attrs (std::vector<Attribute> ()), rule_name (builtin_name),
+      delim_type (delim_type), rules (std::vector<MacroRule> ()),
+      locus (Location ()), associated_transcriber (associated_transcriber),
+      is_builtin_rule (true)
+  {}
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if rule name is empty, so base stripping on that.
+  void mark_for_strip () override { rule_name = ""; }
+  bool is_marked_for_strip () const override { return rule_name.empty (); }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  std::vector<MacroRule> &get_macro_rules () { return rules; }
+  const std::vector<MacroRule> &get_macro_rules () const { return rules; }
+
+  Location get_locus () const override final { return locus; }
+
+  Identifier get_rule_name () const { return rule_name; }
+
+  std::vector<MacroRule> &get_rules () { return rules; }
+  const std::vector<MacroRule> &get_rules () const { return rules; }
+
+  bool is_builtin () const { return is_builtin_rule; }
+  const std::function<ASTFragment (Location, MacroInvocData &)> &
+  get_builtin_transcriber () const
+  {
+    rust_assert (is_builtin ());
+    return associated_transcriber;
+  }
+  void set_builtin_transcriber (
+    std::function<ASTFragment (Location, MacroInvocData &)> transcriber)
+  {
+    associated_transcriber = transcriber;
+    is_builtin_rule = true;
+  }
+
+  Kind get_ast_kind () const override { return Kind::MACRO_RULES_DEFINITION; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MacroRulesDefinition *clone_item_impl () const override
+  {
+    return new MacroRulesDefinition (*this);
+  }
+};
+
+/* AST node of a macro invocation, which is replaced by the macro result at
+ * compile time */
+class MacroInvocation : public TypeNoBounds,
+			public Pattern,
+			public MacroItem,
+			public TraitItem,
+			public TraitImplItem,
+			public InherentImplItem,
+			public ExternalItem,
+			public ExprWithoutBlock
+{
+  std::vector<Attribute> outer_attrs;
+  MacroInvocData invoc_data;
+  Location locus;
+
+  // Important for when we actually expand the macro
+  bool is_semi_coloned;
+
+  NodeId node_id;
+
+public:
+  std::string as_string () const override;
+
+  MacroInvocation (MacroInvocData invoc_data,
+		   std::vector<Attribute> outer_attrs, Location locus,
+		   bool is_semi_coloned = false)
+    : outer_attrs (std::move (outer_attrs)),
+      invoc_data (std::move (invoc_data)), locus (locus),
+      is_semi_coloned (is_semi_coloned),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if path is empty, so base stripping on that.
+  void mark_for_strip () override { invoc_data.mark_for_strip (); }
+  bool is_marked_for_strip () const override
+  {
+    return invoc_data.is_marked_for_strip ();
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  NodeId get_pattern_node_id () const override final
+  {
+    return ExprWithoutBlock::get_node_id ();
+  }
+
+  Kind get_ast_kind () const override { return Kind::MACRO_INVOCATION; }
+
+  NodeId get_macro_node_id () const { return node_id; }
+
+  MacroInvocData &get_invoc_data () { return invoc_data; }
+
+  bool has_semicolon () const { return is_semi_coloned; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MacroInvocation *clone_pattern_impl () const final override
+  {
+    return clone_macro_invocation_impl ();
+  }
+
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MacroInvocation *clone_expr_without_block_impl () const final override
+  {
+    return clone_macro_invocation_impl ();
+  }
+
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  MacroInvocation *clone_type_no_bounds_impl () const final override
+  {
+    return clone_macro_invocation_impl ();
+  }
+
+  MacroInvocation *clone_external_item_impl () const final override
+  {
+    return clone_macro_invocation_impl ();
+  }
+
+  /*virtual*/ MacroInvocation *clone_macro_invocation_impl () const
+  {
+    return new MacroInvocation (*this);
+  }
+
+  Item *clone_item_impl () const override
+  {
+    return clone_macro_invocation_impl ();
+  }
+
+  bool is_item () const override { return !has_semicolon (); }
+
+  TraitItem *clone_trait_item_impl () const override
+  {
+    return clone_macro_invocation_impl ();
+  };
+
+  TraitImplItem *clone_trait_impl_item_impl () const override
+  {
+    return clone_macro_invocation_impl ();
+  };
+
+  InherentImplItem *clone_inherent_impl_item_impl () const override
+  {
+    return clone_macro_invocation_impl ();
+  }
+
+  ExprWithoutBlock *to_stmt () const override
+
+  {
+    auto new_impl = clone_macro_invocation_impl ();
+    new_impl->is_semi_coloned = true;
+
+    return new_impl;
+  }
+};
+
+// more generic meta item path-only form
+class MetaItemPath : public MetaItem
+{
+  SimplePath path;
+
+public:
+  MetaItemPath (SimplePath path) : path (std::move (path)) {}
+
+  std::string as_string () const override { return path.as_string (); }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // HACK: used to simplify parsing - returns non-empty only in this case
+  SimplePath to_path_item () const override
+  {
+    // this should copy construct - TODO ensure it does
+    return path;
+  }
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+  Attribute to_attribute () const override;
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaItemPath *clone_meta_item_inner_impl () const override
+  {
+    return new MetaItemPath (*this);
+  }
+};
+
+// more generic meta item sequence form
+class MetaItemSeq : public MetaItem
+{
+  SimplePath path;
+  std::vector<std::unique_ptr<MetaItemInner> > seq;
+
+public:
+  MetaItemSeq (SimplePath path,
+	       std::vector<std::unique_ptr<MetaItemInner> > seq)
+    : path (std::move (path)), seq (std::move (seq))
+  {}
+
+  // copy constructor with vector clone
+  MetaItemSeq (const MetaItemSeq &other) : path (other.path)
+  {
+    seq.reserve (other.seq.size ());
+    for (const auto &e : other.seq)
+      seq.push_back (e->clone_meta_item_inner ());
+  }
+
+  // overloaded assignment operator with vector clone
+  MetaItemSeq &operator= (const MetaItemSeq &other)
+  {
+    MetaItem::operator= (other);
+    path = other.path;
+
+    seq.reserve (other.seq.size ());
+    for (const auto &e : other.seq)
+      seq.push_back (e->clone_meta_item_inner ());
+
+    return *this;
+  }
+
+  // default move constructors
+  MetaItemSeq (MetaItemSeq &&other) = default;
+  MetaItemSeq &operator= (MetaItemSeq &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+  Attribute to_attribute () const override;
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaItemSeq *clone_meta_item_inner_impl () const override
+  {
+    return new MetaItemSeq (*this);
+  }
+};
+
+// Preferred specialisation for single-identifier meta items.
+class MetaWord : public MetaItem
+{
+  Identifier ident;
+  Location ident_locus;
+
+public:
+  MetaWord (Identifier ident, Location ident_locus)
+    : ident (std::move (ident)), ident_locus (ident_locus)
+  {}
+
+  std::string as_string () const override { return ident; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+  Attribute to_attribute () const override;
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaWord *clone_meta_item_inner_impl () const override
+  {
+    return new MetaWord (*this);
+  }
+};
+
+// Preferred specialisation for "identifier '=' string literal" meta items.
+class MetaNameValueStr : public MetaItem
+{
+  Identifier ident;
+  Location ident_locus;
+
+  // NOTE: str stored without quotes
+  std::string str;
+  Location str_locus;
+
+public:
+  MetaNameValueStr (Identifier ident, Location ident_locus, std::string str,
+		    Location str_locus)
+    : ident (std::move (ident)), ident_locus (ident_locus),
+      str (std::move (str)), str_locus (str_locus)
+  {}
+
+  std::string as_string () const override
+  {
+    return ident + " = \"" + str + "\"";
+  }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // HACK: used to simplify parsing - creates a copy of this
+  std::unique_ptr<MetaNameValueStr> to_meta_name_value_str () const override
+  {
+    return std::unique_ptr<MetaNameValueStr> (clone_meta_item_inner_impl ());
+  }
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+  Attribute to_attribute () const override;
+
+  inline std::pair<Identifier, std::string> get_name_value_pair () const
+  {
+    return std::pair<Identifier, std::string> (ident, str);
+  }
+
+  bool is_key_value_pair () const override { return true; }
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaNameValueStr *clone_meta_item_inner_impl () const override
+  {
+    return new MetaNameValueStr (*this);
+  }
+};
+
+// doubles up as MetaListIdents - determine via iterating through each path?
+// Preferred specialisation for "identifier '(' SimplePath, SimplePath, ... ')'"
+class MetaListPaths : public MetaItem
+{
+  Identifier ident;
+  Location ident_locus;
+  std::vector<SimplePath> paths;
+
+public:
+  MetaListPaths (Identifier ident, Location ident_locus,
+		 std::vector<SimplePath> paths)
+    : ident (std::move (ident)), ident_locus (ident_locus),
+      paths (std::move (paths))
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+  Attribute to_attribute () const override;
+
+private:
+  bool check_path_exists_in_cfg (const Session &session,
+				 const SimplePath &path) const;
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaListPaths *clone_meta_item_inner_impl () const override
+  {
+    return new MetaListPaths (*this);
+  }
+};
+
+// Preferred specialisation for "identifier '(' MetaNameValueStr, ... ')'"
+class MetaListNameValueStr : public MetaItem
+{
+  Identifier ident;
+  Location ident_locus;
+  std::vector<MetaNameValueStr> strs;
+
+public:
+  MetaListNameValueStr (Identifier ident, Location ident_locus,
+			std::vector<MetaNameValueStr> strs)
+    : ident (std::move (ident)), ident_locus (ident_locus),
+      strs (std::move (strs))
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool check_cfg_predicate (const Session &session) const override;
+
+  Attribute to_attribute () const override;
+
+protected:
+  // Use covariance to implement clone function as returning this type
+  MetaListNameValueStr *clone_meta_item_inner_impl () const override
+  {
+    return new MetaListNameValueStr (*this);
+  }
+};
+
+// Object that parses macros from a token stream.
+/* TODO: would "AttributeParser" be a better name? MetaItems are only for
+ * attributes, I believe */
+struct AttributeParser
+{
+private:
+  // TODO: might as well rewrite to use lexer tokens
+  std::vector<std::unique_ptr<Token> > token_stream;
+  int stream_pos;
+
+public:
+  AttributeParser (std::vector<std::unique_ptr<Token> > token_stream,
+		   int stream_start_pos = 0)
+    : token_stream (std::move (token_stream)), stream_pos (stream_start_pos)
+  {}
+
+  ~AttributeParser () = default;
+
+  std::vector<std::unique_ptr<MetaItemInner> > parse_meta_item_seq ();
+
+private:
+  // Parses a MetaItemInner.
+  std::unique_ptr<MetaItemInner> parse_meta_item_inner ();
+  // Returns whether token can end a meta item.
+  bool is_end_meta_item_tok (TokenId id) const;
+  // Parses a simple path.
+  SimplePath parse_simple_path ();
+  // Parses a segment of a simple path (but not scope resolution operator).
+  SimplePathSegment parse_simple_path_segment ();
+  // Parses a MetaItemLitExpr.
+  std::unique_ptr<MetaItemLitExpr> parse_meta_item_lit ();
+  // Parses a literal.
+  Literal parse_literal ();
+  // Parses a meta item that begins with a simple path.
+  std::unique_ptr<MetaItem> parse_path_meta_item ();
+
+  // TODO: should this be const?
+  std::unique_ptr<Token> &peek_token (int i = 0)
+  {
+    return token_stream[stream_pos + i];
+  }
+
+  void skip_token (int i = 0) { stream_pos += 1 + i; }
+};
+} // namespace AST
+} // namespace Rust
+
+/* <https://stackoverflow.com/a/35304501> */
+namespace std {
+template <> struct hash<Rust::AST::MacroFragSpec::Kind>
+{
+  size_t operator() (const Rust::AST::MacroFragSpec::Kind &t) const noexcept
+  {
+    return size_t (t);
+  }
+};
+} // namespace std
+
+#endif
diff --git a/gcc/rust/ast/rust-path.h b/gcc/rust/ast/rust-path.h
new file mode 100644
index 00000000000..cc79e278f05
--- /dev/null
+++ b/gcc/rust/ast/rust-path.h
@@ -0,0 +1,1297 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_PATH_H
+#define RUST_AST_PATH_H
+/* "Path" (identifier within namespaces, essentially) handling. Required include
+ * for virtually all AST-related functionality. */
+
+#include "rust-ast.h"
+#include "system.h"
+
+namespace Rust {
+namespace AST {
+
+// The "identifier" (not generic args) aspect of each path expression segment
+class PathIdentSegment
+{
+  std::string segment_name;
+  Location locus;
+
+  // only allow identifiers, "super", "self", "Self", "crate", or "$crate"
+public:
+  PathIdentSegment (std::string segment_name, Location locus)
+    : segment_name (std::move (segment_name)), locus (locus)
+  {}
+
+  // Creates an error PathIdentSegment.
+  static PathIdentSegment create_error ()
+  {
+    return PathIdentSegment ("", Location ());
+  }
+
+  // Returns whether PathIdentSegment is in an error state.
+  bool is_error () const { return segment_name.empty (); }
+
+  std::string as_string () const { return segment_name; }
+
+  Location get_locus () const { return locus; }
+
+  bool is_super_segment () const { return as_string ().compare ("super") == 0; }
+  bool is_crate_segment () const { return as_string ().compare ("crate") == 0; }
+  bool is_lower_self () const { return as_string ().compare ("self") == 0; }
+  bool is_big_self () const { return as_string ().compare ("Self") == 0; }
+};
+
+// A binding of an identifier to a type used in generic arguments in paths
+struct GenericArgsBinding
+{
+private:
+  Identifier identifier;
+  std::unique_ptr<Type> type;
+  Location locus;
+
+public:
+  // Returns whether binding is in an error state.
+  bool is_error () const
+  {
+    return type == nullptr;
+    // and also identifier is empty, but cheaper computation
+  }
+
+  // Creates an error state generic args binding.
+  static GenericArgsBinding create_error ()
+  {
+    return GenericArgsBinding ("", nullptr);
+  }
+
+  // Pointer type for type in constructor to enable polymorphism
+  GenericArgsBinding (Identifier ident, std::unique_ptr<Type> type_ptr,
+		      Location locus = Location ())
+    : identifier (std::move (ident)), type (std::move (type_ptr)), locus (locus)
+  {}
+
+  // Copy constructor has to deep copy the type as it is a unique pointer
+  GenericArgsBinding (GenericArgsBinding const &other)
+    : identifier (other.identifier), locus (other.locus)
+  {
+    // guard to protect from null pointer dereference
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+  }
+
+  // default destructor
+  ~GenericArgsBinding () = default;
+
+  // Overload assignment operator to deep copy the pointed-to type
+  GenericArgsBinding &operator= (GenericArgsBinding const &other)
+  {
+    identifier = other.identifier;
+    locus = other.locus;
+
+    // guard to protect from null pointer dereference
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  GenericArgsBinding (GenericArgsBinding &&other) = default;
+  GenericArgsBinding &operator= (GenericArgsBinding &&other) = default;
+
+  std::string as_string () const;
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+
+  Location get_locus () const { return locus; }
+
+  Identifier get_identifier () const { return identifier; }
+};
+
+/* Class representing a const generic application */
+class GenericArg
+{
+public:
+  /**
+   * const generic arguments cannot always be differentiated with generic type
+   * arguments during parsing, e.g:
+   * ```rust
+   * let a: Foo<N>;
+   * ```
+   *
+   * Is N a type? A constant defined elsewhere? The parser cannot know, and must
+   * not draw any conclusions. We must wait until later passes of the compiler
+   * to decide whether this refers to a constant item or a type.
+   *
+   * On the other hand, simple expressions like literals or block expressions
+   * will always be constant expressions: There is no ambiguity at all.
+   */
+  enum class Kind
+  {
+    Error,
+    Const,  // A const value
+    Type,   // A type argument (not discernable during parsing)
+    Either, // Either a type or a const value, cleared up during resolving
+  };
+
+  static GenericArg create_error ()
+  {
+    return GenericArg (nullptr, nullptr, "", Kind::Error, Location ());
+  }
+
+  static GenericArg create_const (std::unique_ptr<Expr> expression)
+  {
+    auto locus = expression->get_locus ();
+    return GenericArg (std::move (expression), nullptr, "", Kind::Const, locus);
+  }
+
+  static GenericArg create_type (std::unique_ptr<Type> type)
+  {
+    auto locus = type->get_locus ();
+    return GenericArg (nullptr, std::move (type), "", Kind::Type, locus);
+  }
+
+  static GenericArg create_ambiguous (Identifier path, Location locus)
+  {
+    return GenericArg (nullptr, nullptr, std::move (path), Kind::Either, locus);
+  }
+
+  GenericArg (const GenericArg &other)
+    : path (other.path), kind (other.kind), locus (other.locus)
+  {
+    if (other.expression)
+      expression = other.expression->clone_expr ();
+    if (other.type)
+      type = other.type->clone_type ();
+  }
+
+  GenericArg operator= (const GenericArg &other)
+  {
+    kind = other.kind;
+    path = other.path;
+    locus = other.locus;
+
+    if (other.expression)
+      expression = other.expression->clone_expr ();
+    if (other.type)
+      type = other.type->clone_type ();
+
+    return *this;
+  }
+
+  bool is_error () const { return kind == Kind::Error; }
+
+  Kind get_kind () const { return kind; }
+  const Location &get_locus () const { return locus; }
+
+  std::unique_ptr<Expr> &get_expression ()
+  {
+    rust_assert (kind == Kind::Const);
+
+    return expression;
+  }
+
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (kind == Kind::Type);
+
+    return type;
+  }
+
+  const std::string &get_path () const
+  {
+    rust_assert (kind == Kind::Either);
+
+    return path;
+  }
+
+  std::string as_string () const
+  {
+    switch (get_kind ())
+      {
+      case Kind::Error:
+	gcc_unreachable ();
+      case Kind::Either:
+	return "Ambiguous: " + path;
+      case Kind::Const:
+	return "Const: { " + expression->as_string () + " }";
+      case Kind::Type:
+	return "Type: " + type->as_string ();
+      }
+
+    return "";
+  }
+
+  /**
+   * Disambiguate an ambiguous generic argument to a const generic argument,
+   * unequivocally
+   */
+  GenericArg disambiguate_to_const () const;
+
+  /**
+   * Disambiguate an ambiguous generic argument to a type argument,
+   * unequivocally
+   */
+  GenericArg disambiguate_to_type () const;
+
+private:
+  GenericArg (std::unique_ptr<Expr> expression, std::unique_ptr<Type> type,
+	      Identifier path, Kind kind, Location locus)
+    : expression (std::move (expression)), type (std::move (type)),
+      path (std::move (path)), kind (kind), locus (locus)
+  {}
+
+  /**
+   * Expression associated with a `Clear` const generic application
+   * A null pointer here is allowed in the case that the const argument is
+   * ambiguous.
+   */
+  std::unique_ptr<Expr> expression;
+
+  /**
+   * If the argument ends up being a type argument instead. A null pointer will
+   * be present here until the resolving phase.
+   */
+  std::unique_ptr<Type> type;
+
+  /**
+   * Optional path which cannot be differentiated between a constant item and
+   * a type. Only used for ambiguous const generic arguments, otherwise
+   * empty.
+   */
+  Identifier path;
+
+  /* Which kind of const generic application are we dealing with */
+  Kind kind;
+
+  Location locus;
+};
+
+/**
+ * Representation of const generic parameters
+ */
+class ConstGenericParam : public GenericParam
+{
+  /* Name of the parameter */
+  Identifier name;
+
+  /* Mandatory type of the const parameter - a null pointer is an error */
+  std::unique_ptr<AST::Type> type;
+
+  /**
+   * Default value for the const generic parameter
+   */
+  GenericArg default_value;
+
+  Attribute outer_attr;
+  Location locus;
+
+public:
+  ConstGenericParam (Identifier name, std::unique_ptr<AST::Type> type,
+		     GenericArg default_value, Attribute outer_attr,
+		     Location locus)
+    : name (name), type (std::move (type)),
+      default_value (std::move (default_value)), outer_attr (outer_attr),
+      locus (locus)
+  {}
+
+  ConstGenericParam (const ConstGenericParam &other)
+    : GenericParam (), name (other.name), type (other.type->clone_type ()),
+      default_value (other.default_value), outer_attr (other.outer_attr),
+      locus (other.locus)
+  {}
+
+  bool has_type () const { return type != nullptr; }
+  bool has_default_value () const { return !default_value.is_error (); }
+
+  const Identifier &get_name () const { return name; }
+
+  std::unique_ptr<AST::Type> &get_type ()
+  {
+    rust_assert (has_type ());
+
+    return type;
+  }
+
+  GenericArg &get_default_value ()
+  {
+    rust_assert (has_default_value ());
+
+    return default_value;
+  }
+
+  const GenericArg &get_default_value () const
+  {
+    rust_assert (has_default_value ());
+
+    return default_value;
+  }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  Location get_locus () const override final { return locus; }
+
+  Kind get_kind () const override final { return Kind::Const; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ConstGenericParam *clone_generic_param_impl () const override
+  {
+    return new ConstGenericParam (*this);
+  }
+};
+
+// Generic arguments allowed in each path expression segment - inline?
+struct GenericArgs
+{
+  std::vector<Lifetime> lifetime_args;
+  std::vector<GenericArg> generic_args;
+  std::vector<GenericArgsBinding> binding_args;
+  Location locus;
+
+public:
+  // Returns true if there are any generic arguments
+  bool has_generic_args () const
+  {
+    return !(lifetime_args.empty () && generic_args.empty ()
+	     && binding_args.empty ());
+  }
+
+  GenericArgs (std::vector<Lifetime> lifetime_args,
+	       std::vector<GenericArg> generic_args,
+	       std::vector<GenericArgsBinding> binding_args,
+	       Location locus = Location ())
+    : lifetime_args (std::move (lifetime_args)),
+      generic_args (std::move (generic_args)),
+      binding_args (std::move (binding_args)), locus (locus)
+  {}
+
+  // copy constructor with vector clone
+  GenericArgs (GenericArgs const &other)
+    : lifetime_args (other.lifetime_args), generic_args (other.generic_args),
+      binding_args (other.binding_args), locus (other.locus)
+  {}
+
+  ~GenericArgs () = default;
+
+  // overloaded assignment operator to vector clone
+  GenericArgs &operator= (GenericArgs const &other)
+  {
+    lifetime_args = other.lifetime_args;
+    generic_args = other.generic_args;
+    binding_args = other.binding_args;
+    locus = other.locus;
+
+    return *this;
+  }
+
+  // move constructors
+  GenericArgs (GenericArgs &&other) = default;
+  GenericArgs &operator= (GenericArgs &&other) = default;
+
+  // Creates an empty GenericArgs (no arguments)
+  static GenericArgs create_empty () { return GenericArgs ({}, {}, {}); }
+
+  std::string as_string () const;
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::vector<GenericArg> &get_generic_args () { return generic_args; }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::vector<GenericArgsBinding> &get_binding_args () { return binding_args; }
+
+  std::vector<Lifetime> &get_lifetime_args () { return lifetime_args; };
+
+  Location get_locus () { return locus; }
+};
+
+/* A segment of a path in expression, including an identifier aspect and maybe
+ * generic args */
+class PathExprSegment
+{ // or should this extend PathIdentSegment?
+private:
+  PathIdentSegment segment_name;
+  GenericArgs generic_args;
+  Location locus;
+  NodeId node_id;
+
+public:
+  // Returns true if there are any generic arguments
+  bool has_generic_args () const { return generic_args.has_generic_args (); }
+
+  // Constructor for segment (from IdentSegment and GenericArgs)
+  PathExprSegment (PathIdentSegment segment_name, Location locus,
+		   GenericArgs generic_args = GenericArgs::create_empty ())
+    : segment_name (std::move (segment_name)),
+      generic_args (std::move (generic_args)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  /* Constructor for segment with generic arguments (from segment name and all
+   * args) */
+  PathExprSegment (std::string segment_name, Location locus,
+		   std::vector<Lifetime> lifetime_args = {},
+		   std::vector<GenericArg> generic_args = {},
+		   std::vector<GenericArgsBinding> binding_args = {})
+    : segment_name (PathIdentSegment (std::move (segment_name), locus)),
+      generic_args (GenericArgs (std::move (lifetime_args),
+				 std::move (generic_args),
+				 std::move (binding_args))),
+      locus (locus), node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Returns whether path expression segment is in an error state.
+  bool is_error () const { return segment_name.is_error (); }
+
+  // Creates an error-state path expression segment.
+  static PathExprSegment create_error ()
+  {
+    return PathExprSegment (PathIdentSegment::create_error (), Location ());
+  }
+
+  std::string as_string () const;
+
+  Location get_locus () const { return locus; }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  GenericArgs &get_generic_args ()
+  {
+    rust_assert (has_generic_args ());
+    return generic_args;
+  }
+
+  PathIdentSegment &get_ident_segment () { return segment_name; }
+  const PathIdentSegment &get_ident_segment () const { return segment_name; }
+
+  NodeId get_node_id () const { return node_id; }
+
+  bool is_super_path_seg () const
+  {
+    return !has_generic_args () && get_ident_segment ().is_super_segment ();
+  }
+
+  bool is_crate_path_seg () const
+  {
+    return !has_generic_args () && get_ident_segment ().is_crate_segment ();
+  }
+  bool is_lower_self_seg () const
+  {
+    return !has_generic_args () && get_ident_segment ().is_lower_self ();
+  }
+};
+
+// AST node representing a pattern that involves a "path" - abstract base
+// class
+class PathPattern : public Pattern
+{
+  std::vector<PathExprSegment> segments;
+
+protected:
+  PathPattern (std::vector<PathExprSegment> segments)
+    : segments (std::move (segments))
+  {}
+
+  // Returns whether path has segments.
+  bool has_segments () const { return !segments.empty (); }
+
+  /* Converts path segments to their equivalent SimplePath segments if
+   * possible, and creates a SimplePath from them. */
+  SimplePath convert_to_simple_path (bool with_opening_scope_resolution) const;
+
+  // Removes all segments of the path.
+  void remove_all_segments ()
+  {
+    segments.clear ();
+    segments.shrink_to_fit ();
+  }
+
+public:
+  /* Returns whether the path is a single segment (excluding qualified path
+   * initial as segment). */
+  bool is_single_segment () const { return segments.size () == 1; }
+
+  std::string as_string () const override;
+
+  // TODO: this seems kinda dodgy
+  std::vector<PathExprSegment> &get_segments () { return segments; }
+  const std::vector<PathExprSegment> &get_segments () const { return segments; }
+};
+
+/* AST node representing a path-in-expression pattern (path that allows
+ * generic arguments) */
+class PathInExpression : public PathPattern, public PathExpr
+{
+  std::vector<Attribute> outer_attrs;
+  bool has_opening_scope_resolution;
+  Location locus;
+  NodeId _node_id;
+
+public:
+  std::string as_string () const override;
+
+  // Constructor
+  PathInExpression (std::vector<PathExprSegment> path_segments,
+		    std::vector<Attribute> outer_attrs, Location locus,
+		    bool has_opening_scope_resolution = false)
+    : PathPattern (std::move (path_segments)),
+      outer_attrs (std::move (outer_attrs)),
+      has_opening_scope_resolution (has_opening_scope_resolution),
+      locus (locus), _node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Creates an error state path in expression.
+  static PathInExpression create_error ()
+  {
+    return PathInExpression ({}, {}, Location ());
+  }
+
+  // Returns whether path in expression is in an error state.
+  bool is_error () const { return !has_segments (); }
+
+  /* Converts PathInExpression to SimplePath if possible (i.e. no generic
+   * arguments). Otherwise returns an empty SimplePath. */
+  SimplePath as_simple_path () const
+  {
+    /* delegate to parent class as can't access segments. however,
+     * QualifiedPathInExpression conversion to simple path wouldn't make
+     * sense, so the method in the parent class should be protected, not
+     * public. Have to pass in opening scope resolution as parent class has no
+     * access to it.
+     */
+    return convert_to_simple_path (has_opening_scope_resolution);
+  }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if path is empty (error state), so base stripping on that.
+  void mark_for_strip () override { remove_all_segments (); }
+  bool is_marked_for_strip () const override { return is_error (); }
+
+  bool opening_scope_resolution () const
+  {
+    return has_opening_scope_resolution;
+  }
+
+  NodeId get_node_id () const override { return _node_id; }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  NodeId get_pattern_node_id () const override final { return get_node_id (); }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  PathInExpression *clone_pattern_impl () const final override
+  {
+    return clone_path_in_expression_impl ();
+  }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  PathInExpression *clone_expr_without_block_impl () const final override
+  {
+    return clone_path_in_expression_impl ();
+  }
+
+  /*virtual*/ PathInExpression *clone_path_in_expression_impl () const
+  {
+    return new PathInExpression (*this);
+  }
+};
+
+/* Base class for segments used in type paths - not abstract (represents an
+ * ident-only segment) */
+class TypePathSegment
+{
+public:
+  enum SegmentType
+  {
+    REG,
+    GENERIC,
+    FUNCTION
+  };
+
+private:
+  PathIdentSegment ident_segment;
+  Location locus;
+
+protected:
+  /* This is protected because it is only really used by derived classes, not
+   * the base. */
+  bool has_separating_scope_resolution;
+  NodeId node_id;
+
+  // Clone function implementation - not pure virtual as overrided by
+  // subclasses
+  virtual TypePathSegment *clone_type_path_segment_impl () const
+  {
+    return new TypePathSegment (*this);
+  }
+
+public:
+  virtual ~TypePathSegment () {}
+
+  virtual SegmentType get_type () const { return SegmentType::REG; }
+
+  // Unique pointer custom clone function
+  std::unique_ptr<TypePathSegment> clone_type_path_segment () const
+  {
+    return std::unique_ptr<TypePathSegment> (clone_type_path_segment_impl ());
+  }
+
+  TypePathSegment (PathIdentSegment ident_segment,
+		   bool has_separating_scope_resolution, Location locus)
+    : ident_segment (std::move (ident_segment)), locus (locus),
+      has_separating_scope_resolution (has_separating_scope_resolution),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  TypePathSegment (std::string segment_name,
+		   bool has_separating_scope_resolution, Location locus)
+    : ident_segment (PathIdentSegment (std::move (segment_name), locus)),
+      locus (locus),
+      has_separating_scope_resolution (has_separating_scope_resolution),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  virtual std::string as_string () const { return ident_segment.as_string (); }
+
+  /* Returns whether the type path segment is in an error state. May be
+   * virtual in future. */
+  bool is_error () const { return ident_segment.is_error (); }
+
+  /* Returns whether segment is identifier only (as opposed to generic args or
+   * function). Overridden in derived classes with other segments. */
+  virtual bool is_ident_only () const { return true; }
+
+  Location get_locus () const { return locus; }
+
+  // not pure virtual as class not abstract
+  virtual void accept_vis (ASTVisitor &vis);
+
+  bool get_separating_scope_resolution () const
+  {
+    return has_separating_scope_resolution;
+  }
+
+  PathIdentSegment &get_ident_segment () { return ident_segment; };
+  const PathIdentSegment &get_ident_segment () const { return ident_segment; };
+
+  NodeId get_node_id () const { return node_id; }
+
+  bool is_crate_path_seg () const
+  {
+    return get_ident_segment ().is_crate_segment ();
+  }
+  bool is_super_path_seg () const
+  {
+    return get_ident_segment ().is_super_segment ();
+  }
+  bool is_big_self_seg () const { return get_ident_segment ().is_big_self (); }
+  bool is_lower_self_seg () const
+  {
+    return get_ident_segment ().is_lower_self ();
+  }
+};
+
+// Segment used in type path with generic args
+class TypePathSegmentGeneric : public TypePathSegment
+{
+  GenericArgs generic_args;
+
+public:
+  SegmentType get_type () const override { return SegmentType::GENERIC; }
+
+  bool has_generic_args () const { return generic_args.has_generic_args (); }
+
+  bool is_ident_only () const override { return false; }
+
+  // Constructor with PathIdentSegment and GenericArgs
+  TypePathSegmentGeneric (PathIdentSegment ident_segment,
+			  bool has_separating_scope_resolution,
+			  GenericArgs generic_args, Location locus)
+    : TypePathSegment (std::move (ident_segment),
+		       has_separating_scope_resolution, locus),
+      generic_args (std::move (generic_args))
+  {}
+
+  // Constructor from segment name and all args
+  TypePathSegmentGeneric (std::string segment_name,
+			  bool has_separating_scope_resolution,
+			  std::vector<Lifetime> lifetime_args,
+			  std::vector<GenericArg> generic_args,
+			  std::vector<GenericArgsBinding> binding_args,
+			  Location locus)
+    : TypePathSegment (std::move (segment_name),
+		       has_separating_scope_resolution, locus),
+      generic_args (GenericArgs (std::move (lifetime_args),
+				 std::move (generic_args),
+				 std::move (binding_args)))
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  GenericArgs &get_generic_args ()
+  {
+    rust_assert (has_generic_args ());
+    return generic_args;
+  }
+
+protected:
+  // Use covariance to override base class method
+  TypePathSegmentGeneric *clone_type_path_segment_impl () const override
+  {
+    return new TypePathSegmentGeneric (*this);
+  }
+};
+
+// A function as represented in a type path
+struct TypePathFunction
+{
+private:
+  // TODO: remove
+  /*bool has_inputs;
+  TypePathFnInputs inputs;*/
+  // inlined from TypePathFnInputs
+  std::vector<std::unique_ptr<Type> > inputs;
+
+  // bool has_type;
+  std::unique_ptr<Type> return_type;
+
+  // FIXME: think of better way to mark as invalid than taking up storage
+  bool is_invalid;
+
+  Location locus;
+
+protected:
+  // Constructor only used to create invalid type path functions.
+  TypePathFunction (bool is_invalid, Location locus)
+    : is_invalid (is_invalid), locus (locus)
+  {}
+
+public:
+  // Returns whether the return type of the function has been specified.
+  bool has_return_type () const { return return_type != nullptr; }
+
+  // Returns whether the function has inputs.
+  bool has_inputs () const { return !inputs.empty (); }
+
+  // Returns whether function is in an error state.
+  bool is_error () const { return is_invalid; }
+
+  // Creates an error state function.
+  static TypePathFunction create_error ()
+  {
+    return TypePathFunction (true, Location ());
+  }
+
+  // Constructor
+  TypePathFunction (std::vector<std::unique_ptr<Type> > inputs, Location locus,
+		    std::unique_ptr<Type> type = nullptr)
+    : inputs (std::move (inputs)), return_type (std::move (type)),
+      is_invalid (false), locus (locus)
+  {}
+
+  // Copy constructor with clone
+  TypePathFunction (TypePathFunction const &other)
+    : is_invalid (other.is_invalid)
+  {
+    // guard to protect from null pointer dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+
+    inputs.reserve (other.inputs.size ());
+    for (const auto &e : other.inputs)
+      inputs.push_back (e->clone_type ());
+  }
+
+  ~TypePathFunction () = default;
+
+  // Overloaded assignment operator to clone type
+  TypePathFunction &operator= (TypePathFunction const &other)
+  {
+    is_invalid = other.is_invalid;
+
+    // guard to protect from null pointer dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type ();
+    else
+      return_type = nullptr;
+
+    inputs.reserve (other.inputs.size ());
+    for (const auto &e : other.inputs)
+      inputs.push_back (e->clone_type ());
+
+    return *this;
+  }
+
+  // move constructors
+  TypePathFunction (TypePathFunction &&other) = default;
+  TypePathFunction &operator= (TypePathFunction &&other) = default;
+
+  std::string as_string () const;
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  const std::vector<std::unique_ptr<Type> > &get_params () const
+  {
+    return inputs;
+  }
+  std::vector<std::unique_ptr<Type> > &get_params () { return inputs; }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::unique_ptr<Type> &get_return_type ()
+  {
+    rust_assert (has_return_type ());
+    return return_type;
+  }
+};
+
+// Segment used in type path with a function argument
+class TypePathSegmentFunction : public TypePathSegment
+{
+  TypePathFunction function_path;
+
+public:
+  SegmentType get_type () const override { return SegmentType::FUNCTION; }
+
+  // Constructor with PathIdentSegment and TypePathFn
+  TypePathSegmentFunction (PathIdentSegment ident_segment,
+			   bool has_separating_scope_resolution,
+			   TypePathFunction function_path, Location locus)
+    : TypePathSegment (std::move (ident_segment),
+		       has_separating_scope_resolution, locus),
+      function_path (std::move (function_path))
+  {}
+
+  // Constructor with segment name and TypePathFn
+  TypePathSegmentFunction (std::string segment_name,
+			   bool has_separating_scope_resolution,
+			   TypePathFunction function_path, Location locus)
+    : TypePathSegment (std::move (segment_name),
+		       has_separating_scope_resolution, locus),
+      function_path (std::move (function_path))
+  {}
+
+  std::string as_string () const override;
+
+  bool is_ident_only () const override { return false; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  TypePathFunction &get_type_path_function ()
+  {
+    rust_assert (!function_path.is_error ());
+    return function_path;
+  }
+
+protected:
+  // Use covariance to override base class method
+  TypePathSegmentFunction *clone_type_path_segment_impl () const override
+  {
+    return new TypePathSegmentFunction (*this);
+  }
+};
+
+// Path used inside types
+class TypePath : public TypeNoBounds
+{
+  bool has_opening_scope_resolution;
+  std::vector<std::unique_ptr<TypePathSegment> > segments;
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  TypePath *clone_type_no_bounds_impl () const override
+  {
+    return new TypePath (*this);
+  }
+
+public:
+  /* Returns whether the TypePath has an opening scope resolution operator
+   * (i.e. is global path or crate-relative path, not module-relative) */
+  bool has_opening_scope_resolution_op () const
+  {
+    return has_opening_scope_resolution;
+  }
+
+  // Returns whether the TypePath is in an invalid state.
+  bool is_error () const { return segments.empty (); }
+
+  // Creates an error state TypePath.
+  static TypePath create_error ()
+  {
+    return TypePath (std::vector<std::unique_ptr<TypePathSegment> > (),
+		     Location ());
+  }
+
+  // Constructor
+  TypePath (std::vector<std::unique_ptr<TypePathSegment> > segments,
+	    Location locus, bool has_opening_scope_resolution = false)
+    : TypeNoBounds (),
+      has_opening_scope_resolution (has_opening_scope_resolution),
+      segments (std::move (segments)), locus (locus)
+  {}
+
+  // Copy constructor with vector clone
+  TypePath (TypePath const &other)
+    : has_opening_scope_resolution (other.has_opening_scope_resolution),
+      locus (other.locus)
+  {
+    segments.reserve (other.segments.size ());
+    for (const auto &e : other.segments)
+      segments.push_back (e->clone_type_path_segment ());
+  }
+
+  // Overloaded assignment operator with clone
+  TypePath &operator= (TypePath const &other)
+  {
+    has_opening_scope_resolution = other.has_opening_scope_resolution;
+    locus = other.locus;
+
+    segments.reserve (other.segments.size ());
+    for (const auto &e : other.segments)
+      segments.push_back (e->clone_type_path_segment ());
+
+    return *this;
+  }
+
+  // move constructors
+  TypePath (TypePath &&other) = default;
+  TypePath &operator= (TypePath &&other) = default;
+
+  std::string as_string () const override;
+
+  /* Converts TypePath to SimplePath if possible (i.e. no generic or function
+   * arguments). Otherwise returns an empty SimplePath. */
+  SimplePath as_simple_path () const;
+
+  // Creates a trait bound with a clone of this type path as its only element.
+  TraitBound *to_trait_bound (bool in_parens) const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this seems kinda dodgy
+  std::vector<std::unique_ptr<TypePathSegment> > &get_segments ()
+  {
+    return segments;
+  }
+  const std::vector<std::unique_ptr<TypePathSegment> > &get_segments () const
+  {
+    return segments;
+  }
+
+  size_t get_num_segments () const { return segments.size (); }
+};
+
+struct QualifiedPathType
+{
+private:
+  std::unique_ptr<Type> type_to_invoke_on;
+  TypePath trait_path;
+  Location locus;
+  NodeId node_id;
+
+public:
+  // Constructor
+  QualifiedPathType (std::unique_ptr<Type> invoke_on_type,
+		     Location locus = Location (),
+		     TypePath trait_path = TypePath::create_error ())
+    : type_to_invoke_on (std::move (invoke_on_type)),
+      trait_path (std::move (trait_path)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor uses custom deep copy for Type to preserve polymorphism
+  QualifiedPathType (QualifiedPathType const &other)
+    : trait_path (other.trait_path), locus (other.locus)
+  {
+    node_id = other.node_id;
+    // guard to prevent null dereference
+    if (other.type_to_invoke_on != nullptr)
+      type_to_invoke_on = other.type_to_invoke_on->clone_type ();
+  }
+
+  // default destructor
+  ~QualifiedPathType () = default;
+
+  // overload assignment operator to use custom clone method
+  QualifiedPathType &operator= (QualifiedPathType const &other)
+  {
+    node_id = other.node_id;
+    trait_path = other.trait_path;
+    locus = other.locus;
+
+    // guard to prevent null dereference
+    if (other.type_to_invoke_on != nullptr)
+      type_to_invoke_on = other.type_to_invoke_on->clone_type ();
+    else
+      type_to_invoke_on = nullptr;
+
+    return *this;
+  }
+
+  // move constructor
+  QualifiedPathType (QualifiedPathType &&other) = default;
+  QualifiedPathType &operator= (QualifiedPathType &&other) = default;
+
+  // Returns whether the qualified path type has a rebind as clause.
+  bool has_as_clause () const { return !trait_path.is_error (); }
+
+  // Returns whether the qualified path type is in an error state.
+  bool is_error () const { return type_to_invoke_on == nullptr; }
+
+  // Creates an error state qualified path type.
+  static QualifiedPathType create_error ()
+  {
+    return QualifiedPathType (nullptr);
+  }
+
+  std::string as_string () const;
+
+  Location get_locus () const { return locus; }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (type_to_invoke_on != nullptr);
+    return type_to_invoke_on;
+  }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  TypePath &get_as_type_path ()
+  {
+    rust_assert (has_as_clause ());
+    return trait_path;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+};
+
+/* AST node representing a qualified path-in-expression pattern (path that
+ * allows specifying trait functions) */
+class QualifiedPathInExpression : public PathPattern, public PathExpr
+{
+  std::vector<Attribute> outer_attrs;
+  QualifiedPathType path_type;
+  Location locus;
+  NodeId _node_id;
+
+public:
+  std::string as_string () const override;
+
+  QualifiedPathInExpression (QualifiedPathType qual_path_type,
+			     std::vector<PathExprSegment> path_segments,
+			     std::vector<Attribute> outer_attrs, Location locus)
+    : PathPattern (std::move (path_segments)),
+      outer_attrs (std::move (outer_attrs)),
+      path_type (std::move (qual_path_type)), locus (locus),
+      _node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  /* TODO: maybe make a shortcut constructor that has QualifiedPathType
+   * elements as params */
+
+  // Returns whether qualified path in expression is in an error state.
+  bool is_error () const { return path_type.is_error (); }
+
+  // Creates an error qualified path in expression.
+  static QualifiedPathInExpression create_error ()
+  {
+    return QualifiedPathInExpression (QualifiedPathType::create_error (), {},
+				      {}, Location ());
+  }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if path_type is error, so base stripping on that.
+  void mark_for_strip () override
+  {
+    path_type = QualifiedPathType::create_error ();
+  }
+  bool is_marked_for_strip () const override { return is_error (); }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  QualifiedPathType &get_qualified_path_type ()
+  {
+    rust_assert (!path_type.is_error ());
+    return path_type;
+  }
+
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+
+  void set_outer_attrs (std::vector<Attribute> new_attrs) override
+  {
+    outer_attrs = std::move (new_attrs);
+  }
+
+  NodeId get_node_id () const override { return _node_id; }
+
+  NodeId get_pattern_node_id () const override final { return get_node_id (); }
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  QualifiedPathInExpression *clone_pattern_impl () const final override
+  {
+    return clone_qual_path_in_expression_impl ();
+  }
+
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  QualifiedPathInExpression *
+  clone_expr_without_block_impl () const final override
+  {
+    return clone_qual_path_in_expression_impl ();
+  }
+
+  /*virtual*/ QualifiedPathInExpression *
+  clone_qual_path_in_expression_impl () const
+  {
+    return new QualifiedPathInExpression (*this);
+  }
+};
+
+/* Represents a qualified path in a type; used for disambiguating trait
+ * function calls */
+class QualifiedPathInType : public TypeNoBounds
+{
+  QualifiedPathType path_type;
+  std::unique_ptr<TypePathSegment> associated_segment;
+  std::vector<std::unique_ptr<TypePathSegment> > segments;
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object
+   * rather than base */
+  QualifiedPathInType *clone_type_no_bounds_impl () const override
+  {
+    return new QualifiedPathInType (*this);
+  }
+
+public:
+  QualifiedPathInType (
+    QualifiedPathType qual_path_type,
+    std::unique_ptr<TypePathSegment> associated_segment,
+    std::vector<std::unique_ptr<TypePathSegment> > path_segments,
+    Location locus)
+    : path_type (std::move (qual_path_type)),
+      associated_segment (std::move (associated_segment)),
+      segments (std::move (path_segments)), locus (locus)
+  {}
+
+  /* TODO: maybe make a shortcut constructor that has QualifiedPathType
+   * elements as params */
+
+  // Copy constructor with vector clone
+  QualifiedPathInType (QualifiedPathInType const &other)
+    : path_type (other.path_type), locus (other.locus)
+  {
+    segments.reserve (other.segments.size ());
+    for (const auto &e : other.segments)
+      segments.push_back (e->clone_type_path_segment ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  QualifiedPathInType &operator= (QualifiedPathInType const &other)
+  {
+    path_type = other.path_type;
+    locus = other.locus;
+
+    segments.reserve (other.segments.size ());
+    for (const auto &e : other.segments)
+      segments.push_back (e->clone_type_path_segment ());
+
+    return *this;
+  }
+
+  // move constructors
+  QualifiedPathInType (QualifiedPathInType &&other) = default;
+  QualifiedPathInType &operator= (QualifiedPathInType &&other) = default;
+
+  // Returns whether qualified path in type is in an error state.
+  bool is_error () const { return path_type.is_error (); }
+
+  // Creates an error state qualified path in type.
+  static QualifiedPathInType create_error ()
+  {
+    return QualifiedPathInType (
+      QualifiedPathType::create_error (), nullptr,
+      std::vector<std::unique_ptr<TypePathSegment> > (), Location ());
+  }
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  QualifiedPathType &get_qualified_path_type ()
+  {
+    rust_assert (!path_type.is_error ());
+    return path_type;
+  }
+
+  std::unique_ptr<TypePathSegment> &get_associated_segment ()
+  {
+    return associated_segment;
+  }
+
+  // TODO: this seems kinda dodgy
+  std::vector<std::unique_ptr<TypePathSegment> > &get_segments ()
+  {
+    return segments;
+  }
+  const std::vector<std::unique_ptr<TypePathSegment> > &get_segments () const
+  {
+    return segments;
+  }
+
+  Location get_locus () const override final { return locus; }
+};
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-pattern.h b/gcc/rust/ast/rust-pattern.h
new file mode 100644
index 00000000000..247af5dbe05
--- /dev/null
+++ b/gcc/rust/ast/rust-pattern.h
@@ -0,0 +1,1576 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_PATTERN_H
+#define RUST_AST_PATTERN_H
+
+#include "rust-ast.h"
+
+namespace Rust {
+namespace AST {
+// Literal pattern AST node (comparing to a literal)
+class LiteralPattern : public Pattern
+{
+  Literal lit;
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override;
+
+  // Constructor for a literal pattern
+  LiteralPattern (Literal lit, Location locus)
+    : lit (std::move (lit)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  LiteralPattern (std::string val, Literal::LitType type, Location locus)
+    : lit (Literal (std::move (val), type, PrimitiveCoreType::CORETYPE_STR)),
+      locus (locus), node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+  Literal &get_literal () { return lit; }
+
+  const Literal &get_literal () const { return lit; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  virtual LiteralPattern *clone_pattern_impl () const override
+  {
+    return new LiteralPattern (*this);
+  }
+};
+
+// Identifier pattern AST node (bind value matched to a variable)
+class IdentifierPattern : public Pattern
+{
+  Identifier variable_ident;
+  bool is_ref;
+  bool is_mut;
+
+  // bool has_pattern;
+  std::unique_ptr<Pattern> to_bind;
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether the IdentifierPattern has a pattern to bind.
+  bool has_pattern_to_bind () const { return to_bind != nullptr; }
+
+  // Constructor
+  IdentifierPattern (Identifier ident, Location locus, bool is_ref = false,
+		     bool is_mut = false,
+		     std::unique_ptr<Pattern> to_bind = nullptr)
+    : Pattern (), variable_ident (std::move (ident)), is_ref (is_ref),
+      is_mut (is_mut), to_bind (std::move (to_bind)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  IdentifierPattern (NodeId node_id, Identifier ident, Location locus,
+		     bool is_ref = false, bool is_mut = false,
+		     std::unique_ptr<Pattern> to_bind = nullptr)
+    : Pattern (), variable_ident (std::move (ident)), is_ref (is_ref),
+      is_mut (is_mut), to_bind (std::move (to_bind)), locus (locus),
+      node_id (node_id)
+  {}
+
+  // Copy constructor with clone
+  IdentifierPattern (IdentifierPattern const &other)
+    : variable_ident (other.variable_ident), is_ref (other.is_ref),
+      is_mut (other.is_mut), locus (other.locus), node_id (other.node_id)
+  {
+    // fix to get prevent null pointer dereference
+    if (other.to_bind != nullptr)
+      to_bind = other.to_bind->clone_pattern ();
+  }
+
+  // Overload assignment operator to use clone
+  IdentifierPattern &operator= (IdentifierPattern const &other)
+  {
+    variable_ident = other.variable_ident;
+    is_ref = other.is_ref;
+    is_mut = other.is_mut;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    // fix to prevent null pointer dereference
+    if (other.to_bind != nullptr)
+      to_bind = other.to_bind->clone_pattern ();
+    else
+      to_bind = nullptr;
+
+    return *this;
+  }
+
+  // default move semantics
+  IdentifierPattern (IdentifierPattern &&other) = default;
+  IdentifierPattern &operator= (IdentifierPattern &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::unique_ptr<Pattern> &get_pattern_to_bind ()
+  {
+    rust_assert (has_pattern_to_bind ());
+    return to_bind;
+  }
+
+  Identifier get_ident () const { return variable_ident; }
+
+  bool get_is_mut () const { return is_mut; }
+  bool get_is_ref () const { return is_ref; }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  IdentifierPattern *clone_pattern_impl () const override
+  {
+    return new IdentifierPattern (*this);
+  }
+};
+
+// AST node for using the '_' wildcard "match any value" pattern
+class WildcardPattern : public Pattern
+{
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override { return std::string (1, '_'); }
+
+  WildcardPattern (Location locus)
+    : locus (locus), node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  WildcardPattern *clone_pattern_impl () const override
+  {
+    return new WildcardPattern (*this);
+  }
+};
+
+// Base range pattern bound (lower or upper limit) - abstract
+class RangePatternBound
+{
+public:
+  enum RangePatternBoundType
+  {
+    LITERAL,
+    PATH,
+    QUALPATH
+  };
+
+  virtual ~RangePatternBound () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<RangePatternBound> clone_range_pattern_bound () const
+  {
+    return std::unique_ptr<RangePatternBound> (
+      clone_range_pattern_bound_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual RangePatternBoundType get_bound_type () const = 0;
+
+protected:
+  // pure virtual as RangePatternBound is abstract
+  virtual RangePatternBound *clone_range_pattern_bound_impl () const = 0;
+};
+
+// Literal-based pattern bound
+class RangePatternBoundLiteral : public RangePatternBound
+{
+  Literal literal;
+  /* Can only be a char, byte, int, or float literal - same impl here as
+   * previously */
+
+  // Minus prefixed to literal (if integer or floating-point)
+  bool has_minus;
+
+  Location locus;
+
+public:
+  // Constructor
+  RangePatternBoundLiteral (Literal literal, Location locus,
+			    bool has_minus = false)
+    : literal (literal), has_minus (has_minus), locus (locus)
+  {}
+
+  std::string as_string () const override;
+
+  Literal get_literal () const { return literal; }
+
+  bool get_has_minus () const { return has_minus; }
+
+  Location get_locus () const { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  RangePatternBoundType get_bound_type () const override
+  {
+    return RangePatternBoundType::LITERAL;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangePatternBoundLiteral *clone_range_pattern_bound_impl () const override
+  {
+    return new RangePatternBoundLiteral (*this);
+  }
+};
+
+// Path-based pattern bound
+class RangePatternBoundPath : public RangePatternBound
+{
+  PathInExpression path;
+
+  /* TODO: should this be refactored so that PathInExpression is a subclass of
+   * RangePatternBound? */
+
+public:
+  RangePatternBoundPath (PathInExpression path) : path (std::move (path)) {}
+
+  std::string as_string () const override { return path.as_string (); }
+
+  Location get_locus () const { return path.get_locus (); }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems kinda dodgy
+  PathInExpression &get_path () { return path; }
+  const PathInExpression &get_path () const { return path; }
+
+  RangePatternBoundType get_bound_type () const override
+  {
+    return RangePatternBoundType::PATH;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangePatternBoundPath *clone_range_pattern_bound_impl () const override
+  {
+    return new RangePatternBoundPath (*this);
+  }
+};
+
+// Qualified path-based pattern bound
+class RangePatternBoundQualPath : public RangePatternBound
+{
+  QualifiedPathInExpression path;
+
+  /* TODO: should this be refactored so that QualifiedPathInExpression is a
+   * subclass of RangePatternBound? */
+
+public:
+  RangePatternBoundQualPath (QualifiedPathInExpression path)
+    : path (std::move (path))
+  {}
+
+  std::string as_string () const override { return path.as_string (); }
+
+  Location get_locus () const { return path.get_locus (); }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems kinda dodgy
+  QualifiedPathInExpression &get_qualified_path () { return path; }
+  const QualifiedPathInExpression &get_qualified_path () const { return path; }
+
+  RangePatternBoundType get_bound_type () const override
+  {
+    return RangePatternBoundType::QUALPATH;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangePatternBoundQualPath *clone_range_pattern_bound_impl () const override
+  {
+    return new RangePatternBoundQualPath (*this);
+  }
+};
+
+// AST node for matching within a certain range (range pattern)
+class RangePattern : public Pattern
+{
+  std::unique_ptr<RangePatternBound> lower;
+  std::unique_ptr<RangePatternBound> upper;
+
+  bool has_ellipsis_syntax;
+
+  /* location only stored to avoid a dereference - lower pattern should give
+   * correct location so maybe change in future */
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override;
+
+  // Constructor
+  RangePattern (std::unique_ptr<RangePatternBound> lower,
+		std::unique_ptr<RangePatternBound> upper, Location locus,
+		bool has_ellipsis_syntax = false)
+    : lower (std::move (lower)), upper (std::move (upper)),
+      has_ellipsis_syntax (has_ellipsis_syntax), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor with clone
+  RangePattern (RangePattern const &other)
+    : lower (other.lower->clone_range_pattern_bound ()),
+      upper (other.upper->clone_range_pattern_bound ()),
+      has_ellipsis_syntax (other.has_ellipsis_syntax), locus (other.locus),
+      node_id (other.node_id)
+  {}
+
+  // Overloaded assignment operator to clone
+  RangePattern &operator= (RangePattern const &other)
+  {
+    lower = other.lower->clone_range_pattern_bound ();
+    upper = other.upper->clone_range_pattern_bound ();
+    has_ellipsis_syntax = other.has_ellipsis_syntax;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    return *this;
+  }
+
+  // default move semantics
+  RangePattern (RangePattern &&other) = default;
+  RangePattern &operator= (RangePattern &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? or is a "vis_bound" better?
+  std::unique_ptr<RangePatternBound> &get_lower_bound ()
+  {
+    rust_assert (lower != nullptr);
+    return lower;
+  }
+
+  std::unique_ptr<RangePatternBound> &get_upper_bound ()
+  {
+    rust_assert (upper != nullptr);
+    return upper;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RangePattern *clone_pattern_impl () const override
+  {
+    return new RangePattern (*this);
+  }
+};
+
+// AST node for pattern based on dereferencing the pointers given
+class ReferencePattern : public Pattern
+{
+  bool has_two_amps;
+  bool is_mut;
+  std::unique_ptr<Pattern> pattern;
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override;
+
+  ReferencePattern (std::unique_ptr<Pattern> pattern, bool is_mut_reference,
+		    bool ref_has_two_amps, Location locus)
+    : has_two_amps (ref_has_two_amps), is_mut (is_mut_reference),
+      pattern (std::move (pattern)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor requires clone
+  ReferencePattern (ReferencePattern const &other)
+    : has_two_amps (other.has_two_amps), is_mut (other.is_mut),
+      pattern (other.pattern->clone_pattern ()), locus (other.locus),
+      node_id (other.node_id)
+  {}
+
+  // Overload assignment operator to clone
+  ReferencePattern &operator= (ReferencePattern const &other)
+  {
+    pattern = other.pattern->clone_pattern ();
+    is_mut = other.is_mut;
+    has_two_amps = other.has_two_amps;
+    locus = other.locus;
+    node_id = other.node_id;
+
+    return *this;
+  }
+
+  // default move semantics
+  ReferencePattern (ReferencePattern &&other) = default;
+  ReferencePattern &operator= (ReferencePattern &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::unique_ptr<Pattern> &get_referenced_pattern ()
+  {
+    rust_assert (pattern != nullptr);
+    return pattern;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ReferencePattern *clone_pattern_impl () const override
+  {
+    return new ReferencePattern (*this);
+  }
+};
+
+#if 0
+// aka StructPatternEtCetera; potential element in struct pattern
+struct StructPatternEtc
+{
+private:
+  std::vector<Attribute> outer_attrs;
+
+  // should this store location data?
+
+public:
+  StructPatternEtc (std::vector<Attribute> outer_attribs)
+    : outer_attrs (std::move (outer_attribs))
+  {}
+
+  // Creates an empty StructPatternEtc
+  static StructPatternEtc create_empty ()
+  {
+    return StructPatternEtc (std::vector<Attribute> ());
+  }
+};
+#endif
+
+// Base class for a single field in a struct pattern - abstract
+class StructPatternField
+{
+  std::vector<Attribute> outer_attrs;
+  Location locus;
+
+protected:
+  NodeId node_id;
+
+public:
+  enum ItemType
+  {
+    TUPLE_PAT,
+    IDENT_PAT,
+    IDENT
+  };
+
+  virtual ~StructPatternField () {}
+
+  // Unique pointer custom clone function
+  std::unique_ptr<StructPatternField> clone_struct_pattern_field () const
+  {
+    return std::unique_ptr<StructPatternField> (
+      clone_struct_pattern_field_impl ());
+  }
+
+  virtual std::string as_string () const;
+
+  Location get_locus () const { return locus; }
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual void mark_for_strip () = 0;
+  virtual bool is_marked_for_strip () const = 0;
+  virtual ItemType get_item_type () const = 0;
+
+  NodeId get_node_id () const { return node_id; }
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+protected:
+  StructPatternField (std::vector<Attribute> outer_attribs, Location locus,
+		      NodeId node_id)
+    : outer_attrs (std::move (outer_attribs)), locus (locus), node_id (node_id)
+  {}
+
+  // Clone function implementation as pure virtual method
+  virtual StructPatternField *clone_struct_pattern_field_impl () const = 0;
+};
+
+// Tuple pattern single field in a struct pattern
+class StructPatternFieldTuplePat : public StructPatternField
+{
+  TupleIndex index;
+  std::unique_ptr<Pattern> tuple_pattern;
+
+public:
+  StructPatternFieldTuplePat (TupleIndex index,
+			      std::unique_ptr<Pattern> tuple_pattern,
+			      std::vector<Attribute> outer_attribs,
+			      Location locus)
+    : StructPatternField (std::move (outer_attribs), locus,
+			  Analysis::Mappings::get ()->get_next_node_id ()),
+      index (index), tuple_pattern (std::move (tuple_pattern))
+  {}
+
+  // Copy constructor requires clone
+  StructPatternFieldTuplePat (StructPatternFieldTuplePat const &other)
+    : StructPatternField (other), index (other.index)
+  {
+    // guard to prevent null dereference (only required if error state)
+    node_id = other.get_node_id ();
+    if (other.tuple_pattern != nullptr)
+      tuple_pattern = other.tuple_pattern->clone_pattern ();
+  }
+
+  // Overload assignment operator to perform clone
+  StructPatternFieldTuplePat &
+  operator= (StructPatternFieldTuplePat const &other)
+  {
+    StructPatternField::operator= (other);
+    index = other.index;
+    // outer_attrs = other.outer_attrs;
+    node_id = other.get_node_id ();
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.tuple_pattern != nullptr)
+      tuple_pattern = other.tuple_pattern->clone_pattern ();
+    else
+      tuple_pattern = nullptr;
+
+    return *this;
+  }
+
+  // default move semantics
+  StructPatternFieldTuplePat (StructPatternFieldTuplePat &&other) = default;
+  StructPatternFieldTuplePat &operator= (StructPatternFieldTuplePat &&other)
+    = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // based on idea of tuple pattern no longer existing
+  void mark_for_strip () override { tuple_pattern = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return tuple_pattern == nullptr;
+  }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::unique_ptr<Pattern> &get_index_pattern ()
+  {
+    rust_assert (tuple_pattern != nullptr);
+    return tuple_pattern;
+  }
+
+  ItemType get_item_type () const override final { return ItemType::TUPLE_PAT; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructPatternFieldTuplePat *clone_struct_pattern_field_impl () const override
+  {
+    return new StructPatternFieldTuplePat (*this);
+  }
+};
+
+// Identifier pattern single field in a struct pattern
+class StructPatternFieldIdentPat : public StructPatternField
+{
+  Identifier ident;
+  std::unique_ptr<Pattern> ident_pattern;
+
+public:
+  StructPatternFieldIdentPat (Identifier ident,
+			      std::unique_ptr<Pattern> ident_pattern,
+			      std::vector<Attribute> outer_attrs,
+			      Location locus)
+    : StructPatternField (std::move (outer_attrs), locus,
+			  Analysis::Mappings::get ()->get_next_node_id ()),
+      ident (std::move (ident)), ident_pattern (std::move (ident_pattern))
+  {}
+
+  // Copy constructor requires clone
+  StructPatternFieldIdentPat (StructPatternFieldIdentPat const &other)
+    : StructPatternField (other), ident (other.ident)
+  {
+    // guard to prevent null dereference (only required if error state)
+    node_id = other.get_node_id ();
+    if (other.ident_pattern != nullptr)
+      ident_pattern = other.ident_pattern->clone_pattern ();
+  }
+
+  // Overload assignment operator to clone
+  StructPatternFieldIdentPat &
+  operator= (StructPatternFieldIdentPat const &other)
+  {
+    StructPatternField::operator= (other);
+    ident = other.ident;
+    // outer_attrs = other.outer_attrs;
+    node_id = other.get_node_id ();
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.ident_pattern != nullptr)
+      ident_pattern = other.ident_pattern->clone_pattern ();
+    else
+      ident_pattern = nullptr;
+
+    return *this;
+  }
+
+  // default move semantics
+  StructPatternFieldIdentPat (StructPatternFieldIdentPat &&other) = default;
+  StructPatternFieldIdentPat &operator= (StructPatternFieldIdentPat &&other)
+    = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // based on idea of identifier pattern no longer existing
+  void mark_for_strip () override { ident_pattern = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return ident_pattern == nullptr;
+  }
+
+  const Identifier &get_identifier () const { return ident; }
+
+  // TODO: is this better? Or is a "vis_pattern" better?
+  std::unique_ptr<Pattern> &get_ident_pattern ()
+  {
+    rust_assert (ident_pattern != nullptr);
+    return ident_pattern;
+  }
+
+  ItemType get_item_type () const override final { return ItemType::IDENT_PAT; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructPatternFieldIdentPat *clone_struct_pattern_field_impl () const override
+  {
+    return new StructPatternFieldIdentPat (*this);
+  }
+};
+
+// Identifier only (with no pattern) single field in a struct pattern
+class StructPatternFieldIdent : public StructPatternField
+{
+  bool has_ref;
+  bool has_mut;
+  Identifier ident;
+
+public:
+  StructPatternFieldIdent (Identifier ident, bool is_ref, bool is_mut,
+			   std::vector<Attribute> outer_attrs, Location locus)
+    : StructPatternField (std::move (outer_attrs), locus,
+			  Analysis::Mappings::get ()->get_next_node_id ()),
+      has_ref (is_ref), has_mut (is_mut), ident (std::move (ident))
+  {}
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // based on idea of identifier no longer existing
+  void mark_for_strip () override { ident = {}; }
+  bool is_marked_for_strip () const override { return ident.empty (); }
+
+  const Identifier &get_identifier () const { return ident; }
+
+  ItemType get_item_type () const override final { return ItemType::IDENT; }
+
+  bool is_ref () const { return has_ref; }
+
+  bool is_mut () const { return has_mut; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructPatternFieldIdent *clone_struct_pattern_field_impl () const override
+  {
+    return new StructPatternFieldIdent (*this);
+  }
+};
+
+// Elements of a struct pattern
+struct StructPatternElements
+{
+private:
+  // bool has_struct_pattern_fields;
+  std::vector<std::unique_ptr<StructPatternField> > fields;
+
+  bool has_struct_pattern_etc;
+  std::vector<Attribute> struct_pattern_etc_attrs;
+  // StructPatternEtc etc;
+
+  // must have at least one of the two and maybe both
+
+  // should this store location data?
+
+public:
+  // Returns whether there are any struct pattern fields
+  bool has_struct_pattern_fields () const { return !fields.empty (); }
+
+  /* Returns whether the struct pattern elements is entirely empty (no fields,
+   * no etc). */
+  bool is_empty () const
+  {
+    return !has_struct_pattern_fields () && !has_struct_pattern_etc;
+  }
+
+  bool has_etc () const { return has_struct_pattern_etc; }
+
+  // Constructor for StructPatternElements with both (potentially)
+  StructPatternElements (
+    std::vector<std::unique_ptr<StructPatternField> > fields,
+    std::vector<Attribute> etc_attrs)
+    : fields (std::move (fields)), has_struct_pattern_etc (true),
+      struct_pattern_etc_attrs (std::move (etc_attrs))
+  {}
+
+  // Constructor for StructPatternElements with no StructPatternEtc
+  StructPatternElements (
+    std::vector<std::unique_ptr<StructPatternField> > fields)
+    : fields (std::move (fields)), has_struct_pattern_etc (false),
+      struct_pattern_etc_attrs ()
+  {}
+
+  // Copy constructor with vector clone
+  StructPatternElements (StructPatternElements const &other)
+    : has_struct_pattern_etc (other.has_struct_pattern_etc),
+      struct_pattern_etc_attrs (other.struct_pattern_etc_attrs)
+  {
+    fields.reserve (other.fields.size ());
+    for (const auto &e : other.fields)
+      fields.push_back (e->clone_struct_pattern_field ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  StructPatternElements &operator= (StructPatternElements const &other)
+  {
+    struct_pattern_etc_attrs = other.struct_pattern_etc_attrs;
+    has_struct_pattern_etc = other.has_struct_pattern_etc;
+
+    fields.reserve (other.fields.size ());
+    for (const auto &e : other.fields)
+      fields.push_back (e->clone_struct_pattern_field ());
+
+    return *this;
+  }
+
+  // move constructors
+  StructPatternElements (StructPatternElements &&other) = default;
+  StructPatternElements &operator= (StructPatternElements &&other) = default;
+
+  // Creates an empty StructPatternElements
+  static StructPatternElements create_empty ()
+  {
+    return StructPatternElements (
+      std::vector<std::unique_ptr<StructPatternField> > ());
+  }
+
+  std::string as_string () const;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<StructPatternField> > &
+  get_struct_pattern_fields ()
+  {
+    return fields;
+  }
+  const std::vector<std::unique_ptr<StructPatternField> > &
+  get_struct_pattern_fields () const
+  {
+    return fields;
+  }
+
+  std::vector<Attribute> &get_etc_outer_attrs ()
+  {
+    return struct_pattern_etc_attrs;
+  }
+  const std::vector<Attribute> &get_etc_outer_attrs () const
+  {
+    return struct_pattern_etc_attrs;
+  }
+
+  void strip_etc ()
+  {
+    has_struct_pattern_etc = false;
+    struct_pattern_etc_attrs.clear ();
+    struct_pattern_etc_attrs.shrink_to_fit ();
+  }
+};
+
+// Struct pattern AST node representation
+class StructPattern : public Pattern
+{
+  PathInExpression path;
+
+  // bool has_struct_pattern_elements;
+  StructPatternElements elems;
+
+  NodeId node_id;
+  Location locus;
+
+public:
+  std::string as_string () const override;
+
+  // Constructs a struct pattern from specified StructPatternElements
+  StructPattern (PathInExpression struct_path, Location locus,
+		 StructPatternElements elems
+		 = StructPatternElements::create_empty ())
+    : path (std::move (struct_path)), elems (std::move (elems)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ()), locus (locus)
+  {}
+
+  /* TODO: constructor to construct via elements included in
+   * StructPatternElements */
+
+  /* Returns whether struct pattern has any struct pattern elements (if not, it
+   * is empty). */
+  bool has_struct_pattern_elems () const { return !elems.is_empty (); }
+
+  Location get_locus () const { return path.get_locus (); }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  StructPatternElements &get_struct_pattern_elems () { return elems; }
+  const StructPatternElements &get_struct_pattern_elems () const
+  {
+    return elems;
+  }
+
+  PathInExpression &get_path () { return path; }
+  const PathInExpression &get_path () const { return path; }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  StructPattern *clone_pattern_impl () const override
+  {
+    return new StructPattern (*this);
+  }
+};
+
+// Base abstract class for patterns used in TupleStructPattern
+class TupleStructItems
+{
+public:
+  enum ItemType
+  {
+    RANGE,
+    NO_RANGE
+  };
+
+  virtual ~TupleStructItems () {}
+
+  // TODO: should this store location data?
+
+  // Unique pointer custom clone function
+  std::unique_ptr<TupleStructItems> clone_tuple_struct_items () const
+  {
+    return std::unique_ptr<TupleStructItems> (clone_tuple_struct_items_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual ItemType get_item_type () const = 0;
+
+protected:
+  // pure virtual clone implementation
+  virtual TupleStructItems *clone_tuple_struct_items_impl () const = 0;
+};
+
+// Class for non-ranged tuple struct pattern patterns
+class TupleStructItemsNoRange : public TupleStructItems
+{
+  std::vector<std::unique_ptr<Pattern> > patterns;
+
+public:
+  TupleStructItemsNoRange (std::vector<std::unique_ptr<Pattern> > patterns)
+    : patterns (std::move (patterns))
+  {}
+
+  // Copy constructor with vector clone
+  TupleStructItemsNoRange (TupleStructItemsNoRange const &other)
+  {
+    patterns.reserve (other.patterns.size ());
+    for (const auto &e : other.patterns)
+      patterns.push_back (e->clone_pattern ());
+  }
+
+  // Overloaded assignment operator with vector clone
+  TupleStructItemsNoRange &operator= (TupleStructItemsNoRange const &other)
+  {
+    patterns.reserve (other.patterns.size ());
+    for (const auto &e : other.patterns)
+      patterns.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  TupleStructItemsNoRange (TupleStructItemsNoRange &&other) = default;
+  TupleStructItemsNoRange &operator= (TupleStructItemsNoRange &&other)
+    = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<Pattern> > &get_patterns () { return patterns; }
+  const std::vector<std::unique_ptr<Pattern> > &get_patterns () const
+  {
+    return patterns;
+  }
+
+  ItemType get_item_type () const override final { return ItemType::NO_RANGE; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TupleStructItemsNoRange *clone_tuple_struct_items_impl () const override
+  {
+    return new TupleStructItemsNoRange (*this);
+  }
+};
+
+// Class for ranged tuple struct pattern patterns
+class TupleStructItemsRange : public TupleStructItems
+{
+  std::vector<std::unique_ptr<Pattern> > lower_patterns;
+  std::vector<std::unique_ptr<Pattern> > upper_patterns;
+
+public:
+  TupleStructItemsRange (std::vector<std::unique_ptr<Pattern> > lower_patterns,
+			 std::vector<std::unique_ptr<Pattern> > upper_patterns)
+    : lower_patterns (std::move (lower_patterns)),
+      upper_patterns (std::move (upper_patterns))
+  {}
+
+  // Copy constructor with vector clone
+  TupleStructItemsRange (TupleStructItemsRange const &other)
+  {
+    lower_patterns.reserve (other.lower_patterns.size ());
+    for (const auto &e : other.lower_patterns)
+      lower_patterns.push_back (e->clone_pattern ());
+
+    upper_patterns.reserve (other.upper_patterns.size ());
+    for (const auto &e : other.upper_patterns)
+      upper_patterns.push_back (e->clone_pattern ());
+  }
+
+  // Overloaded assignment operator to clone
+  TupleStructItemsRange &operator= (TupleStructItemsRange const &other)
+  {
+    lower_patterns.reserve (other.lower_patterns.size ());
+    for (const auto &e : other.lower_patterns)
+      lower_patterns.push_back (e->clone_pattern ());
+
+    upper_patterns.reserve (other.upper_patterns.size ());
+    for (const auto &e : other.upper_patterns)
+      upper_patterns.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  TupleStructItemsRange (TupleStructItemsRange &&other) = default;
+  TupleStructItemsRange &operator= (TupleStructItemsRange &&other) = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<Pattern> > &get_lower_patterns ()
+  {
+    return lower_patterns;
+  }
+  const std::vector<std::unique_ptr<Pattern> > &get_lower_patterns () const
+  {
+    return lower_patterns;
+  }
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<Pattern> > &get_upper_patterns ()
+  {
+    return upper_patterns;
+  }
+  const std::vector<std::unique_ptr<Pattern> > &get_upper_patterns () const
+  {
+    return upper_patterns;
+  }
+
+  ItemType get_item_type () const override final { return ItemType::RANGE; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TupleStructItemsRange *clone_tuple_struct_items_impl () const override
+  {
+    return new TupleStructItemsRange (*this);
+  }
+};
+
+// AST node representing a tuple struct pattern
+class TupleStructPattern : public Pattern
+{
+  PathInExpression path;
+  std::unique_ptr<TupleStructItems> items;
+  NodeId node_id;
+
+  /* TOOD: should this store location data? current accessor uses path location
+   * data */
+
+public:
+  std::string as_string () const override;
+
+  // Returns whether the pattern has tuple struct items.
+  bool has_items () const { return items != nullptr; }
+
+  TupleStructPattern (PathInExpression tuple_struct_path,
+		      std::unique_ptr<TupleStructItems> items)
+    : path (std::move (tuple_struct_path)), items (std::move (items)),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor required to clone
+  TupleStructPattern (TupleStructPattern const &other) : path (other.path)
+  {
+    // guard to protect from null dereference
+    node_id = other.node_id;
+    if (other.items != nullptr)
+      items = other.items->clone_tuple_struct_items ();
+  }
+
+  // Operator overload assignment operator to clone
+  TupleStructPattern &operator= (TupleStructPattern const &other)
+  {
+    path = other.path;
+    node_id = other.node_id;
+
+    // guard to protect from null dereference
+    if (other.items != nullptr)
+      items = other.items->clone_tuple_struct_items ();
+    else
+      items = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  TupleStructPattern (TupleStructPattern &&other) = default;
+  TupleStructPattern &operator= (TupleStructPattern &&other) = default;
+
+  Location get_locus () const override { return path.get_locus (); }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::unique_ptr<TupleStructItems> &get_items ()
+  {
+    rust_assert (has_items ());
+    return items;
+  }
+
+  PathInExpression &get_path () { return path; }
+  const PathInExpression &get_path () const { return path; }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TupleStructPattern *clone_pattern_impl () const override
+  {
+    return new TupleStructPattern (*this);
+  }
+};
+
+// Base abstract class representing TuplePattern patterns
+class TuplePatternItems
+{
+public:
+  enum TuplePatternItemType
+  {
+    MULTIPLE,
+    RANGED,
+  };
+
+  virtual ~TuplePatternItems () {}
+
+  // TODO: should this store location data?
+
+  // Unique pointer custom clone function
+  std::unique_ptr<TuplePatternItems> clone_tuple_pattern_items () const
+  {
+    return std::unique_ptr<TuplePatternItems> (
+      clone_tuple_pattern_items_impl ());
+  }
+
+  virtual std::string as_string () const = 0;
+
+  virtual void accept_vis (ASTVisitor &vis) = 0;
+
+  virtual TuplePatternItemType get_pattern_type () const = 0;
+
+protected:
+  // pure virtual clone implementation
+  virtual TuplePatternItems *clone_tuple_pattern_items_impl () const = 0;
+};
+
+// Class representing TuplePattern patterns where there is only a single pattern
+/*class TuplePatternItemsSingle : public TuplePatternItems {
+    // Pattern pattern;
+    std::unique_ptr<Pattern> pattern;
+
+  public:
+    TuplePatternItemsSingle(Pattern* pattern) : pattern(pattern) {}
+
+    // Copy constructor uses clone
+    TuplePatternItemsSingle(TuplePatternItemsSingle const& other) :
+      pattern(other.pattern->clone_pattern()) {}
+
+    // Destructor - define here if required
+
+    // Overload assignment operator to clone
+    TuplePatternItemsSingle& operator=(TuplePatternItemsSingle const& other) {
+	pattern = other.pattern->clone_pattern();
+
+	return *this;
+    }
+
+    // move constructors
+    TuplePatternItemsSingle(TuplePatternItemsSingle&& other) = default;
+    TuplePatternItemsSingle& operator=(TuplePatternItemsSingle&& other) =
+default;
+
+  protected:
+    // Use covariance to implement clone function as returning this object
+rather than base virtual TuplePatternItemsSingle*
+clone_tuple_pattern_items_impl() const override { return new
+TuplePatternItemsSingle(*this);
+    }
+};*/
+// removed in favour of single-element TuplePatternItemsMultiple
+
+// Class representing TuplePattern patterns where there are multiple patterns
+class TuplePatternItemsMultiple : public TuplePatternItems
+{
+  std::vector<std::unique_ptr<Pattern> > patterns;
+
+public:
+  TuplePatternItemsMultiple (std::vector<std::unique_ptr<Pattern> > patterns)
+    : patterns (std::move (patterns))
+  {}
+
+  // Copy constructor with vector clone
+  TuplePatternItemsMultiple (TuplePatternItemsMultiple const &other)
+  {
+    patterns.reserve (other.patterns.size ());
+    for (const auto &e : other.patterns)
+      patterns.push_back (e->clone_pattern ());
+  }
+
+  // Overloaded assignment operator to vector clone
+  TuplePatternItemsMultiple &operator= (TuplePatternItemsMultiple const &other)
+  {
+    patterns.reserve (other.patterns.size ());
+    for (const auto &e : other.patterns)
+      patterns.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  TuplePatternItemsMultiple (TuplePatternItemsMultiple &&other) = default;
+  TuplePatternItemsMultiple &operator= (TuplePatternItemsMultiple &&other)
+    = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<Pattern> > &get_patterns () { return patterns; }
+  const std::vector<std::unique_ptr<Pattern> > &get_patterns () const
+  {
+    return patterns;
+  }
+
+  TuplePatternItemType get_pattern_type () const override
+  {
+    return TuplePatternItemType::MULTIPLE;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TuplePatternItemsMultiple *clone_tuple_pattern_items_impl () const override
+  {
+    return new TuplePatternItemsMultiple (*this);
+  }
+};
+
+// Class representing TuplePattern patterns where there are a range of patterns
+class TuplePatternItemsRanged : public TuplePatternItems
+{
+  std::vector<std::unique_ptr<Pattern> > lower_patterns;
+  std::vector<std::unique_ptr<Pattern> > upper_patterns;
+
+public:
+  TuplePatternItemsRanged (
+    std::vector<std::unique_ptr<Pattern> > lower_patterns,
+    std::vector<std::unique_ptr<Pattern> > upper_patterns)
+    : lower_patterns (std::move (lower_patterns)),
+      upper_patterns (std::move (upper_patterns))
+  {}
+
+  // Copy constructor with vector clone
+  TuplePatternItemsRanged (TuplePatternItemsRanged const &other)
+  {
+    lower_patterns.reserve (other.lower_patterns.size ());
+    for (const auto &e : other.lower_patterns)
+      lower_patterns.push_back (e->clone_pattern ());
+
+    upper_patterns.reserve (other.upper_patterns.size ());
+    for (const auto &e : other.upper_patterns)
+      upper_patterns.push_back (e->clone_pattern ());
+  }
+
+  // Overloaded assignment operator to clone
+  TuplePatternItemsRanged &operator= (TuplePatternItemsRanged const &other)
+  {
+    lower_patterns.reserve (other.lower_patterns.size ());
+    for (const auto &e : other.lower_patterns)
+      lower_patterns.push_back (e->clone_pattern ());
+
+    upper_patterns.reserve (other.upper_patterns.size ());
+    for (const auto &e : other.upper_patterns)
+      upper_patterns.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  TuplePatternItemsRanged (TuplePatternItemsRanged &&other) = default;
+  TuplePatternItemsRanged &operator= (TuplePatternItemsRanged &&other)
+    = default;
+
+  std::string as_string () const override;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<Pattern> > &get_lower_patterns ()
+  {
+    return lower_patterns;
+  }
+  const std::vector<std::unique_ptr<Pattern> > &get_lower_patterns () const
+  {
+    return lower_patterns;
+  }
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<Pattern> > &get_upper_patterns ()
+  {
+    return upper_patterns;
+  }
+  const std::vector<std::unique_ptr<Pattern> > &get_upper_patterns () const
+  {
+    return upper_patterns;
+  }
+
+  TuplePatternItemType get_pattern_type () const override
+  {
+    return TuplePatternItemType::RANGED;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TuplePatternItemsRanged *clone_tuple_pattern_items_impl () const override
+  {
+    return new TuplePatternItemsRanged (*this);
+  }
+};
+
+// AST node representing a tuple pattern
+class TuplePattern : public Pattern
+{
+  // bool has_tuple_pattern_items;
+  std::unique_ptr<TuplePatternItems> items;
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override;
+
+  // Returns true if the tuple pattern has items
+  bool has_tuple_pattern_items () const { return items != nullptr; }
+
+  TuplePattern (std::unique_ptr<TuplePatternItems> items, Location locus)
+    : items (std::move (items)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor requires clone
+  TuplePattern (TuplePattern const &other) : locus (other.locus)
+  {
+    // guard to prevent null dereference
+    node_id = other.node_id;
+    if (other.items != nullptr)
+      items = other.items->clone_tuple_pattern_items ();
+  }
+
+  // Overload assignment operator to clone
+  TuplePattern &operator= (TuplePattern const &other)
+  {
+    locus = other.locus;
+    node_id = other.node_id;
+
+    // guard to prevent null dereference
+    if (other.items != nullptr)
+      items = other.items->clone_tuple_pattern_items ();
+    else
+      items = nullptr;
+
+    return *this;
+  }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::unique_ptr<TuplePatternItems> &get_items ()
+  {
+    rust_assert (has_tuple_pattern_items ());
+    return items;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TuplePattern *clone_pattern_impl () const override
+  {
+    return new TuplePattern (*this);
+  }
+};
+
+// AST node representing a pattern in parentheses, used to control precedence
+class GroupedPattern : public Pattern
+{
+  std::unique_ptr<Pattern> pattern_in_parens;
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override
+  {
+    return "(" + pattern_in_parens->as_string () + ")";
+  }
+
+  GroupedPattern (std::unique_ptr<Pattern> pattern_in_parens, Location locus)
+    : pattern_in_parens (std::move (pattern_in_parens)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor uses clone
+  GroupedPattern (GroupedPattern const &other)
+    : pattern_in_parens (other.pattern_in_parens->clone_pattern ()),
+      locus (other.locus), node_id (other.node_id)
+  {}
+
+  // Overload assignment operator to clone
+  GroupedPattern &operator= (GroupedPattern const &other)
+  {
+    pattern_in_parens = other.pattern_in_parens->clone_pattern ();
+    locus = other.locus;
+    node_id = other.node_id;
+
+    return *this;
+  }
+
+  // default move semantics
+  GroupedPattern (GroupedPattern &&other) = default;
+  GroupedPattern &operator= (GroupedPattern &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::unique_ptr<Pattern> &get_pattern_in_parens ()
+  {
+    rust_assert (pattern_in_parens != nullptr);
+    return pattern_in_parens;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  GroupedPattern *clone_pattern_impl () const override
+  {
+    return new GroupedPattern (*this);
+  }
+};
+
+// AST node representing patterns that can match slices and arrays
+class SlicePattern : public Pattern
+{
+  std::vector<std::unique_ptr<Pattern> > items;
+  Location locus;
+  NodeId node_id;
+
+public:
+  std::string as_string () const override;
+
+  SlicePattern (std::vector<std::unique_ptr<Pattern> > items, Location locus)
+    : items (std::move (items)), locus (locus),
+      node_id (Analysis::Mappings::get ()->get_next_node_id ())
+  {}
+
+  // Copy constructor with vector clone
+  SlicePattern (SlicePattern const &other) : locus (other.locus)
+  {
+    node_id = other.node_id;
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_pattern ());
+  }
+
+  // Overloaded assignment operator to vector clone
+  SlicePattern &operator= (SlicePattern const &other)
+  {
+    locus = other.locus;
+    node_id = other.node_id;
+
+    items.reserve (other.items.size ());
+    for (const auto &e : other.items)
+      items.push_back (e->clone_pattern ());
+
+    return *this;
+  }
+
+  // move constructors
+  SlicePattern (SlicePattern &&other) = default;
+  SlicePattern &operator= (SlicePattern &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: seems kinda dodgy. Think of better way.
+  std::vector<std::unique_ptr<Pattern> > &get_items () { return items; }
+  const std::vector<std::unique_ptr<Pattern> > &get_items () const
+  {
+    return items;
+  }
+
+  NodeId get_node_id () const { return node_id; }
+
+  NodeId get_pattern_node_id () const override final { return node_id; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  SlicePattern *clone_pattern_impl () const override
+  {
+    return new SlicePattern (*this);
+  }
+};
+
+// Moved definition to rust-path.h
+class PathPattern;
+
+// Forward decls for paths (defined in rust-path.h)
+class PathInExpression;
+class QualifiedPathInExpression;
+
+// Replaced with forward decl - defined in rust-macro.h
+class MacroInvocation;
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-stmt.h b/gcc/rust/ast/rust-stmt.h
new file mode 100644
index 00000000000..9d95c3e27e8
--- /dev/null
+++ b/gcc/rust/ast/rust-stmt.h
@@ -0,0 +1,358 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_STATEMENT_H
+#define RUST_AST_STATEMENT_H
+
+#include "rust-ast.h"
+#include "rust-path.h"
+#include "rust-expr.h"
+
+namespace Rust {
+namespace AST {
+// Just a semi-colon, which apparently is a statement.
+class EmptyStmt : public Stmt
+{
+  Location locus;
+
+  // TODO: find another way to store this to save memory?
+  bool marked_for_strip = false;
+
+public:
+  std::string as_string () const override { return std::string (1, ';'); }
+
+  EmptyStmt (Location locus) : locus (locus) {}
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Can't think of any invalid invariants, so store boolean.
+  void mark_for_strip () override { marked_for_strip = true; }
+  bool is_marked_for_strip () const override { return marked_for_strip; }
+
+  bool is_item () const override final { return false; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  EmptyStmt *clone_stmt_impl () const override { return new EmptyStmt (*this); }
+};
+
+/* Variable assignment let statement - type of "declaration statement" as it
+ * introduces new name into scope */
+class LetStmt : public Stmt
+{
+  // bool has_outer_attrs;
+  std::vector<Attribute> outer_attrs;
+
+  std::unique_ptr<Pattern> variables_pattern;
+
+  // bool has_type;
+  std::unique_ptr<Type> type;
+
+  // bool has_init_expr;
+  std::unique_ptr<Expr> init_expr;
+
+  Location locus;
+
+public:
+  Type *inferedType;
+
+  // Returns whether let statement has outer attributes.
+  bool has_outer_attrs () const { return !outer_attrs.empty (); }
+
+  // Returns whether let statement has a given return type.
+  bool has_type () const { return type != nullptr; }
+
+  // Returns whether let statement has an initialisation expression.
+  bool has_init_expr () const { return init_expr != nullptr; }
+
+  std::string as_string () const override;
+
+  LetStmt (std::unique_ptr<Pattern> variables_pattern,
+	   std::unique_ptr<Expr> init_expr, std::unique_ptr<Type> type,
+	   std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)),
+      variables_pattern (std::move (variables_pattern)),
+      type (std::move (type)), init_expr (std::move (init_expr)), locus (locus)
+  {}
+
+  // Copy constructor with clone
+  LetStmt (LetStmt const &other)
+    : outer_attrs (other.outer_attrs), locus (other.locus)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.variables_pattern != nullptr)
+      variables_pattern = other.variables_pattern->clone_pattern ();
+
+    // guard to prevent null dereference (always required)
+    if (other.init_expr != nullptr)
+      init_expr = other.init_expr->clone_expr ();
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+  }
+
+  // Overloaded assignment operator to clone
+  LetStmt &operator= (LetStmt const &other)
+  {
+    outer_attrs = other.outer_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.variables_pattern != nullptr)
+      variables_pattern = other.variables_pattern->clone_pattern ();
+    else
+      variables_pattern = nullptr;
+
+    // guard to prevent null dereference (always required)
+    if (other.init_expr != nullptr)
+      init_expr = other.init_expr->clone_expr ();
+    else
+      init_expr = nullptr;
+    if (other.type != nullptr)
+      type = other.type->clone_type ();
+    else
+      type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  LetStmt (LetStmt &&other) = default;
+  LetStmt &operator= (LetStmt &&other) = default;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if pattern is null, so base stripping on that.
+  void mark_for_strip () override { variables_pattern = nullptr; }
+  bool is_marked_for_strip () const override
+  {
+    return variables_pattern == nullptr;
+  }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<Expr> &get_init_expr ()
+  {
+    rust_assert (has_init_expr ());
+    return init_expr;
+  }
+
+  std::unique_ptr<Pattern> &get_pattern ()
+  {
+    rust_assert (variables_pattern != nullptr);
+    return variables_pattern;
+  }
+
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (has_type ());
+    return type;
+  }
+
+  bool is_item () const override final { return false; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  LetStmt *clone_stmt_impl () const override { return new LetStmt (*this); }
+};
+
+/* Abstract base class for expression statements (statements containing an
+ * expression) */
+class ExprStmt : public Stmt
+{
+public:
+  enum ExprStmtType
+  {
+    WITH_BLOCK,
+    WITHOUT_BLOCK
+  };
+
+protected:
+  Location locus;
+
+public:
+  Location get_locus () const override final { return locus; }
+
+  bool is_item () const override final { return false; }
+
+  virtual ExprStmtType get_type () const = 0;
+
+protected:
+  ExprStmt (Location locus) : locus (locus) {}
+};
+
+/* Statement containing an expression without a block (or, due to technical
+ * difficulties, can only be guaranteed to hold an expression). */
+class ExprStmtWithoutBlock : public ExprStmt
+{
+  // TODO: ensure that this works
+  std::unique_ptr<ExprWithoutBlock> expr;
+  /* HACK: cannot ensure type safety of ExprWithoutBlock due to Pratt parsing,
+   * so have to store more general type of Expr. FIXME: fix this issue somehow
+   * or redesign AST. */
+  // std::unique_ptr<Expr> expr;
+
+public:
+  std::string as_string () const override;
+
+  ExprStmtWithoutBlock (std::unique_ptr<ExprWithoutBlock> expr, Location locus)
+    : ExprStmt (locus), expr (std::move (expr->to_stmt ()))
+  {}
+
+  /*ExprStmtWithoutBlock (std::unique_ptr<Expr> expr, Location locus)
+    : ExprStmt (locus), expr (std::move (expr))
+  {}*/
+
+  // Copy constructor with clone
+  ExprStmtWithoutBlock (ExprStmtWithoutBlock const &other) : ExprStmt (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr_without_block ();
+  }
+  /*ExprStmtWithoutBlock (ExprStmtWithoutBlock const &other)
+    : ExprStmt (other), expr (other.expr->clone_expr ())
+  {}*/
+
+  // Overloaded assignment operator to clone
+  ExprStmtWithoutBlock &operator= (ExprStmtWithoutBlock const &other)
+  {
+    ExprStmt::operator= (other);
+    // expr = other.expr->clone_expr ();
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr_without_block ();
+    else
+      expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ExprStmtWithoutBlock (ExprStmtWithoutBlock &&other) = default;
+  ExprStmtWithoutBlock &operator= (ExprStmtWithoutBlock &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if expr is null, so base stripping on that.
+  void mark_for_strip () override { expr = nullptr; }
+  bool is_marked_for_strip () const override { return expr == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<ExprWithoutBlock> &get_expr ()
+  {
+    rust_assert (expr != nullptr);
+    return expr;
+  }
+
+  ExprStmtType get_type () const override
+  {
+    return ExprStmtType::WITHOUT_BLOCK;
+  };
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ExprStmtWithoutBlock *clone_stmt_impl () const override
+  {
+    return new ExprStmtWithoutBlock (*this);
+  }
+};
+
+// Statement containing an expression with a block
+class ExprStmtWithBlock : public ExprStmt
+{
+  std::unique_ptr<ExprWithBlock> expr;
+  bool semicolon_followed;
+
+public:
+  std::string as_string () const override;
+
+  std::vector<LetStmt *> locals;
+
+  ExprStmtWithBlock (std::unique_ptr<ExprWithBlock> expr, Location locus,
+		     bool semicolon_followed)
+    : ExprStmt (locus), expr (std::move (expr)),
+      semicolon_followed (semicolon_followed)
+  {}
+
+  // Copy constructor with clone
+  ExprStmtWithBlock (ExprStmtWithBlock const &other) : ExprStmt (other)
+  {
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr_with_block ();
+  }
+
+  // Overloaded assignment operator to clone
+  ExprStmtWithBlock &operator= (ExprStmtWithBlock const &other)
+  {
+    ExprStmt::operator= (other);
+
+    // guard to prevent null dereference (only required if error state)
+    if (other.expr != nullptr)
+      expr = other.expr->clone_expr_with_block ();
+    else
+      expr = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  ExprStmtWithBlock (ExprStmtWithBlock &&other) = default;
+  ExprStmtWithBlock &operator= (ExprStmtWithBlock &&other) = default;
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // Invalid if expr is null, so base stripping on that.
+  void mark_for_strip () override { expr = nullptr; }
+  bool is_marked_for_strip () const override { return expr == nullptr; }
+
+  // TODO: is this better? Or is a "vis_block" better?
+  std::unique_ptr<ExprWithBlock> &get_expr ()
+  {
+    rust_assert (expr != nullptr);
+    return expr;
+  }
+
+  bool is_semicolon_followed () const { return semicolon_followed; }
+
+  ExprStmtType get_type () const override { return ExprStmtType::WITH_BLOCK; };
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ExprStmtWithBlock *clone_stmt_impl () const override
+  {
+    return new ExprStmtWithBlock (*this);
+  }
+};
+
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/ast/rust-type.h b/gcc/rust/ast/rust-type.h
new file mode 100644
index 00000000000..7e9e07d0c18
--- /dev/null
+++ b/gcc/rust/ast/rust-type.h
@@ -0,0 +1,962 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_AST_TYPE_H
+#define RUST_AST_TYPE_H
+
+#include "rust-ast.h"
+#include "rust-path.h"
+
+namespace Rust {
+namespace AST {
+// definitions moved to rust-ast.h
+class TypeParamBound;
+class Lifetime;
+
+// A trait bound
+class TraitBound : public TypeParamBound
+{
+  bool in_parens;
+  bool opening_question_mark;
+
+  // bool has_for_lifetimes;
+  // LifetimeParams for_lifetimes;
+  std::vector<LifetimeParam> for_lifetimes; // inlined LifetimeParams
+
+  TypePath type_path;
+
+  Location locus;
+
+public:
+  // Returns whether trait bound has "for" lifetimes
+  bool has_for_lifetimes () const { return !for_lifetimes.empty (); }
+
+  TraitBound (TypePath type_path, Location locus, bool in_parens = false,
+	      bool opening_question_mark = false,
+	      std::vector<LifetimeParam> for_lifetimes
+	      = std::vector<LifetimeParam> ())
+    : TypeParamBound (Analysis::Mappings::get ()->get_next_node_id ()),
+      in_parens (in_parens), opening_question_mark (opening_question_mark),
+      for_lifetimes (std::move (for_lifetimes)),
+      type_path (std::move (type_path)), locus (locus)
+  {}
+
+  TraitBound (NodeId id, TypePath type_path, Location locus,
+	      bool in_parens = false, bool opening_question_mark = false,
+	      std::vector<LifetimeParam> for_lifetimes
+	      = std::vector<LifetimeParam> ())
+    : TypeParamBound (id), in_parens (in_parens),
+      opening_question_mark (opening_question_mark),
+      for_lifetimes (std::move (for_lifetimes)),
+      type_path (std::move (type_path)), locus (locus)
+  {}
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems kinda dodgy
+  TypePath &get_type_path () { return type_path; }
+  const TypePath &get_type_path () const { return type_path; }
+
+  bool is_in_parens () const { return in_parens; }
+  bool has_opening_question_mark () const { return opening_question_mark; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TraitBound *clone_type_param_bound_impl () const override
+  {
+    return new TraitBound (node_id, type_path, locus, in_parens,
+			   opening_question_mark, for_lifetimes);
+  }
+};
+
+// definition moved to rust-ast.h
+class TypeNoBounds;
+
+// An impl trait? Poor reference material here.
+class ImplTraitType : public Type
+{
+  // TypeParamBounds type_param_bounds;
+  // inlined form
+  std::vector<std::unique_ptr<TypeParamBound> > type_param_bounds;
+
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ImplTraitType *clone_type_impl () const override
+  {
+    return new ImplTraitType (*this);
+  }
+
+public:
+  ImplTraitType (
+    std::vector<std::unique_ptr<TypeParamBound> > type_param_bounds,
+    Location locus)
+    : type_param_bounds (std::move (type_param_bounds)), locus (locus)
+  {}
+
+  // copy constructor with vector clone
+  ImplTraitType (ImplTraitType const &other) : locus (other.locus)
+  {
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+  }
+
+  // overloaded assignment operator to clone
+  ImplTraitType &operator= (ImplTraitType const &other)
+  {
+    locus = other.locus;
+
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+
+    return *this;
+  }
+
+  // move constructors
+  ImplTraitType (ImplTraitType &&other) = default;
+  ImplTraitType &operator= (ImplTraitType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: mutable getter seems kinda dodgy
+  std::vector<std::unique_ptr<TypeParamBound> > &get_type_param_bounds ()
+  {
+    return type_param_bounds;
+  }
+  const std::vector<std::unique_ptr<TypeParamBound> > &
+  get_type_param_bounds () const
+  {
+    return type_param_bounds;
+  }
+};
+
+// An opaque value of another type that implements a set of traits
+class TraitObjectType : public Type
+{
+  bool has_dyn;
+  std::vector<std::unique_ptr<TypeParamBound> > type_param_bounds;
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TraitObjectType *clone_type_impl () const override
+  {
+    return new TraitObjectType (*this);
+  }
+
+public:
+  TraitObjectType (
+    std::vector<std::unique_ptr<TypeParamBound> > type_param_bounds,
+    Location locus, bool is_dyn_dispatch)
+    : has_dyn (is_dyn_dispatch),
+      type_param_bounds (std::move (type_param_bounds)), locus (locus)
+  {}
+
+  // copy constructor with vector clone
+  TraitObjectType (TraitObjectType const &other)
+    : has_dyn (other.has_dyn), locus (other.locus)
+  {
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+  }
+
+  // overloaded assignment operator to clone
+  TraitObjectType &operator= (TraitObjectType const &other)
+  {
+    has_dyn = other.has_dyn;
+    locus = other.locus;
+    type_param_bounds.reserve (other.type_param_bounds.size ());
+    for (const auto &e : other.type_param_bounds)
+      type_param_bounds.push_back (e->clone_type_param_bound ());
+
+    return *this;
+  }
+
+  // move constructors
+  TraitObjectType (TraitObjectType &&other) = default;
+  TraitObjectType &operator= (TraitObjectType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  bool is_dyn () const { return has_dyn; }
+
+  // TODO: mutable getter seems kinda dodgy
+  std::vector<std::unique_ptr<TypeParamBound> > &get_type_param_bounds ()
+  {
+    return type_param_bounds;
+  }
+  const std::vector<std::unique_ptr<TypeParamBound> > &
+  get_type_param_bounds () const
+  {
+    return type_param_bounds;
+  }
+};
+
+// A type with parentheses around it, used to avoid ambiguity.
+class ParenthesisedType : public TypeNoBounds
+{
+  std::unique_ptr<Type> type_in_parens;
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ParenthesisedType *clone_type_no_bounds_impl () const override
+  {
+    return new ParenthesisedType (*this);
+  }
+
+public:
+  // Constructor uses Type pointer for polymorphism
+  ParenthesisedType (std::unique_ptr<Type> type_inside_parens, Location locus)
+    : type_in_parens (std::move (type_inside_parens)), locus (locus)
+  {}
+
+  /* Copy constructor uses custom deep copy method for type to preserve
+   * polymorphism */
+  ParenthesisedType (ParenthesisedType const &other)
+    : type_in_parens (other.type_in_parens->clone_type ()), locus (other.locus)
+  {}
+
+  // overload assignment operator to use custom clone method
+  ParenthesisedType &operator= (ParenthesisedType const &other)
+  {
+    type_in_parens = other.type_in_parens->clone_type ();
+    locus = other.locus;
+    return *this;
+  }
+
+  // default move semantics
+  ParenthesisedType (ParenthesisedType &&other) = default;
+  ParenthesisedType &operator= (ParenthesisedType &&other) = default;
+
+  std::string as_string () const override
+  {
+    return "(" + type_in_parens->as_string () + ")";
+  }
+
+  // Creates a trait bound (clone of this one's trait bound) - HACK
+  TraitBound *to_trait_bound (bool) const override
+  {
+    /* NOTE: obviously it is unknown whether the internal type is a trait bound
+     * due to polymorphism, so just let the internal type handle it. As
+     * parenthesised type, it must be in parentheses. */
+    return type_in_parens->to_trait_bound (true);
+  }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: would a "vis_type" be better?
+  std::unique_ptr<Type> &get_type_in_parens ()
+  {
+    rust_assert (type_in_parens != nullptr);
+    return type_in_parens;
+  }
+};
+
+// Impl trait with a single bound? Poor reference material here.
+class ImplTraitTypeOneBound : public TypeNoBounds
+{
+  TraitBound trait_bound;
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ImplTraitTypeOneBound *clone_type_no_bounds_impl () const override
+  {
+    return new ImplTraitTypeOneBound (*this);
+  }
+
+public:
+  ImplTraitTypeOneBound (TraitBound trait_bound, Location locus)
+    : trait_bound (std::move (trait_bound)), locus (locus)
+  {}
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: would a "vis_type" be better?
+  TraitBound &get_trait_bound ()
+  {
+    // TODO: check to ensure invariants are met?
+    return trait_bound;
+  }
+};
+
+/* A trait object with a single trait bound. The "trait bound" is really just
+ * the trait. Basically like using an interface as a type in an OOP language. */
+class TraitObjectTypeOneBound : public TypeNoBounds
+{
+  bool has_dyn;
+  TraitBound trait_bound;
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TraitObjectTypeOneBound *clone_type_no_bounds_impl () const override
+  {
+    return new TraitObjectTypeOneBound (*this);
+  }
+
+public:
+  TraitObjectTypeOneBound (TraitBound trait_bound, Location locus,
+			   bool is_dyn_dispatch = false)
+    : has_dyn (is_dyn_dispatch), trait_bound (std::move (trait_bound)),
+      locus (locus)
+  {}
+
+  std::string as_string () const override;
+
+  // Creates a trait bound (clone of this one's trait bound) - HACK
+  TraitBound *to_trait_bound (bool) const override
+  {
+    /* NOTE: this assumes there is no dynamic dispatch specified- if there was,
+     * this cloning would not be required as parsing is unambiguous. */
+    return new TraitBound (trait_bound);
+  }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: would a "vis_type" be better?
+  TraitBound &get_trait_bound ()
+  {
+    // TODO: check to ensure invariants are met?
+    return trait_bound;
+  }
+
+  bool is_dyn () const { return has_dyn; }
+};
+
+class TypePath; // definition moved to "rust-path.h"
+
+/* A type consisting of the "product" of others (the tuple's elements) in a
+ * specific order */
+class TupleType : public TypeNoBounds
+{
+  std::vector<std::unique_ptr<Type> > elems;
+  Location locus;
+
+public:
+  // Returns whether the tuple type is the unit type, i.e. has no elements.
+  bool is_unit_type () const { return elems.empty (); }
+
+  TupleType (std::vector<std::unique_ptr<Type> > elems, Location locus)
+    : elems (std::move (elems)), locus (locus)
+  {}
+
+  // copy constructor with vector clone
+  TupleType (TupleType const &other) : locus (other.locus)
+  {
+    elems.reserve (other.elems.size ());
+    for (const auto &e : other.elems)
+      elems.push_back (e->clone_type ());
+  }
+
+  // overloaded assignment operator to clone
+  TupleType &operator= (TupleType const &other)
+  {
+    locus = other.locus;
+
+    elems.reserve (other.elems.size ());
+    for (const auto &e : other.elems)
+      elems.push_back (e->clone_type ());
+
+    return *this;
+  }
+
+  // move constructors
+  TupleType (TupleType &&other) = default;
+  TupleType &operator= (TupleType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: mutable getter seems kinda dodgy
+  std::vector<std::unique_ptr<Type> > &get_elems () { return elems; }
+  const std::vector<std::unique_ptr<Type> > &get_elems () const
+  {
+    return elems;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  TupleType *clone_type_no_bounds_impl () const override
+  {
+    return new TupleType (*this);
+  }
+};
+
+/* A type with no values, representing the result of computations that never
+ * complete. Expressions of NeverType can be coerced into any other types.
+ * Represented as "!". */
+class NeverType : public TypeNoBounds
+{
+  Location locus;
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  NeverType *clone_type_no_bounds_impl () const override
+  {
+    return new NeverType (*this);
+  }
+
+public:
+  NeverType (Location locus) : locus (locus) {}
+
+  std::string as_string () const override { return "! (never type)"; }
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+};
+
+// A type consisting of a pointer without safety or liveness guarantees
+class RawPointerType : public TypeNoBounds
+{
+public:
+  enum PointerType
+  {
+    MUT,
+    CONST
+  };
+
+private:
+  PointerType pointer_type;
+  std::unique_ptr<TypeNoBounds> type;
+  Location locus;
+
+public:
+  // Returns whether the pointer is mutable or constant.
+  PointerType get_pointer_type () const { return pointer_type; }
+
+  // Constructor requires pointer for polymorphism reasons
+  RawPointerType (PointerType pointer_type,
+		  std::unique_ptr<TypeNoBounds> type_no_bounds, Location locus)
+    : pointer_type (pointer_type), type (std::move (type_no_bounds)),
+      locus (locus)
+  {}
+
+  // Copy constructor calls custom polymorphic clone function
+  RawPointerType (RawPointerType const &other)
+    : pointer_type (other.pointer_type),
+      type (other.type->clone_type_no_bounds ()), locus (other.locus)
+  {}
+
+  // overload assignment operator to use custom clone method
+  RawPointerType &operator= (RawPointerType const &other)
+  {
+    pointer_type = other.pointer_type;
+    type = other.type->clone_type_no_bounds ();
+    locus = other.locus;
+    return *this;
+  }
+
+  // default move semantics
+  RawPointerType (RawPointerType &&other) = default;
+  RawPointerType &operator= (RawPointerType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: would a "vis_type" be better?
+  std::unique_ptr<TypeNoBounds> &get_type_pointed_to ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  RawPointerType *clone_type_no_bounds_impl () const override
+  {
+    return new RawPointerType (*this);
+  }
+};
+
+// A type pointing to memory owned by another value
+class ReferenceType : public TypeNoBounds
+{
+  // bool has_lifetime; // TODO: handle in lifetime or something?
+  Lifetime lifetime;
+
+  bool has_mut;
+  std::unique_ptr<TypeNoBounds> type;
+  Location locus;
+
+public:
+  // Returns whether the reference is mutable or immutable.
+  bool is_mut () const { return has_mut; }
+
+  // Returns whether the reference has a lifetime.
+  bool has_lifetime () const { return !lifetime.is_error (); }
+
+  // Constructor
+  ReferenceType (bool is_mut, std::unique_ptr<TypeNoBounds> type_no_bounds,
+		 Location locus, Lifetime lifetime = Lifetime::error ())
+    : lifetime (std::move (lifetime)), has_mut (is_mut),
+      type (std::move (type_no_bounds)), locus (locus)
+  {}
+
+  // Copy constructor with custom clone method
+  ReferenceType (ReferenceType const &other)
+    : lifetime (other.lifetime), has_mut (other.has_mut),
+      type (other.type->clone_type_no_bounds ()), locus (other.locus)
+  {}
+
+  // Operator overload assignment operator to custom clone the unique pointer
+  ReferenceType &operator= (ReferenceType const &other)
+  {
+    lifetime = other.lifetime;
+    has_mut = other.has_mut;
+    type = other.type->clone_type_no_bounds ();
+    locus = other.locus;
+
+    return *this;
+  }
+
+  // move constructors
+  ReferenceType (ReferenceType &&other) = default;
+  ReferenceType &operator= (ReferenceType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: would a "vis_type" be better?
+  std::unique_ptr<TypeNoBounds> &get_type_referenced ()
+  {
+    rust_assert (type != nullptr);
+    return type;
+  }
+
+  bool get_has_mut () const { return has_mut; }
+
+  Lifetime &get_lifetime () { return lifetime; }
+
+  std::unique_ptr<TypeNoBounds> &get_base_type () { return type; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ReferenceType *clone_type_no_bounds_impl () const override
+  {
+    return new ReferenceType (*this);
+  }
+};
+
+// A fixed-size sequence of elements of a specified type
+class ArrayType : public TypeNoBounds
+{
+  std::unique_ptr<Type> elem_type;
+  std::unique_ptr<Expr> size;
+  Location locus;
+
+public:
+  // Constructor requires pointers for polymorphism
+  ArrayType (std::unique_ptr<Type> type, std::unique_ptr<Expr> array_size,
+	     Location locus)
+    : elem_type (std::move (type)), size (std::move (array_size)), locus (locus)
+  {}
+
+  // Copy constructor requires deep copies of both unique pointers
+  ArrayType (ArrayType const &other)
+    : elem_type (other.elem_type->clone_type ()),
+      size (other.size->clone_expr ()), locus (other.locus)
+  {}
+
+  // Overload assignment operator to deep copy pointers
+  ArrayType &operator= (ArrayType const &other)
+  {
+    elem_type = other.elem_type->clone_type ();
+    size = other.size->clone_expr ();
+    locus = other.locus;
+    return *this;
+  }
+
+  // move constructors
+  ArrayType (ArrayType &&other) = default;
+  ArrayType &operator= (ArrayType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: would a "vis_type" be better?
+  std::unique_ptr<Type> &get_elem_type ()
+  {
+    rust_assert (elem_type != nullptr);
+    return elem_type;
+  }
+
+  // TODO: would a "vis_expr" be better?
+  std::unique_ptr<Expr> &get_size_expr ()
+  {
+    rust_assert (size != nullptr);
+    return size;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  ArrayType *clone_type_no_bounds_impl () const override
+  {
+    return new ArrayType (*this);
+  }
+};
+
+/* A dynamically-sized type representing a "view" into a sequence of elements of
+ * a type */
+class SliceType : public TypeNoBounds
+{
+  std::unique_ptr<Type> elem_type;
+  Location locus;
+
+public:
+  // Constructor requires pointer for polymorphism
+  SliceType (std::unique_ptr<Type> type, Location locus)
+    : elem_type (std::move (type)), locus (locus)
+  {}
+
+  // Copy constructor requires deep copy of Type smart pointer
+  SliceType (SliceType const &other)
+    : elem_type (other.elem_type->clone_type ()), locus (other.locus)
+  {}
+
+  // Overload assignment operator to deep copy
+  SliceType &operator= (SliceType const &other)
+  {
+    elem_type = other.elem_type->clone_type ();
+    locus = other.locus;
+
+    return *this;
+  }
+
+  // move constructors
+  SliceType (SliceType &&other) = default;
+  SliceType &operator= (SliceType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: would a "vis_type" be better?
+  std::unique_ptr<Type> &get_elem_type ()
+  {
+    rust_assert (elem_type != nullptr);
+    return elem_type;
+  }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  SliceType *clone_type_no_bounds_impl () const override
+  {
+    return new SliceType (*this);
+  }
+};
+
+/* Type used in generic arguments to explicitly request type inference (wildcard
+ * pattern) */
+class InferredType : public TypeNoBounds
+{
+  Location locus;
+
+  // e.g. Vec<_> = whatever
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  InferredType *clone_type_no_bounds_impl () const override
+  {
+    return new InferredType (*this);
+  }
+
+public:
+  InferredType (Location locus) : locus (locus) {}
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+};
+
+class QualifiedPathInType; // definition moved to "rust-path.h"
+
+// A possibly named param used in a BaseFunctionType
+struct MaybeNamedParam
+{
+public:
+  enum ParamKind
+  {
+    UNNAMED,
+    IDENTIFIER,
+    WILDCARD
+  };
+
+private:
+  std::vector<Attribute> outer_attrs;
+
+  std::unique_ptr<Type> param_type;
+
+  ParamKind param_kind;
+  Identifier name; // technically, can be an identifier or '_'
+
+  Location locus;
+
+public:
+  MaybeNamedParam (Identifier name, ParamKind param_kind,
+		   std::unique_ptr<Type> param_type,
+		   std::vector<Attribute> outer_attrs, Location locus)
+    : outer_attrs (std::move (outer_attrs)),
+      param_type (std::move (param_type)), param_kind (param_kind),
+      name (std::move (name)), locus (locus)
+  {}
+
+  // Copy constructor with clone
+  MaybeNamedParam (MaybeNamedParam const &other)
+    : outer_attrs (other.outer_attrs), param_kind (other.param_kind),
+      name (other.name), locus (other.locus)
+  {
+    // guard to prevent null dereference
+    if (other.param_type != nullptr)
+      param_type = other.param_type->clone_type ();
+  }
+
+  ~MaybeNamedParam () = default;
+
+  // Overloaded assignment operator with clone
+  MaybeNamedParam &operator= (MaybeNamedParam const &other)
+  {
+    outer_attrs = other.outer_attrs;
+    name = other.name;
+    param_kind = other.param_kind;
+    locus = other.locus;
+
+    // guard to prevent null dereference
+    if (other.param_type != nullptr)
+      param_type = other.param_type->clone_type ();
+    else
+      param_type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  MaybeNamedParam (MaybeNamedParam &&other) = default;
+  MaybeNamedParam &operator= (MaybeNamedParam &&other) = default;
+
+  std::string as_string () const;
+
+  // Returns whether the param is in an error state.
+  bool is_error () const { return param_type == nullptr; }
+
+  // Creates an error state param.
+  static MaybeNamedParam create_error ()
+  {
+    return MaybeNamedParam ("", UNNAMED, nullptr, {}, Location ());
+  }
+
+  Location get_locus () const { return locus; }
+
+  // TODO: this mutable getter seems really dodgy. Think up better way.
+  std::vector<Attribute> &get_outer_attrs () { return outer_attrs; }
+  const std::vector<Attribute> &get_outer_attrs () const { return outer_attrs; }
+
+  // TODO: would a "vis_type" be better?
+  std::unique_ptr<Type> &get_type ()
+  {
+    rust_assert (param_type != nullptr);
+    return param_type;
+  }
+
+  ParamKind get_param_kind () const { return param_kind; }
+
+  Identifier get_name () const { return name; }
+};
+
+/* A function pointer type - can be created via coercion from function items and
+ * non- capturing closures. */
+class BareFunctionType : public TypeNoBounds
+{
+  // bool has_for_lifetimes;
+  // ForLifetimes for_lifetimes;
+  std::vector<LifetimeParam> for_lifetimes; // inlined version
+
+  FunctionQualifiers function_qualifiers;
+  std::vector<MaybeNamedParam> params;
+  bool is_variadic;
+  std::vector<Attribute> variadic_attrs;
+
+  // bool has_return_type;
+  // BareFunctionReturnType return_type;
+  std::unique_ptr<TypeNoBounds> return_type; // inlined version
+
+  Location locus;
+
+public:
+  // Whether a return type is defined with the function.
+  bool has_return_type () const { return return_type != nullptr; }
+
+  // Whether the function has ForLifetimes.
+  bool has_for_lifetimes () const { return !for_lifetimes.empty (); }
+
+  BareFunctionType (std::vector<LifetimeParam> lifetime_params,
+		    FunctionQualifiers qualifiers,
+		    std::vector<MaybeNamedParam> named_params, bool is_variadic,
+		    std::vector<Attribute> variadic_attrs,
+		    std::unique_ptr<TypeNoBounds> type, Location locus)
+    : for_lifetimes (std::move (lifetime_params)),
+      function_qualifiers (std::move (qualifiers)),
+      params (std::move (named_params)), is_variadic (is_variadic),
+      variadic_attrs (std::move (variadic_attrs)),
+      return_type (std::move (type)), locus (locus)
+  {
+    if (!variadic_attrs.empty ())
+      is_variadic = true;
+  }
+
+  // Copy constructor with clone
+  BareFunctionType (BareFunctionType const &other)
+    : for_lifetimes (other.for_lifetimes),
+      function_qualifiers (other.function_qualifiers), params (other.params),
+      is_variadic (other.is_variadic), variadic_attrs (other.variadic_attrs),
+      locus (other.locus)
+  {
+    // guard to prevent null dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type_no_bounds ();
+  }
+
+  // Overload assignment operator to deep copy
+  BareFunctionType &operator= (BareFunctionType const &other)
+  {
+    for_lifetimes = other.for_lifetimes;
+    function_qualifiers = other.function_qualifiers;
+    params = other.params;
+    is_variadic = other.is_variadic;
+    variadic_attrs = other.variadic_attrs;
+    locus = other.locus;
+
+    // guard to prevent null dereference
+    if (other.return_type != nullptr)
+      return_type = other.return_type->clone_type_no_bounds ();
+    else
+      return_type = nullptr;
+
+    return *this;
+  }
+
+  // move constructors
+  BareFunctionType (BareFunctionType &&other) = default;
+  BareFunctionType &operator= (BareFunctionType &&other) = default;
+
+  std::string as_string () const override;
+
+  Location get_locus () const override final { return locus; }
+
+  void accept_vis (ASTVisitor &vis) override;
+
+  // TODO: this mutable getter seems kinda dodgy
+  std::vector<MaybeNamedParam> &get_function_params () { return params; }
+  const std::vector<MaybeNamedParam> &get_function_params () const
+  {
+    return params;
+  }
+
+  // TODO: would a "vis_type" be better?
+  std::unique_ptr<TypeNoBounds> &get_return_type ()
+  {
+    rust_assert (has_return_type ());
+    return return_type;
+  }
+
+  FunctionQualifiers get_function_qualifiers () { return function_qualifiers; }
+
+protected:
+  /* Use covariance to implement clone function as returning this object rather
+   * than base */
+  BareFunctionType *clone_type_no_bounds_impl () const override
+  {
+    return new BareFunctionType (*this);
+  }
+};
+
+// Forward decl - defined in rust-macro.h
+class MacroInvocation;
+
+/* TODO: possible types
+ * struct type?
+ * "enum" (tagged union) type?
+ * C-like union type?
+ * function item type?
+ * closure expression types?
+ * primitive types (bool, int, float, char, str (the slice))
+ * Although supposedly TypePaths are used to reference these types (including
+ * primitives) */
+
+/* FIXME: Incomplete spec references:
+ *  anonymous type parameters, aka "impl Trait in argument position" - impl then
+ * trait bounds abstract return types, aka "impl Trait in return position" -
+ * impl then trait bounds */
+} // namespace AST
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/operator.h b/gcc/rust/operator.h
new file mode 100644
index 00000000000..6813db3ed13
--- /dev/null
+++ b/gcc/rust/operator.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_OPERATOR_H
+#define RUST_OPERATOR_H
+
+enum class NegationOperator
+{
+  NEGATE,
+  NOT
+};
+
+enum class ArithmeticOrLogicalOperator
+{
+  ADD,	       // std::ops::Add
+  SUBTRACT,    // std::ops::Sub
+  MULTIPLY,    // std::ops::Mul
+  DIVIDE,      // std::ops::Div
+  MODULUS,     // std::ops::Rem
+  BITWISE_AND, // std::ops::BitAnd
+  BITWISE_OR,  // std::ops::BitOr
+  BITWISE_XOR, // std::ops::BitXor
+  LEFT_SHIFT,  // std::ops::Shl
+  RIGHT_SHIFT  // std::ops::Shr
+};
+
+enum class ComparisonOperator
+{
+  EQUAL,	    // std::cmp::PartialEq::eq
+  NOT_EQUAL,	    // std::cmp::PartialEq::ne
+  GREATER_THAN,	    // std::cmp::PartialEq::gt
+  LESS_THAN,	    // std::cmp::PartialEq::lt
+  GREATER_OR_EQUAL, // std::cmp::PartialEq::ge
+  LESS_OR_EQUAL	    // std::cmp::PartialEq::le
+};
+
+enum class LazyBooleanOperator
+{
+  LOGICAL_OR,
+  LOGICAL_AND
+};
+
+enum class CompoundAssignmentOperator
+{
+  ADD,	       // std::ops::AddAssign
+  SUBTRACT,    // std::ops::SubAssign
+  MULTIPLY,    // std::ops::MulAssign
+  DIVIDE,      // std::ops::DivAssign
+  MODULUS,     // std::ops::RemAssign
+  BITWISE_AND, // std::ops::BitAndAssign
+  BITWISE_OR,  // std::ops::BitOrAssign
+  BITWISE_XOR, // std::ops::BitXorAssign
+  LEFT_SHIFT,  // std::ops::ShlAssign
+  RIGHT_SHIFT  // std::ops::ShrAssign
+};
+
+#endif // RUST_OPERATOR_H
-- 
2.25.1


  parent reply	other threads:[~2022-08-24 12:00 UTC|newest]

Thread overview: 59+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-08-24 11:59 Rust frontend patches v2 herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 01/37] Use DW_ATE_UTF for the Rust 'char' type herron.philip
2022-08-24 14:28   ` Jason Merrill
2022-08-24 11:59 ` [PATCH Rust front-end v2 02/37] gccrs: Add nessecary hooks for a Rust front-end testsuite herron.philip
2022-09-10  4:05   ` Mike Stump
2022-08-24 11:59 ` [PATCH Rust front-end v2 03/37] gccrs: Add Debug info testsuite herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 04/37] gccrs: Add link cases testsuite herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 05/37] gccrs: Add general compilation test cases herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 06/37] gccrs: Add execution " herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 07/37] gccrs: Add gcc-check-target check-rust herron.philip
2022-09-14 13:41   ` Richard Biener
2022-09-14 14:04     ` Jakub Jelinek
2022-08-24 11:59 ` herron.philip [this message]
2022-08-24 11:59 ` [PATCH Rust front-end v2 09/37] gccrs: Add Lexer for Rust front-end herron.philip
2022-09-14 13:30   ` Richard Biener
2022-09-14 13:39     ` Jakub Jelinek
2022-08-24 11:59 ` [PATCH Rust front-end v2 10/37] gccrs: Add Parser " herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 11/37] gccrs: Add expansion pass for the " herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 12/37] gccrs: Add name resolution pass to " herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 13/37] gccrs: Add second intermedite representation called HIR herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 14/37] gccrs: Add AST to HIR lowering pass herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 15/37] gccrs: Add wrapper for make_unique herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 16/37] gccrs: Add port of FNV hash used during legacy symbol mangling herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 17/37] gccrs: Add Rust ABI enum helpers herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 18/37] gccrs: Add Base62 implementation herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 19/37] gccrs: Add implementation of Optional herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 20/37] gccrs: Add attributes checker herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 21/37] gccrs: Add helpers mappings canonical path and lang items herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 22/37] gccrs: Add type resolution and trait solving pass herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 23/37] gccrs: Add unsafe checks for Rust herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 24/37] gccrs: Add const checker herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 25/37] gccrs: Add privacy checks herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 26/37] gccrs: Add dead code scan on HIR herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 27/37] gccrs: Add unused variable scan herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 28/37] gccrs: Add metadata ouptput pass herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 29/37] gccrs: HIR to GCC GENERIC lowering herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 30/37] gccrs: These are wrappers ported from reusing gccgo herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 31/37] gccrs: Add GCC Rust front-end Make-lang.in herron.philip
2022-09-14 13:34   ` Richard Biener
2022-12-01 11:05     ` Thomas Schwinge
2022-08-24 11:59 ` [PATCH Rust front-end v2 32/37] gccrs: Add config-lang.in herron.philip
2022-09-14 13:40   ` Richard Biener
2023-02-20 13:33   ` Rust: Don't depend on unused 'target-libffi', 'target-libbacktrace' (was: [PATCH Rust front-end v2 32/37] gccrs: Add config-lang.in) Thomas Schwinge
2022-08-24 11:59 ` [PATCH Rust front-end v2 33/37] gccrs: add lang-spec.h herron.philip
2022-09-14 13:40   ` Richard Biener
2022-10-14 16:33   ` Iain Buclaw
2022-08-24 11:59 ` [PATCH Rust front-end v2 34/37] gccrs: add lang.opt herron.philip
2022-09-14 13:39   ` Richard Biener
2022-09-14 16:20     ` Thomas Schwinge
2022-09-15  6:23       ` Richard Biener
2022-08-24 11:59 ` [PATCH Rust front-end v2 35/37] gccrs: add compiler driver herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 36/37] gccrs: compiler proper interface kicks off the pipeline herron.philip
2022-08-24 11:59 ` [PATCH Rust front-end v2 37/37] gccrs: Add README, CONTRIBUTING and compiler logo herron.philip
2022-08-25  9:46 ` Rust frontend patches v2 Philip Herron
2022-08-25  9:52   ` Martin Liška
2022-08-25 10:18     ` Philip Herron
2022-08-25 12:50       ` Frank Ch. Eigler
2022-08-25 13:44         ` Philip Herron
2022-08-25 11:13     ` Mark Wielaard

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220824115956.737931-9-philip.herron@embecosm.com \
    --to=herron.philip@googlemail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=gcc-rust@gcc.gnu.org \
    --cc=philip.herron@embecosm.com \
    --cc=simplytheother@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).