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..1e8a93d462f
--- /dev/null
+++ b/gcc/rust/ast/rust-ast-full-test.cc
@@ -0,0 +1,5810 @@
+/* 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/>.  */
+
+#include "rust-system.h"
+#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.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/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
