// Copyright (C) 2020-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/>.

#ifndef RUST_HIR_EXPR_H
#define RUST_HIR_EXPR_H

#include "rust-common.h"
#include "rust-ast-full-decls.h"
#include "rust-hir.h"
#include "rust-hir-path.h"
#include "rust-operators.h"

namespace Rust {
namespace HIR {

// HIR node for an expression with an accompanying block - abstract
class ExprWithBlock : public Expr
{
  // TODO: should this mean that a BlockExpr should be a member variable?
protected:
  ExprWithBlock (Analysis::NodeMapping mappings,
		 AST::AttrVec outer_attrs = AST::AttrVec ())
    : Expr (std::move (mappings), std::move (outer_attrs))
  {}

  // pure virtual clone implementation
  virtual ExprWithBlock *clone_expr_with_block_impl () const = 0;

  // prevent having to define multiple clone expressions
  ExprWithBlock *clone_expr_impl () const override
  {
    return clone_expr_with_block_impl ();
  }

public:
  // Unique pointer custom clone function
  std::unique_ptr<ExprWithBlock> clone_expr_with_block () const
  {
    return std::unique_ptr<ExprWithBlock> (clone_expr_with_block_impl ());
  }

  BlockType get_block_expr_type () const final override
  {
    return BlockType::WITH_BLOCK;
  };
};

// Literals? Or literal base?
class LiteralExpr : public ExprWithoutBlock
{
  Literal literal;
  Location locus;

public:
  std::string as_string () const override
  {
    return "( " + literal.as_string () + " (" + get_mappings ().as_string ()
	   + "))";
  }

  Literal::LitType get_lit_type () const { return literal.get_lit_type (); }

  LiteralExpr (Analysis::NodeMapping mappings, std::string value_as_string,
	       Literal::LitType type, PrimitiveCoreType type_hint,
	       Location locus, AST::AttrVec outer_attrs)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attrs)),
      literal (std::move (value_as_string), type, type_hint), locus (locus)
  {}

  LiteralExpr (Analysis::NodeMapping mappings, Literal literal, Location locus,
	       AST::AttrVec outer_attrs)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attrs)),
      literal (std::move (literal)), locus (locus)
  {}

  // Unique pointer custom clone function
  std::unique_ptr<LiteralExpr> clone_literal_expr () const
  {
    return std::unique_ptr<LiteralExpr> (clone_literal_expr_impl ());
  }

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Literal &get_literal () { return literal; }
  const Literal &get_literal () const { return literal; }

  ExprType get_expression_type () const override final { return ExprType::Lit; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  LiteralExpr *clone_expr_impl () const override
  {
    return new LiteralExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  LiteralExpr *clone_expr_without_block_impl () const override
  {
    return new LiteralExpr (*this);
  }

  /* not virtual as currently no subclasses of LiteralExpr, but could be in
   * future */
  /*virtual*/ LiteralExpr *clone_literal_expr_impl () const
  {
    return new LiteralExpr (*this);
  }
};

/* Represents an expression using unary or binary operators as HIR node. Can be
 * overloaded. */
class OperatorExpr : public ExprWithoutBlock
{
  // TODO: create binary and unary operator subclasses?
public:
  Location locus;

protected:
  /* Variable must be protected to allow derived classes to use it as a first
   * class citizen */
  std::unique_ptr<Expr> main_or_left_expr;

  // Constructor (only for initialisation of expr purposes)
  OperatorExpr (Analysis::NodeMapping mappings,
		std::unique_ptr<Expr> main_or_left_expr,
		AST::AttrVec outer_attribs, Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      locus (locus), main_or_left_expr (std::move (main_or_left_expr))
  {}

  // Copy constructor (only for initialisation of expr purposes)
  OperatorExpr (OperatorExpr const &other)
    : ExprWithoutBlock (other), locus (other.locus),
      main_or_left_expr (other.main_or_left_expr->clone_expr ())
  {}

  // Overload assignment operator to deep copy expr
  OperatorExpr &operator= (OperatorExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    main_or_left_expr = other.main_or_left_expr->clone_expr ();
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  OperatorExpr (OperatorExpr &&other) = default;
  OperatorExpr &operator= (OperatorExpr &&other) = default;

public:
  Location get_locus () const override final { return locus; }

  std::unique_ptr<Expr> &get_expr () { return main_or_left_expr; }

  ExprType get_expression_type () const override final
  {
    return ExprType::Operator;
  }
};

/* Unary prefix & or &mut (or && and &&mut) borrow operator. Cannot be
 * overloaded. */
class BorrowExpr : public OperatorExpr
{
  Mutability mut;
  bool double_borrow;

public:
  std::string as_string () const override;

  BorrowExpr (Analysis::NodeMapping mappings,
	      std::unique_ptr<Expr> borrow_lvalue, Mutability mut,
	      AST::AttrVec outer_attribs, Location locus)
    : OperatorExpr (std::move (mappings), std::move (borrow_lvalue),
		    std::move (outer_attribs), locus),
      mut (mut)
  {}

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Mutability get_mut () const { return mut; }
  bool is_mut () const { return mut == Mutability::Mut; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  BorrowExpr *clone_expr_impl () const override
  {
    return new BorrowExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  BorrowExpr *clone_expr_without_block_impl () const override
  {
    return new BorrowExpr (*this);
  }
};

// Unary prefix * deference operator
class DereferenceExpr : public OperatorExpr
{
public:
  std::string as_string () const override;

  // Constructor calls OperatorExpr's protected constructor
  DereferenceExpr (Analysis::NodeMapping mappings,
		   std::unique_ptr<Expr> deref_lvalue,
		   AST::AttrVec outer_attribs, Location locus)
    : OperatorExpr (std::move (mappings), std::move (deref_lvalue),
		    std::move (outer_attribs), locus)
  {}

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  DereferenceExpr *clone_expr_impl () const override
  {
    return new DereferenceExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  DereferenceExpr *clone_expr_without_block_impl () const override
  {
    return new DereferenceExpr (*this);
  }
};

// Unary postfix ? error propogation operator. Cannot be overloaded.
class ErrorPropagationExpr : public OperatorExpr
{
public:
  std::string as_string () const override;

  // Constructor calls OperatorExpr's protected constructor
  ErrorPropagationExpr (Analysis::NodeMapping mappings,
			std::unique_ptr<Expr> potential_error_value,
			AST::AttrVec outer_attribs, Location locus)
    : OperatorExpr (std::move (mappings), std::move (potential_error_value),
		    std::move (outer_attribs), locus)
  {}

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ErrorPropagationExpr *clone_expr_impl () const override
  {
    return new ErrorPropagationExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ErrorPropagationExpr *clone_expr_without_block_impl () const override
  {
    return new ErrorPropagationExpr (*this);
  }
};

// Unary prefix - or ! negation or NOT operators.
class NegationExpr : public OperatorExpr
{
public:
  using ExprType = NegationOperator;

private:
  /* Note: overload negation via std::ops::Neg and not via std::ops::Not
   * Negation only works for signed integer and floating-point types, NOT only
   * works for boolean and integer types (via bitwise NOT) */
  ExprType expr_type;

public:
  std::string as_string () const override;

  ExprType get_expr_type () const { return expr_type; }

  // Constructor calls OperatorExpr's protected constructor
  NegationExpr (Analysis::NodeMapping mappings,
		std::unique_ptr<Expr> negated_value, ExprType expr_kind,
		AST::AttrVec outer_attribs, Location locus)
    : OperatorExpr (std::move (mappings), std::move (negated_value),
		    std::move (outer_attribs), locus),
      expr_type (expr_kind)
  {}

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  NegationExpr *clone_expr_impl () const override
  {
    return new NegationExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  NegationExpr *clone_expr_without_block_impl () const override
  {
    return new NegationExpr (*this);
  }
};

// Infix binary operators. +, -, *, /, %, &, |, ^, <<, >>
class ArithmeticOrLogicalExpr : public OperatorExpr
{
public:
  using ExprType = ArithmeticOrLogicalOperator;

private:
  // Note: overloading trait specified in comments
  ExprType expr_type;

  std::unique_ptr<Expr> right_expr;

public:
  std::string as_string () const override;

  ExprType get_expr_type () const { return expr_type; }

  // Constructor calls OperatorExpr's protected constructor
  ArithmeticOrLogicalExpr (Analysis::NodeMapping mappings,
			   std::unique_ptr<Expr> left_value,
			   std::unique_ptr<Expr> right_value,
			   ExprType expr_kind, Location locus)
    : OperatorExpr (std::move (mappings), std::move (left_value),
		    AST::AttrVec (), locus),
      expr_type (expr_kind), right_expr (std::move (right_value))
  {}
  // outer attributes not allowed

  // Copy constructor - probably required due to unique pointer
  ArithmeticOrLogicalExpr (ArithmeticOrLogicalExpr const &other)
    : OperatorExpr (other), expr_type (other.expr_type),
      right_expr (other.right_expr->clone_expr ())
  {}

  // Overload assignment operator
  ArithmeticOrLogicalExpr &operator= (ArithmeticOrLogicalExpr const &other)
  {
    OperatorExpr::operator= (other);
    // main_or_left_expr = other.main_or_left_expr->clone_expr();
    right_expr = other.right_expr->clone_expr ();
    expr_type = other.expr_type;

    return *this;
  }

  // move constructors
  ArithmeticOrLogicalExpr (ArithmeticOrLogicalExpr &&other) = default;
  ArithmeticOrLogicalExpr &operator= (ArithmeticOrLogicalExpr &&other)
    = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  void visit_lhs (HIRFullVisitor &vis) { main_or_left_expr->accept_vis (vis); }
  void visit_rhs (HIRFullVisitor &vis) { right_expr->accept_vis (vis); }

  Expr *get_lhs () { return main_or_left_expr.get (); }
  Expr *get_rhs () { return right_expr.get (); }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ArithmeticOrLogicalExpr *clone_expr_impl () const override
  {
    return new ArithmeticOrLogicalExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ArithmeticOrLogicalExpr *clone_expr_without_block_impl () const override
  {
    return new ArithmeticOrLogicalExpr (*this);
  }
};

// Infix binary comparison operators. ==, !=, <, <=, >, >=
class ComparisonExpr : public OperatorExpr
{
public:
  using ExprType = ComparisonOperator;

private:
  // Note: overloading trait specified in comments
  ExprType expr_type;

  std::unique_ptr<Expr> right_expr;

public:
  std::string as_string () const override;

  ExprType get_expr_type () const { return expr_type; }

  // Constructor requires pointers for polymorphism
  ComparisonExpr (Analysis::NodeMapping mappings,
		  std::unique_ptr<Expr> left_value,
		  std::unique_ptr<Expr> right_value, ExprType comparison_kind,
		  Location locus)
    : OperatorExpr (std::move (mappings), std::move (left_value),
		    AST::AttrVec (), locus),
      expr_type (comparison_kind), right_expr (std::move (right_value))
  {}
  // outer attributes not allowed

  // Copy constructor also calls OperatorExpr's protected constructor
  ComparisonExpr (ComparisonExpr const &other)
    : OperatorExpr (other), expr_type (other.expr_type),
      right_expr (other.right_expr->clone_expr ())
  {}

  // Overload assignment operator to deep copy
  ComparisonExpr &operator= (ComparisonExpr const &other)
  {
    OperatorExpr::operator= (other);
    // main_or_left_expr = other.main_or_left_expr->clone_expr();
    right_expr = other.right_expr->clone_expr ();
    expr_type = other.expr_type;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  ComparisonExpr (ComparisonExpr &&other) = default;
  ComparisonExpr &operator= (ComparisonExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Expr *get_lhs () { return main_or_left_expr.get (); }
  Expr *get_rhs () { return right_expr.get (); }

  ExprType get_kind () { return expr_type; }

  /* TODO: implement via a function call to std::cmp::PartialEq::eq(&op1, &op2)
   * maybe? */
protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ComparisonExpr *clone_expr_impl () const override
  {
    return new ComparisonExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ComparisonExpr *clone_expr_without_block_impl () const override
  {
    return new ComparisonExpr (*this);
  }
};

// Infix binary lazy boolean logical operators && and ||.
class LazyBooleanExpr : public OperatorExpr
{
public:
  using ExprType = LazyBooleanOperator;

private:
  ExprType expr_type;

  std::unique_ptr<Expr> right_expr;

public:
  // Constructor calls OperatorExpr's protected constructor
  LazyBooleanExpr (Analysis::NodeMapping mappings,
		   std::unique_ptr<Expr> left_bool_expr,
		   std::unique_ptr<Expr> right_bool_expr, ExprType expr_kind,
		   Location locus)
    : OperatorExpr (std::move (mappings), std::move (left_bool_expr),
		    AST::AttrVec (), locus),
      expr_type (expr_kind), right_expr (std::move (right_bool_expr))
  {}
  // outer attributes not allowed

  // Copy constructor also calls OperatorExpr's protected constructor
  LazyBooleanExpr (LazyBooleanExpr const &other)
    : OperatorExpr (other), expr_type (other.expr_type),
      right_expr (other.right_expr->clone_expr ())
  {}

  // Overload assignment operator to deep copy
  LazyBooleanExpr &operator= (LazyBooleanExpr const &other)
  {
    OperatorExpr::operator= (other);
    // main_or_left_expr = other.main_or_left_expr->clone_expr();
    right_expr = other.right_expr->clone_expr ();
    expr_type = other.expr_type;

    return *this;
  }

  // move constructors
  LazyBooleanExpr (LazyBooleanExpr &&other) = default;
  LazyBooleanExpr &operator= (LazyBooleanExpr &&other) = default;

  std::string as_string () const override;

  ExprType get_expr_type () const { return expr_type; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Expr *get_lhs () { return main_or_left_expr.get (); }

  Expr *get_rhs () { return right_expr.get (); }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  LazyBooleanExpr *clone_expr_impl () const override
  {
    return new LazyBooleanExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  LazyBooleanExpr *clone_expr_without_block_impl () const override
  {
    return new LazyBooleanExpr (*this);
  }
};

// Binary infix "as" chir expression.
class TypeCastExpr : public OperatorExpr
{
  std::unique_ptr<Type> type_to_convert_to;

  // Note: only certain type casts allowed, outlined in reference
public:
  std::string as_string () const override;

  // Constructor requires calling protected constructor of OperatorExpr
  TypeCastExpr (Analysis::NodeMapping mappings,
		std::unique_ptr<Expr> expr_to_cast,
		std::unique_ptr<Type> type_to_cast_to, Location locus)
    : OperatorExpr (std::move (mappings), std::move (expr_to_cast),
		    AST::AttrVec (), locus),
      type_to_convert_to (std::move (type_to_cast_to))
  {}
  // outer attributes not allowed

  // Copy constructor also requires calling protected constructor
  TypeCastExpr (TypeCastExpr const &other)
    : OperatorExpr (other),
      type_to_convert_to (other.type_to_convert_to->clone_type ())
  {}

  // Overload assignment operator to deep copy
  TypeCastExpr &operator= (TypeCastExpr const &other)
  {
    OperatorExpr::operator= (other);
    // main_or_left_expr = other.main_or_left_expr->clone_expr();
    type_to_convert_to = other.type_to_convert_to->clone_type ();

    return *this;
  }

  // move constructors as not supported in c++03
  TypeCastExpr (TypeCastExpr &&other) = default;
  TypeCastExpr &operator= (TypeCastExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_casted_expr ()
  {
    rust_assert (main_or_left_expr != nullptr);
    return main_or_left_expr;
  }

  std::unique_ptr<Type> &get_type_to_convert_to ()
  {
    rust_assert (type_to_convert_to != nullptr);
    return type_to_convert_to;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  TypeCastExpr *clone_expr_impl () const override
  {
    return new TypeCastExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  TypeCastExpr *clone_expr_without_block_impl () const override
  {
    return new TypeCastExpr (*this);
  }
};

// Binary assignment expression.
class AssignmentExpr : public OperatorExpr
{
  std::unique_ptr<Expr> right_expr;

public:
  std::string as_string () const override;

  // Call OperatorExpr constructor to initialise left_expr
  AssignmentExpr (Analysis::NodeMapping mappings,
		  std::unique_ptr<Expr> value_to_assign_to,
		  std::unique_ptr<Expr> value_to_assign, Location locus)
    : OperatorExpr (std::move (mappings), std::move (value_to_assign_to),
		    AST::AttrVec (), locus),
      right_expr (std::move (value_to_assign))
  {}
  // outer attributes not allowed

  // Call OperatorExpr constructor in copy constructor, as well as clone
  AssignmentExpr (AssignmentExpr const &other)
    : OperatorExpr (other), right_expr (other.right_expr->clone_expr ())
  {}

  // Overload assignment operator to clone unique_ptr right_expr
  AssignmentExpr &operator= (AssignmentExpr const &other)
  {
    OperatorExpr::operator= (other);
    // main_or_left_expr = other.main_or_left_expr->clone_expr();
    right_expr = other.right_expr->clone_expr ();
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  AssignmentExpr (AssignmentExpr &&other) = default;
  AssignmentExpr &operator= (AssignmentExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  void visit_lhs (HIRFullVisitor &vis) { main_or_left_expr->accept_vis (vis); }
  void visit_rhs (HIRFullVisitor &vis) { right_expr->accept_vis (vis); }

  Expr *get_lhs () { return main_or_left_expr.get (); }
  Expr *get_rhs () { return right_expr.get (); }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  AssignmentExpr *clone_expr_impl () const override
  {
    return new AssignmentExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  AssignmentExpr *clone_expr_without_block_impl () const override
  {
    return new AssignmentExpr (*this);
  }
};

class CompoundAssignmentExpr : public OperatorExpr
{
public:
  using ExprType = ArithmeticOrLogicalOperator;

private:
  // Note: overloading trait specified in comments
  ExprType expr_type;
  std::unique_ptr<Expr> right_expr;

public:
  std::string as_string () const override;

  ExprType get_expr_type () const { return expr_type; }

  // Use pointers in constructor to enable polymorphism
  CompoundAssignmentExpr (Analysis::NodeMapping mappings,
			  std::unique_ptr<Expr> value_to_assign_to,
			  std::unique_ptr<Expr> value_to_assign,
			  ExprType expr_kind, Location locus)
    : OperatorExpr (std::move (mappings), std::move (value_to_assign_to),
		    AST::AttrVec (), locus),
      expr_type (expr_kind), right_expr (std::move (value_to_assign))
  {}
  // outer attributes not allowed

  // Have clone in copy constructor
  CompoundAssignmentExpr (CompoundAssignmentExpr const &other)
    : OperatorExpr (other), expr_type (other.expr_type),
      right_expr (other.right_expr->clone_expr ())
  {}

  // Overload assignment operator to clone
  CompoundAssignmentExpr &operator= (CompoundAssignmentExpr const &other)
  {
    OperatorExpr::operator= (other);
    // main_or_left_expr = other.main_or_left_expr->clone_expr();
    right_expr = other.right_expr->clone_expr ();
    expr_type = other.expr_type;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  CompoundAssignmentExpr (CompoundAssignmentExpr &&other) = default;
  CompoundAssignmentExpr &operator= (CompoundAssignmentExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_left_expr ()
  {
    rust_assert (main_or_left_expr != nullptr);
    return main_or_left_expr;
  }

  std::unique_ptr<Expr> &get_right_expr ()
  {
    rust_assert (right_expr != nullptr);
    return right_expr;
  }

  void visit_lhs (HIRFullVisitor &vis) { main_or_left_expr->accept_vis (vis); }
  void visit_rhs (HIRFullVisitor &vis) { right_expr->accept_vis (vis); }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  CompoundAssignmentExpr *clone_expr_without_block_impl () const override
  {
    return new CompoundAssignmentExpr (*this);
  }
};

// Expression in parentheses (i.e. like literally just any 3 + (2 * 6))
class GroupedExpr : public ExprWithoutBlock
{
  AST::AttrVec inner_attrs;
  std::unique_ptr<Expr> expr_in_parens;

  Location locus;

public:
  std::string as_string () const override;

  AST::AttrVec get_inner_attrs () const { return inner_attrs; }

  GroupedExpr (Analysis::NodeMapping mappings,
	       std::unique_ptr<Expr> parenthesised_expr,
	       AST::AttrVec inner_attribs, AST::AttrVec outer_attribs,
	       Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      inner_attrs (std::move (inner_attribs)),
      expr_in_parens (std::move (parenthesised_expr)), locus (locus)
  {}

  // Copy constructor includes clone for expr_in_parens
  GroupedExpr (GroupedExpr const &other)
    : ExprWithoutBlock (other), inner_attrs (other.inner_attrs),
      expr_in_parens (other.expr_in_parens->clone_expr ()), locus (other.locus)
  {}

  // Overloaded assignment operator to clone expr_in_parens
  GroupedExpr &operator= (GroupedExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    inner_attrs = other.inner_attrs;
    expr_in_parens = other.expr_in_parens->clone_expr ();
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  GroupedExpr (GroupedExpr &&other) = default;
  GroupedExpr &operator= (GroupedExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_expr_in_parens ()
  {
    rust_assert (expr_in_parens != nullptr);
    return expr_in_parens;
  }

  ExprType get_expression_type () const override final
  {
    return ExprType::Grouped;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  GroupedExpr *clone_expr_impl () const override
  {
    return new GroupedExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  GroupedExpr *clone_expr_without_block_impl () const override
  {
    return new GroupedExpr (*this);
  }
};

// Base array initialisation internal element representation thing (abstract)
// aka ArrayElements
class ArrayElems
{
public:
  enum ArrayExprType
  {
    VALUES,
    COPIED,
  };

  ArrayElems (Analysis::NodeMapping mappings) : mappings (mappings){};

  virtual ~ArrayElems () {}

  // Unique pointer custom clone ArrayElems function
  std::unique_ptr<ArrayElems> clone_array_elems () const
  {
    return std::unique_ptr<ArrayElems> (clone_array_elems_impl ());
  }

  virtual std::string as_string () const = 0;

  virtual void accept_vis (HIRFullVisitor &vis) = 0;

  virtual ArrayExprType get_array_expr_type () const = 0;

  Analysis::NodeMapping &get_mappings () { return mappings; }

protected:
  // pure virtual clone implementation
  virtual ArrayElems *clone_array_elems_impl () const = 0;

  Analysis::NodeMapping mappings;
};

// Value array elements
class ArrayElemsValues : public ArrayElems
{
  std::vector<std::unique_ptr<Expr> > values;

  // TODO: should this store location data?

public:
  ArrayElemsValues (Analysis::NodeMapping mappings,
		    std::vector<std::unique_ptr<Expr> > elems)
    : ArrayElems (mappings), values (std::move (elems))
  {}

  // copy constructor with vector clone
  ArrayElemsValues (ArrayElemsValues const &other) : ArrayElems (other)
  {
    values.reserve (other.values.size ());
    for (const auto &e : other.values)
      values.push_back (e->clone_expr ());
  }

  // overloaded assignment operator with vector clone
  ArrayElemsValues &operator= (ArrayElemsValues const &other)
  {
    values.reserve (other.values.size ());
    for (const auto &e : other.values)
      values.push_back (e->clone_expr ());

    return *this;
  }

  // move constructors
  ArrayElemsValues (ArrayElemsValues &&other) = default;
  ArrayElemsValues &operator= (ArrayElemsValues &&other) = default;

  std::string as_string () const override;

  void accept_vis (HIRFullVisitor &vis) override;

  size_t get_num_elements () const { return values.size (); }

  std::vector<std::unique_ptr<Expr> > &get_values () { return values; }

  ArrayElems::ArrayExprType get_array_expr_type () const override final
  {
    return ArrayElems::ArrayExprType::VALUES;
  }

protected:
  ArrayElemsValues *clone_array_elems_impl () const override
  {
    return new ArrayElemsValues (*this);
  }
};

// Copied array element and number of copies
class ArrayElemsCopied : public ArrayElems
{
  std::unique_ptr<Expr> elem_to_copy;
  std::unique_ptr<Expr> num_copies;

public:
  // Constructor requires pointers for polymorphism
  ArrayElemsCopied (Analysis::NodeMapping mappings,
		    std::unique_ptr<Expr> copied_elem,
		    std::unique_ptr<Expr> copy_amount)
    : ArrayElems (mappings), elem_to_copy (std::move (copied_elem)),
      num_copies (std::move (copy_amount))
  {}

  // Copy constructor required due to unique_ptr - uses custom clone
  ArrayElemsCopied (ArrayElemsCopied const &other)
    : ArrayElems (other), elem_to_copy (other.elem_to_copy->clone_expr ()),
      num_copies (other.num_copies->clone_expr ())
  {}

  // Overloaded assignment operator for deep copying
  ArrayElemsCopied &operator= (ArrayElemsCopied const &other)
  {
    elem_to_copy = other.elem_to_copy->clone_expr ();
    num_copies = other.num_copies->clone_expr ();

    return *this;
  }

  // move constructors
  ArrayElemsCopied (ArrayElemsCopied &&other) = default;
  ArrayElemsCopied &operator= (ArrayElemsCopied &&other) = default;

  std::string as_string () const override;

  void accept_vis (HIRFullVisitor &vis) override;

  Expr *get_elem_to_copy () { return elem_to_copy.get (); }

  Expr *get_num_copies_expr () { return num_copies.get (); }

  ArrayElems::ArrayExprType get_array_expr_type () const override final
  {
    return ArrayElems::ArrayExprType::COPIED;
  }

protected:
  ArrayElemsCopied *clone_array_elems_impl () const override
  {
    return new ArrayElemsCopied (*this);
  }
};

// Array definition-ish expression
class ArrayExpr : public ExprWithoutBlock
{
  AST::AttrVec inner_attrs;
  std::unique_ptr<ArrayElems> internal_elements;

  Location locus;

public:
  std::string as_string () const override;

  AST::AttrVec get_inner_attrs () const { return inner_attrs; }

  // Returns whether array expr has array elems or if it is just empty.
  bool has_array_elems () const { return internal_elements != nullptr; }

  // Constructor requires ArrayElems pointer
  ArrayExpr (Analysis::NodeMapping mappings,
	     std::unique_ptr<ArrayElems> array_elems,
	     AST::AttrVec inner_attribs, AST::AttrVec outer_attribs,
	     Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      inner_attrs (std::move (inner_attribs)),
      internal_elements (std::move (array_elems)), locus (locus)
  {}

  // Copy constructor requires cloning ArrayElems for polymorphism to hold
  ArrayExpr (ArrayExpr const &other)
    : ExprWithoutBlock (other), inner_attrs (other.inner_attrs),
      locus (other.locus)
  {
    if (other.has_array_elems ())
      internal_elements = other.internal_elements->clone_array_elems ();
  }

  // Overload assignment operator to clone internal_elements
  ArrayExpr &operator= (ArrayExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    inner_attrs = other.inner_attrs;
    if (other.has_array_elems ())
      internal_elements = other.internal_elements->clone_array_elems ();
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  ArrayExpr (ArrayExpr &&other) = default;
  ArrayExpr &operator= (ArrayExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  ArrayElems *get_internal_elements () { return internal_elements.get (); };

  ExprType get_expression_type () const override final
  {
    return ExprType::Array;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ArrayExpr *clone_expr_impl () const override { return new ArrayExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ArrayExpr *clone_expr_without_block_impl () const override
  {
    return new ArrayExpr (*this);
  }
};

class ArrayIndexExpr : public ExprWithoutBlock
{
  std::unique_ptr<Expr> array_expr;
  std::unique_ptr<Expr> index_expr;

  Location locus;

public:
  std::string as_string () const override;

  ArrayIndexExpr (Analysis::NodeMapping mappings,
		  std::unique_ptr<Expr> array_expr,
		  std::unique_ptr<Expr> array_index_expr,
		  AST::AttrVec outer_attribs, Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      array_expr (std::move (array_expr)),
      index_expr (std::move (array_index_expr)), locus (locus)
  {}

  // Copy constructor requires special cloning due to unique_ptr
  ArrayIndexExpr (ArrayIndexExpr const &other)
    : ExprWithoutBlock (other), array_expr (other.array_expr->clone_expr ()),
      index_expr (other.index_expr->clone_expr ()), locus (other.locus)
  {}

  // Overload assignment operator to clone unique_ptrs
  ArrayIndexExpr &operator= (ArrayIndexExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    array_expr = other.array_expr->clone_expr ();
    index_expr = other.index_expr->clone_expr ();
    // outer_attrs = other.outer_attrs;
    locus = other.locus;

    return *this;
  }

  // move constructors
  ArrayIndexExpr (ArrayIndexExpr &&other) = default;
  ArrayIndexExpr &operator= (ArrayIndexExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Expr *get_array_expr () { return array_expr.get (); }
  Expr *get_index_expr () { return index_expr.get (); }

  ExprType get_expression_type () const override final
  {
    return ExprType::ArrayIndex;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ArrayIndexExpr *clone_expr_impl () const override
  {
    return new ArrayIndexExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ArrayIndexExpr *clone_expr_without_block_impl () const override
  {
    return new ArrayIndexExpr (*this);
  }
};

// HIR representation of a tuple
class TupleExpr : public ExprWithoutBlock
{
  AST::AttrVec inner_attrs;

  std::vector<std::unique_ptr<Expr> > tuple_elems;
  // replaces (inlined version of) TupleElements

  Location locus;

public:
  std::string as_string () const override;

  AST::AttrVec get_inner_attrs () const { return inner_attrs; }

  TupleExpr (Analysis::NodeMapping mappings,
	     std::vector<std::unique_ptr<Expr> > tuple_elements,
	     AST::AttrVec inner_attribs, AST::AttrVec outer_attribs,
	     Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      inner_attrs (std::move (inner_attribs)),
      tuple_elems (std::move (tuple_elements)), locus (locus)
  {}

  // copy constructor with vector clone
  TupleExpr (TupleExpr const &other)
    : ExprWithoutBlock (other), inner_attrs (other.inner_attrs),
      locus (other.locus)
  {
    tuple_elems.reserve (other.tuple_elems.size ());
    for (const auto &e : other.tuple_elems)
      tuple_elems.push_back (e->clone_expr ());
  }

  // overloaded assignment operator to vector clone
  TupleExpr &operator= (TupleExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    inner_attrs = other.inner_attrs;
    locus = other.locus;

    tuple_elems.reserve (other.tuple_elems.size ());
    for (const auto &e : other.tuple_elems)
      tuple_elems.push_back (e->clone_expr ());

    return *this;
  }

  // move constructors
  TupleExpr (TupleExpr &&other) = default;
  TupleExpr &operator= (TupleExpr &&other) = default;

  /* Note: syntactically, can disambiguate single-element tuple from parens with
   * comma, i.e. (0,) rather than (0) */

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  const std::vector<std::unique_ptr<Expr> > &get_tuple_elems () const
  {
    return tuple_elems;
  }
  std::vector<std::unique_ptr<Expr> > &get_tuple_elems ()
  {
    return tuple_elems;
  }

  bool is_unit () const { return tuple_elems.size () == 0; }

  ExprType get_expression_type () const override final
  {
    return ExprType::Tuple;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  TupleExpr *clone_expr_impl () const override { return new TupleExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  TupleExpr *clone_expr_without_block_impl () const override
  {
    return new TupleExpr (*this);
  }
};

class TupleIndexExpr : public ExprWithoutBlock
{
  std::unique_ptr<Expr> tuple_expr;
  TupleIndex tuple_index;
  Location locus;

public:
  std::string as_string () const override;

  TupleIndex get_tuple_index () const { return tuple_index; }

  TupleIndexExpr (Analysis::NodeMapping mappings,
		  std::unique_ptr<Expr> tuple_expr, TupleIndex index,
		  AST::AttrVec outer_attribs, Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      tuple_expr (std::move (tuple_expr)), tuple_index (index), locus (locus)
  {}

  // Copy constructor requires a clone for tuple_expr
  TupleIndexExpr (TupleIndexExpr const &other)
    : ExprWithoutBlock (other), tuple_expr (other.tuple_expr->clone_expr ()),
      tuple_index (other.tuple_index), locus (other.locus)
  {}

  // Overload assignment operator in order to clone
  TupleIndexExpr &operator= (TupleIndexExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    tuple_expr = other.tuple_expr->clone_expr ();
    tuple_index = other.tuple_index;
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  TupleIndexExpr (TupleIndexExpr &&other) = default;
  TupleIndexExpr &operator= (TupleIndexExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_tuple_expr ()
  {
    rust_assert (tuple_expr != nullptr);
    return tuple_expr;
  }

  ExprType get_expression_type () const override final
  {
    return ExprType::TupleIdx;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  TupleIndexExpr *clone_expr_impl () const override
  {
    return new TupleIndexExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  TupleIndexExpr *clone_expr_without_block_impl () const override
  {
    return new TupleIndexExpr (*this);
  }
};

// Base struct/tuple/union value creator HIR node (abstract)
class StructExpr : public ExprWithoutBlock
{
protected:
  PathInExpression struct_name;

  // Protected constructor to allow initialising struct_name
  StructExpr (Analysis::NodeMapping mappings, PathInExpression struct_path,
	      AST::AttrVec outer_attribs)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      struct_name (std::move (struct_path))
  {}

public:
  PathInExpression &get_struct_name () { return struct_name; }

  std::string as_string () const override;

  ExprType get_expression_type () const override final
  {
    return ExprType::Struct;
  }
};

// Actual HIR node of the struct creator (with no fields). Not abstract!
class StructExprStruct : public StructExpr
{
  AST::AttrVec inner_attrs;

  Location locus;

public:
  std::string as_string () const override;

  AST::AttrVec get_inner_attrs () const { return inner_attrs; }

  // Constructor has to call protected constructor of base class
  StructExprStruct (Analysis::NodeMapping mappings,
		    PathInExpression struct_path, AST::AttrVec inner_attribs,
		    AST::AttrVec outer_attribs, Location locus)
    : StructExpr (std::move (mappings), std::move (struct_path),
		  std::move (outer_attribs)),
      inner_attrs (std::move (inner_attribs)), locus (locus)
  {}

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprStruct *clone_expr_impl () const override
  {
    return new StructExprStruct (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprStruct *clone_expr_without_block_impl () const override
  {
    return new StructExprStruct (*this);
  }
};

/* HIR node representing expression used to fill a struct's fields from another
 * struct */
struct StructBase
{
public:
  std::unique_ptr<Expr> base_struct;

  // TODO: should this store location data?
  StructBase (std::unique_ptr<Expr> base_struct_ptr)
    : base_struct (std::move (base_struct_ptr))
  {}

  // Copy constructor requires clone
  StructBase (StructBase const &other)
  {
    /* HACK: gets around base_struct pointer being null (e.g. if no struct base
     * exists) */
    if (other.base_struct != nullptr)
      other.base_struct->clone_expr ();
  }

  // Destructor
  ~StructBase () = default;

  // Overload assignment operator to clone base_struct
  StructBase &operator= (StructBase const &other)
  {
    base_struct = other.base_struct->clone_expr ();

    return *this;
  }

  // move constructors
  StructBase (StructBase &&other) = default;
  StructBase &operator= (StructBase &&other) = default;

  // Returns a null expr-ed StructBase - error state
  static StructBase error () { return StructBase (nullptr); }

  // Returns whether StructBase is in error state
  bool is_invalid () const { return base_struct == nullptr; }

  std::string as_string () const;

  Expr *get_base () { return base_struct.get (); }
};

/* Base HIR node for a single struct expression field (in struct instance
 * creation) - abstract */
class StructExprField
{
public:
  enum StructExprFieldKind
  {
    IDENTIFIER_VALUE,
    IDENTIFIER,
    INDEX_VALUE,
  };

  virtual ~StructExprField () {}

  // Unique pointer custom clone function
  std::unique_ptr<StructExprField> clone_struct_expr_field () const
  {
    return std::unique_ptr<StructExprField> (clone_struct_expr_field_impl ());
  }

  virtual std::string as_string () const = 0;

  virtual void accept_vis (HIRFullVisitor &vis) = 0;
  virtual void accept_vis (HIRExpressionVisitor &vis) = 0;

  Analysis::NodeMapping &get_mappings () { return mappings; }

  Location get_locus () { return locus; }

  virtual StructExprFieldKind get_kind () const = 0;

protected:
  // pure virtual clone implementation
  virtual StructExprField *clone_struct_expr_field_impl () const = 0;

  StructExprField (Analysis::NodeMapping mapping, Location locus)
    : mappings (mapping), locus (locus)
  {}

  Analysis::NodeMapping mappings;
  Location locus;
};

// Identifier-only variant of StructExprField HIR node
class StructExprFieldIdentifier : public StructExprField
{
private:
  Identifier field_name;

  // TODO: should this store location data?
public:
  StructExprFieldIdentifier (Analysis::NodeMapping mapping,
			     Identifier field_identifier, Location locus)
    : StructExprField (mapping, locus),
      field_name (std::move (field_identifier))
  {}

  std::string as_string () const override { return field_name; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Identifier get_field_name () const { return field_name; }

  StructExprFieldKind get_kind () const override
  {
    return StructExprFieldKind::IDENTIFIER;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprFieldIdentifier *clone_struct_expr_field_impl () const override
  {
    return new StructExprFieldIdentifier (*this);
  }
};

/* Base HIR node for a single struct expression field with an assigned value -
 * abstract */
class StructExprFieldWithVal : public StructExprField
{
  std::unique_ptr<Expr> value;

protected:
  StructExprFieldWithVal (Analysis::NodeMapping mapping,
			  std::unique_ptr<Expr> field_value, Location locus)
    : StructExprField (mapping, locus), value (std::move (field_value))
  {}

  // Copy constructor requires clone
  StructExprFieldWithVal (StructExprFieldWithVal const &other)
    : StructExprField (other.mappings, other.locus),
      value (other.value->clone_expr ())
  {}

  // Overload assignment operator to clone unique_ptr
  StructExprFieldWithVal &operator= (StructExprFieldWithVal const &other)
  {
    value = other.value->clone_expr ();
    mappings = other.mappings;
    locus = other.locus;

    return *this;
  }

  // move constructors
  StructExprFieldWithVal (StructExprFieldWithVal &&other) = default;
  StructExprFieldWithVal &operator= (StructExprFieldWithVal &&other) = default;

public:
  std::string as_string () const override;

  Expr *get_value () { return value.get (); }
};

// Identifier and value variant of StructExprField HIR node
class StructExprFieldIdentifierValue : public StructExprFieldWithVal
{
public:
  Identifier field_name;

  // TODO: should this store location data?

  StructExprFieldIdentifierValue (Analysis::NodeMapping mapping,
				  Identifier field_identifier,
				  std::unique_ptr<Expr> field_value,
				  Location locus)
    : StructExprFieldWithVal (mapping, std::move (field_value), locus),
      field_name (std::move (field_identifier))
  {}

  std::string as_string () const override;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  StructExprFieldKind get_kind () const override
  {
    return StructExprFieldKind::IDENTIFIER_VALUE;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprFieldIdentifierValue *clone_struct_expr_field_impl () const override
  {
    return new StructExprFieldIdentifierValue (*this);
  }
};

// Tuple index and value variant of StructExprField HIR node
class StructExprFieldIndexValue : public StructExprFieldWithVal
{
public:
  TupleIndex index;

  // TODO: should this store location data?

  StructExprFieldIndexValue (Analysis::NodeMapping mapping,
			     TupleIndex tuple_index,
			     std::unique_ptr<Expr> field_value, Location locus)
    : StructExprFieldWithVal (mapping, std::move (field_value), locus),
      index (tuple_index)
  {}

  std::string as_string () const override;

  TupleIndex get_tuple_index () const { return index; };

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  StructExprFieldKind get_kind () const override
  {
    return StructExprFieldKind::INDEX_VALUE;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprFieldIndexValue *clone_struct_expr_field_impl () const override
  {
    return new StructExprFieldIndexValue (*this);
  }
};

// HIR node of a struct creator with fields
class StructExprStructFields : public StructExprStruct
{
public:
  // std::vector<StructExprField> fields;
  std::vector<std::unique_ptr<StructExprField> > fields;

  // bool has_struct_base;
  // FIXME make unique_ptr
  StructBase *struct_base;

  // For unions there is just one field, the index
  // is set when type checking
  int union_index = -1;

  std::string as_string () const override;

  bool has_struct_base () const { return struct_base != nullptr; }

  // Constructor for StructExprStructFields when no struct base is used
  StructExprStructFields (
    Analysis::NodeMapping mappings, PathInExpression struct_path,
    std::vector<std::unique_ptr<StructExprField> > expr_fields, Location locus,
    StructBase *base_struct, AST::AttrVec inner_attribs = AST::AttrVec (),
    AST::AttrVec outer_attribs = AST::AttrVec ())
    : StructExprStruct (std::move (mappings), std::move (struct_path),
			std::move (inner_attribs), std::move (outer_attribs),
			locus),
      fields (std::move (expr_fields)), struct_base (base_struct)
  {}

  // copy constructor with vector clone
  StructExprStructFields (StructExprStructFields const &other)
    : StructExprStruct (other), struct_base (other.struct_base),
      union_index (other.union_index)
  {
    fields.reserve (other.fields.size ());
    for (const auto &e : other.fields)
      fields.push_back (e->clone_struct_expr_field ());
  }

  // overloaded assignment operator with vector clone
  StructExprStructFields &operator= (StructExprStructFields const &other)
  {
    StructExprStruct::operator= (other);
    struct_base = other.struct_base;
    union_index = other.union_index;

    fields.reserve (other.fields.size ());
    for (const auto &e : other.fields)
      fields.push_back (e->clone_struct_expr_field ());

    return *this;
  }

  // move constructors
  StructExprStructFields (StructExprStructFields &&other) = default;
  StructExprStructFields &operator= (StructExprStructFields &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::vector<std::unique_ptr<StructExprField> > &get_fields ()
  {
    return fields;
  };

  const std::vector<std::unique_ptr<StructExprField> > &get_fields () const
  {
    return fields;
  };

  void set_fields_as_owner (
    std::vector<std::unique_ptr<StructExprField> > new_fields)
  {
    fields = std::move (new_fields);
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprStructFields *clone_expr_impl () const override
  {
    return new StructExprStructFields (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprStructFields *clone_expr_without_block_impl () const override
  {
    return new StructExprStructFields (*this);
  }
};

// HIR node of the functional update struct creator
class StructExprStructBase : public StructExprStruct
{
  StructBase struct_base;

public:
  std::string as_string () const override;

  /*inline StructBase get_struct_base() const {
      return struct_base;
  }*/

  StructExprStructBase (Analysis::NodeMapping mappings,
			PathInExpression struct_path, StructBase base_struct,
			AST::AttrVec inner_attribs, AST::AttrVec outer_attribs,
			Location locus)
    : StructExprStruct (std::move (mappings), std::move (struct_path),
			std::move (inner_attribs), std::move (outer_attribs),
			locus),
      struct_base (std::move (base_struct))
  {}

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  StructBase *get_struct_base () { return &struct_base; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprStructBase *clone_expr_impl () const override
  {
    return new StructExprStructBase (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  StructExprStructBase *clone_expr_without_block_impl () const override
  {
    return new StructExprStructBase (*this);
  }
};

// Function call expression HIR node
class CallExpr : public ExprWithoutBlock
{
  std::unique_ptr<Expr> function;
  std::vector<std::unique_ptr<Expr> > params;
  Location locus;

public:
  std::string as_string () const override;

  CallExpr (Analysis::NodeMapping mappings, std::unique_ptr<Expr> function_expr,
	    std::vector<std::unique_ptr<Expr> > function_params,
	    AST::AttrVec outer_attribs, Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      function (std::move (function_expr)),
      params (std::move (function_params)), locus (locus)
  {}

  // copy constructor requires clone
  CallExpr (CallExpr const &other)
    : ExprWithoutBlock (other), function (other.function->clone_expr ()),
      locus (other.locus)
  /*, params(other.params),*/ {
    params.reserve (other.params.size ());
    for (const auto &e : other.params)
      params.push_back (e->clone_expr ());
  }

  // Overload assignment operator to clone
  CallExpr &operator= (CallExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    function = other.function->clone_expr ();
    locus = other.locus;
    // params = other.params;
    // outer_attrs = other.outer_attrs;

    params.reserve (other.params.size ());
    for (const auto &e : other.params)
      params.push_back (e->clone_expr ());

    return *this;
  }

  // move constructors
  CallExpr (CallExpr &&other) = default;
  CallExpr &operator= (CallExpr &&other) = default;

  // Returns whether function call has parameters.
  bool has_params () const { return !params.empty (); }

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Expr *get_fnexpr () { return function.get (); }

  size_t num_params () const { return params.size (); }

  std::vector<std::unique_ptr<Expr> > &get_arguments () { return params; }

  const std::vector<std::unique_ptr<Expr> > &get_arguments () const
  {
    return params;
  }

  ExprType get_expression_type () const override final
  {
    return ExprType::Call;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  CallExpr *clone_expr_impl () const override { return new CallExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  CallExpr *clone_expr_without_block_impl () const override
  {
    return new CallExpr (*this);
  }
};

// Method call expression HIR node
class MethodCallExpr : public ExprWithoutBlock
{
  std::unique_ptr<Expr> receiver;
  PathExprSegment method_name;
  std::vector<std::unique_ptr<Expr> > params;
  Location locus;

public:
  std::string as_string () const override;

  MethodCallExpr (Analysis::NodeMapping mappings,
		  std::unique_ptr<Expr> call_receiver,
		  PathExprSegment method_path,
		  std::vector<std::unique_ptr<Expr> > method_params,
		  AST::AttrVec outer_attribs, Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      receiver (std::move (call_receiver)),
      method_name (std::move (method_path)), params (std::move (method_params)),
      locus (locus)
  {}

  // copy constructor required due to cloning
  MethodCallExpr (MethodCallExpr const &other)
    : ExprWithoutBlock (other), receiver (other.receiver->clone_expr ()),
      method_name (other.method_name), locus (other.locus)
  /*, params(other.params),*/ {
    params.reserve (other.params.size ());
    for (const auto &e : other.params)
      params.push_back (e->clone_expr ());
  }

  // Overload assignment operator to clone receiver object
  MethodCallExpr &operator= (MethodCallExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    receiver = other.receiver->clone_expr ();
    method_name = other.method_name;
    locus = other.locus;
    // params = other.params;
    // outer_attrs = other.outer_attrs;

    params.reserve (other.params.size ());
    for (const auto &e : other.params)
      params.push_back (e->clone_expr ());

    return *this;
  }

  // move constructors
  MethodCallExpr (MethodCallExpr &&other) = default;
  MethodCallExpr &operator= (MethodCallExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_receiver () { return receiver; }

  PathExprSegment &get_method_name () { return method_name; };
  const PathExprSegment &get_method_name () const { return method_name; };

  size_t num_params () const { return params.size (); }

  std::vector<std::unique_ptr<Expr> > &get_arguments () { return params; }

  const std::vector<std::unique_ptr<Expr> > &get_arguments () const
  {
    return params;
  }

  ExprType get_expression_type () const override final
  {
    return ExprType::MethodCall;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MethodCallExpr *clone_expr_impl () const override
  {
    return new MethodCallExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MethodCallExpr *clone_expr_without_block_impl () const override
  {
    return new MethodCallExpr (*this);
  }
};

// aka FieldExpression
// Struct or union field access expression HIR node
class FieldAccessExpr : public ExprWithoutBlock
{
  std::unique_ptr<Expr> receiver;
  Identifier field;

  Location locus;

public:
  std::string as_string () const override;

  FieldAccessExpr (Analysis::NodeMapping mappings,
		   std::unique_ptr<Expr> field_access_receiver,
		   Identifier field_name, AST::AttrVec outer_attribs,
		   Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      receiver (std::move (field_access_receiver)),
      field (std::move (field_name)), locus (locus)
  {}

  // Copy constructor required due to unique_ptr cloning
  FieldAccessExpr (FieldAccessExpr const &other)
    : ExprWithoutBlock (other), receiver (other.receiver->clone_expr ()),
      field (other.field), locus (other.locus)
  {}

  // Overload assignment operator to clone unique_ptr
  FieldAccessExpr &operator= (FieldAccessExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    receiver = other.receiver->clone_expr ();
    field = other.field;
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  FieldAccessExpr (FieldAccessExpr &&other) = default;
  FieldAccessExpr &operator= (FieldAccessExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_receiver_expr ()
  {
    rust_assert (receiver != nullptr);
    return receiver;
  }

  Identifier get_field_name () const { return field; }

  ExprType get_expression_type () const override final
  {
    return ExprType::FieldAccess;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  FieldAccessExpr *clone_expr_impl () const override
  {
    return new FieldAccessExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  FieldAccessExpr *clone_expr_without_block_impl () const override
  {
    return new FieldAccessExpr (*this);
  }
};

// Closure parameter data structure
struct ClosureParam
{
private:
  std::vector<AST::Attribute> outer_attrs;
  std::unique_ptr<Pattern> pattern;
  std::unique_ptr<Type> type;
  Location locus;

public:
  // Returns whether the type of the parameter has been given.
  bool has_type_given () const { return type != nullptr; }

  // Constructor for closure parameter
  ClosureParam (std::unique_ptr<Pattern> param_pattern, Location locus,
		std::unique_ptr<Type> param_type = nullptr,
		std::vector<AST::Attribute> outer_attrs = {})
    : outer_attrs (std::move (outer_attrs)),
      pattern (std::move (param_pattern)), type (std::move (param_type)),
      locus (locus)
  {}

  // Copy constructor required due to cloning as a result of unique_ptrs
  ClosureParam (ClosureParam const &other)
    : pattern (other.pattern->clone_pattern ())
  {
    // guard to protect from null pointer dereference
    if (other.pattern != nullptr)
      pattern = other.pattern->clone_pattern ();
    if (other.type != nullptr)
      type = other.type->clone_type ();
  }

  ~ClosureParam () = default;

  // Assignment operator must be overloaded to clone as well
  ClosureParam &operator= (ClosureParam const &other)
  {
    outer_attrs = other.outer_attrs;

    // guard to protect from null pointer dereference
    if (other.pattern != nullptr)
      pattern = other.pattern->clone_pattern ();
    else
      pattern = nullptr;
    if (other.type != nullptr)
      type = other.type->clone_type ();
    else
      type = nullptr;

    return *this;
  }

  // move constructors
  ClosureParam (ClosureParam &&other) = default;
  ClosureParam &operator= (ClosureParam &&other) = default;

  std::string as_string () const;

  const std::vector<AST::Attribute> &get_outer_attrs () const
  {
    return outer_attrs;
  }
  std::vector<AST::Attribute> &get_outer_attrs () { return outer_attrs; }

  std::unique_ptr<Pattern> &get_pattern ()
  {
    rust_assert (pattern != nullptr);
    return pattern;
  }

  std::unique_ptr<Type> &get_type ()
  {
    rust_assert (has_type_given ());
    return type;
  }

  Location get_locus () const { return locus; }
};

// Base closure definition expression HIR node - abstract
class ClosureExpr : public ExprWithoutBlock
{
private:
  bool has_move;
  std::vector<ClosureParam> params;
  Location locus;
  std::unique_ptr<Type> return_type;
  std::unique_ptr<Expr> expr;

public:
  ClosureExpr (Analysis::NodeMapping mappings,
	       std::vector<ClosureParam> closure_params,
	       std::unique_ptr<Type> closure_return_type,
	       std::unique_ptr<Expr> closure_expr, bool has_move,
	       AST::AttrVec outer_attribs, Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      has_move (has_move), params (std::move (closure_params)), locus (locus),
      return_type (std::move (closure_return_type)),
      expr (std::move (closure_expr))
  {}

  // Copy constructor requires cloning
  ClosureExpr (ClosureExpr const &other)
    : ExprWithoutBlock (other.get_mappings (), other.get_outer_attrs ())
  {
    return_type
      = other.has_return_type () ? other.return_type->clone_type () : nullptr;
    expr = other.expr->clone_expr ();
    params = other.params;
    has_move = other.has_move;
  }

  // Overload assignment operator to clone unique_ptrs
  ClosureExpr &operator= (ClosureExpr const &other)
  {
    mappings = other.mappings;
    return_type
      = other.has_return_type () ? other.return_type->clone_type () : nullptr;
    expr = other.expr->clone_expr ();
    params = other.params;
    has_move = other.has_move;

    return *this;
  }

  // move constructors
  ClosureExpr (ClosureExpr &&other) = default;
  ClosureExpr &operator= (ClosureExpr &&other) = default;

  std::string as_string () const override;

  Location get_locus () const override final { return locus; }

  ExprType get_expression_type () const override final
  {
    return ExprType::Closure;
  }

  bool get_has_move () const { return has_move; }

  bool has_return_type () const { return return_type != nullptr; }

  std::unique_ptr<Type> &get_return_type ()
  {
    rust_assert (has_return_type ());
    return return_type;
  };
  std::unique_ptr<Expr> &get_expr () { return expr; }

  std::vector<ClosureParam> &get_params () { return params; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ClosureExpr *clone_expr_impl () const override
  {
    return new ClosureExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ClosureExpr *clone_expr_without_block_impl () const override
  {
    return new ClosureExpr (*this);
  }
};

// A block HIR node
class BlockExpr : public ExprWithBlock
{
public:
  AST::AttrVec inner_attrs;
  std::vector<std::unique_ptr<Stmt> > statements;
  std::unique_ptr<Expr> expr;
  bool tail_reachable;
  Location start_locus;
  Location end_locus;

  std::string as_string () const override;

  // Returns whether the block contains statements.
  bool has_statements () const { return !statements.empty (); }

  // Returns whether the block contains an expression
  bool has_expr () const { return expr != nullptr; }

  bool is_tail_reachable () const { return tail_reachable; }

  BlockExpr (Analysis::NodeMapping mappings,
	     std::vector<std::unique_ptr<Stmt> > block_statements,
	     std::unique_ptr<Expr> block_expr, bool tail_reachable,
	     AST::AttrVec inner_attribs, AST::AttrVec outer_attribs,
	     Location start_locus, Location end_locus)
    : ExprWithBlock (std::move (mappings), std::move (outer_attribs)),
      inner_attrs (std::move (inner_attribs)),
      statements (std::move (block_statements)), expr (std::move (block_expr)),
      tail_reachable (tail_reachable), start_locus (start_locus),
      end_locus (end_locus)
  {}

  // Copy constructor with clone
  BlockExpr (BlockExpr const &other)
    : ExprWithBlock (other), /*statements(other.statements),*/
      inner_attrs (other.inner_attrs), start_locus (other.start_locus),
      end_locus (other.end_locus)
  {
    // guard to protect from null pointer dereference
    if (other.expr != nullptr)
      expr = other.expr->clone_expr ();

    statements.reserve (other.statements.size ());
    for (const auto &e : other.statements)
      statements.push_back (e->clone_stmt ());
  }

  // Overloaded assignment operator to clone pointer
  BlockExpr &operator= (BlockExpr const &other)
  {
    ExprWithBlock::operator= (other);
    // statements = other.statements;
    expr = other.expr->clone_expr ();
    inner_attrs = other.inner_attrs;
    start_locus = other.end_locus;
    end_locus = other.end_locus;
    // outer_attrs = other.outer_attrs;

    statements.reserve (other.statements.size ());
    for (const auto &e : other.statements)
      statements.push_back (e->clone_stmt ());

    return *this;
  }

  // move constructors
  BlockExpr (BlockExpr &&other) = default;
  BlockExpr &operator= (BlockExpr &&other) = default;

  // Unique pointer custom clone function
  std::unique_ptr<BlockExpr> clone_block_expr () const
  {
    return std::unique_ptr<BlockExpr> (clone_block_expr_impl ());
  }

  Location get_locus () const override final { return start_locus; }

  Location get_start_locus () const { return start_locus; }

  Location get_end_locus () const { return end_locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  bool is_final_stmt (Stmt *stmt) { return statements.back ().get () == stmt; }

  std::unique_ptr<Expr> &get_final_expr () { return expr; }

  std::vector<std::unique_ptr<Stmt> > &get_statements () { return statements; }

  ExprType get_expression_type () const final override
  {
    return ExprType::Block;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  BlockExpr *clone_expr_impl () const override
  {
    return clone_block_expr_impl ();
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  BlockExpr *clone_expr_with_block_impl () const override
  {
    return clone_block_expr_impl ();
  }

  /* This is the base method as not an abstract class - not virtual but could be
   * in future if required. */
  /*virtual*/ BlockExpr *clone_block_expr_impl () const
  {
    return new BlockExpr (*this);
  }
};

// HIR node representing continue expression within loops
class ContinueExpr : public ExprWithoutBlock
{
  Lifetime label;
  Location locus;

public:
  std::string as_string () const override;

  // Returns true if the continue expr has a label.
  bool has_label () const { return !label.is_error (); }

  // Constructor for a ContinueExpr with a label.
  ContinueExpr (Analysis::NodeMapping mappings, Location locus, Lifetime label,
		AST::AttrVec outer_attribs = AST::AttrVec ())
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      label (std::move (label)), locus (locus)
  {}

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Lifetime &get_label () { return label; }

  ExprType get_expression_type () const final override
  {
    return ExprType::Continue;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ContinueExpr *clone_expr_impl () const override
  {
    return new ContinueExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ContinueExpr *clone_expr_without_block_impl () const override
  {
    return new ContinueExpr (*this);
  }
};

// HIR node representing break expression within loops
class BreakExpr : public ExprWithoutBlock
{
  // bool has_label;
  Lifetime label;

  // bool has_break_expr;
  std::unique_ptr<Expr> break_expr;

  Location locus;

public:
  std::string as_string () const override;

  // Returns whether the break expression has a label or not.
  bool has_label () const { return !label.is_error (); }

  /* Returns whether the break expression has an expression used in the break or
   * not. */
  bool has_break_expr () const { return break_expr != nullptr; }

  // Constructor for a break expression
  BreakExpr (Analysis::NodeMapping mappings, Location locus,
	     Lifetime break_label,
	     std::unique_ptr<Expr> expr_in_break = nullptr,
	     AST::AttrVec outer_attribs = AST::AttrVec ())
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      label (std::move (break_label)), break_expr (std::move (expr_in_break)),
      locus (locus)
  {}

  // Copy constructor defined to use clone for unique pointer
  BreakExpr (BreakExpr const &other)
    : ExprWithoutBlock (other), label (other.label), locus (other.locus)
  {
    // guard to protect from null pointer dereference
    if (other.break_expr != nullptr)
      break_expr = other.break_expr->clone_expr ();
  }

  // Overload assignment operator to clone unique pointer
  BreakExpr &operator= (BreakExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    label = other.label;
    break_expr = other.break_expr->clone_expr ();
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  BreakExpr (BreakExpr &&other) = default;
  BreakExpr &operator= (BreakExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Lifetime &get_label () { return label; }

  std::unique_ptr<Expr> &get_expr () { return break_expr; }

  ExprType get_expression_type () const override final
  {
    return ExprType::Break;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  BreakExpr *clone_expr_impl () const override { return new BreakExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  BreakExpr *clone_expr_without_block_impl () const override
  {
    return new BreakExpr (*this);
  }
};

// Base range expression HIR node object - abstract
class RangeExpr : public ExprWithoutBlock
{
  Location locus;

protected:
  // outer attributes not allowed before range expressions
  RangeExpr (Analysis::NodeMapping mappings, Location locus)
    : ExprWithoutBlock (std::move (mappings), AST::AttrVec ()), locus (locus)
  {}

public:
  Location get_locus () const override final { return locus; }

  ExprType get_expression_type () const override final
  {
    return ExprType::Range;
  }
};

// Range from (inclusive) and to (exclusive) expression HIR node object
// aka RangeExpr; constructs a std::ops::Range object
class RangeFromToExpr : public RangeExpr
{
  std::unique_ptr<Expr> from;
  std::unique_ptr<Expr> to;

public:
  std::string as_string () const override;

  RangeFromToExpr (Analysis::NodeMapping mappings,
		   std::unique_ptr<Expr> range_from,
		   std::unique_ptr<Expr> range_to, Location locus)
    : RangeExpr (std::move (mappings), locus), from (std::move (range_from)),
      to (std::move (range_to))
  {}

  // Copy constructor with cloning
  RangeFromToExpr (RangeFromToExpr const &other)
    : RangeExpr (other), from (other.from->clone_expr ()),
      to (other.to->clone_expr ())
  {}

  // Overload assignment operator to clone unique pointers
  RangeFromToExpr &operator= (RangeFromToExpr const &other)
  {
    RangeExpr::operator= (other);
    from = other.from->clone_expr ();
    to = other.to->clone_expr ();

    return *this;
  }

  // move constructors
  RangeFromToExpr (RangeFromToExpr &&other) = default;
  RangeFromToExpr &operator= (RangeFromToExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_from_expr () { return from; }
  std::unique_ptr<Expr> &get_to_expr () { return to; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFromToExpr *clone_expr_impl () const override
  {
    return new RangeFromToExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFromToExpr *clone_expr_without_block_impl () const override
  {
    return new RangeFromToExpr (*this);
  }
};

// Range from (inclusive) expression HIR node object
// constructs a std::ops::RangeFrom object
class RangeFromExpr : public RangeExpr
{
  std::unique_ptr<Expr> from;

public:
  std::string as_string () const override;

  RangeFromExpr (Analysis::NodeMapping mappings,
		 std::unique_ptr<Expr> range_from, Location locus)
    : RangeExpr (std::move (mappings), locus), from (std::move (range_from))
  {}

  // Copy constructor with clone
  RangeFromExpr (RangeFromExpr const &other)
    : RangeExpr (other), from (other.from->clone_expr ())
  {}

  // Overload assignment operator to clone unique_ptr
  RangeFromExpr &operator= (RangeFromExpr const &other)
  {
    RangeExpr::operator= (other);
    from = other.from->clone_expr ();

    return *this;
  }

  // move constructors
  RangeFromExpr (RangeFromExpr &&other) = default;
  RangeFromExpr &operator= (RangeFromExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_from_expr () { return from; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFromExpr *clone_expr_impl () const override
  {
    return new RangeFromExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFromExpr *clone_expr_without_block_impl () const override
  {
    return new RangeFromExpr (*this);
  }
};

// Range to (exclusive) expression HIR node object
// constructs a std::ops::RangeTo object
class RangeToExpr : public RangeExpr
{
  std::unique_ptr<Expr> to;

public:
  std::string as_string () const override;

  // outer attributes not allowed
  RangeToExpr (Analysis::NodeMapping mappings, std::unique_ptr<Expr> range_to,
	       Location locus)
    : RangeExpr (std::move (mappings), locus), to (std::move (range_to))
  {}

  // Copy constructor with clone
  RangeToExpr (RangeToExpr const &other)
    : RangeExpr (other), to (other.to->clone_expr ())
  {}

  // Overload assignment operator to clone unique_ptr
  RangeToExpr &operator= (RangeToExpr const &other)
  {
    RangeExpr::operator= (other);
    to = other.to->clone_expr ();

    return *this;
  }

  // move constructors
  RangeToExpr (RangeToExpr &&other) = default;
  RangeToExpr &operator= (RangeToExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_to_expr () { return to; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeToExpr *clone_expr_impl () const override
  {
    return new RangeToExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeToExpr *clone_expr_without_block_impl () const override
  {
    return new RangeToExpr (*this);
  }
};

// Full range expression HIR node object
// constructs a std::ops::RangeFull object
class RangeFullExpr : public RangeExpr
{
public:
  std::string as_string () const override;

  RangeFullExpr (Analysis::NodeMapping mappings, Location locus)
    : RangeExpr (std::move (mappings), locus)
  {}
  // outer attributes not allowed

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFullExpr *clone_expr_impl () const override
  {
    return new RangeFullExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFullExpr *clone_expr_without_block_impl () const override
  {
    return new RangeFullExpr (*this);
  }
};

// Range from (inclusive) and to (inclusive) expression HIR node object
// aka RangeInclusiveExpr; constructs a std::ops::RangeInclusive object
class RangeFromToInclExpr : public RangeExpr
{
  std::unique_ptr<Expr> from;
  std::unique_ptr<Expr> to;

public:
  std::string as_string () const override;

  RangeFromToInclExpr (Analysis::NodeMapping mappings,
		       std::unique_ptr<Expr> range_from,
		       std::unique_ptr<Expr> range_to, Location locus)
    : RangeExpr (std::move (mappings), locus), from (std::move (range_from)),
      to (std::move (range_to))
  {}
  // outer attributes not allowed

  // Copy constructor with clone
  RangeFromToInclExpr (RangeFromToInclExpr const &other)
    : RangeExpr (other), from (other.from->clone_expr ()),
      to (other.to->clone_expr ())
  {}

  // Overload assignment operator to use clone
  RangeFromToInclExpr &operator= (RangeFromToInclExpr const &other)
  {
    RangeExpr::operator= (other);
    from = other.from->clone_expr ();
    to = other.to->clone_expr ();

    return *this;
  }

  // move constructors
  RangeFromToInclExpr (RangeFromToInclExpr &&other) = default;
  RangeFromToInclExpr &operator= (RangeFromToInclExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_from_expr () { return from; }
  std::unique_ptr<Expr> &get_to_expr () { return to; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFromToInclExpr *clone_expr_impl () const override
  {
    return new RangeFromToInclExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeFromToInclExpr *clone_expr_without_block_impl () const override
  {
    return new RangeFromToInclExpr (*this);
  }
};

// Range to (inclusive) expression HIR node object
// aka RangeToInclusiveExpr; constructs a std::ops::RangeToInclusive object
class RangeToInclExpr : public RangeExpr
{
  std::unique_ptr<Expr> to;

public:
  std::string as_string () const override;

  RangeToInclExpr (Analysis::NodeMapping mappings,
		   std::unique_ptr<Expr> range_to, Location locus)
    : RangeExpr (std::move (mappings), locus), to (std::move (range_to))
  {}
  // outer attributes not allowed

  // Copy constructor with clone
  RangeToInclExpr (RangeToInclExpr const &other)
    : RangeExpr (other), to (other.to->clone_expr ())
  {}

  // Overload assignment operator to clone pointer
  RangeToInclExpr &operator= (RangeToInclExpr const &other)
  {
    RangeExpr::operator= (other);
    to = other.to->clone_expr ();

    return *this;
  }

  // move constructors
  RangeToInclExpr (RangeToInclExpr &&other) = default;
  RangeToInclExpr &operator= (RangeToInclExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_to_expr () { return to; };

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeToInclExpr *clone_expr_impl () const override
  {
    return new RangeToInclExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  RangeToInclExpr *clone_expr_without_block_impl () const override
  {
    return new RangeToInclExpr (*this);
  }
};

// Return expression HIR node representation
class ReturnExpr : public ExprWithoutBlock
{
public:
  std::unique_ptr<Expr> return_expr;

  Location locus;

  std::string as_string () const override;

  /* Returns whether the object has an expression returned (i.e. not void return
   * type). */
  bool has_return_expr () const { return return_expr != nullptr; }

  // Constructor for ReturnExpr.
  ReturnExpr (Analysis::NodeMapping mappings, Location locus,
	      std::unique_ptr<Expr> returned_expr = nullptr,
	      AST::AttrVec outer_attribs = AST::AttrVec ())
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attribs)),
      return_expr (std::move (returned_expr)), locus (locus)
  {}

  // Copy constructor with clone
  ReturnExpr (ReturnExpr const &other)
    : ExprWithoutBlock (other), locus (other.locus)
  {
    // guard to protect from null pointer dereference
    if (other.return_expr != nullptr)
      return_expr = other.return_expr->clone_expr ();
  }

  // Overloaded assignment operator to clone return_expr pointer
  ReturnExpr &operator= (ReturnExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    return_expr = other.return_expr->clone_expr ();
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  ReturnExpr (ReturnExpr &&other) = default;
  ReturnExpr &operator= (ReturnExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  Expr *get_expr () { return return_expr.get (); }

  ExprType get_expression_type () const override final
  {
    return ExprType::Return;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ReturnExpr *clone_expr_impl () const override
  {
    return new ReturnExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ReturnExpr *clone_expr_without_block_impl () const override
  {
    return new ReturnExpr (*this);
  }
};

// An unsafe block HIR node
class UnsafeBlockExpr : public ExprWithBlock
{
  // Or just have it extend BlockExpr
  std::unique_ptr<BlockExpr> expr;
  Location locus;

public:
  std::string as_string () const override;

  UnsafeBlockExpr (Analysis::NodeMapping mappings,
		   std::unique_ptr<BlockExpr> block_expr,
		   AST::AttrVec outer_attribs, Location locus)
    : ExprWithBlock (std::move (mappings), std::move (outer_attribs)),
      expr (std::move (block_expr)), locus (locus)
  {}

  // Copy constructor with clone
  UnsafeBlockExpr (UnsafeBlockExpr const &other)
    : ExprWithBlock (other), expr (other.expr->clone_block_expr ()),
      locus (other.locus)
  {}

  // Overloaded assignment operator to clone
  UnsafeBlockExpr &operator= (UnsafeBlockExpr const &other)
  {
    ExprWithBlock::operator= (other);
    expr = other.expr->clone_block_expr ();
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  UnsafeBlockExpr (UnsafeBlockExpr &&other) = default;
  UnsafeBlockExpr &operator= (UnsafeBlockExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<BlockExpr> &get_block_expr () { return expr; }

  ExprType get_expression_type () const override final
  {
    return ExprType::UnsafeBlock;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  UnsafeBlockExpr *clone_expr_impl () const override
  {
    return new UnsafeBlockExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  UnsafeBlockExpr *clone_expr_with_block_impl () const override
  {
    return new UnsafeBlockExpr (*this);
  }
};

// Loop label expression HIR node used with break and continue expressions
// TODO: inline?
class LoopLabel /*: public Node*/
{
  Lifetime label; // or type LIFETIME_OR_LABEL

  Location locus;

  Analysis::NodeMapping mappings;

public:
  std::string as_string () const;

  LoopLabel (Analysis::NodeMapping mapping, Lifetime loop_label, Location locus)
    : label (std::move (loop_label)), locus (locus), mappings (mapping)
  {}

  // Returns whether the LoopLabel is in an error state.
  bool is_error () const { return label.is_error (); }

  Location get_locus () const { return locus; }

  Analysis::NodeMapping &get_mappings () { return mappings; }

  Lifetime &get_lifetime () { return label; }
};

// Base loop expression HIR node - aka LoopExpr
class BaseLoopExpr : public ExprWithBlock
{
protected:
  LoopLabel loop_label;
  std::unique_ptr<BlockExpr> loop_block;

private:
  Location locus;

protected:
  // Constructor for BaseLoopExpr
  BaseLoopExpr (Analysis::NodeMapping mappings,
		std::unique_ptr<BlockExpr> loop_block, Location locus,
		LoopLabel loop_label,
		AST::AttrVec outer_attribs = AST::AttrVec ())
    : ExprWithBlock (std::move (mappings), std::move (outer_attribs)),
      loop_label (std::move (loop_label)), loop_block (std::move (loop_block)),
      locus (locus)
  {}

  // Copy constructor for BaseLoopExpr with clone
  BaseLoopExpr (BaseLoopExpr const &other)
    : ExprWithBlock (other), loop_label (other.loop_label),
      loop_block (other.loop_block->clone_block_expr ()), locus (other.locus)
  {}

  // Overloaded assignment operator to clone
  BaseLoopExpr &operator= (BaseLoopExpr const &other)
  {
    ExprWithBlock::operator= (other);
    loop_block = other.loop_block->clone_block_expr ();
    loop_label = other.loop_label;
    locus = other.locus;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  BaseLoopExpr (BaseLoopExpr &&other) = default;
  BaseLoopExpr &operator= (BaseLoopExpr &&other) = default;

  ExprType get_expression_type () const final override
  {
    return ExprType::BaseLoop;
  }

public:
  bool has_loop_label () const { return !loop_label.is_error (); }

  Location get_locus () const override final { return locus; }

  std::unique_ptr<HIR::BlockExpr> &get_loop_block () { return loop_block; };

  LoopLabel &get_loop_label () { return loop_label; }
};

// 'Loop' expression (i.e. the infinite loop) HIR node
class LoopExpr : public BaseLoopExpr
{
public:
  std::string as_string () const override;

  // Constructor for LoopExpr
  LoopExpr (Analysis::NodeMapping mappings,
	    std::unique_ptr<BlockExpr> loop_block, Location locus,
	    LoopLabel loop_label, AST::AttrVec outer_attribs = AST::AttrVec ())
    : BaseLoopExpr (std::move (mappings), std::move (loop_block), locus,
		    std::move (loop_label), std::move (outer_attribs))
  {}

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  LoopExpr *clone_expr_impl () const override { return new LoopExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  LoopExpr *clone_expr_with_block_impl () const override
  {
    return new LoopExpr (*this);
  }
};

// While loop expression HIR node (predicate loop)
class WhileLoopExpr : public BaseLoopExpr
{
  std::unique_ptr<Expr> condition;

public:
  std::string as_string () const override;

  // Constructor for while loop with loop label
  WhileLoopExpr (Analysis::NodeMapping mappings,
		 std::unique_ptr<Expr> loop_condition,
		 std::unique_ptr<BlockExpr> loop_block, Location locus,
		 LoopLabel loop_label,
		 AST::AttrVec outer_attribs = AST::AttrVec ())
    : BaseLoopExpr (std::move (mappings), std::move (loop_block), locus,
		    std::move (loop_label), std::move (outer_attribs)),
      condition (std::move (loop_condition))
  {}

  // Copy constructor with clone
  WhileLoopExpr (WhileLoopExpr const &other)
    : BaseLoopExpr (other), condition (other.condition->clone_expr ())
  {}

  // Overloaded assignment operator to clone
  WhileLoopExpr &operator= (WhileLoopExpr const &other)
  {
    BaseLoopExpr::operator= (other);
    condition = other.condition->clone_expr ();
    // loop_block = other.loop_block->clone_block_expr();
    // loop_label = other.loop_label;
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  WhileLoopExpr (WhileLoopExpr &&other) = default;
  WhileLoopExpr &operator= (WhileLoopExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_predicate_expr () { return condition; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  WhileLoopExpr *clone_expr_impl () const override
  {
    return new WhileLoopExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  WhileLoopExpr *clone_expr_with_block_impl () const override
  {
    return new WhileLoopExpr (*this);
  }
};

// While let loop expression HIR node (predicate pattern loop)
class WhileLetLoopExpr : public BaseLoopExpr
{
  // MatchArmPatterns patterns;
  std::vector<std::unique_ptr<Pattern> > match_arm_patterns; // inlined
  std::unique_ptr<Expr> condition;

public:
  std::string as_string () const override;

  // Constructor with a loop label
  WhileLetLoopExpr (Analysis::NodeMapping mappings,
		    std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
		    std::unique_ptr<Expr> condition,
		    std::unique_ptr<BlockExpr> loop_block, Location locus,
		    LoopLabel loop_label,
		    AST::AttrVec outer_attribs = AST::AttrVec ())
    : BaseLoopExpr (std::move (mappings), std::move (loop_block), locus,
		    std::move (loop_label), std::move (outer_attribs)),
      match_arm_patterns (std::move (match_arm_patterns)),
      condition (std::move (condition))
  {}

  // Copy constructor with clone
  WhileLetLoopExpr (WhileLetLoopExpr const &other)
    : BaseLoopExpr (other),
      /*match_arm_patterns(other.match_arm_patterns),*/ condition (
	other.condition->clone_expr ())
  {
    match_arm_patterns.reserve (other.match_arm_patterns.size ());
    for (const auto &e : other.match_arm_patterns)
      match_arm_patterns.push_back (e->clone_pattern ());
  }

  // Overloaded assignment operator to clone pointers
  WhileLetLoopExpr &operator= (WhileLetLoopExpr const &other)
  {
    BaseLoopExpr::operator= (other);
    // match_arm_patterns = other.match_arm_patterns;
    condition = other.condition->clone_expr ();
    // loop_block = other.loop_block->clone_block_expr();
    // loop_label = other.loop_label;
    // outer_attrs = other.outer_attrs;

    match_arm_patterns.reserve (other.match_arm_patterns.size ());
    for (const auto &e : other.match_arm_patterns)
      match_arm_patterns.push_back (e->clone_pattern ());

    return *this;
  }

  // move constructors
  WhileLetLoopExpr (WhileLetLoopExpr &&other) = default;
  WhileLetLoopExpr &operator= (WhileLetLoopExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_cond () { return condition; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  WhileLetLoopExpr *clone_expr_impl () const override
  {
    return new WhileLetLoopExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  WhileLetLoopExpr *clone_expr_with_block_impl () const override
  {
    return new WhileLetLoopExpr (*this);
  }
};

// For loop expression HIR node (iterator loop)
class ForLoopExpr : public BaseLoopExpr
{
  std::unique_ptr<Pattern> pattern;
  std::unique_ptr<Expr> iterator_expr;

public:
  std::string as_string () const override;

  // Constructor with loop label
  ForLoopExpr (Analysis::NodeMapping mappings,
	       std::unique_ptr<Pattern> loop_pattern,
	       std::unique_ptr<Expr> iterator_expr,
	       std::unique_ptr<BlockExpr> loop_body, Location locus,
	       LoopLabel loop_label,
	       AST::AttrVec outer_attribs = AST::AttrVec ())
    : BaseLoopExpr (std::move (mappings), std::move (loop_body), locus,
		    std::move (loop_label), std::move (outer_attribs)),
      pattern (std::move (loop_pattern)),
      iterator_expr (std::move (iterator_expr))
  {}

  // Copy constructor with clone
  ForLoopExpr (ForLoopExpr const &other)
    : BaseLoopExpr (other), pattern (other.pattern->clone_pattern ()),
      iterator_expr (other.iterator_expr->clone_expr ())
  {}

  // Overloaded assignment operator to clone
  ForLoopExpr &operator= (ForLoopExpr const &other)
  {
    BaseLoopExpr::operator= (other);
    pattern = other.pattern->clone_pattern ();
    iterator_expr = other.iterator_expr->clone_expr ();
    /*loop_block = other.loop_block->clone_block_expr();
    loop_label = other.loop_label;
    outer_attrs = other.outer_attrs;*/

    return *this;
  }

  // move constructors
  ForLoopExpr (ForLoopExpr &&other) = default;
  ForLoopExpr &operator= (ForLoopExpr &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_iterator_expr () { return iterator_expr; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ForLoopExpr *clone_expr_impl () const override
  {
    return new ForLoopExpr (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  ForLoopExpr *clone_expr_with_block_impl () const override
  {
    return new ForLoopExpr (*this);
  }
};

// forward decl for IfExpr
class IfLetExpr;

// Base if expression with no "else" or "if let" HIR node
class IfExpr : public ExprWithBlock
{
  std::unique_ptr<Expr> condition;
  std::unique_ptr<BlockExpr> if_block;

  Location locus;

public:
  std::string as_string () const override;

  IfExpr (Analysis::NodeMapping mappings, std::unique_ptr<Expr> condition,
	  std::unique_ptr<BlockExpr> if_block, Location locus)
    : ExprWithBlock (std::move (mappings), AST::AttrVec ()),
      condition (std::move (condition)), if_block (std::move (if_block)),
      locus (locus)
  {}
  // outer attributes are never allowed on IfExprs

  // Copy constructor with clone
  IfExpr (IfExpr const &other)
    : ExprWithBlock (other), condition (other.condition->clone_expr ()),
      if_block (other.if_block->clone_block_expr ()), locus (other.locus)
  {}

  // Overloaded assignment operator to clone expressions
  IfExpr &operator= (IfExpr const &other)
  {
    ExprWithBlock::operator= (other);
    condition = other.condition->clone_expr ();
    if_block = other.if_block->clone_block_expr ();
    locus = other.locus;

    return *this;
  }

  // move constructors
  IfExpr (IfExpr &&other) = default;
  IfExpr &operator= (IfExpr &&other) = default;

  // Unique pointer custom clone function
  std::unique_ptr<IfExpr> clone_if_expr () const
  {
    return std::unique_ptr<IfExpr> (clone_if_expr_impl ());
  }

  /* Note that multiple "else if"s are handled via nested HIRs rather than a
   * vector of else ifs - i.e. not like a switch statement. TODO - is this a
   * better approach? or does it not parse correctly and have downsides? */

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  void vis_if_condition (HIRFullVisitor &vis) { condition->accept_vis (vis); }
  void vis_if_block (HIRFullVisitor &vis) { if_block->accept_vis (vis); }

  Expr *get_if_condition () { return condition.get (); }
  BlockExpr *get_if_block () { return if_block.get (); }

  ExprType get_expression_type () const final override { return ExprType::If; }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExpr *clone_expr_impl () const override { return new IfExpr (*this); }

  // Base clone function but still concrete as concrete base class
  virtual IfExpr *clone_if_expr_impl () const { return new IfExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExpr *clone_expr_with_block_impl () const override
  {
    return new IfExpr (*this);
  }
};

// If expression with an ending "else" expression HIR node (trailing)
class IfExprConseqElse : public IfExpr
{
  std::unique_ptr<BlockExpr> else_block;

public:
  std::string as_string () const override;

  IfExprConseqElse (Analysis::NodeMapping mappings,
		    std::unique_ptr<Expr> condition,
		    std::unique_ptr<BlockExpr> if_block,
		    std::unique_ptr<BlockExpr> else_block, Location locus)
    : IfExpr (std::move (mappings), std::move (condition), std::move (if_block),
	      locus),
      else_block (std::move (else_block))
  {}
  // again, outer attributes not allowed

  // Copy constructor with clone
  IfExprConseqElse (IfExprConseqElse const &other)
    : IfExpr (other), else_block (other.else_block->clone_block_expr ())
  {}

  // Overloaded assignment operator with cloning
  IfExprConseqElse &operator= (IfExprConseqElse const &other)
  {
    IfExpr::operator= (other);
    // condition = other.condition->clone_expr();
    // if_block = other.if_block->clone_block_expr();
    else_block = other.else_block->clone_block_expr ();

    return *this;
  }

  // move constructors
  IfExprConseqElse (IfExprConseqElse &&other) = default;
  IfExprConseqElse &operator= (IfExprConseqElse &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  void vis_else_block (HIRFullVisitor &vis) { else_block->accept_vis (vis); }

  BlockExpr *get_else_block () { return else_block.get (); }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqElse *clone_expr_impl () const override
  {
    return new IfExprConseqElse (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqElse *clone_expr_with_block_impl () const override
  {
    return new IfExprConseqElse (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqElse *clone_if_expr_impl () const override
  {
    return new IfExprConseqElse (*this);
  }
};

// If expression with an ending "else if" expression HIR node
class IfExprConseqIf : public IfExpr
{
  std::unique_ptr<IfExpr> conseq_if_expr;

public:
  std::string as_string () const override;

  IfExprConseqIf (Analysis::NodeMapping mappings,
		  std::unique_ptr<Expr> condition,
		  std::unique_ptr<BlockExpr> if_block,
		  std::unique_ptr<IfExpr> conseq_if_expr, Location locus)
    : IfExpr (std::move (mappings), std::move (condition), std::move (if_block),
	      locus),
      conseq_if_expr (std::move (conseq_if_expr))
  {}
  // outer attributes not allowed

  // Copy constructor with clone
  IfExprConseqIf (IfExprConseqIf const &other)
    : IfExpr (other), conseq_if_expr (other.conseq_if_expr->clone_if_expr ())
  {}

  // Overloaded assignment operator to use clone
  IfExprConseqIf &operator= (IfExprConseqIf const &other)
  {
    IfExpr::operator= (other);
    // condition = other.condition->clone_expr();
    // if_block = other.if_block->clone_block_expr();
    conseq_if_expr = other.conseq_if_expr->clone_if_expr ();

    return *this;
  }

  // move constructors
  IfExprConseqIf (IfExprConseqIf &&other) = default;
  IfExprConseqIf &operator= (IfExprConseqIf &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  void vis_conseq_if_expr (HIRFullVisitor &vis)
  {
    conseq_if_expr->accept_vis (vis);
  }

  IfExpr *get_conseq_if_expr () { return conseq_if_expr.get (); }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqIf *clone_expr_impl () const override
  {
    return new IfExprConseqIf (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqIf *clone_expr_with_block_impl () const override
  {
    return new IfExprConseqIf (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqIf *clone_if_expr_impl () const override
  {
    return new IfExprConseqIf (*this);
  }
};

// Basic "if let" expression HIR node with no else
class IfLetExpr : public ExprWithBlock
{
  // MatchArmPatterns patterns;
  std::vector<std::unique_ptr<Pattern> > match_arm_patterns; // inlined
  std::unique_ptr<Expr> value;
  std::unique_ptr<BlockExpr> if_block;

  Location locus;

public:
  std::string as_string () const override;

  IfLetExpr (Analysis::NodeMapping mappings,
	     std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
	     std::unique_ptr<Expr> value, std::unique_ptr<BlockExpr> if_block,
	     Location locus)
    : ExprWithBlock (std::move (mappings), AST::AttrVec ()),
      match_arm_patterns (std::move (match_arm_patterns)),
      value (std::move (value)), if_block (std::move (if_block)), locus (locus)
  {}
  // outer attributes not allowed on if let exprs either

  // copy constructor with clone
  IfLetExpr (IfLetExpr const &other)
    : ExprWithBlock (other),
      /*match_arm_patterns(other.match_arm_patterns),*/ value (
	other.value->clone_expr ()),
      if_block (other.if_block->clone_block_expr ()), locus (other.locus)
  {
    match_arm_patterns.reserve (other.match_arm_patterns.size ());
    for (const auto &e : other.match_arm_patterns)
      match_arm_patterns.push_back (e->clone_pattern ());
  }

  // overload assignment operator to clone
  IfLetExpr &operator= (IfLetExpr const &other)
  {
    ExprWithBlock::operator= (other);
    // match_arm_patterns = other.match_arm_patterns;
    value = other.value->clone_expr ();
    if_block = other.if_block->clone_block_expr ();
    locus = other.locus;

    match_arm_patterns.reserve (other.match_arm_patterns.size ());
    for (const auto &e : other.match_arm_patterns)
      match_arm_patterns.push_back (e->clone_pattern ());

    return *this;
  }

  // move constructors
  IfLetExpr (IfLetExpr &&other) = default;
  IfLetExpr &operator= (IfLetExpr &&other) = default;

  // Unique pointer custom clone function
  std::unique_ptr<IfLetExpr> clone_if_let_expr () const
  {
    return std::unique_ptr<IfLetExpr> (clone_if_let_expr_impl ());
  }

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_scrutinee_expr ()
  {
    rust_assert (value != nullptr);
    return value;
  }

  std::vector<std::unique_ptr<Pattern> > &get_patterns ()
  {
    return match_arm_patterns;
  }

  BlockExpr *get_if_block () { return if_block.get (); }

  ExprType get_expression_type () const final override
  {
    return ExprType::IfLet;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExpr *clone_expr_impl () const override { return new IfLetExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExpr *clone_expr_with_block_impl () const override
  {
    return new IfLetExpr (*this);
  }

  // Base clone function but still concrete as concrete base class
  virtual IfLetExpr *clone_if_let_expr_impl () const
  {
    return new IfLetExpr (*this);
  }
};

// If expression with an ending "else if let" expression HIR node
class IfExprConseqIfLet : public IfExpr
{
  std::unique_ptr<IfLetExpr> if_let_expr;

public:
  std::string as_string () const override;

  IfExprConseqIfLet (Analysis::NodeMapping mappings,
		     std::unique_ptr<Expr> condition,
		     std::unique_ptr<BlockExpr> if_block,
		     std::unique_ptr<IfLetExpr> conseq_if_let_expr,
		     Location locus)
    : IfExpr (std::move (mappings), std::move (condition), std::move (if_block),
	      locus),
      if_let_expr (std::move (conseq_if_let_expr))
  {}
  // outer attributes not allowed

  // Copy constructor with clone
  IfExprConseqIfLet (IfExprConseqIfLet const &other)
    : IfExpr (other), if_let_expr (other.if_let_expr->clone_if_let_expr ())
  {}

  // Overloaded assignment operator to use clone
  IfExprConseqIfLet &operator= (IfExprConseqIfLet const &other)
  {
    IfExpr::operator= (other);
    // condition = other.condition->clone_expr();
    // if_block = other.if_block->clone_block_expr();
    if_let_expr = other.if_let_expr->clone_if_let_expr ();

    return *this;
  }

  // move constructors
  IfExprConseqIfLet (IfExprConseqIfLet &&other) = default;
  IfExprConseqIfLet &operator= (IfExprConseqIfLet &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqIfLet *clone_expr_impl () const override
  {
    return new IfExprConseqIfLet (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqIfLet *clone_expr_with_block_impl () const override
  {
    return new IfExprConseqIfLet (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfExprConseqIfLet *clone_if_expr_impl () const override
  {
    return new IfExprConseqIfLet (*this);
  }
};

/* HIR node representing "if let" expression with an "else" expression at the
 * end */
class IfLetExprConseqElse : public IfLetExpr
{
  std::unique_ptr<BlockExpr> else_block;

public:
  std::string as_string () const override;

  IfLetExprConseqElse (
    Analysis::NodeMapping mappings,
    std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
    std::unique_ptr<Expr> value, std::unique_ptr<BlockExpr> if_block,
    std::unique_ptr<BlockExpr> else_block, Location locus)
    : IfLetExpr (std::move (mappings), std::move (match_arm_patterns),
		 std::move (value), std::move (if_block), locus),
      else_block (std::move (else_block))
  {}
  // outer attributes not allowed

  // copy constructor with clone
  IfLetExprConseqElse (IfLetExprConseqElse const &other)
    : IfLetExpr (other), else_block (other.else_block->clone_block_expr ())
  {}

  // overload assignment operator to clone
  IfLetExprConseqElse &operator= (IfLetExprConseqElse const &other)
  {
    IfLetExpr::operator= (other);
    // match_arm_patterns = other.match_arm_patterns;
    // value = other.value->clone_expr();
    // if_block = other.if_block->clone_block_expr();
    else_block = other.else_block->clone_block_expr ();
    // outer_attrs = other.outer_attrs;

    return *this;
  }

  // move constructors
  IfLetExprConseqElse (IfLetExprConseqElse &&other) = default;
  IfLetExprConseqElse &operator= (IfLetExprConseqElse &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqElse *clone_expr_impl () const override
  {
    return new IfLetExprConseqElse (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqElse *clone_expr_with_block_impl () const override
  {
    return new IfLetExprConseqElse (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqElse *clone_if_let_expr_impl () const override
  {
    return new IfLetExprConseqElse (*this);
  }
};

/* HIR node representing "if let" expression with an "else if" expression at the
 * end */
class IfLetExprConseqIf : public IfLetExpr
{
  std::unique_ptr<IfExpr> if_expr;

public:
  std::string as_string () const override;

  IfLetExprConseqIf (Analysis::NodeMapping mappings,
		     std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
		     std::unique_ptr<Expr> value,
		     std::unique_ptr<BlockExpr> if_block,
		     std::unique_ptr<IfExpr> if_expr, Location locus)
    : IfLetExpr (std::move (mappings), std::move (match_arm_patterns),
		 std::move (value), std::move (if_block), locus),
      if_expr (std::move (if_expr))
  {}
  // again, outer attributes not allowed

  // copy constructor with clone
  IfLetExprConseqIf (IfLetExprConseqIf const &other)
    : IfLetExpr (other), if_expr (other.if_expr->clone_if_expr ())
  {}

  // overload assignment operator to clone
  IfLetExprConseqIf &operator= (IfLetExprConseqIf const &other)
  {
    IfLetExpr::operator= (other);
    // match_arm_patterns = other.match_arm_patterns;
    // value = other.value->clone_expr();
    // if_block = other.if_block->clone_block_expr();
    if_expr = other.if_expr->clone_if_expr ();

    return *this;
  }

  // move constructors
  IfLetExprConseqIf (IfLetExprConseqIf &&other) = default;
  IfLetExprConseqIf &operator= (IfLetExprConseqIf &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqIf *clone_expr_impl () const override
  {
    return new IfLetExprConseqIf (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqIf *clone_expr_with_block_impl () const override
  {
    return new IfLetExprConseqIf (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqIf *clone_if_let_expr_impl () const override
  {
    return new IfLetExprConseqIf (*this);
  }
};

/* HIR node representing "if let" expression with an "else if let" expression at
 * the end */
class IfLetExprConseqIfLet : public IfLetExpr
{
  std::unique_ptr<IfLetExpr> if_let_expr;

public:
  std::string as_string () const override;

  IfLetExprConseqIfLet (
    Analysis::NodeMapping mappings,
    std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
    std::unique_ptr<Expr> value, std::unique_ptr<BlockExpr> if_block,
    std::unique_ptr<IfLetExpr> if_let_expr, Location locus)
    : IfLetExpr (std::move (mappings), std::move (match_arm_patterns),
		 std::move (value), std::move (if_block), locus),
      if_let_expr (std::move (if_let_expr))
  {}
  // outer attributes not allowed

  // copy constructor with clone
  IfLetExprConseqIfLet (IfLetExprConseqIfLet const &other)
    : IfLetExpr (other), if_let_expr (other.if_let_expr->clone_if_let_expr ())
  {}

  // overload assignment operator to clone
  IfLetExprConseqIfLet &operator= (IfLetExprConseqIfLet const &other)
  {
    IfLetExpr::operator= (other);
    // match_arm_patterns = other.match_arm_patterns;
    // value = other.value->clone_expr();
    // if_block = other.if_block->clone_block_expr();
    if_let_expr = other.if_let_expr->clone_if_let_expr ();

    return *this;
  }

  // move constructors
  IfLetExprConseqIfLet (IfLetExprConseqIfLet &&other) = default;
  IfLetExprConseqIfLet &operator= (IfLetExprConseqIfLet &&other) = default;

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqIfLet *clone_expr_impl () const override
  {
    return new IfLetExprConseqIfLet (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqIfLet *clone_expr_with_block_impl () const override
  {
    return new IfLetExprConseqIfLet (*this);
  }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  IfLetExprConseqIfLet *clone_if_let_expr_impl () const override
  {
    return new IfLetExprConseqIfLet (*this);
  }
};

// Match arm expression
struct MatchArm
{
private:
  AST::AttrVec outer_attrs;
  std::vector<std::unique_ptr<Pattern> > match_arm_patterns;
  std::unique_ptr<Expr> guard_expr;
  Location locus;

public:
  // Returns whether the MatchArm has a match arm guard expression
  bool has_match_arm_guard () const { return guard_expr != nullptr; }

  // Constructor for match arm with a guard expression
  MatchArm (std::vector<std::unique_ptr<Pattern> > match_arm_patterns,
	    Location locus, std::unique_ptr<Expr> guard_expr = nullptr,
	    AST::AttrVec outer_attrs = AST::AttrVec ())
    : outer_attrs (std::move (outer_attrs)),
      match_arm_patterns (std::move (match_arm_patterns)),
      guard_expr (std::move (guard_expr)), locus (locus)
  {}

  // Copy constructor with clone
  MatchArm (MatchArm const &other) : outer_attrs (other.outer_attrs)
  {
    // guard to protect from null pointer dereference
    if (other.guard_expr != nullptr)
      guard_expr = other.guard_expr->clone_expr ();

    match_arm_patterns.reserve (other.match_arm_patterns.size ());
    for (const auto &e : other.match_arm_patterns)
      match_arm_patterns.push_back (e->clone_pattern ());

    locus = other.locus;
  }

  ~MatchArm () = default;

  // Overload assignment operator to clone
  MatchArm &operator= (MatchArm const &other)
  {
    outer_attrs = other.outer_attrs;

    if (other.guard_expr != nullptr)
      guard_expr = other.guard_expr->clone_expr ();

    match_arm_patterns.clear ();
    match_arm_patterns.reserve (other.match_arm_patterns.size ());
    for (const auto &e : other.match_arm_patterns)
      match_arm_patterns.push_back (e->clone_pattern ());

    return *this;
  }

  // move constructors
  MatchArm (MatchArm &&other) = default;
  MatchArm &operator= (MatchArm &&other) = default;

  // Returns whether match arm is in an error state.
  bool is_error () const { return match_arm_patterns.empty (); }

  // Creates a match arm in an error state.
  static MatchArm create_error ()
  {
    Location locus = Location ();
    return MatchArm (std::vector<std::unique_ptr<Pattern> > (), locus);
  }

  std::string as_string () const;

  std::vector<std::unique_ptr<Pattern> > &get_patterns ()
  {
    return match_arm_patterns;
  }

  std::unique_ptr<Expr> &get_guard_expr ()
  {
    rust_assert (has_match_arm_guard ());
    return guard_expr;
  }

  Location get_locus () const { return locus; }
};

/* A "match case" - a correlated match arm and resulting expression. Not
 * abstract. */
struct MatchCase
{
private:
  Analysis::NodeMapping mappings;
  MatchArm arm;
  std::unique_ptr<Expr> expr;

public:
  MatchCase (Analysis::NodeMapping mappings, MatchArm arm,
	     std::unique_ptr<Expr> expr)
    : mappings (mappings), arm (std::move (arm)), expr (std::move (expr))
  {}

  MatchCase (const MatchCase &other)
    : mappings (other.mappings), arm (other.arm),
      expr (other.expr->clone_expr ())
  {}

  MatchCase &operator= (const MatchCase &other)
  {
    mappings = other.mappings;
    arm = other.arm;
    expr = other.expr->clone_expr ();

    return *this;
  }

  MatchCase (MatchCase &&other) = default;
  MatchCase &operator= (MatchCase &&other) = default;

  ~MatchCase () = default;

  std::string as_string () const;

  Analysis::NodeMapping get_mappings () const { return mappings; }

  MatchArm &get_arm () { return arm; }
  std::unique_ptr<Expr> &get_expr () { return expr; }
};

// Match expression HIR node
class MatchExpr : public ExprWithBlock
{
  std::unique_ptr<Expr> branch_value;
  AST::AttrVec inner_attrs;
  std::vector<MatchCase> match_arms;
  Location locus;

public:
  std::string as_string () const override;

  bool has_match_arms () const { return !match_arms.empty (); }

  MatchExpr (Analysis::NodeMapping mappings, std::unique_ptr<Expr> branch_value,
	     std::vector<MatchCase> match_arms, AST::AttrVec inner_attrs,
	     AST::AttrVec outer_attrs, Location locus)
    : ExprWithBlock (std::move (mappings), std::move (outer_attrs)),
      branch_value (std::move (branch_value)),
      inner_attrs (std::move (inner_attrs)),
      match_arms (std::move (match_arms)), locus (locus)
  {}

  // Copy constructor requires clone due to unique_ptr
  MatchExpr (MatchExpr const &other)
    : ExprWithBlock (other), branch_value (other.branch_value->clone_expr ()),
      inner_attrs (other.inner_attrs), match_arms (other.match_arms),
      locus (other.locus)
  {
    /*match_arms.reserve (other.match_arms.size ());
    for (const auto &e : other.match_arms)
      match_arms.push_back (e->clone_match_case ());*/
  }

  // Overloaded assignment operator to clone due to unique_ptr
  MatchExpr &operator= (MatchExpr const &other)
  {
    ExprWithBlock::operator= (other);
    branch_value = other.branch_value->clone_expr ();
    inner_attrs = other.inner_attrs;
    match_arms = other.match_arms;
    // outer_attrs = other.outer_attrs;
    locus = other.locus;

    /*match_arms.reserve (other.match_arms.size ());
    for (const auto &e : other.match_arms)
      match_arms.push_back (e->clone_match_case ());*/

    return *this;
  }

  // move constructors
  MatchExpr (MatchExpr &&other) = default;
  MatchExpr &operator= (MatchExpr &&other) = default;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  std::unique_ptr<Expr> &get_scrutinee_expr ()
  {
    rust_assert (branch_value != nullptr);
    return branch_value;
  }

  const std::vector<MatchCase> &get_match_cases () const { return match_arms; }
  std::vector<MatchCase> &get_match_cases () { return match_arms; }

  ExprType get_expression_type () const final override
  {
    return ExprType::Match;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MatchExpr *clone_expr_impl () const override { return new MatchExpr (*this); }

  /* Use covariance to implement clone function as returning this object rather
   * than base */
  MatchExpr *clone_expr_with_block_impl () const override
  {
    return new MatchExpr (*this);
  }
};

// Await expression HIR node (pseudo-member variable access)
class AwaitExpr : public ExprWithoutBlock
{
  std::unique_ptr<Expr> awaited_expr;
  Location locus;

public:
  // TODO: ensure outer attributes are actually allowed
  AwaitExpr (Analysis::NodeMapping mappings, std::unique_ptr<Expr> awaited_expr,
	     AST::AttrVec outer_attrs, Location locus)
    : ExprWithoutBlock (std::move (mappings), std::move (outer_attrs)),
      awaited_expr (std::move (awaited_expr)), locus (locus)
  {}

  // copy constructor with clone
  AwaitExpr (AwaitExpr const &other)
    : ExprWithoutBlock (other),
      awaited_expr (other.awaited_expr->clone_expr ()), locus (other.locus)
  {}

  // overloaded assignment operator with clone
  AwaitExpr &operator= (AwaitExpr const &other)
  {
    ExprWithoutBlock::operator= (other);
    awaited_expr = other.awaited_expr->clone_expr ();
    locus = other.locus;

    return *this;
  }

  // move constructors
  AwaitExpr (AwaitExpr &&other) = default;
  AwaitExpr &operator= (AwaitExpr &&other) = default;

  std::string as_string () const override;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  ExprType get_expression_type () const final override
  {
    return ExprType::Await;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  AwaitExpr *clone_expr_without_block_impl () const override
  {
    return new AwaitExpr (*this);
  }
};

// Async block expression HIR node (block expr that evaluates to a future)
class AsyncBlockExpr : public ExprWithBlock
{
  bool has_move;
  std::unique_ptr<BlockExpr> block_expr;
  Location locus;

public:
  AsyncBlockExpr (Analysis::NodeMapping mappings,
		  std::unique_ptr<BlockExpr> block_expr, bool has_move,
		  AST::AttrVec outer_attrs, Location locus)
    : ExprWithBlock (std::move (mappings), std::move (outer_attrs)),
      has_move (has_move), block_expr (std::move (block_expr)), locus (locus)
  {}

  // copy constructor with clone
  AsyncBlockExpr (AsyncBlockExpr const &other)
    : ExprWithBlock (other), has_move (other.has_move),
      block_expr (other.block_expr->clone_block_expr ()), locus (other.locus)
  {}

  // overloaded assignment operator to clone
  AsyncBlockExpr &operator= (AsyncBlockExpr const &other)
  {
    ExprWithBlock::operator= (other);
    has_move = other.has_move;
    block_expr = other.block_expr->clone_block_expr ();
    locus = other.locus;

    return *this;
  }

  // move constructors
  AsyncBlockExpr (AsyncBlockExpr &&other) = default;
  AsyncBlockExpr &operator= (AsyncBlockExpr &&other) = default;

  std::string as_string () const override;

  Location get_locus () const override final { return locus; }

  void accept_vis (HIRFullVisitor &vis) override;
  void accept_vis (HIRExpressionVisitor &vis) override;

  ExprType get_expression_type () const final override
  {
    return ExprType::AsyncBlock;
  }

protected:
  /* Use covariance to implement clone function as returning this object rather
   * than base */
  AsyncBlockExpr *clone_expr_with_block_impl () const override
  {
    return new AsyncBlockExpr (*this);
  }
};

// this is a utility helper class for type-checking and code-generation
class OperatorExprMeta
{
public:
  OperatorExprMeta (HIR::CompoundAssignmentExpr &expr)
    : node_mappings (expr.get_mappings ()),
      lvalue_mappings (expr.get_expr ()->get_mappings ()),
      locus (expr.get_locus ())
  {}

  OperatorExprMeta (HIR::ArithmeticOrLogicalExpr &expr)
    : node_mappings (expr.get_mappings ()),
      lvalue_mappings (expr.get_expr ()->get_mappings ()),
      locus (expr.get_locus ())
  {}

  OperatorExprMeta (HIR::NegationExpr &expr)
    : node_mappings (expr.get_mappings ()),
      lvalue_mappings (expr.get_expr ()->get_mappings ()),
      locus (expr.get_locus ())
  {}

  OperatorExprMeta (HIR::DereferenceExpr &expr)
    : node_mappings (expr.get_mappings ()),
      lvalue_mappings (expr.get_expr ()->get_mappings ()),
      locus (expr.get_locus ())
  {}

  OperatorExprMeta (HIR::ArrayIndexExpr &expr)
    : node_mappings (expr.get_mappings ()),
      lvalue_mappings (expr.get_array_expr ()->get_mappings ()),
      locus (expr.get_locus ())
  {}

  const Analysis::NodeMapping &get_mappings () const { return node_mappings; }

  const Analysis::NodeMapping &get_lvalue_mappings () const
  {
    return lvalue_mappings;
  }

  Location get_locus () const { return locus; }

private:
  const Analysis::NodeMapping node_mappings;
  const Analysis::NodeMapping lvalue_mappings;
  Location locus;
};

} // namespace HIR
} // namespace Rust

#endif
