/* General AST-related method implementations for Rust frontend.
   Copyright (C) 2009-2023 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 "rust-operators.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);

  // TODO: deal with macro_2_0
  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: ";
  auto is_builtin = kind == InvocKind::Builtin;

  if (is_builtin)
    str += "[builtin] ";
  else
    str += "[regular] ";

  str += append_attributes (outer_attrs, OUTER);

  str += "\n " + invoc_data.as_string ();

  str += "\n has semicolon: ";
  str += has_semicolon () ? "true" : "false";

  if (is_builtin)
    {
      str += "[PENDING EAGER INVOCATIONS]: ";
      for (auto &pending : pending_eager_invocs)
	{
	  str += pending->as_string ();
	  str += "\n";
	}
    }

  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
AltPattern::as_string () const
{
  std::string str ("AltPattern: ");

  for (const auto &pattern : alts)
    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 ();

  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
AltPattern::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