diff --git a/gcc/rust/expand/rust-attribute-visitor.cc b/gcc/rust/expand/rust-attribute-visitor.cc
new file mode 100644
index 00000000000..8016f9430eb
--- /dev/null
+++ b/gcc/rust/expand/rust-attribute-visitor.cc
@@ -0,0 +1,3445 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-attribute-visitor.h"
+#include "rust-session-manager.h"
+
+namespace Rust {
+
+// Visitor used to expand attributes.
+void
+AttrVisitor::expand_struct_fields (std::vector<AST::StructField> &fields)
+{
+  for (auto it = fields.begin (); it != fields.end ();)
+    {
+      auto &field = *it;
+
+      auto &field_attrs = field.get_outer_attrs ();
+      expander.expand_cfg_attrs (field_attrs);
+      if (expander.fails_cfg_with_expand (field_attrs))
+	{
+	  it = fields.erase (it);
+	  continue;
+	}
+
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      // expand sub-types of type, but can't strip type itself
+      auto &type = field.get_field_type ();
+      type->accept_vis (*this);
+
+      maybe_expand_type (type);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+
+      // if nothing else happens, increment
+      ++it;
+    }
+}
+
+void
+AttrVisitor::expand_tuple_fields (std::vector<AST::TupleField> &fields)
+{
+  for (auto it = fields.begin (); it != fields.end ();)
+    {
+      auto &field = *it;
+
+      auto &field_attrs = field.get_outer_attrs ();
+      expander.expand_cfg_attrs (field_attrs);
+      if (expander.fails_cfg_with_expand (field_attrs))
+	{
+	  it = fields.erase (it);
+	  continue;
+	}
+
+      // expand sub-types of type, but can't strip type itself
+      auto &type = field.get_field_type ();
+      type->accept_vis (*this);
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      // if nothing else happens, increment
+      ++it;
+    }
+}
+
+void
+AttrVisitor::expand_function_params (std::vector<AST::FunctionParam> &params)
+{
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  for (auto it = params.begin (); it != params.end ();)
+    {
+      auto &param = *it;
+
+      auto &param_attrs = param.get_outer_attrs ();
+      expander.expand_cfg_attrs (param_attrs);
+      if (expander.fails_cfg_with_expand (param_attrs))
+	{
+	  it = params.erase (it);
+	  continue;
+	}
+
+      // TODO: should an unwanted strip lead to break out of loop?
+      auto &pattern = param.get_pattern ();
+      pattern->accept_vis (*this);
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+
+      auto &type = param.get_type ();
+      type->accept_vis (*this);
+
+      maybe_expand_type (type);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      // increment
+      ++it;
+    }
+
+  expander.pop_context ();
+}
+
+void
+AttrVisitor::expand_generic_args (AST::GenericArgs &args)
+{
+  // lifetime args can't be expanded
+  // FIXME: Can we have macro invocations for lifetimes?
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  // expand type args - strip sub-types only
+  for (auto &arg : args.get_generic_args ())
+    {
+      switch (arg.get_kind ())
+	{
+	  case AST::GenericArg::Kind::Type: {
+	    auto &type = arg.get_type ();
+	    type->accept_vis (*this);
+	    maybe_expand_type (type);
+
+	    if (type->is_marked_for_strip ())
+	      rust_error_at (type->get_locus (),
+			     "cannot strip type in this position");
+	    break;
+	  }
+	  case AST::GenericArg::Kind::Const: {
+	    auto &expr = arg.get_expression ();
+	    expr->accept_vis (*this);
+	    maybe_expand_expr (expr);
+
+	    if (expr->is_marked_for_strip ())
+	      rust_error_at (expr->get_locus (),
+			     "cannot strip expression in this position");
+	    break;
+	  }
+	default:
+	  break;
+	  // FIXME: Figure out what to do here if there is ambiguity. Since the
+	  // resolver comes after the expansion, we need to figure out a way to
+	  // strip ambiguous values here
+	  // TODO: Arthur: Probably add a `mark_as_strip` method to `GenericArg`
+	  // or something. This would clean up this whole thing
+	}
+    }
+
+  expander.pop_context ();
+
+  // FIXME: Can we have macro invocations in generic type bindings?
+  // expand binding args - strip sub-types only
+  for (auto &binding : args.get_binding_args ())
+    {
+      auto &type = binding.get_type ();
+      type->accept_vis (*this);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+    }
+}
+
+void
+AttrVisitor::expand_qualified_path_type (AST::QualifiedPathType &path_type)
+{
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  auto &type = path_type.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  expander.pop_context ();
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  if (path_type.has_as_clause ())
+    {
+      auto &type_path = path_type.get_as_type_path ();
+      visit (type_path);
+      if (type_path.is_marked_for_strip ())
+	rust_error_at (type_path.get_locus (),
+		       "cannot strip type path in this position");
+    }
+}
+
+void
+AttrVisitor::AttrVisitor::expand_closure_params (
+  std::vector<AST::ClosureParam> &params)
+{
+  for (auto it = params.begin (); it != params.end ();)
+    {
+      auto &param = *it;
+
+      auto &param_attrs = param.get_outer_attrs ();
+      expander.expand_cfg_attrs (param_attrs);
+      if (expander.fails_cfg_with_expand (param_attrs))
+	{
+	  it = params.erase (it);
+	  continue;
+	}
+
+      auto &pattern = param.get_pattern ();
+      pattern->accept_vis (*this);
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+
+      if (param.has_type_given ())
+	{
+	  expander.push_context (MacroExpander::ContextType::TYPE);
+	  auto &type = param.get_type ();
+	  type->accept_vis (*this);
+
+	  maybe_expand_type (type);
+
+	  if (type->is_marked_for_strip ())
+	    rust_error_at (type->get_locus (),
+			   "cannot strip type in this position");
+
+	  expander.pop_context ();
+	}
+
+      // increment if found nothing else so far
+      ++it;
+    }
+}
+
+void
+AttrVisitor::expand_self_param (AST::SelfParam &self_param)
+{
+  if (self_param.has_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+      auto &type = self_param.get_type ();
+      type->accept_vis (*this);
+
+      maybe_expand_type (type);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+  /* TODO: maybe check for invariants being violated - e.g. both type and
+   * lifetime? */
+}
+
+void
+AttrVisitor::expand_where_clause (AST::WhereClause &where_clause)
+{
+  // items cannot be stripped conceptually, so just accept visitor
+  for (auto &item : where_clause.get_items ())
+    item->accept_vis (*this);
+}
+
+void
+AttrVisitor::expand_trait_function_decl (AST::TraitFunctionDecl &decl)
+{
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : decl.get_generic_params ())
+    param->accept_vis (*this);
+
+  /* strip function parameters if required - this is specifically
+   * allowed by spec */
+  expand_function_params (decl.get_function_params ());
+
+  if (decl.has_return_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &return_type = decl.get_return_type ();
+      return_type->accept_vis (*this);
+
+      maybe_expand_type (return_type);
+
+      if (return_type->is_marked_for_strip ())
+	rust_error_at (return_type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+
+  if (decl.has_where_clause ())
+    expand_where_clause (decl.get_where_clause ());
+}
+
+void
+AttrVisitor::expand_trait_method_decl (AST::TraitMethodDecl &decl)
+{
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : decl.get_generic_params ())
+    param->accept_vis (*this);
+
+  /* assuming you can't strip self param - wouldn't be a method
+   * anymore. spec allows outer attrs on self param, but doesn't
+   * specify whether cfg is used. */
+  expand_self_param (decl.get_self_param ());
+
+  /* strip function parameters if required - this is specifically
+   * allowed by spec */
+  expand_function_params (decl.get_function_params ());
+
+  if (decl.has_return_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &return_type = decl.get_return_type ();
+      return_type->accept_vis (*this);
+
+      maybe_expand_type (return_type);
+
+      if (return_type->is_marked_for_strip ())
+	rust_error_at (return_type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+
+  if (decl.has_where_clause ())
+    expand_where_clause (decl.get_where_clause ());
+}
+
+void
+AttrVisitor::visit (AST::Token &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::DelimTokenTree &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::AttrInputMetaItemContainer &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::IdentifierExpr &ident_expr)
+{
+  // strip test based on outer attrs
+  expander.expand_cfg_attrs (ident_expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (ident_expr.get_outer_attrs ()))
+    {
+      ident_expr.mark_for_strip ();
+      return;
+    }
+}
+void
+AttrVisitor::visit (AST::Lifetime &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::LifetimeParam &)
+{
+  // supposedly does not require - cfg does nothing
+}
+void
+AttrVisitor::visit (AST::ConstGenericParam &)
+{
+  // likewise
+}
+
+void
+AttrVisitor::visit (AST::MacroInvocation &macro_invoc)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (macro_invoc.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (macro_invoc.get_outer_attrs ()))
+    {
+      macro_invoc.mark_for_strip ();
+      return;
+    }
+
+  // can't strip simple path
+
+  // I don't think any macro token trees can be stripped in any way
+
+  // TODO: maybe have cfg! macro stripping behaviour here?
+  expander.expand_invoc (macro_invoc, macro_invoc.has_semicolon ());
+}
+
+void
+AttrVisitor::visit (AST::PathInExpression &path)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (path.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (path.get_outer_attrs ()))
+    {
+      path.mark_for_strip ();
+      return;
+    }
+
+  for (auto &segment : path.get_segments ())
+    {
+      if (segment.has_generic_args ())
+	expand_generic_args (segment.get_generic_args ());
+    }
+}
+void
+AttrVisitor::visit (AST::TypePathSegment &)
+{
+  // shouldn't require
+}
+void
+AttrVisitor::visit (AST::TypePathSegmentGeneric &segment)
+{
+  // TODO: strip inside generic args
+
+  if (!segment.has_generic_args ())
+    return;
+
+  expand_generic_args (segment.get_generic_args ());
+}
+void
+AttrVisitor::visit (AST::TypePathSegmentFunction &segment)
+{
+  auto &type_path_function = segment.get_type_path_function ();
+
+  for (auto &type : type_path_function.get_params ())
+    {
+      type->accept_vis (*this);
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+    }
+
+  if (type_path_function.has_return_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &return_type = type_path_function.get_return_type ();
+      return_type->accept_vis (*this);
+
+      maybe_expand_type (return_type);
+
+      if (return_type->is_marked_for_strip ())
+	rust_error_at (return_type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+}
+void
+AttrVisitor::visit (AST::TypePath &path)
+{
+  // this shouldn't strip any segments, but can strip inside them
+  for (auto &segment : path.get_segments ())
+    segment->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::QualifiedPathInExpression &path)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (path.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (path.get_outer_attrs ()))
+    {
+      path.mark_for_strip ();
+      return;
+    }
+
+  expand_qualified_path_type (path.get_qualified_path_type ());
+
+  for (auto &segment : path.get_segments ())
+    {
+      if (segment.has_generic_args ())
+	expand_generic_args (segment.get_generic_args ());
+    }
+}
+void
+AttrVisitor::visit (AST::QualifiedPathInType &path)
+{
+  expand_qualified_path_type (path.get_qualified_path_type ());
+
+  // this shouldn't strip any segments, but can strip inside them
+  for (auto &segment : path.get_segments ())
+    segment->accept_vis (*this);
+}
+
+void
+AttrVisitor::visit (AST::LiteralExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+}
+void
+AttrVisitor::visit (AST::AttrInputLiteral &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::MetaItemLitExpr &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::MetaItemPathLit &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::BorrowExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &borrowed_expr = expr.get_borrowed_expr ();
+  borrowed_expr->accept_vis (*this);
+  if (borrowed_expr->is_marked_for_strip ())
+    rust_error_at (borrowed_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::DereferenceExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &dereferenced_expr = expr.get_dereferenced_expr ();
+  dereferenced_expr->accept_vis (*this);
+  if (dereferenced_expr->is_marked_for_strip ())
+    rust_error_at (dereferenced_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ErrorPropagationExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &propagating_expr = expr.get_propagating_expr ();
+  propagating_expr->accept_vis (*this);
+  if (propagating_expr->is_marked_for_strip ())
+    rust_error_at (propagating_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::NegationExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &negated_expr = expr.get_negated_expr ();
+  negated_expr->accept_vis (*this);
+  if (negated_expr->is_marked_for_strip ())
+    rust_error_at (negated_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ArithmeticOrLogicalExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * two direct descendant expressions, can strip ones below that */
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  auto &l_expr = expr.get_left_expr ();
+  l_expr->accept_vis (*this);
+  maybe_expand_expr (l_expr);
+
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  auto &r_expr = expr.get_right_expr ();
+  r_expr->accept_vis (*this);
+  maybe_expand_expr (r_expr);
+
+  // ensure that they are not marked for strip
+  if (expr.get_left_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_left_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed "
+		   "before binary op exprs");
+  if (expr.get_right_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_right_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ComparisonExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * two direct descendant expressions, can strip ones below that */
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  auto &l_expr = expr.get_left_expr ();
+  l_expr->accept_vis (*this);
+  maybe_expand_expr (l_expr);
+
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  auto &r_expr = expr.get_right_expr ();
+  r_expr->accept_vis (*this);
+  maybe_expand_expr (r_expr);
+
+  // ensure that they are not marked for strip
+  if (expr.get_left_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_left_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed "
+		   "before binary op exprs");
+  if (expr.get_right_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_right_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::LazyBooleanExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * two direct descendant expressions, can strip ones below that */
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  auto &l_expr = expr.get_left_expr ();
+  l_expr->accept_vis (*this);
+  maybe_expand_expr (l_expr);
+
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  auto &r_expr = expr.get_right_expr ();
+  r_expr->accept_vis (*this);
+  maybe_expand_expr (r_expr);
+
+  // ensure that they are not marked for strip
+  if (expr.get_left_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_left_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed "
+		   "before binary op exprs");
+  if (expr.get_right_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_right_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::TypeCastExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * direct descendant expression, can strip ones below that */
+
+  auto &casted_expr = expr.get_casted_expr ();
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  casted_expr->accept_vis (*this);
+
+  // ensure that they are not marked for strip
+  if (casted_expr->is_marked_for_strip ())
+    rust_error_at (casted_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed before cast exprs");
+
+  // TODO: strip sub-types of type
+  auto &type = expr.get_type_to_cast_to ();
+  type->accept_vis (*this);
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+}
+void
+AttrVisitor::visit (AST::AssignmentExpr &expr)
+{
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  auto &l_expr = expr.get_left_expr ();
+  l_expr->accept_vis (*this);
+  maybe_expand_expr (l_expr);
+
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  auto &r_expr = expr.get_right_expr ();
+  r_expr->accept_vis (*this);
+  maybe_expand_expr (r_expr);
+
+  // ensure that they are not marked for strip
+  if (expr.get_left_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_left_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed "
+		   "before binary op exprs");
+  if (expr.get_right_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_right_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::CompoundAssignmentExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * two direct descendant expressions, can strip ones below that */
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  auto &l_expr = expr.get_left_expr ();
+  l_expr->accept_vis (*this);
+  maybe_expand_expr (l_expr);
+
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  auto &r_expr = expr.get_right_expr ();
+  r_expr->accept_vis (*this);
+  maybe_expand_expr (r_expr);
+
+  // ensure that they are not marked for strip
+  if (expr.get_left_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_left_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed "
+		   "before binary op exprs");
+  if (expr.get_right_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_right_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::GroupedExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip test based on inner attrs - spec says these are inner
+   * attributes, not outer attributes of inner expr */
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &inner_expr = expr.get_expr_in_parens ();
+  inner_expr->accept_vis (*this);
+  if (inner_expr->is_marked_for_strip ())
+    rust_error_at (inner_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ArrayElemsValues &elems)
+{
+  /* apparently outer attributes are allowed in "elements of array
+   * expressions" according to spec */
+  expand_pointer_allow_strip (elems.get_values ());
+}
+void
+AttrVisitor::visit (AST::ArrayElemsCopied &elems)
+{
+  /* apparently outer attributes are allowed in "elements of array
+   * expressions" according to spec. on the other hand, it would not
+   * make conceptual sense to be able to remove either expression. As
+   * such, not implementing. TODO clear up the ambiguity here */
+
+  // only intend stripping for internal sub-expressions
+  auto &copied_expr = elems.get_elem_to_copy ();
+  copied_expr->accept_vis (*this);
+  if (copied_expr->is_marked_for_strip ())
+    rust_error_at (copied_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  auto &copy_count = elems.get_num_copies ();
+  copy_count->accept_vis (*this);
+  if (copy_count->is_marked_for_strip ())
+    rust_error_at (copy_count->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ArrayExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip test based on inner attrs - spec says there are separate
+   * inner attributes, not just outer attributes of inner exprs */
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* assuming you can't strip away the ArrayElems type, but can strip
+   * internal expressions and whatever */
+  expr.get_array_elems ()->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::ArrayIndexExpr &expr)
+{
+  /* it is unclear whether outer attributes are supposed to be
+   * allowed, but conceptually it wouldn't make much sense, but
+   * having expansion code anyway. TODO */
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &array_expr = expr.get_array_expr ();
+  array_expr->accept_vis (*this);
+  if (array_expr->is_marked_for_strip ())
+    rust_error_at (array_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  auto &index_expr = expr.get_index_expr ();
+  index_expr->accept_vis (*this);
+  if (index_expr->is_marked_for_strip ())
+    rust_error_at (index_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::TupleExpr &expr)
+{
+  /* according to spec, outer attributes are allowed on "elements of
+   * tuple expressions" */
+
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip test based on inner attrs - spec says these are inner
+   * attributes, not outer attributes of inner expr */
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* apparently outer attributes are allowed in "elements of tuple
+   * expressions" according to spec */
+  expand_pointer_allow_strip (expr.get_tuple_elems ());
+}
+void
+AttrVisitor::visit (AST::TupleIndexExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* wouldn't strip this directly (as outer attrs should be
+   * associated with this level), but any sub-expressions would be
+   * stripped. Thus, no need to erase when strip check called. */
+  auto &tuple_expr = expr.get_tuple_expr ();
+  tuple_expr->accept_vis (*this);
+  if (tuple_expr->is_marked_for_strip ())
+    rust_error_at (tuple_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::StructExprStruct &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip test based on inner attrs - spec says these are inner
+   * attributes, not outer attributes of inner expr */
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // strip sub-exprs of path
+  auto &struct_name = expr.get_struct_name ();
+  visit (struct_name);
+  if (struct_name.is_marked_for_strip ())
+    rust_error_at (struct_name.get_locus (),
+		   "cannot strip path in this position");
+}
+void
+AttrVisitor::visit (AST::StructExprFieldIdentifier &)
+{
+  // as no attrs (at moment, at least), no stripping possible
+}
+void
+AttrVisitor::visit (AST::StructExprFieldIdentifierValue &field)
+{
+  /* as no attrs possible (at moment, at least), only sub-expression
+   * stripping is possible */
+  auto &value = field.get_value ();
+  value->accept_vis (*this);
+  if (value->is_marked_for_strip ())
+    rust_error_at (value->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::StructExprFieldIndexValue &field)
+{
+  /* as no attrs possible (at moment, at least), only sub-expression
+   * stripping is possible */
+  auto &value = field.get_value ();
+  value->accept_vis (*this);
+  if (value->is_marked_for_strip ())
+    rust_error_at (value->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::StructExprStructFields &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip test based on inner attrs - spec says these are inner
+   * attributes, not outer attributes of inner expr */
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // strip sub-exprs of path
+  auto &struct_name = expr.get_struct_name ();
+  visit (struct_name);
+  if (struct_name.is_marked_for_strip ())
+    rust_error_at (struct_name.get_locus (),
+		   "cannot strip path in this position");
+
+  /* spec does not specify whether expressions are allowed to be
+   * stripped at top level of struct fields, but I wouldn't think
+   * that they would be, so operating under the assumption that only
+   * sub-expressions can be stripped. */
+  for (auto &field : expr.get_fields ())
+    {
+      field->accept_vis (*this);
+      // shouldn't strip in this
+    }
+
+  /* struct base presumably can't be stripped, as the '..' is before
+   * the expression. as such, can only strip sub-expressions. */
+  if (expr.has_struct_base ())
+    {
+      auto &base_struct_expr = expr.get_struct_base ().get_base_struct ();
+      base_struct_expr->accept_vis (*this);
+      if (base_struct_expr->is_marked_for_strip ())
+	rust_error_at (base_struct_expr->get_locus (),
+		       "cannot strip expression in this position - outer "
+		       "attributes not allowed");
+    }
+}
+void
+AttrVisitor::visit (AST::StructExprStructBase &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip test based on inner attrs - spec says these are inner
+   * attributes, not outer attributes of inner expr */
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // strip sub-exprs of path
+  auto &struct_name = expr.get_struct_name ();
+  visit (struct_name);
+  if (struct_name.is_marked_for_strip ())
+    rust_error_at (struct_name.get_locus (),
+		   "cannot strip path in this position");
+
+  /* struct base presumably can't be stripped, as the '..' is before
+   * the expression. as such, can only strip sub-expressions. */
+  rust_assert (!expr.get_struct_base ().is_invalid ());
+  auto &base_struct_expr = expr.get_struct_base ().get_base_struct ();
+  base_struct_expr->accept_vis (*this);
+  if (base_struct_expr->is_marked_for_strip ())
+    rust_error_at (base_struct_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::CallExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* should not be outer attrs on "function" expression - outer attrs
+   * should be associated with call expr as a whole. only sub-expr
+   * expansion is possible. */
+  auto &function = expr.get_function_expr ();
+  function->accept_vis (*this);
+  if (function->is_marked_for_strip ())
+    rust_error_at (function->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  /* spec says outer attributes are specifically allowed for elements
+   * of call expressions, so full stripping possible */
+  // FIXME: Arthur: Figure out how to refactor this - This is similar to
+  // expanding items in the crate or stmts in blocks
+  expand_pointer_allow_strip (expr.get_params ());
+  auto &params = expr.get_params ();
+  for (auto it = params.begin (); it != params.end ();)
+    {
+      auto &stmt = *it;
+
+      stmt->accept_vis (*this);
+
+      auto final_fragment = expand_macro_fragment_recursive ();
+      if (final_fragment.should_expand ())
+	{
+	  // Remove the current expanded invocation
+	  it = params.erase (it);
+	  for (auto &node : final_fragment.get_nodes ())
+	    {
+	      it = params.insert (it, node.take_expr ());
+	      it++;
+	    }
+	}
+      else if (stmt->is_marked_for_strip ())
+	it = params.erase (it);
+      else
+	it++;
+    }
+}
+void
+AttrVisitor::visit (AST::MethodCallExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* should not be outer attrs on "receiver" expression - outer attrs
+   * should be associated with call expr as a whole. only sub-expr
+   * expansion is possible. */
+  auto &receiver = expr.get_receiver_expr ();
+  receiver->accept_vis (*this);
+  if (receiver->is_marked_for_strip ())
+    rust_error_at (receiver->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  auto &method_name = expr.get_method_name ();
+  if (method_name.has_generic_args ())
+    expand_generic_args (method_name.get_generic_args ());
+
+  /* spec says outer attributes are specifically allowed for elements
+   * of method call expressions, so full stripping possible */
+  expand_pointer_allow_strip (expr.get_params ());
+}
+void
+AttrVisitor::visit (AST::FieldAccessExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* should not be outer attrs on "receiver" expression - outer attrs
+   * should be associated with field expr as a whole. only sub-expr
+   * expansion is possible. */
+  auto &receiver = expr.get_receiver_expr ();
+  receiver->accept_vis (*this);
+  if (receiver->is_marked_for_strip ())
+    rust_error_at (receiver->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ClosureExprInner &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip closure parameters if required - this is specifically
+   * allowed by spec */
+  expand_closure_params (expr.get_params ());
+
+  // can't strip expression itself, but can strip sub-expressions
+  auto &definition_expr = expr.get_definition_expr ();
+  definition_expr->accept_vis (*this);
+  if (definition_expr->is_marked_for_strip ())
+    rust_error_at (definition_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+
+void
+AttrVisitor::visit (AST::BlockExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip test based on inner attrs - spec says there are inner
+   * attributes, not just outer attributes of inner stmts */
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  std::function<std::unique_ptr<AST::Stmt> (AST::SingleASTNode)> extractor
+    = [] (AST::SingleASTNode node) { return node.take_stmt (); };
+
+  expand_macro_children (MacroExpander::BLOCK, expr.get_statements (),
+			 extractor);
+
+  expander.push_context (MacroExpander::BLOCK);
+
+  // strip tail expression if exists - can actually fully remove it
+  if (expr.has_tail_expr ())
+    {
+      auto &tail_expr = expr.get_tail_expr ();
+
+      tail_expr->accept_vis (*this);
+      maybe_expand_expr (tail_expr);
+
+      if (tail_expr->is_marked_for_strip ())
+	expr.strip_tail_expr ();
+    }
+  expander.pop_context ();
+}
+
+void
+AttrVisitor::visit (AST::ClosureExprInnerTyped &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* strip closure parameters if required - this is specifically
+   * allowed by spec */
+  expand_closure_params (expr.get_params ());
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  // can't strip return type, but can strip sub-types
+  auto &type = expr.get_return_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+
+  // can't strip expression itself, but can strip sub-expressions
+  auto &definition_block = expr.get_definition_block ();
+  definition_block->accept_vis (*this);
+  if (definition_block->is_marked_for_strip ())
+    rust_error_at (definition_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ContinueExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+}
+void
+AttrVisitor::visit (AST::BreakExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* spec does not say that you can have outer attributes on
+   * expression, so assuming you can't. stripping for sub-expressions
+   * is the only thing that can be done */
+  if (expr.has_break_expr ())
+    {
+      auto &break_expr = expr.get_break_expr ();
+
+      break_expr->accept_vis (*this);
+
+      if (break_expr->is_marked_for_strip ())
+	rust_error_at (break_expr->get_locus (),
+		       "cannot strip expression in this position - outer "
+		       "attributes not allowed");
+    }
+}
+void
+AttrVisitor::visit (AST::RangeFromToExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * two direct descendant expressions, can strip ones below that */
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  expr.get_from_expr ()->accept_vis (*this);
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  expr.get_to_expr ()->accept_vis (*this);
+
+  // ensure that they are not marked for strip
+  if (expr.get_from_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_from_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed "
+		   "before range exprs");
+  if (expr.get_to_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_to_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::RangeFromExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * direct descendant expression, can strip ones below that */
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  auto &from_expr = expr.get_from_expr ();
+
+  from_expr->accept_vis (*this);
+
+  if (from_expr->is_marked_for_strip ())
+    rust_error_at (from_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed before range exprs");
+}
+void
+AttrVisitor::visit (AST::RangeToExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * direct descendant expression, can strip ones below that */
+
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  auto &to_expr = expr.get_to_expr ();
+
+  to_expr->accept_vis (*this);
+
+  if (to_expr->is_marked_for_strip ())
+    rust_error_at (to_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::RangeFullExpr &)
+{
+  // outer attributes never allowed before these, so no stripping
+}
+void
+AttrVisitor::visit (AST::RangeFromToInclExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * two direct descendant expressions, can strip ones below that */
+
+  /* should have no possibility for outer attrs as would be parsed
+   * with outer expr */
+  expr.get_from_expr ()->accept_vis (*this);
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  expr.get_to_expr ()->accept_vis (*this);
+
+  // ensure that they are not marked for strip
+  if (expr.get_from_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_from_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes are never allowed "
+		   "before range exprs");
+  if (expr.get_to_expr ()->is_marked_for_strip ())
+    rust_error_at (expr.get_to_expr ()->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::RangeToInclExpr &expr)
+{
+  /* outer attributes never allowed before these. while cannot strip
+   * direct descendant expression, can strip ones below that */
+
+  /* should syntactically not have outer attributes, though this may
+   * not have worked in practice */
+  auto &to_expr = expr.get_to_expr ();
+
+  to_expr->accept_vis (*this);
+
+  if (to_expr->is_marked_for_strip ())
+    rust_error_at (to_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ReturnExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* spec does not say that you can have outer attributes on
+   * expression, so assuming you can't. stripping for sub-expressions
+   * is the only thing that can be done */
+  if (expr.has_returned_expr ())
+    {
+      auto &returned_expr = expr.get_returned_expr ();
+
+      returned_expr->accept_vis (*this);
+
+      if (returned_expr->is_marked_for_strip ())
+	rust_error_at (returned_expr->get_locus (),
+		       "cannot strip expression in this position - outer "
+		       "attributes not allowed");
+    }
+  /* TODO: conceptually, you would maybe be able to remove a returned
+   * expr - e.g. if you had conditional compilation returning void or
+   * returning a type. On the other hand, I think that function
+   * return type cannot be conditionally compiled, so I assumed you
+   * can't do this either. */
+}
+void
+AttrVisitor::visit (AST::UnsafeBlockExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip block itself, but can strip sub-expressions
+  auto &block_expr = expr.get_block_expr ();
+  block_expr->accept_vis (*this);
+  if (block_expr->is_marked_for_strip ())
+    rust_error_at (block_expr->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::LoopExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip block itself, but can strip sub-expressions
+  auto &loop_block = expr.get_loop_block ();
+  loop_block->accept_vis (*this);
+  if (loop_block->is_marked_for_strip ())
+    rust_error_at (loop_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::WhileLoopExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip predicate expr itself, but can strip sub-expressions
+  auto &predicate_expr = expr.get_predicate_expr ();
+  predicate_expr->accept_vis (*this);
+  if (predicate_expr->is_marked_for_strip ())
+    rust_error_at (predicate_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip block itself, but can strip sub-expressions
+  auto &loop_block = expr.get_loop_block ();
+  loop_block->accept_vis (*this);
+  if (loop_block->is_marked_for_strip ())
+    rust_error_at (loop_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::WhileLetLoopExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  for (auto &pattern : expr.get_patterns ())
+    {
+      pattern->accept_vis (*this);
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+    }
+
+  // can't strip scrutinee expr itself, but can strip sub-expressions
+  auto &scrutinee_expr = expr.get_scrutinee_expr ();
+  scrutinee_expr->accept_vis (*this);
+  if (scrutinee_expr->is_marked_for_strip ())
+    rust_error_at (scrutinee_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip block itself, but can strip sub-expressions
+  auto &loop_block = expr.get_loop_block ();
+  loop_block->accept_vis (*this);
+  if (loop_block->is_marked_for_strip ())
+    rust_error_at (loop_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::ForLoopExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // strip sub-patterns of pattern
+  auto &pattern = expr.get_pattern ();
+  pattern->accept_vis (*this);
+  if (pattern->is_marked_for_strip ())
+    rust_error_at (pattern->get_locus (),
+		   "cannot strip pattern in this position");
+
+  // can't strip scrutinee expr itself, but can strip sub-expressions
+  auto &iterator_expr = expr.get_iterator_expr ();
+  iterator_expr->accept_vis (*this);
+  if (iterator_expr->is_marked_for_strip ())
+    rust_error_at (iterator_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip block itself, but can strip sub-expressions
+  auto &loop_block = expr.get_loop_block ();
+  loop_block->accept_vis (*this);
+  if (loop_block->is_marked_for_strip ())
+    rust_error_at (loop_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::IfExpr &expr)
+{
+  // rust playground test shows that IfExpr does support outer attrs, at least
+  // when used as statement
+
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip condition expr itself, but can strip sub-expressions
+  auto &condition_expr = expr.get_condition_expr ();
+  condition_expr->accept_vis (*this);
+  maybe_expand_expr (condition_expr);
+  if (condition_expr->is_marked_for_strip ())
+    rust_error_at (condition_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::IfExprConseqElse &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip condition expr itself, but can strip sub-expressions
+  auto &condition_expr = expr.get_condition_expr ();
+  condition_expr->accept_vis (*this);
+  maybe_expand_expr (condition_expr);
+  if (condition_expr->is_marked_for_strip ())
+    rust_error_at (condition_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip else block itself, but can strip sub-expressions
+  auto &else_block = expr.get_else_block ();
+  else_block->accept_vis (*this);
+  if (else_block->is_marked_for_strip ())
+    rust_error_at (else_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::IfExprConseqIf &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip condition expr itself, but can strip sub-expressions
+  auto &condition_expr = expr.get_condition_expr ();
+  condition_expr->accept_vis (*this);
+  maybe_expand_expr (condition_expr);
+  if (condition_expr->is_marked_for_strip ())
+    rust_error_at (condition_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if expr itself, but can strip sub-expressions
+  auto &conseq_if_expr = expr.get_conseq_if_expr ();
+  conseq_if_expr->accept_vis (*this);
+  if (conseq_if_expr->is_marked_for_strip ())
+    rust_error_at (conseq_if_expr->get_locus (),
+		   "cannot strip consequent if expression in this "
+		   "position - outer attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::IfExprConseqIfLet &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip condition expr itself, but can strip sub-expressions
+  auto &condition_expr = expr.get_condition_expr ();
+  condition_expr->accept_vis (*this);
+  maybe_expand_expr (condition_expr);
+  if (condition_expr->is_marked_for_strip ())
+    rust_error_at (condition_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if let expr itself, but can strip sub-expressions
+  auto &conseq_if_let_expr = expr.get_conseq_if_let_expr ();
+  conseq_if_let_expr->accept_vis (*this);
+  if (conseq_if_let_expr->is_marked_for_strip ())
+    rust_error_at (conseq_if_let_expr->get_locus (),
+		   "cannot strip consequent if let expression in this "
+		   "position - outer attributes not "
+		   "allowed");
+}
+void
+AttrVisitor::visit (AST::IfLetExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  for (auto &pattern : expr.get_patterns ())
+    {
+      pattern->accept_vis (*this);
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+    }
+
+  // can't strip value expr itself, but can strip sub-expressions
+  auto &value_expr = expr.get_value_expr ();
+  value_expr->accept_vis (*this);
+  if (value_expr->is_marked_for_strip ())
+    rust_error_at (value_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::IfLetExprConseqElse &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  for (auto &pattern : expr.get_patterns ())
+    {
+      pattern->accept_vis (*this);
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+    }
+
+  // can't strip value expr itself, but can strip sub-expressions
+  auto &value_expr = expr.get_value_expr ();
+  value_expr->accept_vis (*this);
+  if (value_expr->is_marked_for_strip ())
+    rust_error_at (value_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip else block itself, but can strip sub-expressions
+  auto &else_block = expr.get_else_block ();
+  else_block->accept_vis (*this);
+  if (else_block->is_marked_for_strip ())
+    rust_error_at (else_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::IfLetExprConseqIf &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  for (auto &pattern : expr.get_patterns ())
+    {
+      pattern->accept_vis (*this);
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+    }
+
+  // can't strip value expr itself, but can strip sub-expressions
+  auto &value_expr = expr.get_value_expr ();
+  value_expr->accept_vis (*this);
+  if (value_expr->is_marked_for_strip ())
+    rust_error_at (value_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if expr itself, but can strip sub-expressions
+  auto &conseq_if_expr = expr.get_conseq_if_expr ();
+  conseq_if_expr->accept_vis (*this);
+  if (conseq_if_expr->is_marked_for_strip ())
+    rust_error_at (conseq_if_expr->get_locus (),
+		   "cannot strip consequent if expression in this "
+		   "position - outer attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::IfLetExprConseqIfLet &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  for (auto &pattern : expr.get_patterns ())
+    {
+      pattern->accept_vis (*this);
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+    }
+
+  // can't strip value expr itself, but can strip sub-expressions
+  auto &value_expr = expr.get_value_expr ();
+  value_expr->accept_vis (*this);
+  if (value_expr->is_marked_for_strip ())
+    rust_error_at (value_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if block itself, but can strip sub-expressions
+  auto &if_block = expr.get_if_block ();
+  if_block->accept_vis (*this);
+  if (if_block->is_marked_for_strip ())
+    rust_error_at (if_block->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+
+  // can't strip if let expr itself, but can strip sub-expressions
+  auto &conseq_if_let_expr = expr.get_conseq_if_let_expr ();
+  conseq_if_let_expr->accept_vis (*this);
+  if (conseq_if_let_expr->is_marked_for_strip ())
+    rust_error_at (conseq_if_let_expr->get_locus (),
+		   "cannot strip consequent if let expression in this "
+		   "position - outer attributes not "
+		   "allowed");
+}
+void
+AttrVisitor::visit (AST::MatchExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // inner attr strip test
+  expander.expand_cfg_attrs (expr.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_inner_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip scrutinee expr itself, but can strip sub-expressions
+  auto &scrutinee_expr = expr.get_scrutinee_expr ();
+  scrutinee_expr->accept_vis (*this);
+  if (scrutinee_expr->is_marked_for_strip ())
+    rust_error_at (scrutinee_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+
+  // strip match cases
+  auto &match_cases = expr.get_match_cases ();
+  for (auto it = match_cases.begin (); it != match_cases.end ();)
+    {
+      auto &match_case = *it;
+
+      // strip match case based on outer attributes in match arm
+      auto &match_arm = match_case.get_arm ();
+      expander.expand_cfg_attrs (match_arm.get_outer_attrs ());
+      if (expander.fails_cfg_with_expand (match_arm.get_outer_attrs ()))
+	{
+	  // strip match case
+	  it = match_cases.erase (it);
+	  continue;
+	}
+
+      for (auto &pattern : match_arm.get_patterns ())
+	{
+	  pattern->accept_vis (*this);
+	  if (pattern->is_marked_for_strip ())
+	    rust_error_at (pattern->get_locus (),
+			   "cannot strip pattern in this position");
+	}
+
+      /* assuming that guard expression cannot be stripped as
+       * strictly speaking you would have to strip the whole guard to
+       * make syntactical sense, which you can't do. as such, only
+       * strip sub-expressions */
+      if (match_arm.has_match_arm_guard ())
+	{
+	  auto &guard_expr = match_arm.get_guard_expr ();
+	  guard_expr->accept_vis (*this);
+	  if (guard_expr->is_marked_for_strip ())
+	    rust_error_at (guard_expr->get_locus (),
+			   "cannot strip expression in this position - outer "
+			   "attributes not allowed");
+	}
+
+      // strip sub-expressions from match cases
+      auto &case_expr = match_case.get_expr ();
+      case_expr->accept_vis (*this);
+      if (case_expr->is_marked_for_strip ())
+	rust_error_at (case_expr->get_locus (),
+		       "cannot strip expression in this position - outer "
+		       "attributes not allowed");
+
+      // increment to next case if haven't continued
+      ++it;
+    }
+}
+void
+AttrVisitor::visit (AST::AwaitExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  /* can't strip awaited expr itself, but can strip sub-expressions
+   * - this is because you can't have no expr to await */
+  auto &awaited_expr = expr.get_awaited_expr ();
+  awaited_expr->accept_vis (*this);
+  if (awaited_expr->is_marked_for_strip ())
+    rust_error_at (awaited_expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::AsyncBlockExpr &expr)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (expr.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (expr.get_outer_attrs ()))
+    {
+      expr.mark_for_strip ();
+      return;
+    }
+
+  // can't strip block itself, but can strip sub-expressions
+  auto &block_expr = expr.get_block_expr ();
+  block_expr->accept_vis (*this);
+  if (block_expr->is_marked_for_strip ())
+    rust_error_at (block_expr->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+
+void
+AttrVisitor::visit (AST::TypeParam &param)
+{
+  // outer attributes don't actually do anything, so ignore them
+
+  if (param.has_type_param_bounds ())
+    {
+      // don't strip directly, only components of bounds
+      for (auto &bound : param.get_type_param_bounds ())
+	bound->accept_vis (*this);
+    }
+
+  if (param.has_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+      auto &type = param.get_type ();
+      type->accept_vis (*this);
+
+      maybe_expand_type (type);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+}
+void
+AttrVisitor::visit (AST::LifetimeWhereClauseItem &)
+{
+  // shouldn't require
+}
+void
+AttrVisitor::visit (AST::TypeBoundWhereClauseItem &item)
+{
+  // for lifetimes shouldn't require
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  auto &type = item.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+
+  // don't strip directly, only components of bounds
+  for (auto &bound : item.get_type_param_bounds ())
+    bound->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::Method &method)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (method.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (method.get_outer_attrs ()))
+    {
+      method.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : method.get_generic_params ())
+    param->accept_vis (*this);
+
+  /* assuming you can't strip self param - wouldn't be a method
+   * anymore. spec allows outer attrs on self param, but doesn't
+   * specify whether cfg is used. */
+  expand_self_param (method.get_self_param ());
+
+  /* strip method parameters if required - this is specifically
+   * allowed by spec */
+  expand_function_params (method.get_function_params ());
+
+  if (method.has_return_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &return_type = method.get_return_type ();
+      return_type->accept_vis (*this);
+
+      maybe_expand_type (return_type);
+
+      if (return_type->is_marked_for_strip ())
+	rust_error_at (return_type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+
+  if (method.has_where_clause ())
+    expand_where_clause (method.get_where_clause ());
+
+  /* body should always exist - if error state, should have returned
+   * before now */
+  // can't strip block itself, but can strip sub-expressions
+  auto &block_expr = method.get_definition ();
+  block_expr->accept_vis (*this);
+  if (block_expr->is_marked_for_strip ())
+    rust_error_at (block_expr->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::Module &module)
+{
+  // strip test based on outer attrs
+  expander.expand_cfg_attrs (module.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (module.get_outer_attrs ()))
+    {
+      module.mark_for_strip ();
+      return;
+    }
+
+  // A loaded module might have inner attributes
+  if (module.get_kind () == AST::Module::ModuleKind::LOADED)
+    {
+      // strip test based on inner attrs
+      expander.expand_cfg_attrs (module.get_inner_attrs ());
+      if (expander.fails_cfg_with_expand (module.get_inner_attrs ()))
+	{
+	  module.mark_for_strip ();
+	  return;
+	}
+    }
+
+  // Parse the module's items if they haven't been expanded and the file
+  // should be parsed (i.e isn't hidden behind an untrue or impossible cfg
+  // directive)
+  if (!module.is_marked_for_strip ()
+      && module.get_kind () == AST::Module::ModuleKind::UNLOADED)
+    {
+      module.load_items ();
+    }
+
+  // strip items if required
+  expand_pointer_allow_strip (module.get_items ());
+}
+void
+AttrVisitor::visit (AST::ExternCrate &extern_crate)
+{
+  // strip test based on outer attrs
+  expander.expand_cfg_attrs (extern_crate.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (extern_crate.get_outer_attrs ()))
+    {
+      extern_crate.mark_for_strip ();
+      return;
+    }
+
+  if (!extern_crate.references_self ())
+    {
+      Session &session = Session::get_instance ();
+      session.load_extern_crate (extern_crate.get_referenced_crate (),
+				 extern_crate.get_locus ());
+    }
+}
+void
+AttrVisitor::visit (AST::UseTreeGlob &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::UseTreeList &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::UseTreeRebind &)
+{
+  // shouldn't require?
+}
+void
+AttrVisitor::visit (AST::UseDeclaration &use_decl)
+{
+  // strip test based on outer attrs
+  expander.expand_cfg_attrs (use_decl.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (use_decl.get_outer_attrs ()))
+    {
+      use_decl.mark_for_strip ();
+      return;
+    }
+}
+void
+AttrVisitor::visit (AST::Function &function)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (function.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (function.get_outer_attrs ()))
+    {
+      function.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : function.get_generic_params ())
+    param->accept_vis (*this);
+
+  /* strip function parameters if required - this is specifically
+   * allowed by spec */
+  expand_function_params (function.get_function_params ());
+
+  if (function.has_return_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &return_type = function.get_return_type ();
+      return_type->accept_vis (*this);
+
+      maybe_expand_type (return_type);
+
+      if (return_type->is_marked_for_strip ())
+	rust_error_at (return_type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+
+  if (function.has_where_clause ())
+    expand_where_clause (function.get_where_clause ());
+
+  /* body should always exist - if error state, should have returned
+   * before now */
+  // can't strip block itself, but can strip sub-expressions
+  auto &block_expr = function.get_definition ();
+  block_expr->accept_vis (*this);
+  if (block_expr->is_marked_for_strip ())
+    rust_error_at (block_expr->get_locus (),
+		   "cannot strip block expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::TypeAlias &type_alias)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (type_alias.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (type_alias.get_outer_attrs ()))
+    {
+      type_alias.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : type_alias.get_generic_params ())
+    param->accept_vis (*this);
+
+  if (type_alias.has_where_clause ())
+    expand_where_clause (type_alias.get_where_clause ());
+
+  auto &type = type_alias.get_type_aliased ();
+  type->accept_vis (*this);
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+}
+void
+AttrVisitor::visit (AST::StructStruct &struct_item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (struct_item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (struct_item.get_outer_attrs ()))
+    {
+      struct_item.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : struct_item.get_generic_params ())
+    param->accept_vis (*this);
+
+  if (struct_item.has_where_clause ())
+    expand_where_clause (struct_item.get_where_clause ());
+
+  /* strip struct fields if required - this is presumably
+   * allowed by spec */
+  expand_struct_fields (struct_item.get_fields ());
+}
+void
+AttrVisitor::visit (AST::TupleStruct &tuple_struct)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (tuple_struct.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (tuple_struct.get_outer_attrs ()))
+    {
+      tuple_struct.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : tuple_struct.get_generic_params ())
+    param->accept_vis (*this);
+
+  /* strip struct fields if required - this is presumably
+   * allowed by spec */
+  expand_tuple_fields (tuple_struct.get_fields ());
+
+  if (tuple_struct.has_where_clause ())
+    expand_where_clause (tuple_struct.get_where_clause ());
+}
+void
+AttrVisitor::visit (AST::EnumItem &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+}
+void
+AttrVisitor::visit (AST::EnumItemTuple &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  /* strip item fields if required - this is presumably
+   * allowed by spec */
+  expand_tuple_fields (item.get_tuple_fields ());
+}
+void
+AttrVisitor::visit (AST::EnumItemStruct &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  /* strip item fields if required - this is presumably
+   * allowed by spec */
+  expand_struct_fields (item.get_struct_fields ());
+}
+void
+AttrVisitor::visit (AST::EnumItemDiscriminant &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &expr = item.get_expr ();
+  expr->accept_vis (*this);
+  if (expr->is_marked_for_strip ())
+    rust_error_at (expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::Enum &enum_item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (enum_item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (enum_item.get_outer_attrs ()))
+    {
+      enum_item.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : enum_item.get_generic_params ())
+    param->accept_vis (*this);
+
+  if (enum_item.has_where_clause ())
+    expand_where_clause (enum_item.get_where_clause ());
+
+  /* strip enum fields if required - this is presumably
+   * allowed by spec */
+  expand_pointer_allow_strip (enum_item.get_variants ());
+}
+void
+AttrVisitor::visit (AST::Union &union_item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (union_item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (union_item.get_outer_attrs ()))
+    {
+      union_item.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : union_item.get_generic_params ())
+    param->accept_vis (*this);
+
+  if (union_item.has_where_clause ())
+    expand_where_clause (union_item.get_where_clause ());
+
+  /* strip union fields if required - this is presumably
+   * allowed by spec */
+  expand_struct_fields (union_item.get_variants ());
+}
+void
+AttrVisitor::visit (AST::ConstantItem &const_item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (const_item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (const_item.get_outer_attrs ()))
+    {
+      const_item.mark_for_strip ();
+      return;
+    }
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  // strip any sub-types
+  auto &type = const_item.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &expr = const_item.get_expr ();
+  expr->accept_vis (*this);
+  if (expr->is_marked_for_strip ())
+    rust_error_at (expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::StaticItem &static_item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (static_item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (static_item.get_outer_attrs ()))
+    {
+      static_item.mark_for_strip ();
+      return;
+    }
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  // strip any sub-types
+  auto &type = static_item.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped. */
+  auto &expr = static_item.get_expr ();
+  expr->accept_vis (*this);
+  if (expr->is_marked_for_strip ())
+    rust_error_at (expr->get_locus (),
+		   "cannot strip expression in this position - outer "
+		   "attributes not allowed");
+}
+void
+AttrVisitor::visit (AST::TraitItemFunc &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  expand_trait_function_decl (item.get_trait_function_decl ());
+
+  if (item.has_definition ())
+    {
+      /* strip any internal sub-expressions - expression itself isn't
+       * allowed to have external attributes in this position so can't be
+       * stripped. */
+      auto &block = item.get_definition ();
+      block->accept_vis (*this);
+      if (block->is_marked_for_strip ())
+	rust_error_at (block->get_locus (),
+		       "cannot strip block expression in this "
+		       "position - outer attributes not allowed");
+    }
+}
+void
+AttrVisitor::visit (AST::TraitItemMethod &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  expand_trait_method_decl (item.get_trait_method_decl ());
+
+  if (item.has_definition ())
+    {
+      /* strip any internal sub-expressions - expression itself isn't
+       * allowed to have external attributes in this position so can't be
+       * stripped. */
+      auto &block = item.get_definition ();
+      block->accept_vis (*this);
+      if (block->is_marked_for_strip ())
+	rust_error_at (block->get_locus (),
+		       "cannot strip block expression in this "
+		       "position - outer attributes not allowed");
+    }
+}
+void
+AttrVisitor::visit (AST::TraitItemConst &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  // strip any sub-types
+  auto &type = item.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped */
+  if (item.has_expression ())
+    {
+      auto &expr = item.get_expr ();
+      expr->accept_vis (*this);
+      if (expr->is_marked_for_strip ())
+	rust_error_at (expr->get_locus (),
+		       "cannot strip expression in this position - outer "
+		       "attributes not allowed");
+    }
+}
+void
+AttrVisitor::visit (AST::TraitItemType &item)
+{
+  // initial test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  if (item.has_type_param_bounds ())
+    {
+      // don't strip directly, only components of bounds
+      for (auto &bound : item.get_type_param_bounds ())
+	bound->accept_vis (*this);
+    }
+}
+void
+AttrVisitor::visit (AST::Trait &trait)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (trait.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (trait.get_outer_attrs ()))
+    {
+      trait.mark_for_strip ();
+      return;
+    }
+
+  // strip test based on inner attrs
+  expander.expand_cfg_attrs (trait.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (trait.get_inner_attrs ()))
+    {
+      trait.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : trait.get_generic_params ())
+    param->accept_vis (*this);
+
+  if (trait.has_type_param_bounds ())
+    {
+      // don't strip directly, only components of bounds
+      for (auto &bound : trait.get_type_param_bounds ())
+	bound->accept_vis (*this);
+    }
+
+  if (trait.has_where_clause ())
+    expand_where_clause (trait.get_where_clause ());
+
+  std::function<std::unique_ptr<AST::TraitItem> (AST::SingleASTNode)> extractor
+    = [] (AST::SingleASTNode node) { return node.take_trait_item (); };
+
+  expand_macro_children (MacroExpander::TRAIT, trait.get_trait_items (),
+			 extractor);
+}
+void
+AttrVisitor::visit (AST::InherentImpl &impl)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (impl.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (impl.get_outer_attrs ()))
+    {
+      impl.mark_for_strip ();
+      return;
+    }
+
+  // strip test based on inner attrs
+  expander.expand_cfg_attrs (impl.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (impl.get_inner_attrs ()))
+    {
+      impl.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : impl.get_generic_params ())
+    param->accept_vis (*this);
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  auto &type = impl.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+
+  if (impl.has_where_clause ())
+    expand_where_clause (impl.get_where_clause ());
+
+  std::function<std::unique_ptr<AST::InherentImplItem> (AST::SingleASTNode)>
+    extractor = [] (AST::SingleASTNode node) { return node.take_impl_item (); };
+
+  expand_macro_children (MacroExpander::IMPL, impl.get_impl_items (),
+			 extractor);
+}
+void
+AttrVisitor::visit (AST::TraitImpl &impl)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (impl.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (impl.get_outer_attrs ()))
+    {
+      impl.mark_for_strip ();
+      return;
+    }
+
+  // strip test based on inner attrs
+  expander.expand_cfg_attrs (impl.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (impl.get_inner_attrs ()))
+    {
+      impl.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : impl.get_generic_params ())
+    param->accept_vis (*this);
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  auto &type = impl.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+
+  auto &trait_path = impl.get_trait_path ();
+  visit (trait_path);
+  if (trait_path.is_marked_for_strip ())
+    rust_error_at (trait_path.get_locus (),
+		   "cannot strip typepath in this position");
+
+  if (impl.has_where_clause ())
+    expand_where_clause (impl.get_where_clause ());
+
+  std::function<std::unique_ptr<AST::TraitImplItem> (AST::SingleASTNode)>
+    extractor
+    = [] (AST::SingleASTNode node) { return node.take_trait_impl_item (); };
+
+  expand_macro_children (MacroExpander::TRAIT_IMPL, impl.get_impl_items (),
+			 extractor);
+}
+void
+AttrVisitor::visit (AST::ExternalStaticItem &item)
+{
+  // strip test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  expander.push_context (MacroExpander::ContextType::TYPE);
+
+  auto &type = item.get_type ();
+  type->accept_vis (*this);
+
+  maybe_expand_type (type);
+
+  if (type->is_marked_for_strip ())
+    rust_error_at (type->get_locus (), "cannot strip type in this position");
+
+  expander.pop_context ();
+}
+void
+AttrVisitor::visit (AST::ExternalFunctionItem &item)
+{
+  // strip test based on outer attrs
+  expander.expand_cfg_attrs (item.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (item.get_outer_attrs ()))
+    {
+      item.mark_for_strip ();
+      return;
+    }
+
+  // just expand sub-stuff - can't actually strip generic params themselves
+  for (auto &param : item.get_generic_params ())
+    param->accept_vis (*this);
+
+  /* strip function parameters if required - this is specifically
+   * allowed by spec */
+  auto &params = item.get_function_params ();
+  for (auto it = params.begin (); it != params.end ();)
+    {
+      auto &param = *it;
+
+      auto &param_attrs = param.get_outer_attrs ();
+      expander.expand_cfg_attrs (param_attrs);
+      if (expander.fails_cfg_with_expand (param_attrs))
+	{
+	  it = params.erase (it);
+	  continue;
+	}
+
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &type = param.get_type ();
+      type->accept_vis (*this);
+
+      maybe_expand_type (type);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+
+      // increment if nothing else happens
+      ++it;
+    }
+  /* NOTE: these are extern function params, which may have different
+   * rules and restrictions to "normal" function params. So expansion
+   * handled separately. */
+
+  /* TODO: assuming that variadic nature cannot be stripped. If this
+   * is not true, then have code here to do so. */
+
+  if (item.has_return_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &return_type = item.get_return_type ();
+      return_type->accept_vis (*this);
+
+      maybe_expand_type (return_type);
+
+      if (return_type->is_marked_for_strip ())
+	rust_error_at (return_type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+
+  if (item.has_where_clause ())
+    expand_where_clause (item.get_where_clause ());
+}
+
+void
+AttrVisitor::visit (AST::ExternBlock &block)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (block.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (block.get_outer_attrs ()))
+    {
+      block.mark_for_strip ();
+      return;
+    }
+
+  // strip test based on inner attrs
+  expander.expand_cfg_attrs (block.get_inner_attrs ());
+  if (expander.fails_cfg_with_expand (block.get_inner_attrs ()))
+    {
+      block.mark_for_strip ();
+      return;
+    }
+
+  std::function<std::unique_ptr<AST::ExternalItem> (AST::SingleASTNode)>
+    extractor
+    = [] (AST::SingleASTNode node) { return node.take_external_item (); };
+
+  expand_macro_children (MacroExpander::EXTERN, block.get_extern_items (),
+			 extractor);
+}
+
+// I don't think it would be possible to strip macros without expansion
+void
+AttrVisitor::visit (AST::MacroMatchFragment &)
+{}
+void
+AttrVisitor::visit (AST::MacroMatchRepetition &)
+{}
+void
+AttrVisitor::visit (AST::MacroMatcher &)
+{}
+void
+AttrVisitor::visit (AST::MacroRulesDefinition &rules_def)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (rules_def.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (rules_def.get_outer_attrs ()))
+    {
+      rules_def.mark_for_strip ();
+      return;
+    }
+
+  // I don't think any macro rules can be stripped in any way
+
+  auto path = Resolver::CanonicalPath::new_seg (rules_def.get_node_id (),
+						rules_def.get_rule_name ());
+  expander.resolver->get_macro_scope ().insert (path, rules_def.get_node_id (),
+						rules_def.get_locus ());
+  expander.mappings->insert_macro_def (&rules_def);
+  rust_debug_loc (rules_def.get_locus (), "inserting macro def: [%s]",
+		  path.get ().c_str ());
+}
+
+void
+AttrVisitor::visit (AST::MetaItemPath &)
+{}
+void
+AttrVisitor::visit (AST::MetaItemSeq &)
+{}
+void
+AttrVisitor::visit (AST::MetaWord &)
+{}
+void
+AttrVisitor::visit (AST::MetaNameValueStr &)
+{}
+void
+AttrVisitor::visit (AST::MetaListPaths &)
+{}
+void
+AttrVisitor::visit (AST::MetaListNameValueStr &)
+{}
+
+void
+AttrVisitor::visit (AST::LiteralPattern &)
+{
+  // not possible
+}
+void
+AttrVisitor::visit (AST::IdentifierPattern &pattern)
+{
+  // can only strip sub-patterns of the inner pattern to bind
+  if (!pattern.has_pattern_to_bind ())
+    return;
+
+  auto &sub_pattern = pattern.get_pattern_to_bind ();
+  sub_pattern->accept_vis (*this);
+  if (sub_pattern->is_marked_for_strip ())
+    rust_error_at (sub_pattern->get_locus (),
+		   "cannot strip pattern in this position");
+}
+void
+AttrVisitor::visit (AST::WildcardPattern &)
+{
+  // not possible
+}
+void
+AttrVisitor::visit (AST::RangePatternBoundLiteral &)
+{
+  // not possible
+}
+void
+AttrVisitor::visit (AST::RangePatternBoundPath &bound)
+{
+  // can expand path, but not strip it directly
+  auto &path = bound.get_path ();
+  visit (path);
+  if (path.is_marked_for_strip ())
+    rust_error_at (path.get_locus (), "cannot strip path in this position");
+}
+void
+AttrVisitor::visit (AST::RangePatternBoundQualPath &bound)
+{
+  // can expand path, but not strip it directly
+  auto &path = bound.get_qualified_path ();
+  visit (path);
+  if (path.is_marked_for_strip ())
+    rust_error_at (path.get_locus (), "cannot strip path in this position");
+}
+void
+AttrVisitor::visit (AST::RangePattern &pattern)
+{
+  // should have no capability to strip lower or upper bounds, only expand
+  pattern.get_lower_bound ()->accept_vis (*this);
+  pattern.get_upper_bound ()->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::ReferencePattern &pattern)
+{
+  auto &sub_pattern = pattern.get_referenced_pattern ();
+  sub_pattern->accept_vis (*this);
+  if (sub_pattern->is_marked_for_strip ())
+    rust_error_at (sub_pattern->get_locus (),
+		   "cannot strip pattern in this position");
+}
+void
+AttrVisitor::visit (AST::StructPatternFieldTuplePat &field)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (field.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (field.get_outer_attrs ()))
+    {
+      field.mark_for_strip ();
+      return;
+    }
+
+  // strip sub-patterns (can't strip top-level pattern)
+  auto &sub_pattern = field.get_index_pattern ();
+  sub_pattern->accept_vis (*this);
+  if (sub_pattern->is_marked_for_strip ())
+    rust_error_at (sub_pattern->get_locus (),
+		   "cannot strip pattern in this position");
+}
+void
+AttrVisitor::visit (AST::StructPatternFieldIdentPat &field)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (field.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (field.get_outer_attrs ()))
+    {
+      field.mark_for_strip ();
+      return;
+    }
+
+  // strip sub-patterns (can't strip top-level pattern)
+  auto &sub_pattern = field.get_ident_pattern ();
+  sub_pattern->accept_vis (*this);
+  if (sub_pattern->is_marked_for_strip ())
+    rust_error_at (sub_pattern->get_locus (),
+		   "cannot strip pattern in this position");
+}
+void
+AttrVisitor::visit (AST::StructPatternFieldIdent &field)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (field.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (field.get_outer_attrs ()))
+    {
+      field.mark_for_strip ();
+      return;
+    }
+}
+void
+AttrVisitor::visit (AST::StructPattern &pattern)
+{
+  // expand (but don't strip) path
+  auto &path = pattern.get_path ();
+  visit (path);
+  if (path.is_marked_for_strip ())
+    rust_error_at (path.get_locus (), "cannot strip path in this position");
+
+  /* TODO: apparently struct pattern fields can have outer attrs. so can they
+   * be stripped? */
+  if (!pattern.has_struct_pattern_elems ())
+    return;
+
+  auto &elems = pattern.get_struct_pattern_elems ();
+
+  // assuming you can strip struct pattern fields
+  expand_pointer_allow_strip (elems.get_struct_pattern_fields ());
+
+  // assuming you can strip the ".." part
+  if (elems.has_etc ())
+    {
+      expander.expand_cfg_attrs (elems.get_etc_outer_attrs ());
+      if (expander.fails_cfg_with_expand (elems.get_etc_outer_attrs ()))
+	elems.strip_etc ();
+    }
+}
+void
+AttrVisitor::visit (AST::TupleStructItemsNoRange &tuple_items)
+{
+  // can't strip individual patterns, only sub-patterns
+  for (auto &pattern : tuple_items.get_patterns ())
+    {
+      pattern->accept_vis (*this);
+
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+      // TODO: quit stripping now? or keep going?
+    }
+}
+void
+AttrVisitor::visit (AST::TupleStructItemsRange &tuple_items)
+{
+  // can't strip individual patterns, only sub-patterns
+  for (auto &lower_pattern : tuple_items.get_lower_patterns ())
+    {
+      lower_pattern->accept_vis (*this);
+
+      if (lower_pattern->is_marked_for_strip ())
+	rust_error_at (lower_pattern->get_locus (),
+		       "cannot strip pattern in this position");
+      // TODO: quit stripping now? or keep going?
+    }
+  for (auto &upper_pattern : tuple_items.get_upper_patterns ())
+    {
+      upper_pattern->accept_vis (*this);
+
+      if (upper_pattern->is_marked_for_strip ())
+	rust_error_at (upper_pattern->get_locus (),
+		       "cannot strip pattern in this position");
+      // TODO: quit stripping now? or keep going?
+    }
+}
+void
+AttrVisitor::visit (AST::TupleStructPattern &pattern)
+{
+  // expand (but don't strip) path
+  auto &path = pattern.get_path ();
+  visit (path);
+  if (path.is_marked_for_strip ())
+    rust_error_at (path.get_locus (), "cannot strip path in this position");
+
+  if (pattern.has_items ())
+    pattern.get_items ()->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::TuplePatternItemsMultiple &tuple_items)
+{
+  // can't strip individual patterns, only sub-patterns
+  for (auto &pattern : tuple_items.get_patterns ())
+    {
+      pattern->accept_vis (*this);
+
+      if (pattern->is_marked_for_strip ())
+	rust_error_at (pattern->get_locus (),
+		       "cannot strip pattern in this position");
+      // TODO: quit stripping now? or keep going?
+    }
+}
+void
+AttrVisitor::visit (AST::TuplePatternItemsRanged &tuple_items)
+{
+  // can't strip individual patterns, only sub-patterns
+  for (auto &lower_pattern : tuple_items.get_lower_patterns ())
+    {
+      lower_pattern->accept_vis (*this);
+
+      if (lower_pattern->is_marked_for_strip ())
+	rust_error_at (lower_pattern->get_locus (),
+		       "cannot strip pattern in this position");
+      // TODO: quit stripping now? or keep going?
+    }
+  for (auto &upper_pattern : tuple_items.get_upper_patterns ())
+    {
+      upper_pattern->accept_vis (*this);
+
+      if (upper_pattern->is_marked_for_strip ())
+	rust_error_at (upper_pattern->get_locus (),
+		       "cannot strip pattern in this position");
+      // TODO: quit stripping now? or keep going?
+    }
+}
+void
+AttrVisitor::visit (AST::TuplePattern &pattern)
+{
+  if (pattern.has_tuple_pattern_items ())
+    pattern.get_items ()->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::GroupedPattern &pattern)
+{
+  // can't strip inner pattern, only sub-patterns
+  auto &pattern_in_parens = pattern.get_pattern_in_parens ();
+
+  pattern_in_parens->accept_vis (*this);
+
+  if (pattern_in_parens->is_marked_for_strip ())
+    rust_error_at (pattern_in_parens->get_locus (),
+		   "cannot strip pattern in this position");
+}
+void
+AttrVisitor::visit (AST::SlicePattern &pattern)
+{
+  // can't strip individual patterns, only sub-patterns
+  for (auto &item : pattern.get_items ())
+    {
+      item->accept_vis (*this);
+
+      if (item->is_marked_for_strip ())
+	rust_error_at (item->get_locus (),
+		       "cannot strip pattern in this position");
+      // TODO: quit stripping now? or keep going?
+    }
+}
+
+void
+AttrVisitor::visit (AST::EmptyStmt &)
+{
+  // assuming no outer attributes, so nothing can happen
+}
+void
+AttrVisitor::visit (AST::LetStmt &stmt)
+{
+  // initial strip test based on outer attrs
+  expander.expand_cfg_attrs (stmt.get_outer_attrs ());
+  if (expander.fails_cfg_with_expand (stmt.get_outer_attrs ()))
+    {
+      stmt.mark_for_strip ();
+      return;
+    }
+
+  // can't strip pattern, but call for sub-patterns
+  auto &pattern = stmt.get_pattern ();
+  pattern->accept_vis (*this);
+  if (pattern->is_marked_for_strip ())
+    rust_error_at (pattern->get_locus (),
+		   "cannot strip pattern in this position");
+
+  // similar for type
+  if (stmt.has_type ())
+    {
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &type = stmt.get_type ();
+      type->accept_vis (*this);
+
+      maybe_expand_type (type);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+    }
+
+  /* strip any internal sub-expressions - expression itself isn't
+   * allowed to have external attributes in this position so can't be
+   * stripped */
+  if (stmt.has_init_expr ())
+    {
+      auto &init_expr = stmt.get_init_expr ();
+      init_expr->accept_vis (*this);
+
+      if (init_expr->is_marked_for_strip ())
+	rust_error_at (init_expr->get_locus (),
+		       "cannot strip expression in this position - outer "
+		       "attributes not allowed");
+
+      maybe_expand_expr (init_expr);
+    }
+}
+void
+AttrVisitor::visit (AST::ExprStmtWithoutBlock &stmt)
+{
+  // outer attributes associated with expr, so rely on expr
+
+  // guard - should prevent null pointer expr
+  if (stmt.is_marked_for_strip ())
+    return;
+
+  // strip if expr is to be stripped
+  auto &expr = stmt.get_expr ();
+  expr->accept_vis (*this);
+  if (expr->is_marked_for_strip ())
+    {
+      stmt.mark_for_strip ();
+      return;
+    }
+}
+void
+AttrVisitor::visit (AST::ExprStmtWithBlock &stmt)
+{
+  // outer attributes associated with expr, so rely on expr
+
+  // guard - should prevent null pointer expr
+  if (stmt.is_marked_for_strip ())
+    return;
+
+  // strip if expr is to be stripped
+  auto &expr = stmt.get_expr ();
+  expr->accept_vis (*this);
+  if (expr->is_marked_for_strip ())
+    {
+      stmt.mark_for_strip ();
+      return;
+    }
+}
+
+void
+AttrVisitor::visit (AST::TraitBound &bound)
+{
+  // nothing in for lifetimes to strip
+
+  // expand but don't strip type path
+  auto &path = bound.get_type_path ();
+  visit (path);
+  if (path.is_marked_for_strip ())
+    rust_error_at (path.get_locus (),
+		   "cannot strip type path in this position");
+}
+void
+AttrVisitor::visit (AST::ImplTraitType &type)
+{
+  // don't strip directly, only components of bounds
+  for (auto &bound : type.get_type_param_bounds ())
+    bound->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::TraitObjectType &type)
+{
+  // don't strip directly, only components of bounds
+  for (auto &bound : type.get_type_param_bounds ())
+    bound->accept_vis (*this);
+}
+void
+AttrVisitor::visit (AST::ParenthesisedType &type)
+{
+  // expand but don't strip inner type
+  auto &inner_type = type.get_type_in_parens ();
+  inner_type->accept_vis (*this);
+  if (inner_type->is_marked_for_strip ())
+    rust_error_at (inner_type->get_locus (),
+		   "cannot strip type in this position");
+}
+void
+AttrVisitor::visit (AST::ImplTraitTypeOneBound &type)
+{
+  // no stripping possible
+  visit (type.get_trait_bound ());
+}
+void
+AttrVisitor::visit (AST::TraitObjectTypeOneBound &type)
+{
+  // no stripping possible
+  visit (type.get_trait_bound ());
+}
+void
+AttrVisitor::visit (AST::TupleType &type)
+{
+  // TODO: assuming that types can't be stripped as types don't have outer
+  // attributes
+  for (auto &elem_type : type.get_elems ())
+    {
+      elem_type->accept_vis (*this);
+      if (elem_type->is_marked_for_strip ())
+	rust_error_at (elem_type->get_locus (),
+		       "cannot strip type in this position");
+    }
+}
+void
+AttrVisitor::visit (AST::NeverType &)
+{
+  // no stripping possible
+}
+void
+AttrVisitor::visit (AST::RawPointerType &type)
+{
+  // expand but don't strip type pointed to
+  auto &pointed_type = type.get_type_pointed_to ();
+  pointed_type->accept_vis (*this);
+  if (pointed_type->is_marked_for_strip ())
+    rust_error_at (pointed_type->get_locus (),
+		   "cannot strip type in this position");
+}
+void
+AttrVisitor::visit (AST::ReferenceType &type)
+{
+  // expand but don't strip type referenced
+  auto &referenced_type = type.get_type_referenced ();
+  referenced_type->accept_vis (*this);
+  if (referenced_type->is_marked_for_strip ())
+    rust_error_at (referenced_type->get_locus (),
+		   "cannot strip type in this position");
+}
+void
+AttrVisitor::visit (AST::ArrayType &type)
+{
+  // expand but don't strip type referenced
+  auto &base_type = type.get_elem_type ();
+  base_type->accept_vis (*this);
+  if (base_type->is_marked_for_strip ())
+    rust_error_at (base_type->get_locus (),
+		   "cannot strip type in this position");
+
+  // same for expression
+  auto &size_expr = type.get_size_expr ();
+  size_expr->accept_vis (*this);
+  if (size_expr->is_marked_for_strip ())
+    rust_error_at (size_expr->get_locus (),
+		   "cannot strip expression in this position");
+}
+void
+AttrVisitor::visit (AST::SliceType &type)
+{
+  // expand but don't strip elem type
+  auto &elem_type = type.get_elem_type ();
+  elem_type->accept_vis (*this);
+  if (elem_type->is_marked_for_strip ())
+    rust_error_at (elem_type->get_locus (),
+		   "cannot strip type in this position");
+}
+void
+AttrVisitor::visit (AST::InferredType &)
+{
+  // none possible
+}
+void
+AttrVisitor::visit (AST::BareFunctionType &type)
+{
+  // seem to be no generics
+
+  // presumably function params can be stripped
+  auto &params = type.get_function_params ();
+  for (auto it = params.begin (); it != params.end ();)
+    {
+      auto &param = *it;
+
+      auto &param_attrs = param.get_outer_attrs ();
+      expander.expand_cfg_attrs (param_attrs);
+      if (expander.fails_cfg_with_expand (param_attrs))
+	{
+	  it = params.erase (it);
+	  continue;
+	}
+
+      expander.push_context (MacroExpander::ContextType::TYPE);
+
+      auto &type = param.get_type ();
+      type->accept_vis (*this);
+
+      maybe_expand_type (type);
+
+      if (type->is_marked_for_strip ())
+	rust_error_at (type->get_locus (),
+		       "cannot strip type in this position");
+
+      expander.pop_context ();
+
+      // increment if nothing else happens
+      ++it;
+    }
+
+  /* TODO: assuming that variadic nature cannot be stripped. If this
+   * is not true, then have code here to do so. */
+
+  if (type.has_return_type ())
+    {
+      // FIXME: Can we have type expansion in this position?
+      // In that case, we need to handle AST::TypeNoBounds on top of just
+      // AST::Types
+      auto &return_type = type.get_return_type ();
+      return_type->accept_vis (*this);
+      if (return_type->is_marked_for_strip ())
+	rust_error_at (return_type->get_locus (),
+		       "cannot strip type in this position");
+    }
+
+  // no where clause, apparently
+}
+void
+AttrVisitor::maybe_expand_expr (std::unique_ptr<AST::Expr> &expr)
+{
+  auto final_fragment = expand_macro_fragment_recursive ();
+  if (final_fragment.should_expand ())
+    expr = final_fragment.take_expression_fragment ();
+}
+
+void
+AttrVisitor::maybe_expand_type (std::unique_ptr<AST::Type> &type)
+{
+  auto final_fragment = expand_macro_fragment_recursive ();
+  if (final_fragment.should_expand ())
+    type = final_fragment.take_type_fragment ();
+}
+} // namespace Rust
diff --git a/gcc/rust/expand/rust-attribute-visitor.h b/gcc/rust/expand/rust-attribute-visitor.h
new file mode 100644
index 00000000000..0f9d1065334
--- /dev/null
+++ b/gcc/rust/expand/rust-attribute-visitor.h
@@ -0,0 +1,316 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-ast-visitor.h"
+#include "rust-ast.h"
+#include "rust-macro-expand.h"
+
+namespace Rust {
+// Visitor used to expand attributes.
+class AttrVisitor : public AST::ASTVisitor
+{
+private:
+  MacroExpander &expander;
+  void maybe_expand_expr (std::unique_ptr<AST::Expr> &expr);
+  void maybe_expand_type (std::unique_ptr<AST::Type> &expr);
+
+public:
+  AttrVisitor (MacroExpander &expander) : expander (expander) {}
+
+  void expand_struct_fields (std::vector<AST::StructField> &fields);
+  void expand_tuple_fields (std::vector<AST::TupleField> &fields);
+  void expand_function_params (std::vector<AST::FunctionParam> &params);
+  void expand_generic_args (AST::GenericArgs &args);
+  void expand_qualified_path_type (AST::QualifiedPathType &path_type);
+  void expand_closure_params (std::vector<AST::ClosureParam> &params);
+  void expand_self_param (AST::SelfParam &self_param);
+  void expand_where_clause (AST::WhereClause &where_clause);
+  void expand_trait_function_decl (AST::TraitFunctionDecl &decl);
+  void expand_trait_method_decl (AST::TraitMethodDecl &decl);
+
+  /**
+   * Expand The current macro fragment recursively until it could not be
+   * expanded further.
+   *
+   * The return value checking works because correctly
+   * expanded fragment can never be an error (if the fragment can not be
+   * expanded, a stand-in error fragment will be returned; for fragments that
+   * could not be further expanded: the fragment prior to the expansion failure
+   * will be returned).
+   *
+   * @return Either the expanded fragment or an empty errored-out fragment
+   * indicating an expansion failure.
+   */
+  AST::ASTFragment expand_macro_fragment_recursive ()
+  {
+    auto fragment = expander.take_expanded_fragment (*this);
+    unsigned int original_depth = expander.expansion_depth;
+    auto final_fragment = AST::ASTFragment ({}, true);
+
+    while (fragment.should_expand ())
+      {
+	final_fragment = std::move (fragment);
+	expander.expansion_depth++;
+	// further expand the previously expanded macro fragment
+	auto new_fragment = expander.take_expanded_fragment (*this);
+	if (new_fragment.is_error ())
+	  break;
+	fragment = std::move (new_fragment);
+      }
+    expander.expansion_depth = original_depth;
+    return final_fragment;
+  }
+
+  /**
+   * Expand a set of values, erasing them if they are marked for strip, and
+   * replacing them with expanded macro nodes if necessary.
+   * This function is slightly different from `expand_pointer_allow_strip` as
+   * it can only be called in certain expansion contexts - where macro
+   * invocations are allowed.
+   *
+   * @param ctx Context to use for macro expansion
+   * @param values Iterable reference over values to replace or erase
+   * @param extractor Function to call when replacing values with the content
+   * 		of an expanded AST node
+   */
+  template <typename T, typename U>
+  void expand_macro_children (MacroExpander::ContextType ctx, T &values,
+			      std::function<U (AST::SingleASTNode)> extractor)
+  {
+    expander.push_context (ctx);
+
+    for (auto it = values.begin (); it != values.end ();)
+      {
+	auto &value = *it;
+
+	// mark for stripping if required
+	value->accept_vis (*this);
+
+	// recursively expand the children
+	auto final_fragment = expand_macro_fragment_recursive ();
+
+	if (final_fragment.should_expand ())
+	  {
+	    it = values.erase (it);
+	    for (auto &node : final_fragment.get_nodes ())
+	      {
+		auto new_node = extractor (node);
+		if (new_node != nullptr && !new_node->is_marked_for_strip ())
+		  {
+		    it = values.insert (it, std::move (new_node));
+		    it++;
+		  }
+	      }
+	  }
+	else if (value->is_marked_for_strip ())
+	  {
+	    it = values.erase (it);
+	  }
+	else
+	  {
+	    ++it;
+	  }
+      }
+
+    expander.pop_context ();
+  }
+
+  template <typename T> void expand_pointer_allow_strip (T &values)
+  {
+    for (auto it = values.begin (); it != values.end ();)
+      {
+	auto &value = *it;
+
+	// mark for stripping if required
+	value->accept_vis (*this);
+	if (value->is_marked_for_strip ())
+	  {
+	    it = values.erase (it);
+	  }
+	else
+	  {
+	    ++it;
+	  }
+      }
+  }
+
+  void visit (AST::Token &) override;
+  void visit (AST::DelimTokenTree &) override;
+  void visit (AST::AttrInputMetaItemContainer &) override;
+  void visit (AST::IdentifierExpr &ident_expr) override;
+  void visit (AST::Lifetime &) override;
+  void visit (AST::LifetimeParam &) override;
+  void visit (AST::ConstGenericParam &) override;
+
+  void visit (AST::MacroInvocation &macro_invoc) override;
+
+  void visit (AST::PathInExpression &path) override;
+  void visit (AST::TypePathSegment &) override;
+  void visit (AST::TypePathSegmentGeneric &segment) override;
+  void visit (AST::TypePathSegmentFunction &segment) override;
+  void visit (AST::TypePath &path) override;
+  void visit (AST::QualifiedPathInExpression &path) override;
+  void visit (AST::QualifiedPathInType &path) override;
+
+  void visit (AST::LiteralExpr &expr) override;
+  void visit (AST::AttrInputLiteral &) override;
+  void visit (AST::MetaItemLitExpr &) override;
+  void visit (AST::MetaItemPathLit &) override;
+  void visit (AST::BorrowExpr &expr) override;
+  void visit (AST::DereferenceExpr &expr) override;
+  void visit (AST::ErrorPropagationExpr &expr) override;
+  void visit (AST::NegationExpr &expr) override;
+  void visit (AST::ArithmeticOrLogicalExpr &expr) override;
+  void visit (AST::ComparisonExpr &expr) override;
+  void visit (AST::LazyBooleanExpr &expr) override;
+  void visit (AST::TypeCastExpr &expr) override;
+  void visit (AST::AssignmentExpr &expr) override;
+  void visit (AST::CompoundAssignmentExpr &expr) override;
+  void visit (AST::GroupedExpr &expr) override;
+  void visit (AST::ArrayElemsValues &elems) override;
+  void visit (AST::ArrayElemsCopied &elems) override;
+  void visit (AST::ArrayExpr &expr) override;
+  void visit (AST::ArrayIndexExpr &expr) override;
+  void visit (AST::TupleExpr &expr) override;
+  void visit (AST::TupleIndexExpr &expr) override;
+  void visit (AST::StructExprStruct &expr) override;
+  void visit (AST::StructExprFieldIdentifier &) override;
+  void visit (AST::StructExprFieldIdentifierValue &field) override;
+
+  void visit (AST::StructExprFieldIndexValue &field) override;
+  void visit (AST::StructExprStructFields &expr) override;
+  void visit (AST::StructExprStructBase &expr) override;
+  void visit (AST::CallExpr &expr) override;
+  void visit (AST::MethodCallExpr &expr) override;
+  void visit (AST::FieldAccessExpr &expr) override;
+  void visit (AST::ClosureExprInner &expr) override;
+
+  void visit (AST::BlockExpr &expr) override;
+
+  void visit (AST::ClosureExprInnerTyped &expr) override;
+  void visit (AST::ContinueExpr &expr) override;
+  void visit (AST::BreakExpr &expr) override;
+  void visit (AST::RangeFromToExpr &expr) override;
+  void visit (AST::RangeFromExpr &expr) override;
+  void visit (AST::RangeToExpr &expr) override;
+  void visit (AST::RangeFullExpr &) override;
+  void visit (AST::RangeFromToInclExpr &expr) override;
+  void visit (AST::RangeToInclExpr &expr) override;
+  void visit (AST::ReturnExpr &expr) override;
+  void visit (AST::UnsafeBlockExpr &expr) override;
+  void visit (AST::LoopExpr &expr) override;
+  void visit (AST::WhileLoopExpr &expr) override;
+  void visit (AST::WhileLetLoopExpr &expr) override;
+  void visit (AST::ForLoopExpr &expr) override;
+  void visit (AST::IfExpr &expr) override;
+  void visit (AST::IfExprConseqElse &expr) override;
+  void visit (AST::IfExprConseqIf &expr) override;
+  void visit (AST::IfExprConseqIfLet &expr) override;
+  void visit (AST::IfLetExpr &expr) override;
+  void visit (AST::IfLetExprConseqElse &expr) override;
+  void visit (AST::IfLetExprConseqIf &expr) override;
+  void visit (AST::IfLetExprConseqIfLet &expr) override;
+  void visit (AST::MatchExpr &expr) override;
+  void visit (AST::AwaitExpr &expr) override;
+  void visit (AST::AsyncBlockExpr &expr) override;
+  void visit (AST::TypeParam &param) override;
+  void visit (AST::LifetimeWhereClauseItem &) override;
+  void visit (AST::TypeBoundWhereClauseItem &item) override;
+  void visit (AST::Method &method) override;
+  void visit (AST::Module &module) override;
+  void visit (AST::ExternCrate &crate) override;
+  void visit (AST::UseTreeGlob &) override;
+  void visit (AST::UseTreeList &) override;
+  void visit (AST::UseTreeRebind &) override;
+  void visit (AST::UseDeclaration &use_decl) override;
+  void visit (AST::Function &function) override;
+  void visit (AST::TypeAlias &type_alias) override;
+  void visit (AST::StructStruct &struct_item) override;
+  void visit (AST::TupleStruct &tuple_struct) override;
+  void visit (AST::EnumItem &item) override;
+  void visit (AST::EnumItemTuple &item) override;
+  void visit (AST::EnumItemStruct &item) override;
+  void visit (AST::EnumItemDiscriminant &item) override;
+  void visit (AST::Enum &enum_item) override;
+  void visit (AST::Union &union_item) override;
+  void visit (AST::ConstantItem &const_item) override;
+  void visit (AST::StaticItem &static_item) override;
+  void visit (AST::TraitItemFunc &item) override;
+  void visit (AST::TraitItemMethod &item) override;
+  void visit (AST::TraitItemConst &item) override;
+  void visit (AST::TraitItemType &item) override;
+  void visit (AST::Trait &trait) override;
+  void visit (AST::InherentImpl &impl) override;
+  void visit (AST::TraitImpl &impl) override;
+  void visit (AST::ExternalStaticItem &item) override;
+  void visit (AST::ExternalFunctionItem &item) override;
+  void visit (AST::ExternBlock &block) override;
+
+  // I don't think it would be possible to strip macros without expansion
+  void visit (AST::MacroMatchFragment &) override;
+  void visit (AST::MacroMatchRepetition &) override;
+  void visit (AST::MacroMatcher &) override;
+  void visit (AST::MacroRulesDefinition &rules_def) override;
+  void visit (AST::MetaItemPath &) override;
+  void visit (AST::MetaItemSeq &) override;
+  void visit (AST::MetaWord &) override;
+  void visit (AST::MetaNameValueStr &) override;
+  void visit (AST::MetaListPaths &) override;
+  void visit (AST::MetaListNameValueStr &) override;
+  void visit (AST::LiteralPattern &) override;
+  void visit (AST::IdentifierPattern &pattern) override;
+  void visit (AST::WildcardPattern &) override;
+  void visit (AST::RangePatternBoundLiteral &) override;
+  void visit (AST::RangePatternBoundPath &bound) override;
+  void visit (AST::RangePatternBoundQualPath &bound) override;
+  void visit (AST::RangePattern &pattern) override;
+  void visit (AST::ReferencePattern &pattern) override;
+  void visit (AST::StructPatternFieldTuplePat &field) override;
+  void visit (AST::StructPatternFieldIdentPat &field) override;
+  void visit (AST::StructPatternFieldIdent &field) override;
+  void visit (AST::StructPattern &pattern) override;
+  void visit (AST::TupleStructItemsNoRange &tuple_items) override;
+  void visit (AST::TupleStructItemsRange &tuple_items) override;
+  void visit (AST::TupleStructPattern &pattern) override;
+  void visit (AST::TuplePatternItemsMultiple &tuple_items) override;
+  void visit (AST::TuplePatternItemsRanged &tuple_items) override;
+  void visit (AST::TuplePattern &pattern) override;
+  void visit (AST::GroupedPattern &pattern) override;
+  void visit (AST::SlicePattern &pattern) override;
+
+  void visit (AST::EmptyStmt &) override;
+  void visit (AST::LetStmt &stmt) override;
+  void visit (AST::ExprStmtWithoutBlock &stmt) override;
+  void visit (AST::ExprStmtWithBlock &stmt) override;
+
+  void visit (AST::TraitBound &bound) override;
+  void visit (AST::ImplTraitType &type) override;
+  void visit (AST::TraitObjectType &type) override;
+  void visit (AST::ParenthesisedType &type) override;
+  void visit (AST::ImplTraitTypeOneBound &type) override;
+  void visit (AST::TraitObjectTypeOneBound &type) override;
+  void visit (AST::TupleType &type) override;
+  void visit (AST::NeverType &) override;
+  void visit (AST::RawPointerType &type) override;
+  void visit (AST::ReferenceType &type) override;
+  void visit (AST::ArrayType &type) override;
+  void visit (AST::SliceType &type) override;
+  void visit (AST::InferredType &) override;
+  void visit (AST::BareFunctionType &type) override;
+};
+} // namespace Rust
diff --git a/gcc/rust/expand/rust-macro-builtins.cc b/gcc/rust/expand/rust-macro-builtins.cc
new file mode 100644
index 00000000000..5eace13d197
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-builtins.cc
@@ -0,0 +1,484 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-macro-builtins.h"
+#include "rust-diagnostics.h"
+#include "rust-expr.h"
+#include "rust-session-manager.h"
+#include "rust-macro-invoc-lexer.h"
+#include "rust-lex.h"
+#include "rust-parse.h"
+
+namespace Rust {
+namespace {
+std::unique_ptr<AST::Expr>
+make_string (Location locus, std::string value)
+{
+  return std::unique_ptr<AST::Expr> (
+    new AST::LiteralExpr (value, AST::Literal::STRING,
+			  PrimitiveCoreType::CORETYPE_STR, {}, locus));
+}
+
+/* Match the end token of a macro given the start delimiter of the macro */
+
+static inline TokenId
+macro_end_token (AST::DelimTokenTree &invoc_token_tree,
+		 Parser<MacroInvocLexer> &parser)
+{
+  auto last_token_id = TokenId::RIGHT_CURLY;
+  switch (invoc_token_tree.get_delim_type ())
+    {
+    case AST::DelimType::PARENS:
+      last_token_id = TokenId::RIGHT_PAREN;
+      rust_assert (parser.skip_token (LEFT_PAREN));
+      break;
+
+    case AST::DelimType::CURLY:
+      rust_assert (parser.skip_token (LEFT_CURLY));
+      break;
+
+    case AST::DelimType::SQUARE:
+      last_token_id = TokenId::RIGHT_SQUARE;
+      rust_assert (parser.skip_token (LEFT_SQUARE));
+      break;
+    }
+
+  return last_token_id;
+}
+
+/* Parse a single string literal from the given delimited token tree,
+   and return the LiteralExpr for it. Allow for an optional trailing comma,
+   but otherwise enforce that these are the only tokens.  */
+
+std::unique_ptr<AST::LiteralExpr>
+parse_single_string_literal (AST::DelimTokenTree &invoc_token_tree,
+			     Location invoc_locus)
+{
+  MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
+  Parser<MacroInvocLexer> parser (lex);
+
+  auto last_token_id = macro_end_token (invoc_token_tree, parser);
+
+  std::unique_ptr<AST::LiteralExpr> lit_expr = nullptr;
+
+  if (parser.peek_current_token ()->get_id () == STRING_LITERAL)
+    {
+      lit_expr = parser.parse_literal_expr ();
+      parser.maybe_skip_token (COMMA);
+      if (parser.peek_current_token ()->get_id () != last_token_id)
+	{
+	  lit_expr = nullptr;
+	  rust_error_at (invoc_locus, "macro takes 1 argument");
+	}
+    }
+  else if (parser.peek_current_token ()->get_id () == last_token_id)
+    rust_error_at (invoc_locus, "macro takes 1 argument");
+  else
+    rust_error_at (invoc_locus, "argument must be a string literal");
+
+  parser.skip_token (last_token_id);
+
+  return lit_expr;
+}
+
+/* Treat PATH as a path relative to the source file currently being
+   compiled, and return the absolute path for it.  */
+
+std::string
+source_relative_path (std::string path, Location locus)
+{
+  std::string compile_fname
+    = Session::get_instance ().linemap->location_file (locus);
+
+  auto dir_separator_pos = compile_fname.rfind (file_separator);
+
+  /* If there is no file_separator in the path, use current dir ('.').  */
+  std::string dirname;
+  if (dir_separator_pos == std::string::npos)
+    dirname = std::string (".") + file_separator;
+  else
+    dirname = compile_fname.substr (0, dir_separator_pos) + file_separator;
+
+  return dirname + path;
+}
+
+/* Read the full contents of the file FILENAME and return them in a vector.
+   FIXME: platform specific.  */
+
+std::vector<uint8_t>
+load_file_bytes (const char *filename)
+{
+  RAIIFile file_wrap (filename);
+  if (file_wrap.get_raw () == nullptr)
+    {
+      rust_error_at (Location (), "cannot open filename %s: %m", filename);
+      return std::vector<uint8_t> ();
+    }
+
+  FILE *f = file_wrap.get_raw ();
+  fseek (f, 0L, SEEK_END);
+  long fsize = ftell (f);
+  fseek (f, 0L, SEEK_SET);
+
+  std::vector<uint8_t> buf (fsize);
+
+  if (fread (&buf[0], fsize, 1, f) != 1)
+    {
+      rust_error_at (Location (), "error reading file %s: %m", filename);
+      return std::vector<uint8_t> ();
+    }
+
+  return buf;
+}
+} // namespace
+
+AST::ASTFragment
+MacroBuiltin::assert (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  rust_debug ("assert!() called");
+
+  return AST::ASTFragment::create_error ();
+}
+
+AST::ASTFragment
+MacroBuiltin::file (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  auto current_file
+    = Session::get_instance ().linemap->location_file (invoc_locus);
+  auto file_str = AST::SingleASTNode (make_string (invoc_locus, current_file));
+
+  return AST::ASTFragment ({file_str});
+}
+
+AST::ASTFragment
+MacroBuiltin::column (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  auto current_column
+    = Session::get_instance ().linemap->location_to_column (invoc_locus);
+
+  auto column_no = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
+    new AST::LiteralExpr (std::to_string (current_column), AST::Literal::INT,
+			  PrimitiveCoreType::CORETYPE_U32, {}, invoc_locus)));
+
+  return AST::ASTFragment ({column_no});
+}
+
+/* Expand builtin macro include_bytes!("filename"), which includes the contents
+   of the given file as reference to a byte array. Yields an expression of type
+   &'static [u8; N].  */
+
+AST::ASTFragment
+MacroBuiltin::include_bytes (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  /* Get target filename from the macro invocation, which is treated as a path
+     relative to the include!-ing file (currently being compiled).  */
+  auto lit_expr
+    = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus);
+  if (lit_expr == nullptr)
+    return AST::ASTFragment::create_error ();
+
+  std::string target_filename
+    = source_relative_path (lit_expr->as_string (), invoc_locus);
+
+  std::vector<uint8_t> bytes = load_file_bytes (target_filename.c_str ());
+
+  /* Is there a more efficient way to do this?  */
+  std::vector<std::unique_ptr<AST::Expr>> elts;
+  for (uint8_t b : bytes)
+    {
+      elts.emplace_back (
+	new AST::LiteralExpr (std::string (1, (char) b), AST::Literal::BYTE,
+			      PrimitiveCoreType::CORETYPE_U8,
+			      {} /* outer_attrs */, invoc_locus));
+    }
+
+  auto elems = std::unique_ptr<AST::ArrayElems> (
+    new AST::ArrayElemsValues (std::move (elts), invoc_locus));
+
+  auto array = std::unique_ptr<AST::Expr> (
+    new AST::ArrayExpr (std::move (elems), {}, {}, invoc_locus));
+
+  auto borrow = std::unique_ptr<AST::Expr> (
+    new AST::BorrowExpr (std::move (array), false, false, {}, invoc_locus));
+
+  auto node = AST::SingleASTNode (std::move (borrow));
+  return AST::ASTFragment ({node});
+}
+
+/* Expand builtin macro include_str!("filename"), which includes the contents
+   of the given file as a string. The file must be UTF-8 encoded. Yields an
+   expression of type &'static str.  */
+
+AST::ASTFragment
+MacroBuiltin::include_str (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  /* Get target filename from the macro invocation, which is treated as a path
+     relative to the include!-ing file (currently being compiled).  */
+  auto lit_expr
+    = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus);
+  if (lit_expr == nullptr)
+    return AST::ASTFragment::create_error ();
+
+  std::string target_filename
+    = source_relative_path (lit_expr->as_string (), invoc_locus);
+
+  std::vector<uint8_t> bytes = load_file_bytes (target_filename.c_str ());
+
+  /* FIXME: Enforce that the file contents are valid UTF-8.  */
+  std::string str ((const char *) &bytes[0], bytes.size ());
+
+  auto node = AST::SingleASTNode (make_string (invoc_locus, str));
+  return AST::ASTFragment ({node});
+}
+
+/* Expand builtin macro compile_error!("error"), which forces a compile error
+   during the compile time. */
+AST::ASTFragment
+MacroBuiltin::compile_error (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  auto lit_expr
+    = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus);
+  if (lit_expr == nullptr)
+    return AST::ASTFragment::create_error ();
+
+  std::string error_string = lit_expr->as_string ();
+  rust_error_at (invoc_locus, "%s", error_string.c_str ());
+
+  return AST::ASTFragment::create_error ();
+}
+
+/* Expand builtin macro concat!(), which joins all the literal parameters
+   into a string with no delimiter. */
+
+AST::ASTFragment
+MacroBuiltin::concat (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  auto invoc_token_tree = invoc.get_delim_tok_tree ();
+  MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
+  Parser<MacroInvocLexer> parser (lex);
+
+  auto str = std::string ();
+  bool has_error = false;
+
+  auto last_token_id = macro_end_token (invoc_token_tree, parser);
+
+  /* NOTE: concat! could accept no argument, so we don't have any checks here */
+  while (parser.peek_current_token ()->get_id () != last_token_id)
+    {
+      auto lit_expr = parser.parse_literal_expr ();
+      if (lit_expr)
+	{
+	  str += lit_expr->as_string ();
+	}
+      else
+	{
+	  auto current_token = parser.peek_current_token ();
+	  rust_error_at (current_token->get_locus (),
+			 "argument must be a constant literal");
+	  has_error = true;
+	  // Just crash if the current token can't be skipped
+	  rust_assert (parser.skip_token (current_token->get_id ()));
+	}
+      parser.maybe_skip_token (COMMA);
+    }
+
+  parser.skip_token (last_token_id);
+
+  if (has_error)
+    return AST::ASTFragment::create_error ();
+
+  auto node = AST::SingleASTNode (make_string (invoc_locus, str));
+  return AST::ASTFragment ({node});
+}
+
+/* Expand builtin macro env!(), which inspects an environment variable at
+   compile time. */
+
+AST::ASTFragment
+MacroBuiltin::env (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  auto invoc_token_tree = invoc.get_delim_tok_tree ();
+  MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
+  Parser<MacroInvocLexer> parser (lex);
+
+  auto last_token_id = macro_end_token (invoc_token_tree, parser);
+
+  if (parser.peek_current_token ()->get_id () != STRING_LITERAL)
+    {
+      if (parser.peek_current_token ()->get_id () == last_token_id)
+	rust_error_at (invoc_locus, "env! takes 1 or 2 arguments");
+      else
+	rust_error_at (parser.peek_current_token ()->get_locus (),
+		       "argument must be a string literal");
+      return AST::ASTFragment::create_error ();
+    }
+
+  auto lit_expr = parser.parse_literal_expr ();
+  auto comma_skipped = parser.maybe_skip_token (COMMA);
+
+  std::unique_ptr<AST::LiteralExpr> error_expr = nullptr;
+
+  if (parser.peek_current_token ()->get_id () != last_token_id)
+    {
+      if (!comma_skipped)
+	{
+	  rust_error_at (parser.peek_current_token ()->get_locus (),
+			 "expected token: %<,%>");
+	  return AST::ASTFragment::create_error ();
+	}
+      if (parser.peek_current_token ()->get_id () != STRING_LITERAL)
+	{
+	  rust_error_at (parser.peek_current_token ()->get_locus (),
+			 "argument must be a string literal");
+	  return AST::ASTFragment::create_error ();
+	}
+
+      error_expr = parser.parse_literal_expr ();
+      parser.maybe_skip_token (COMMA);
+    }
+
+  if (parser.peek_current_token ()->get_id () != last_token_id)
+    {
+      rust_error_at (invoc_locus, "env! takes 1 or 2 arguments");
+      return AST::ASTFragment::create_error ();
+    }
+
+  parser.skip_token (last_token_id);
+
+  auto env_value = getenv (lit_expr->as_string ().c_str ());
+
+  if (env_value == nullptr)
+    {
+      if (error_expr == nullptr)
+	rust_error_at (invoc_locus, "environment variable %qs not defined",
+		       lit_expr->as_string ().c_str ());
+      else
+	rust_error_at (invoc_locus, "%s", error_expr->as_string ().c_str ());
+      return AST::ASTFragment::create_error ();
+    }
+
+  auto node = AST::SingleASTNode (make_string (invoc_locus, env_value));
+  return AST::ASTFragment ({node});
+}
+
+AST::ASTFragment
+MacroBuiltin::cfg (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  // only parse if not already parsed
+  if (!invoc.is_parsed ())
+    {
+      std::unique_ptr<AST::AttrInputMetaItemContainer> converted_input (
+	invoc.get_delim_tok_tree ().parse_to_meta_item ());
+
+      if (converted_input == nullptr)
+	{
+	  rust_debug ("DEBUG: failed to parse macro to meta item");
+	  // TODO: do something now? is this an actual error?
+	}
+      else
+	{
+	  std::vector<std::unique_ptr<AST::MetaItemInner>> meta_items (
+	    std::move (converted_input->get_items ()));
+	  invoc.set_meta_item_output (std::move (meta_items));
+	}
+    }
+
+  /* TODO: assuming that cfg! macros can only have one meta item inner, like cfg
+   * attributes */
+  if (invoc.get_meta_items ().size () != 1)
+    return AST::ASTFragment::create_error ();
+
+  bool result = invoc.get_meta_items ()[0]->check_cfg_predicate (
+    Session::get_instance ());
+  auto literal_exp = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
+    new AST::LiteralExpr (result ? "true" : "false", AST::Literal::BOOL,
+			  PrimitiveCoreType::CORETYPE_BOOL, {}, invoc_locus)));
+
+  return AST::ASTFragment ({literal_exp});
+}
+
+/* Expand builtin macro include!(), which includes a source file at the current
+ scope compile time. */
+
+AST::ASTFragment
+MacroBuiltin::include (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  /* Get target filename from the macro invocation, which is treated as a path
+     relative to the include!-ing file (currently being compiled).  */
+  auto lit_expr
+    = parse_single_string_literal (invoc.get_delim_tok_tree (), invoc_locus);
+  if (lit_expr == nullptr)
+    return AST::ASTFragment::create_error ();
+
+  std::string filename
+    = source_relative_path (lit_expr->as_string (), invoc_locus);
+  auto target_filename
+    = Rust::Session::get_instance ().include_extra_file (std::move (filename));
+
+  RAIIFile target_file (target_filename);
+  Linemap *linemap = Session::get_instance ().linemap;
+
+  if (!target_file.ok ())
+    {
+      rust_error_at (lit_expr->get_locus (),
+		     "cannot open included file %qs: %m", target_filename);
+      return AST::ASTFragment::create_error ();
+    }
+
+  rust_debug ("Attempting to parse included file %s", target_filename);
+
+  Lexer lex (target_filename, std::move (target_file), linemap);
+  Parser<Lexer> parser (lex);
+
+  auto parsed_items = parser.parse_items ();
+  bool has_error = !parser.get_errors ().empty ();
+
+  for (const auto &error : parser.get_errors ())
+    error.emit_error ();
+
+  if (has_error)
+    {
+      // inform the user that the errors above are from a included file
+      rust_inform (invoc_locus, "included from here");
+      return AST::ASTFragment::create_error ();
+    }
+
+  std::vector<AST::SingleASTNode> nodes{};
+  for (auto &item : parsed_items)
+    {
+      AST::SingleASTNode node (std::move (item));
+      nodes.push_back (node);
+    }
+
+  return AST::ASTFragment (nodes);
+}
+
+AST::ASTFragment
+MacroBuiltin::line (Location invoc_locus, AST::MacroInvocData &invoc)
+{
+  auto current_line
+    = Session::get_instance ().linemap->location_to_line (invoc_locus);
+
+  auto line_no = AST::SingleASTNode (std::unique_ptr<AST::Expr> (
+    new AST::LiteralExpr (std::to_string (current_line), AST::Literal::INT,
+			  PrimitiveCoreType::CORETYPE_U32, {}, invoc_locus)));
+
+  return AST::ASTFragment ({line_no});
+}
+
+} // namespace Rust
diff --git a/gcc/rust/expand/rust-macro-builtins.h b/gcc/rust/expand/rust-macro-builtins.h
new file mode 100644
index 00000000000..91f3727d450
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-builtins.h
@@ -0,0 +1,107 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_MACRO_BUILTINS_H
+#define RUST_MACRO_BUILTINS_H
+
+#include "rust-ast.h"
+#include "rust-location.h"
+
+/**
+ * This class provides a list of builtin macros implemented by the compiler.
+ * The functions defined are called "builtin transcribers" in that they replace
+ * the transcribing part of a macro definition.
+ *
+ * Like regular macro transcribers, they are responsible for building and
+ * returning an AST fragment: basically a vector of AST nodes put together.
+ *
+ * Unlike regular declarative macros where each match arm has its own associated
+ * transcriber, builtin transcribers are responsible for handling all match arms
+ * of the macro. This means that you should take extra care when implementing a
+ * builtin containing multiple match arms: You will probably need to do some
+ * lookahead in order to determine which match arm the user intended to use.
+ *
+ * An example of this is the `assert!()` macro:
+ *
+ * ```
+ *  macro_rules! assert {
+ *	($cond:expr $(,)?) => {{ ... }};
+ *	($cond : expr, $ ($arg : tt) +) = > {{ ... }};
+ * }
+ * ```
+ *
+ * If more tokens exist beyond the optional comma, they need to be handled as
+ * a token-tree for a custom panic message.
+ *
+ * These builtin macros with empty transcribers are defined in the standard
+ * library. They are marked with a special attribute, `#[rustc_builtin_macro]`.
+ * When this attribute is present on a macro definition, the compiler should
+ * look for an associated transcriber in the mappings. Meaning that you must
+ * remember to insert your transcriber in the `builtin_macros` map of the
+ *`Mappings`.
+ *
+ * This map is built as a static variable in the `insert_macro_def()` method
+ * of the `Mappings` class.
+ */
+
+/* If assert is defined as a macro this file will not parse, so undefine this
+   before continuing.  */
+#ifdef assert
+#undef assert
+#endif
+
+namespace Rust {
+class MacroBuiltin
+{
+public:
+  static AST::ASTFragment assert (Location invoc_locus,
+				  AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment file (Location invoc_locus,
+				AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment column (Location invoc_locus,
+				  AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment include_bytes (Location invoc_locus,
+					 AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment include_str (Location invoc_locus,
+				       AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment compile_error (Location invoc_locus,
+					 AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment concat (Location invoc_locus,
+				  AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment env (Location invoc_locus,
+			       AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment cfg (Location invoc_locus,
+			       AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment include (Location invoc_locus,
+				   AST::MacroInvocData &invoc);
+
+  static AST::ASTFragment line (Location invoc_locus,
+				AST::MacroInvocData &invoc);
+};
+} // namespace Rust
+
+#endif // RUST_MACRO_BUILTINS_H
diff --git a/gcc/rust/expand/rust-macro-expand.cc b/gcc/rust/expand/rust-macro-expand.cc
new file mode 100644
index 00000000000..1d57e394220
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-expand.cc
@@ -0,0 +1,1012 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-macro-expand.h"
+#include "rust-macro-substitute-ctx.h"
+#include "rust-ast-full.h"
+#include "rust-ast-visitor.h"
+#include "rust-diagnostics.h"
+#include "rust-parse.h"
+#include "rust-attribute-visitor.h"
+
+namespace Rust {
+AST::ASTFragment
+MacroExpander::expand_decl_macro (Location invoc_locus,
+				  AST::MacroInvocData &invoc,
+				  AST::MacroRulesDefinition &rules_def,
+				  bool semicolon)
+{
+  // ensure that both invocation and rules are in a valid state
+  rust_assert (!invoc.is_marked_for_strip ());
+  rust_assert (!rules_def.is_marked_for_strip ());
+  rust_assert (rules_def.get_macro_rules ().size () > 0);
+
+  /* probably something here about parsing invoc and rules def token trees to
+   * token stream. if not, how would parser handle the captures of exprs and
+   * stuff? on the other hand, token trees may be kind of useful in rules def as
+   * creating a point where recursion can occur (like having
+   * "compare_macro_match" and then it calling itself when it finds delimiters)
+   */
+
+  /* find matching rule to invoc token tree, based on macro rule's matcher. if
+   * none exist, error.
+   * - specifically, check each matcher in order. if one fails to match, move
+   * onto next. */
+  /* TODO: does doing this require parsing expressions and whatever in the
+   * invoc? if so, might as well save the results if referenced using $ or
+   * whatever. If not, do another pass saving them. Except this is probably
+   * useless as different rules could have different starting points for exprs
+   * or whatever. Decision trees could avoid this, but they have their own
+   * issues. */
+  /* TODO: will need to modify the parser so that it can essentially "catch"
+   * errors - maybe "try_parse_expr" or whatever methods. */
+  // this technically creates a back-tracking parser - this will be the
+  // implementation style
+
+  /* then, after results are saved, generate the macro output from the
+   * transcriber token tree. if i understand this correctly, the macro
+   * invocation gets replaced by the transcriber tokens, except with
+   * substitutions made (e.g. for $i variables) */
+
+  /* TODO: it is probably better to modify AST::Token to store a pointer to a
+   * Lexer::Token (rather than being converted) - i.e. not so much have
+   * AST::Token as a Token but rather a TokenContainer (as it is another type of
+   * TokenTree). This will prevent re-conversion of Tokens between each type
+   * all the time, while still allowing the heterogenous storage of token trees.
+   */
+
+  AST::DelimTokenTree &invoc_token_tree = invoc.get_delim_tok_tree ();
+
+  // find matching arm
+  AST::MacroRule *matched_rule = nullptr;
+  std::map<std::string, MatchedFragmentContainer> matched_fragments;
+  for (auto &rule : rules_def.get_rules ())
+    {
+      sub_stack.push ();
+      bool did_match_rule = try_match_rule (rule, invoc_token_tree);
+      matched_fragments = sub_stack.pop ();
+
+      if (did_match_rule)
+	{
+	  //  // Debugging
+	  //  for (auto &kv : matched_fragments)
+	  //    rust_debug ("[fragment]: %s (%ld - %s)", kv.first.c_str (),
+	  //		kv.second.get_fragments ().size (),
+	  //		kv.second.get_kind ()
+	  //		    == MatchedFragmentContainer::Kind::Repetition
+	  //		  ? "repetition"
+	  //		  : "metavar");
+
+	  matched_rule = &rule;
+	  break;
+	}
+    }
+
+  if (matched_rule == nullptr)
+    {
+      RichLocation r (invoc_locus);
+      r.add_range (rules_def.get_locus ());
+      rust_error_at (r, "Failed to match any rule within macro");
+      return AST::ASTFragment::create_error ();
+    }
+
+  return transcribe_rule (*matched_rule, invoc_token_tree, matched_fragments,
+			  semicolon, peek_context ());
+}
+
+void
+MacroExpander::expand_invoc (AST::MacroInvocation &invoc, bool has_semicolon)
+{
+  if (depth_exceeds_recursion_limit ())
+    {
+      rust_error_at (invoc.get_locus (), "reached recursion limit");
+      return;
+    }
+
+  AST::MacroInvocData &invoc_data = invoc.get_invoc_data ();
+
+  // ??
+  // switch on type of macro:
+  //  - '!' syntax macro (inner switch)
+  //      - procedural macro - "A token-based function-like macro"
+  //      - 'macro_rules' (by example/pattern-match) macro? or not? "an
+  // AST-based function-like macro"
+  //      - else is unreachable
+  //  - attribute syntax macro (inner switch)
+  //  - procedural macro attribute syntax - "A token-based attribute
+  // macro"
+  //      - legacy macro attribute syntax? - "an AST-based attribute macro"
+  //      - non-macro attribute: mark known
+  //      - else is unreachable
+  //  - derive macro (inner switch)
+  //      - derive or legacy derive - "token-based" vs "AST-based"
+  //      - else is unreachable
+  //  - derive container macro - unreachable
+
+  // lookup the rules for this macro
+  NodeId resolved_node = UNKNOWN_NODEID;
+  NodeId source_node = UNKNOWN_NODEID;
+  if (has_semicolon)
+    source_node = invoc.get_macro_node_id ();
+  else
+    source_node = invoc.get_pattern_node_id ();
+  auto seg
+    = Resolver::CanonicalPath::new_seg (source_node,
+					invoc_data.get_path ().as_string ());
+
+  bool found = resolver->get_macro_scope ().lookup (seg, &resolved_node);
+  if (!found)
+    {
+      rust_error_at (invoc.get_locus (), "unknown macro: [%s]",
+		     seg.get ().c_str ());
+      return;
+    }
+
+  // lookup the rules
+  AST::MacroRulesDefinition *rules_def = nullptr;
+  bool ok = mappings->lookup_macro_def (resolved_node, &rules_def);
+  rust_assert (ok);
+
+  auto fragment = AST::ASTFragment::create_error ();
+
+  if (rules_def->is_builtin ())
+    fragment
+      = rules_def->get_builtin_transcriber () (invoc.get_locus (), invoc_data);
+  else
+    fragment = expand_decl_macro (invoc.get_locus (), invoc_data, *rules_def,
+				  has_semicolon);
+
+  set_expanded_fragment (std::move (fragment));
+}
+
+/* Determines whether any cfg predicate is false and hence item with attributes
+ * should be stripped. Note that attributes must be expanded before calling. */
+bool
+MacroExpander::fails_cfg (const AST::AttrVec &attrs) const
+{
+  for (const auto &attr : attrs)
+    {
+      if (attr.get_path () == "cfg" && !attr.check_cfg_predicate (session))
+	return true;
+    }
+  return false;
+}
+
+/* Determines whether any cfg predicate is false and hence item with attributes
+ * should be stripped. Will expand attributes as well. */
+bool
+MacroExpander::fails_cfg_with_expand (AST::AttrVec &attrs) const
+{
+  // TODO: maybe have something that strips cfg attributes that evaluate true?
+  for (auto &attr : attrs)
+    {
+      if (attr.get_path () == "cfg")
+	{
+	  if (!attr.is_parsed_to_meta_item ())
+	    attr.parse_attr_to_meta_item ();
+
+	  // DEBUG
+	  if (!attr.is_parsed_to_meta_item ())
+	    rust_debug ("failed to parse attr to meta item, right before "
+			"cfg predicate check");
+	  else
+	    rust_debug ("attr has been successfully parsed to meta item, "
+			"right before cfg predicate check");
+
+	  if (!attr.check_cfg_predicate (session))
+	    {
+	      // DEBUG
+	      rust_debug (
+		"cfg predicate failed for attribute: \033[0;31m'%s'\033[0m",
+		attr.as_string ().c_str ());
+
+	      return true;
+	    }
+	  else
+	    {
+	      // DEBUG
+	      rust_debug ("cfg predicate succeeded for attribute: "
+			  "\033[0;31m'%s'\033[0m",
+			  attr.as_string ().c_str ());
+	    }
+	}
+    }
+  return false;
+}
+
+// Expands cfg_attr attributes.
+void
+MacroExpander::expand_cfg_attrs (AST::AttrVec &attrs)
+{
+  for (std::size_t i = 0; i < attrs.size (); i++)
+    {
+      auto &attr = attrs[i];
+      if (attr.get_path () == "cfg_attr")
+	{
+	  if (!attr.is_parsed_to_meta_item ())
+	    attr.parse_attr_to_meta_item ();
+
+	  if (attr.check_cfg_predicate (session))
+	    {
+	      // split off cfg_attr
+	      AST::AttrVec new_attrs = attr.separate_cfg_attrs ();
+
+	      // remove attr from vector
+	      attrs.erase (attrs.begin () + i);
+
+	      // add new attrs to vector
+	      attrs.insert (attrs.begin () + i,
+			    std::make_move_iterator (new_attrs.begin ()),
+			    std::make_move_iterator (new_attrs.end ()));
+	    }
+
+	  /* do something - if feature (first token in tree) is in fact enabled,
+	   * make tokens listed afterwards into attributes. i.e.: for
+	   * [cfg_attr(feature = "wow", wow1, wow2)], if "wow" is true, then add
+	   * attributes [wow1] and [wow2] to attribute list. This can also be
+	   * recursive, so check for expanded attributes being recursive and
+	   * possibly recursively call the expand_attrs? */
+	}
+      else
+	{
+	  i++;
+	}
+    }
+  attrs.shrink_to_fit ();
+}
+
+void
+MacroExpander::expand_crate ()
+{
+  NodeId scope_node_id = crate.get_node_id ();
+  resolver->get_macro_scope ().push (scope_node_id);
+
+  /* fill macro/decorator map from init list? not sure where init list comes
+   * from? */
+
+  // TODO: does cfg apply for inner attributes? research.
+  // the apparent answer (from playground test) is yes
+
+  // expand crate cfg_attr attributes
+  expand_cfg_attrs (crate.inner_attrs);
+
+  if (fails_cfg_with_expand (crate.inner_attrs))
+    {
+      // basically, delete whole crate
+      crate.strip_crate ();
+      // TODO: maybe create warning here? probably not desired behaviour
+    }
+  // expand module attributes?
+
+  push_context (ITEM);
+
+  // expand attributes recursively and strip items if required
+  AttrVisitor attr_visitor (*this);
+  auto &items = crate.items;
+  for (auto it = items.begin (); it != items.end ();)
+    {
+      auto &item = *it;
+
+      // mark for stripping if required
+      item->accept_vis (attr_visitor);
+
+      auto fragment = take_expanded_fragment (attr_visitor);
+      if (fragment.should_expand ())
+	{
+	  // Remove the current expanded invocation
+	  it = items.erase (it);
+	  for (auto &node : fragment.get_nodes ())
+	    {
+	      it = items.insert (it, node.take_item ());
+	      it++;
+	    }
+	}
+      else if (item->is_marked_for_strip ())
+	it = items.erase (it);
+      else
+	it++;
+    }
+
+  pop_context ();
+
+  // TODO: should recursive attribute and macro expansion be done in the same
+  // transversal? Or in separate ones like currently?
+
+  // expand module tree recursively
+
+  // post-process
+
+  // extract exported macros?
+}
+
+bool
+MacroExpander::depth_exceeds_recursion_limit () const
+{
+  return expansion_depth >= cfg.recursion_limit;
+}
+
+bool
+MacroExpander::try_match_rule (AST::MacroRule &match_rule,
+			       AST::DelimTokenTree &invoc_token_tree)
+{
+  MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
+  Parser<MacroInvocLexer> parser (lex);
+
+  AST::MacroMatcher &matcher = match_rule.get_matcher ();
+
+  expansion_depth++;
+  if (!match_matcher (parser, matcher))
+    {
+      expansion_depth--;
+      return false;
+    }
+  expansion_depth--;
+
+  bool used_all_input_tokens = parser.skip_token (END_OF_FILE);
+  return used_all_input_tokens;
+}
+
+bool
+MacroExpander::match_fragment (Parser<MacroInvocLexer> &parser,
+			       AST::MacroMatchFragment &fragment)
+{
+  switch (fragment.get_frag_spec ().get_kind ())
+    {
+    case AST::MacroFragSpec::EXPR:
+      parser.parse_expr ();
+      break;
+
+    case AST::MacroFragSpec::BLOCK:
+      parser.parse_block_expr ();
+      break;
+
+    case AST::MacroFragSpec::IDENT:
+      parser.parse_identifier_pattern ();
+      break;
+
+    case AST::MacroFragSpec::LITERAL:
+      parser.parse_literal_expr ();
+      break;
+
+    case AST::MacroFragSpec::ITEM:
+      parser.parse_item (false);
+      break;
+
+    case AST::MacroFragSpec::TY:
+      parser.parse_type ();
+      break;
+
+    case AST::MacroFragSpec::PAT:
+      parser.parse_pattern ();
+      break;
+
+    case AST::MacroFragSpec::PATH:
+      parser.parse_path_in_expression ();
+      break;
+
+    case AST::MacroFragSpec::VIS:
+      parser.parse_visibility ();
+      break;
+
+      case AST::MacroFragSpec::STMT: {
+	auto restrictions = ParseRestrictions ();
+	restrictions.consume_semi = false;
+	parser.parse_stmt (restrictions);
+	break;
+      }
+
+    case AST::MacroFragSpec::LIFETIME:
+      parser.parse_lifetime_params ();
+      break;
+
+      // is meta attributes?
+    case AST::MacroFragSpec::META:
+      parser.parse_attribute_body ();
+      break;
+
+    case AST::MacroFragSpec::TT:
+      parser.parse_token_tree ();
+      break;
+
+      // i guess we just ignore invalid and just error out
+    case AST::MacroFragSpec::INVALID:
+      return false;
+    }
+
+  // it matches if the parser did not produce errors trying to parse that type
+  // of item
+  return !parser.has_errors ();
+}
+
+bool
+MacroExpander::match_matcher (Parser<MacroInvocLexer> &parser,
+			      AST::MacroMatcher &matcher)
+{
+  if (depth_exceeds_recursion_limit ())
+    {
+      rust_error_at (matcher.get_match_locus (), "reached recursion limit");
+      return false;
+    }
+
+  auto delimiter = parser.peek_current_token ();
+
+  // this is used so we can check that we delimit the stream correctly.
+  switch (delimiter->get_id ())
+    {
+      case LEFT_PAREN: {
+	if (!parser.skip_token (LEFT_PAREN))
+	  return false;
+      }
+      break;
+
+      case LEFT_SQUARE: {
+	if (!parser.skip_token (LEFT_SQUARE))
+	  return false;
+      }
+      break;
+
+      case LEFT_CURLY: {
+	if (!parser.skip_token (LEFT_CURLY))
+	  return false;
+      }
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  const MacroInvocLexer &source = parser.get_token_source ();
+
+  for (auto &match : matcher.get_matches ())
+    {
+      size_t offs_begin = source.get_offs ();
+
+      switch (match->get_macro_match_type ())
+	{
+	  case AST::MacroMatch::MacroMatchType::Fragment: {
+	    AST::MacroMatchFragment *fragment
+	      = static_cast<AST::MacroMatchFragment *> (match.get ());
+	    if (!match_fragment (parser, *fragment))
+	      return false;
+
+	    // matched fragment get the offset in the token stream
+	    size_t offs_end = source.get_offs ();
+	    sub_stack.insert_metavar (
+	      MatchedFragment (fragment->get_ident (), offs_begin, offs_end));
+	  }
+	  break;
+
+	  case AST::MacroMatch::MacroMatchType::Tok: {
+	    AST::Token *tok = static_cast<AST::Token *> (match.get ());
+	    if (!match_token (parser, *tok))
+	      return false;
+	  }
+	  break;
+
+	  case AST::MacroMatch::MacroMatchType::Repetition: {
+	    AST::MacroMatchRepetition *rep
+	      = static_cast<AST::MacroMatchRepetition *> (match.get ());
+	    if (!match_repetition (parser, *rep))
+	      return false;
+	  }
+	  break;
+
+	  case AST::MacroMatch::MacroMatchType::Matcher: {
+	    AST::MacroMatcher *m
+	      = static_cast<AST::MacroMatcher *> (match.get ());
+	    expansion_depth++;
+	    if (!match_matcher (parser, *m))
+	      {
+		expansion_depth--;
+		return false;
+	      }
+	    expansion_depth--;
+	  }
+	  break;
+	}
+    }
+
+  switch (delimiter->get_id ())
+    {
+      case LEFT_PAREN: {
+	if (!parser.skip_token (RIGHT_PAREN))
+	  return false;
+      }
+      break;
+
+      case LEFT_SQUARE: {
+	if (!parser.skip_token (RIGHT_SQUARE))
+	  return false;
+      }
+      break;
+
+      case LEFT_CURLY: {
+	if (!parser.skip_token (RIGHT_CURLY))
+	  return false;
+      }
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  return true;
+}
+
+bool
+MacroExpander::match_token (Parser<MacroInvocLexer> &parser, AST::Token &token)
+{
+  // FIXME this needs to actually match the content and the type
+  return parser.skip_token (token.get_id ());
+}
+
+bool
+MacroExpander::match_n_matches (Parser<MacroInvocLexer> &parser,
+				AST::MacroMatchRepetition &rep,
+				size_t &match_amount, size_t lo_bound,
+				size_t hi_bound)
+{
+  match_amount = 0;
+  auto &matches = rep.get_matches ();
+
+  const MacroInvocLexer &source = parser.get_token_source ();
+  while (true)
+    {
+      // If the current token is a closing macro delimiter, break away.
+      // TODO: Is this correct?
+      auto t_id = parser.peek_current_token ()->get_id ();
+      if (t_id == RIGHT_PAREN || t_id == RIGHT_SQUARE || t_id == RIGHT_CURLY)
+	break;
+
+      // Skip parsing a separator on the first match, otherwise consume it.
+      // If it isn't present, this is an error
+      if (rep.has_sep () && match_amount > 0)
+	if (!match_token (parser, *rep.get_sep ()))
+	  break;
+
+      bool valid_current_match = false;
+      for (auto &match : matches)
+	{
+	  size_t offs_begin = source.get_offs ();
+	  switch (match->get_macro_match_type ())
+	    {
+	      case AST::MacroMatch::MacroMatchType::Fragment: {
+		AST::MacroMatchFragment *fragment
+		  = static_cast<AST::MacroMatchFragment *> (match.get ());
+		valid_current_match = match_fragment (parser, *fragment);
+
+		// matched fragment get the offset in the token stream
+		size_t offs_end = source.get_offs ();
+
+		// The main difference with match_matcher happens here: Instead
+		// of inserting a new fragment, we append to one. If that
+		// fragment does not exist, then the operation is similar to
+		// `insert_fragment` with the difference that we are not
+		// creating a metavariable, but a repetition of one, which is
+		// really different.
+		sub_stack.append_fragment (
+		  MatchedFragment (fragment->get_ident (), offs_begin,
+				   offs_end));
+	      }
+	      break;
+
+	      case AST::MacroMatch::MacroMatchType::Tok: {
+		AST::Token *tok = static_cast<AST::Token *> (match.get ());
+		valid_current_match = match_token (parser, *tok);
+	      }
+	      break;
+
+	      case AST::MacroMatch::MacroMatchType::Repetition: {
+		AST::MacroMatchRepetition *rep
+		  = static_cast<AST::MacroMatchRepetition *> (match.get ());
+		valid_current_match = match_repetition (parser, *rep);
+	      }
+	      break;
+
+	      case AST::MacroMatch::MacroMatchType::Matcher: {
+		AST::MacroMatcher *m
+		  = static_cast<AST::MacroMatcher *> (match.get ());
+		valid_current_match = match_matcher (parser, *m);
+	      }
+	      break;
+	    }
+	}
+      // If we've encountered an error once, stop trying to match more
+      // repetitions
+      if (!valid_current_match)
+	break;
+
+      match_amount++;
+
+      // Break early if we notice there's too many expressions already
+      if (hi_bound && match_amount > hi_bound)
+	break;
+    }
+
+  // Check if the amount of matches we got is valid: Is it more than the lower
+  // bound and less than the higher bound?
+  bool did_meet_lo_bound = match_amount >= lo_bound;
+  bool did_meet_hi_bound = hi_bound ? match_amount <= hi_bound : true;
+
+  // If the end-result is valid, then we can clear the parse errors: Since
+  // repetitions are parsed eagerly, it is okay to fail in some cases
+  auto res = did_meet_lo_bound && did_meet_hi_bound;
+  if (res)
+    parser.clear_errors ();
+
+  return res;
+}
+
+bool
+MacroExpander::match_repetition (Parser<MacroInvocLexer> &parser,
+				 AST::MacroMatchRepetition &rep)
+{
+  size_t match_amount = 0;
+  bool res = false;
+
+  std::string lo_str;
+  std::string hi_str;
+  switch (rep.get_op ())
+    {
+    case AST::MacroMatchRepetition::MacroRepOp::ANY:
+      lo_str = "0";
+      hi_str = "+inf";
+      res = match_n_matches (parser, rep, match_amount);
+      break;
+    case AST::MacroMatchRepetition::MacroRepOp::ONE_OR_MORE:
+      lo_str = "1";
+      hi_str = "+inf";
+      res = match_n_matches (parser, rep, match_amount, 1);
+      break;
+    case AST::MacroMatchRepetition::MacroRepOp::ZERO_OR_ONE:
+      lo_str = "0";
+      hi_str = "1";
+      res = match_n_matches (parser, rep, match_amount, 0, 1);
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  if (!res)
+    rust_error_at (rep.get_match_locus (),
+		   "invalid amount of matches for macro invocation. Expected "
+		   "between %s and %s, got %lu",
+		   lo_str.c_str (), hi_str.c_str (),
+		   (unsigned long) match_amount);
+
+  rust_debug_loc (rep.get_match_locus (), "%s matched %lu times",
+		  res ? "successfully" : "unsuccessfully",
+		  (unsigned long) match_amount);
+
+  // We have to handle zero fragments differently: They will not have been
+  // "matched" but they are still valid and should be inserted as a special
+  // case. So we go through the stack map, and for every fragment which doesn't
+  // exist, insert a zero-matched fragment.
+  auto &stack_map = sub_stack.peek ();
+  for (auto &match : rep.get_matches ())
+    {
+      if (match->get_macro_match_type ()
+	  == AST::MacroMatch::MacroMatchType::Fragment)
+	{
+	  auto fragment = static_cast<AST::MacroMatchFragment *> (match.get ());
+	  auto it = stack_map.find (fragment->get_ident ());
+
+	  if (it == stack_map.end ())
+	    sub_stack.insert_matches (fragment->get_ident (),
+				      MatchedFragmentContainer::zero ());
+	}
+    }
+
+  return res;
+}
+
+/**
+ * Helper function to refactor calling a parsing function 0 or more times
+ */
+static AST::ASTFragment
+parse_many (Parser<MacroInvocLexer> &parser, TokenId &delimiter,
+	    std::function<AST::SingleASTNode ()> parse_fn)
+{
+  std::vector<AST::SingleASTNode> nodes;
+  while (true)
+    {
+      if (parser.peek_current_token ()->get_id () == delimiter)
+	break;
+
+      auto node = parse_fn ();
+      nodes.emplace_back (std::move (node));
+    }
+
+  return AST::ASTFragment (std::move (nodes));
+}
+
+/**
+ * Transcribe 0 or more items from a macro invocation
+ *
+ * @param parser Parser to extract items from
+ * @param delimiter Id of the token on which parsing should stop
+ */
+static AST::ASTFragment
+transcribe_many_items (Parser<MacroInvocLexer> &parser, TokenId &delimiter)
+{
+  return parse_many (parser, delimiter, [&parser] () {
+    auto item = parser.parse_item (true);
+    return AST::SingleASTNode (std::move (item));
+  });
+}
+
+/**
+ * Transcribe 0 or more external items from a macro invocation
+ *
+ * @param parser Parser to extract items from
+ * @param delimiter Id of the token on which parsing should stop
+ */
+static AST::ASTFragment
+transcribe_many_ext (Parser<MacroInvocLexer> &parser, TokenId &delimiter)
+{
+  return parse_many (parser, delimiter, [&parser] () {
+    auto item = parser.parse_external_item ();
+    return AST::SingleASTNode (std::move (item));
+  });
+}
+
+/**
+ * Transcribe 0 or more trait items from a macro invocation
+ *
+ * @param parser Parser to extract items from
+ * @param delimiter Id of the token on which parsing should stop
+ */
+static AST::ASTFragment
+transcribe_many_trait_items (Parser<MacroInvocLexer> &parser,
+			     TokenId &delimiter)
+{
+  return parse_many (parser, delimiter, [&parser] () {
+    auto item = parser.parse_trait_item ();
+    return AST::SingleASTNode (std::move (item));
+  });
+}
+
+/**
+ * Transcribe 0 or more impl items from a macro invocation
+ *
+ * @param parser Parser to extract items from
+ * @param delimiter Id of the token on which parsing should stop
+ */
+static AST::ASTFragment
+transcribe_many_impl_items (Parser<MacroInvocLexer> &parser, TokenId &delimiter)
+{
+  return parse_many (parser, delimiter, [&parser] () {
+    auto item = parser.parse_inherent_impl_item ();
+    return AST::SingleASTNode (std::move (item));
+  });
+}
+
+/**
+ * Transcribe 0 or more trait impl items from a macro invocation
+ *
+ * @param parser Parser to extract items from
+ * @param delimiter Id of the token on which parsing should stop
+ */
+static AST::ASTFragment
+transcribe_many_trait_impl_items (Parser<MacroInvocLexer> &parser,
+				  TokenId &delimiter)
+{
+  return parse_many (parser, delimiter, [&parser] () {
+    auto item = parser.parse_trait_impl_item ();
+    return AST::SingleASTNode (std::move (item));
+  });
+}
+
+/**
+ * Transcribe 0 or more statements from a macro invocation
+ *
+ * @param parser Parser to extract statements from
+ * @param delimiter Id of the token on which parsing should stop
+ */
+static AST::ASTFragment
+transcribe_many_stmts (Parser<MacroInvocLexer> &parser, TokenId &delimiter)
+{
+  auto restrictions = ParseRestrictions ();
+  restrictions.consume_semi = false;
+
+  // FIXME: This is invalid! It needs to also handle cases where the macro
+  // transcriber is an expression, but since the macro call is followed by
+  // a semicolon, it's a valid ExprStmt
+  return parse_many (parser, delimiter, [&parser, restrictions] () {
+    auto stmt = parser.parse_stmt (restrictions);
+    return AST::SingleASTNode (std::move (stmt));
+  });
+}
+
+/**
+ * Transcribe one expression from a macro invocation
+ *
+ * @param parser Parser to extract statements from
+ */
+static AST::ASTFragment
+transcribe_expression (Parser<MacroInvocLexer> &parser)
+{
+  auto expr = parser.parse_expr ();
+
+  return AST::ASTFragment ({std::move (expr)});
+}
+
+/**
+ * Transcribe one type from a macro invocation
+ *
+ * @param parser Parser to extract statements from
+ */
+static AST::ASTFragment
+transcribe_type (Parser<MacroInvocLexer> &parser)
+{
+  auto type = parser.parse_type ();
+
+  return AST::ASTFragment ({std::move (type)});
+}
+
+static AST::ASTFragment
+transcribe_on_delimiter (Parser<MacroInvocLexer> &parser, bool semicolon,
+			 AST::DelimType delimiter, TokenId last_token_id)
+{
+  if (semicolon || delimiter == AST::DelimType::CURLY)
+    return transcribe_many_stmts (parser, last_token_id);
+  else
+    return transcribe_expression (parser);
+} // namespace Rust
+
+static AST::ASTFragment
+transcribe_context (MacroExpander::ContextType ctx,
+		    Parser<MacroInvocLexer> &parser, bool semicolon,
+		    AST::DelimType delimiter, TokenId last_token_id)
+{
+  // The flow-chart in order to choose a parsing function is as follows:
+  //
+  // [switch special context]
+  //     -- Item --> parser.parse_item();
+  //     -- Trait --> parser.parse_trait_item();
+  //     -- Impl --> parser.parse_impl_item();
+  //     -- Extern --> parser.parse_extern_item();
+  //     -- None --> [has semicolon?]
+  //                 -- Yes --> parser.parse_stmt();
+  //                 -- No --> [switch invocation.delimiter()]
+  //                             -- { } --> parser.parse_stmt();
+  //                             -- _ --> parser.parse_expr(); // once!
+
+  // If there is a semicolon OR we are expanding a MacroInvocationSemi, then
+  // we can parse multiple items. Otherwise, parse *one* expression
+
+  switch (ctx)
+    {
+    case MacroExpander::ContextType::ITEM:
+      return transcribe_many_items (parser, last_token_id);
+      break;
+    case MacroExpander::ContextType::TRAIT:
+      return transcribe_many_trait_items (parser, last_token_id);
+      break;
+    case MacroExpander::ContextType::IMPL:
+      return transcribe_many_impl_items (parser, last_token_id);
+      break;
+    case MacroExpander::ContextType::TRAIT_IMPL:
+      return transcribe_many_trait_impl_items (parser, last_token_id);
+      break;
+    case MacroExpander::ContextType::EXTERN:
+      return transcribe_many_ext (parser, last_token_id);
+      break;
+    case MacroExpander::ContextType::TYPE:
+      return transcribe_type (parser);
+      break;
+    default:
+      return transcribe_on_delimiter (parser, semicolon, delimiter,
+				      last_token_id);
+    }
+}
+
+static std::string
+tokens_to_str (std::vector<std::unique_ptr<AST::Token>> &tokens)
+{
+  std::string str;
+  if (!tokens.empty ())
+    {
+      str += tokens[0]->as_string ();
+      for (size_t i = 1; i < tokens.size (); i++)
+	str += " " + tokens[i]->as_string ();
+    }
+
+  return str;
+}
+
+AST::ASTFragment
+MacroExpander::transcribe_rule (
+  AST::MacroRule &match_rule, AST::DelimTokenTree &invoc_token_tree,
+  std::map<std::string, MatchedFragmentContainer> &matched_fragments,
+  bool semicolon, ContextType ctx)
+{
+  // we can manipulate the token tree to substitute the dollar identifiers so
+  // that when we call parse its already substituted for us
+  AST::MacroTranscriber &transcriber = match_rule.get_transcriber ();
+  AST::DelimTokenTree &transcribe_tree = transcriber.get_token_tree ();
+
+  auto invoc_stream = invoc_token_tree.to_token_stream ();
+  auto macro_rule_tokens = transcribe_tree.to_token_stream ();
+
+  auto substitute_context
+    = SubstituteCtx (invoc_stream, macro_rule_tokens, matched_fragments);
+  std::vector<std::unique_ptr<AST::Token>> substituted_tokens
+    = substitute_context.substitute_tokens ();
+
+  rust_debug ("substituted tokens: %s",
+	      tokens_to_str (substituted_tokens).c_str ());
+
+  // parse it to an ASTFragment
+  MacroInvocLexer lex (std::move (substituted_tokens));
+  Parser<MacroInvocLexer> parser (lex);
+
+  auto last_token_id = TokenId::RIGHT_CURLY;
+
+  // this is used so we can check that we delimit the stream correctly.
+  switch (transcribe_tree.get_delim_type ())
+    {
+    case AST::DelimType::PARENS:
+      last_token_id = TokenId::RIGHT_PAREN;
+      rust_assert (parser.skip_token (LEFT_PAREN));
+      break;
+
+    case AST::DelimType::CURLY:
+      rust_assert (parser.skip_token (LEFT_CURLY));
+      break;
+
+    case AST::DelimType::SQUARE:
+      last_token_id = TokenId::RIGHT_SQUARE;
+      rust_assert (parser.skip_token (LEFT_SQUARE));
+      break;
+    }
+
+  // see https://github.com/Rust-GCC/gccrs/issues/22
+  // TL;DR:
+  //   - Treat all macro invocations with parentheses, (), or square brackets,
+  //   [], as expressions.
+  //   - If the macro invocation has curly brackets, {}, it may be parsed as a
+  //   statement depending on the context.
+  //   - If the macro invocation has a semicolon at the end, it must be parsed
+  //   as a statement (either via ExpressionStatement or
+  //   MacroInvocationWithSemi)
+
+  auto fragment
+    = transcribe_context (ctx, parser, semicolon,
+			  invoc_token_tree.get_delim_type (), last_token_id);
+
+  // emit any errors
+  if (parser.has_errors ())
+    {
+      for (auto &err : parser.get_errors ())
+	rust_error_at (err.locus, "%s", err.message.c_str ());
+      return AST::ASTFragment::create_error ();
+    }
+
+  // are all the tokens used?
+  bool did_delimit = parser.skip_token (last_token_id);
+
+  bool reached_end_of_stream = did_delimit && parser.skip_token (END_OF_FILE);
+  if (!reached_end_of_stream)
+    {
+      const_TokenPtr current_token = parser.peek_current_token ();
+      rust_error_at (current_token->get_locus (),
+		     "tokens here and after are unparsed");
+    }
+
+  return fragment;
+}
+} // namespace Rust
diff --git a/gcc/rust/expand/rust-macro-expand.h b/gcc/rust/expand/rust-macro-expand.h
new file mode 100644
index 00000000000..94d6702ecb8
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-expand.h
@@ -0,0 +1,366 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_MACRO_EXPAND_H
+#define RUST_MACRO_EXPAND_H
+
+#include "rust-buffered-queue.h"
+#include "rust-parse.h"
+#include "rust-token.h"
+#include "rust-ast.h"
+#include "rust-macro.h"
+#include "rust-hir-map.h"
+#include "rust-name-resolver.h"
+#include "rust-macro-invoc-lexer.h"
+
+// Provides objects and method prototypes for macro expansion
+
+namespace Rust {
+// forward decls for AST
+namespace AST {
+class MacroInvocation;
+}
+
+// Object used to store configuration data for macro expansion.
+// NOTE: Keep all these items complying with the latest rustc.
+struct ExpansionCfg
+{
+  // features?
+  // TODO: Add `features' when we have it.
+  unsigned int recursion_limit = 1024;
+  bool trace_mac = false;   // trace macro
+  bool should_test = false; // strip #[test] nodes if false
+  bool keep_macs = false;   // keep macro definitions
+  std::string crate_name = "";
+};
+
+struct MatchedFragment
+{
+  std::string fragment_ident;
+  size_t token_offset_begin;
+  size_t token_offset_end;
+
+  MatchedFragment (std::string identifier, size_t token_offset_begin,
+		   size_t token_offset_end)
+    : fragment_ident (identifier), token_offset_begin (token_offset_begin),
+      token_offset_end (token_offset_end)
+  {}
+
+  /**
+   * Empty constructor for uninitialized fragments
+   */
+  MatchedFragment () : MatchedFragment ("", 0, 0) {}
+
+  std::string as_string () const
+  {
+    return fragment_ident + "=" + std::to_string (token_offset_begin) + ":"
+	   + std::to_string (token_offset_end);
+  }
+};
+
+class MatchedFragmentContainer
+{
+public:
+  // Does the container refer to a simple metavariable, different from a
+  // repetition repeated once
+  enum class Kind
+  {
+    MetaVar,
+    Repetition,
+  };
+
+  MatchedFragmentContainer (std::vector<MatchedFragment> fragments,
+			    Kind kind = Kind::Repetition)
+    : fragments (fragments), kind (kind)
+  {}
+
+  /**
+   * Create a valid fragment matched zero times. This is useful for repetitions
+   * which allow the absence of a fragment, such as * and ?
+   */
+  static MatchedFragmentContainer zero ()
+  {
+    return MatchedFragmentContainer ({});
+  }
+
+  /**
+   * Create a valid fragment matched one time
+   */
+  static MatchedFragmentContainer metavar (MatchedFragment fragment)
+  {
+    return MatchedFragmentContainer ({fragment}, Kind::MetaVar);
+  }
+
+  /**
+   * Add a matched fragment to the container
+   */
+  void add_fragment (MatchedFragment fragment)
+  {
+    rust_assert (!is_single_fragment ());
+
+    fragments.emplace_back (fragment);
+  }
+
+  size_t get_match_amount () const { return fragments.size (); }
+  const std::vector<MatchedFragment> &get_fragments () const
+  {
+    return fragments;
+  }
+  // const std::string &get_fragment_name () const { return fragment_name; }
+
+  bool is_single_fragment () const
+  {
+    return get_match_amount () == 1 && kind == Kind::MetaVar;
+  }
+
+  const MatchedFragment get_single_fragment () const
+  {
+    rust_assert (is_single_fragment ());
+
+    return fragments[0];
+  }
+
+  const Kind &get_kind () const { return kind; }
+
+private:
+  /**
+   * Fragments matched `match_amount` times. This can be an empty vector
+   * in case having zero matches is allowed (i.e ? or * operators)
+   */
+  std::vector<MatchedFragment> fragments;
+  Kind kind;
+};
+
+class SubstitutionScope
+{
+public:
+  SubstitutionScope () : stack () {}
+
+  void push () { stack.push_back ({}); }
+
+  std::map<std::string, MatchedFragmentContainer> pop ()
+  {
+    auto top = stack.back ();
+    stack.pop_back ();
+    return top;
+  }
+
+  std::map<std::string, MatchedFragmentContainer> &peek ()
+  {
+    return stack.back ();
+  }
+
+  /**
+   * Insert a new matched metavar into the current substitution map
+   */
+  void insert_metavar (MatchedFragment fragment)
+  {
+    auto &current_map = stack.back ();
+    auto it = current_map.find (fragment.fragment_ident);
+
+    if (it == current_map.end ())
+      current_map.insert ({fragment.fragment_ident,
+			   MatchedFragmentContainer::metavar (fragment)});
+    else
+      gcc_unreachable ();
+  }
+
+  /**
+   * Append a new matched fragment to a repetition into the current substitution
+   * map
+   */
+  void append_fragment (MatchedFragment fragment)
+  {
+    auto &current_map = stack.back ();
+    auto it = current_map.find (fragment.fragment_ident);
+
+    if (it == current_map.end ())
+      current_map.insert (
+	{fragment.fragment_ident, MatchedFragmentContainer ({fragment})});
+    else
+      it->second.add_fragment (fragment);
+  }
+
+  void insert_matches (std::string key, MatchedFragmentContainer matches)
+  {
+    auto &current_map = stack.back ();
+    auto it = current_map.find (key);
+    rust_assert (it == current_map.end ());
+
+    current_map.insert ({key, matches});
+  }
+
+private:
+  std::vector<std::map<std::string, MatchedFragmentContainer>> stack;
+};
+
+// Object used to store shared data (between functions) for macro expansion.
+struct MacroExpander
+{
+  enum ContextType
+  {
+    ITEM,
+    BLOCK,
+    EXTERN,
+    TYPE,
+    TRAIT,
+    IMPL,
+    TRAIT_IMPL,
+  };
+
+  ExpansionCfg cfg;
+  unsigned int expansion_depth = 0;
+
+  MacroExpander (AST::Crate &crate, ExpansionCfg cfg, Session &session)
+    : cfg (cfg), crate (crate), session (session),
+      sub_stack (SubstitutionScope ()),
+      expanded_fragment (AST::ASTFragment::create_error ()),
+      resolver (Resolver::Resolver::get ()),
+      mappings (Analysis::Mappings::get ())
+  {}
+
+  ~MacroExpander () = default;
+
+  // Expands all macros in the crate passed in.
+  void expand_crate ();
+
+  /* Expands a macro invocation - possibly make both
+   * have similar duck-typed interface and use templates?*/
+  // should this be public or private?
+  void expand_invoc (AST::MacroInvocation &invoc, bool has_semicolon);
+
+  // Expands a single declarative macro.
+  AST::ASTFragment expand_decl_macro (Location locus,
+				      AST::MacroInvocData &invoc,
+				      AST::MacroRulesDefinition &rules_def,
+				      bool semicolon);
+
+  void expand_cfg_attrs (AST::AttrVec &attrs);
+  bool fails_cfg (const AST::AttrVec &attr) const;
+  bool fails_cfg_with_expand (AST::AttrVec &attrs) const;
+
+  bool depth_exceeds_recursion_limit () const;
+
+  bool try_match_rule (AST::MacroRule &match_rule,
+		       AST::DelimTokenTree &invoc_token_tree);
+
+  AST::ASTFragment transcribe_rule (
+    AST::MacroRule &match_rule, AST::DelimTokenTree &invoc_token_tree,
+    std::map<std::string, MatchedFragmentContainer> &matched_fragments,
+    bool semicolon, ContextType ctx);
+
+  bool match_fragment (Parser<MacroInvocLexer> &parser,
+		       AST::MacroMatchFragment &fragment);
+
+  bool match_token (Parser<MacroInvocLexer> &parser, AST::Token &token);
+
+  bool match_repetition (Parser<MacroInvocLexer> &parser,
+			 AST::MacroMatchRepetition &rep);
+
+  bool match_matcher (Parser<MacroInvocLexer> &parser,
+		      AST::MacroMatcher &matcher);
+
+  /**
+   * Match any amount of matches
+   *
+   * @param parser Parser to use for matching
+   * @param rep Repetition to try and match
+   * @param match_amount Reference in which to store the ammount of succesful
+   * and valid matches
+   *
+   * @param lo_bound Lower bound of the matcher. When specified, the matcher
+   * will only succeed if it parses at *least* `lo_bound` fragments. If
+   * unspecified, the matcher could succeed when parsing 0 fragments.
+   *
+   * @param hi_bound Higher bound of the matcher. When specified, the matcher
+   * will only succeed if it parses *less than* `hi_bound` fragments. If
+   * unspecified, the matcher could succeed when parsing an infinity of
+   * fragments.
+   *
+   * @return true if matching was successful and within the given limits, false
+   * otherwise
+   */
+  bool match_n_matches (Parser<MacroInvocLexer> &parser,
+			AST::MacroMatchRepetition &rep, size_t &match_amount,
+			size_t lo_bound = 0, size_t hi_bound = 0);
+
+  void push_context (ContextType t) { context.push_back (t); }
+
+  ContextType pop_context ()
+  {
+    rust_assert (!context.empty ());
+
+    ContextType t = context.back ();
+    context.pop_back ();
+
+    return t;
+  }
+
+  ContextType peek_context () { return context.back (); }
+
+  void set_expanded_fragment (AST::ASTFragment &&fragment)
+  {
+    expanded_fragment = std::move (fragment);
+  }
+
+  AST::ASTFragment take_expanded_fragment (AST::ASTVisitor &vis)
+  {
+    AST::ASTFragment old_fragment = std::move (expanded_fragment);
+    auto accumulator = std::vector<AST::SingleASTNode> ();
+    expanded_fragment = AST::ASTFragment::create_error ();
+
+    for (auto &node : old_fragment.get_nodes ())
+      {
+	expansion_depth++;
+	node.accept_vis (vis);
+	// we'll decide the next move according to the outcome of the macro
+	// expansion
+	if (expanded_fragment.is_error ())
+	  accumulator.push_back (node); // if expansion fails, there might be a
+					// non-macro expression we need to keep
+	else
+	  {
+	    // if expansion succeeded, then we need to merge the fragment with
+	    // the contents in the accumulator, so that our final expansion
+	    // result will contain non-macro nodes as it should
+	    auto new_nodes = expanded_fragment.get_nodes ();
+	    std::move (new_nodes.begin (), new_nodes.end (),
+		       std::back_inserter (accumulator));
+	    expanded_fragment = AST::ASTFragment (accumulator);
+	  }
+	expansion_depth--;
+      }
+
+    return old_fragment;
+  }
+
+private:
+  AST::Crate &crate;
+  Session &session;
+  SubstitutionScope sub_stack;
+  std::vector<ContextType> context;
+  AST::ASTFragment expanded_fragment;
+
+public:
+  Resolver::Resolver *resolver;
+  Analysis::Mappings *mappings;
+};
+
+} // namespace Rust
+
+#endif
diff --git a/gcc/rust/expand/rust-macro-invoc-lexer.cc b/gcc/rust/expand/rust-macro-invoc-lexer.cc
new file mode 100644
index 00000000000..8a43d29e0d1
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-invoc-lexer.cc
@@ -0,0 +1,29 @@
+#include "rust-macro-invoc-lexer.h"
+
+namespace Rust {
+
+const_TokenPtr
+MacroInvocLexer::peek_token (int n)
+{
+  if ((offs + n) >= token_stream.size ())
+    return Token::make (END_OF_FILE, Location ());
+
+  return token_stream.at (offs + n)->get_tok_ptr ();
+}
+
+// Advances current token to n + 1 tokens ahead of current position.
+void
+MacroInvocLexer::skip_token (int n)
+{
+  offs += (n + 1);
+}
+
+void
+MacroInvocLexer::split_current_token (TokenId new_left __attribute__ ((unused)),
+				      TokenId new_right
+				      __attribute__ ((unused)))
+{
+  // FIXME
+  gcc_unreachable ();
+}
+} // namespace Rust
diff --git a/gcc/rust/expand/rust-macro-invoc-lexer.h b/gcc/rust/expand/rust-macro-invoc-lexer.h
new file mode 100644
index 00000000000..0fd4554d02f
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-invoc-lexer.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#ifndef RUST_MACRO_INVOC_LEXER_H
+#define RUST_MACRO_INVOC_LEXER_H
+
+#include "rust-ast.h"
+
+namespace Rust {
+class MacroInvocLexer
+{
+public:
+  MacroInvocLexer (std::vector<std::unique_ptr<AST::Token>> stream)
+    : offs (0), token_stream (std::move (stream))
+  {}
+
+  // Returns token n tokens ahead of current position.
+  const_TokenPtr peek_token (int n);
+
+  // Peeks the current token.
+  const_TokenPtr peek_token () { return peek_token (0); }
+
+  // Advances current token to n + 1 tokens ahead of current position.
+  void skip_token (int n);
+
+  // Skips the current token.
+  void skip_token () { skip_token (0); }
+
+  // Splits the current token into two. Intended for use with nested generics
+  // closes (i.e. T<U<X>> where >> is wrongly lexed as one token). Note that
+  // this will only work with "simple" tokens like punctuation.
+  void split_current_token (TokenId new_left, TokenId new_right);
+
+  std::string get_filename () const
+  {
+    // FIXME
+    gcc_unreachable ();
+    return "FIXME";
+  }
+
+  size_t get_offs () const { return offs; }
+
+private:
+  size_t offs;
+  std::vector<std::unique_ptr<AST::Token>> token_stream;
+};
+} // namespace Rust
+
+#endif // RUST_MACRO_INVOC_LEXER_H
diff --git a/gcc/rust/expand/rust-macro-substitute-ctx.cc b/gcc/rust/expand/rust-macro-substitute-ctx.cc
new file mode 100644
index 00000000000..9592d2d2a9e
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-substitute-ctx.cc
@@ -0,0 +1,312 @@
+#include "rust-macro-substitute-ctx.h"
+
+namespace Rust {
+
+std::vector<std::unique_ptr<AST::Token>>
+SubstituteCtx::substitute_metavar (std::unique_ptr<AST::Token> &metavar)
+{
+  auto metavar_name = metavar->get_str ();
+
+  std::vector<std::unique_ptr<AST::Token>> expanded;
+  auto it = fragments.find (metavar_name);
+  if (it == fragments.end ())
+    {
+      // Return a copy of the original token
+      expanded.push_back (metavar->clone_token ());
+    }
+  else
+    {
+      // If we are expanding a metavar which has a lof of matches, we are
+      // currently expanding a repetition metavar - not a simple metavar. We
+      // need to error out and inform the user.
+      // Associated test case for an example: compile/macro-issue1224.rs
+      if (it->second.get_match_amount () != 1)
+	{
+	  rust_error_at (metavar->get_locus (),
+			 "metavariable is still repeating at this depth");
+	  rust_inform (
+	    metavar->get_locus (),
+	    "you probably forgot the repetition operator: %<%s%s%s%>", "$(",
+	    metavar->as_string ().c_str (), ")*");
+	  return expanded;
+	}
+
+      // We only care about the vector when expanding repetitions.
+      // Just access the first element of the vector.
+      auto &frag = it->second.get_single_fragment ();
+      for (size_t offs = frag.token_offset_begin; offs < frag.token_offset_end;
+	   offs++)
+	{
+	  auto &tok = input.at (offs);
+	  expanded.push_back (tok->clone_token ());
+	}
+    }
+
+  return expanded;
+}
+
+bool
+SubstituteCtx::check_repetition_amount (size_t pattern_start,
+					size_t pattern_end,
+					size_t &expected_repetition_amount)
+{
+  bool first_fragment_found = false;
+  bool is_valid = true;
+
+  for (size_t i = pattern_start; i < pattern_end; i++)
+    {
+      if (macro.at (i)->get_id () == DOLLAR_SIGN)
+	{
+	  auto &frag_token = macro.at (i + 1);
+	  if (frag_token->get_id () == IDENTIFIER)
+	    {
+	      auto it = fragments.find (frag_token->get_str ());
+	      if (it == fragments.end ())
+		{
+		  // If the repetition is not anything we know (ie no declared
+		  // metavars, or metavars which aren't present in the
+		  // fragment), we can just error out. No need to paste the
+		  // tokens as if nothing had happened.
+		  rust_error_at (frag_token->get_locus (),
+				 "metavar %s used in repetition does not exist",
+				 frag_token->get_str ().c_str ());
+
+		  is_valid = false;
+		}
+
+	      auto &fragment = it->second;
+
+	      size_t repeat_amount = fragment.get_match_amount ();
+	      if (!first_fragment_found)
+		{
+		  first_fragment_found = true;
+		  expected_repetition_amount = repeat_amount;
+		}
+	      else
+		{
+		  if (repeat_amount != expected_repetition_amount
+		      && !fragment.is_single_fragment ())
+		    {
+		      rust_error_at (
+			frag_token->get_locus (),
+			"different amount of matches used in merged "
+			"repetitions: expected %lu, got %lu",
+			(unsigned long) expected_repetition_amount,
+			(unsigned long) repeat_amount);
+		      is_valid = false;
+		    }
+		}
+	    }
+	}
+    }
+
+  return is_valid;
+}
+
+std::vector<std::unique_ptr<AST::Token>>
+SubstituteCtx::substitute_repetition (
+  size_t pattern_start, size_t pattern_end,
+  std::unique_ptr<AST::Token> separator_token)
+{
+  rust_assert (pattern_end < macro.size ());
+
+  size_t repeat_amount = 0;
+  if (!check_repetition_amount (pattern_start, pattern_end, repeat_amount))
+    return {};
+
+  rust_debug ("repetition amount to use: %lu", (unsigned long) repeat_amount);
+  std::vector<std::unique_ptr<AST::Token>> expanded;
+  std::vector<std::unique_ptr<AST::Token>> new_macro;
+
+  // We want to generate a "new macro" to substitute with. This new macro
+  // should contain only the tokens inside the pattern
+  for (size_t tok_idx = pattern_start; tok_idx < pattern_end; tok_idx++)
+    new_macro.emplace_back (macro.at (tok_idx)->clone_token ());
+
+  // Then, we want to create a subset of the matches so that
+  // `substitute_tokens()` can only see one fragment per metavar. Let's say we
+  // have the following user input: (1 145 'h')
+  // on the following match arm: ($($lit:literal)*)
+  // which causes the following matches: { "lit": [1, 145, 'h'] }
+  //
+  // The pattern (new_macro) is `$lit:literal`
+  // The first time we expand it, we want $lit to have the following token: 1
+  // The second time, 145
+  // The third and final time, 'h'
+  //
+  // In order to do so we must create "sub maps", which only contain parts of
+  // the original matches
+  // sub-maps: [ { "lit": 1 }, { "lit": 145 }, { "lit": 'h' } ]
+  //
+  // and give them to `substitute_tokens` one by one.
+
+  for (size_t i = 0; i < repeat_amount; i++)
+    {
+      std::map<std::string, MatchedFragmentContainer> sub_map;
+      for (auto &kv_match : fragments)
+	{
+	  MatchedFragment sub_fragment;
+
+	  // FIXME: Hack: If a fragment is not repeated, how does it fit in the
+	  // submap? Do we really want to expand it? Is this normal behavior?
+	  if (kv_match.second.is_single_fragment ())
+	    sub_fragment = kv_match.second.get_single_fragment ();
+	  else
+	    sub_fragment = kv_match.second.get_fragments ()[i];
+
+	  sub_map.insert (
+	    {kv_match.first, MatchedFragmentContainer::metavar (sub_fragment)});
+	}
+
+      auto substitute_context = SubstituteCtx (input, new_macro, sub_map);
+      auto new_tokens = substitute_context.substitute_tokens ();
+
+      // Skip the first repetition, but add the separator to the expanded
+      // tokens if it is present
+      if (i != 0 && separator_token)
+	expanded.emplace_back (separator_token->clone_token ());
+
+      for (auto &new_token : new_tokens)
+	expanded.emplace_back (new_token->clone_token ());
+    }
+
+  // FIXME: We also need to make sure that all subsequent fragments
+  // contain the same amount of repetitions as the first one
+
+  return expanded;
+}
+
+static bool
+is_rep_op (std::unique_ptr<AST::Token> &tok)
+{
+  auto id = tok->get_id ();
+  return id == QUESTION_MARK || id == ASTERISK || id == PLUS;
+}
+
+std::pair<std::vector<std::unique_ptr<AST::Token>>, size_t>
+SubstituteCtx::substitute_token (size_t token_idx)
+{
+  auto &token = macro.at (token_idx);
+  switch (token->get_id ())
+    {
+    case IDENTIFIER:
+      rust_debug ("expanding metavar: %s", token->get_str ().c_str ());
+      return {substitute_metavar (token), 1};
+      case LEFT_PAREN: {
+	// We need to parse up until the closing delimiter and expand this
+	// fragment->n times.
+	rust_debug ("expanding repetition");
+
+	// We're in a context where macro repetitions have already been
+	// parsed and validated: This means that
+	// 1/ There will be no delimiters as that is an error
+	// 2/ There are no fragment specifiers anymore, which prevents us
+	// from reusing parser functions.
+	//
+	// Repetition patterns are also special in that they cannot contain
+	// "rogue" delimiters: For example, this is invalid, as they are
+	// parsed as MacroMatches and must contain a correct amount of
+	// delimiters.
+	// `$($e:expr ) )`
+	//            ^ rogue closing parenthesis
+	//
+	// With all of that in mind, we can simply skip ahead from one
+	// parenthesis to the other to find the pattern to expand. Of course,
+	// pairs of delimiters, including parentheses, are allowed.
+	// `$($e:expr ( ) )`
+	// Parentheses are the sole delimiter for which we need a special
+	// behavior since they delimit the repetition pattern
+
+	size_t pattern_start = token_idx + 1;
+	size_t pattern_end = pattern_start;
+	auto parentheses_stack = 0;
+	for (size_t idx = pattern_start; idx < macro.size (); idx++)
+	  {
+	    if (macro.at (idx)->get_id () == LEFT_PAREN)
+	      {
+		parentheses_stack++;
+	      }
+	    else if (macro.at (idx)->get_id () == RIGHT_PAREN)
+	      {
+		if (parentheses_stack == 0)
+		  {
+		    pattern_end = idx;
+		    break;
+		  }
+		parentheses_stack--;
+	      }
+	  }
+
+	// Unreachable case, but let's make sure we don't ever run into it
+	rust_assert (pattern_end != pattern_start);
+
+	std::unique_ptr<AST::Token> separator_token = nullptr;
+	if (pattern_end + 1 <= macro.size ())
+	  {
+	    auto &post_pattern_token = macro.at (pattern_end + 1);
+	    if (!is_rep_op (post_pattern_token))
+	      separator_token = post_pattern_token->clone_token ();
+	  }
+
+	// Amount of tokens to skip
+	auto to_skip = 0;
+	// Parentheses
+	to_skip += 2;
+	// Repetition operator
+	to_skip += 1;
+	// Separator
+	if (separator_token)
+	  to_skip += 1;
+
+	return {substitute_repetition (pattern_start, pattern_end,
+				       std::move (separator_token)),
+		pattern_end - pattern_start + to_skip};
+      }
+      // TODO: We need to check if the $ was alone. In that case, do
+      // not error out: Simply act as if there was an empty identifier
+      // with no associated fragment and paste the dollar sign in the
+      // transcription. Unsure how to do that since we always have at
+      // least the closing curly brace after an empty $...
+    default:
+      rust_error_at (token->get_locus (),
+		     "unexpected token in macro transcribe: expected "
+		     "%<(%> or identifier after %<$%>, got %<%s%>",
+		     get_token_description (token->get_id ()));
+    }
+
+  // FIXME: gcc_unreachable() error case?
+  return {std::vector<std::unique_ptr<AST::Token>> (), 0};
+}
+
+std::vector<std::unique_ptr<AST::Token>>
+SubstituteCtx::substitute_tokens ()
+{
+  std::vector<std::unique_ptr<AST::Token>> replaced_tokens;
+  rust_debug ("expanding tokens");
+
+  for (size_t i = 0; i < macro.size (); i++)
+    {
+      auto &tok = macro.at (i);
+      if (tok->get_id () == DOLLAR_SIGN)
+	{
+	  // Aaaaah, if only we had C++17 :)
+	  // auto [expanded, tok_to_skip] = ...
+	  auto p = substitute_token (i + 1);
+	  auto expanded = std::move (p.first);
+	  auto tok_to_skip = p.second;
+
+	  i += tok_to_skip;
+
+	  for (auto &token : expanded)
+	    replaced_tokens.emplace_back (token->clone_token ());
+	}
+      else
+	{
+	  replaced_tokens.emplace_back (tok->clone_token ());
+	}
+    }
+
+  return replaced_tokens;
+}
+
+} // namespace Rust
diff --git a/gcc/rust/expand/rust-macro-substitute-ctx.h b/gcc/rust/expand/rust-macro-substitute-ctx.h
new file mode 100644
index 00000000000..81dcab7643b
--- /dev/null
+++ b/gcc/rust/expand/rust-macro-substitute-ctx.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2020-2022 Free Software Foundation, Inc.
+
+// This file is part of GCC.
+
+// GCC is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3, or (at your option) any later
+// version.
+
+// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with GCC; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+#include "rust-ast.h"
+#include "rust-macro-expand.h"
+
+namespace Rust {
+class SubstituteCtx
+{
+  std::vector<std::unique_ptr<AST::Token>> &input;
+  std::vector<std::unique_ptr<AST::Token>> &macro;
+  std::map<std::string, MatchedFragmentContainer> &fragments;
+
+  /**
+   * Find the repetition amount to use when expanding a repetition, and
+   * check that all fragments used respect that repetition amount
+   *
+   * @param pattern_start Start of the repetition pattern
+   * @param pattern_end End of the repetition pattern
+   * @param repeat_amount Reference to fill with the matched repetition amount
+   */
+  bool check_repetition_amount (size_t pattern_start, size_t pattern_end,
+				size_t &repeat_amount);
+
+public:
+  SubstituteCtx (std::vector<std::unique_ptr<AST::Token>> &input,
+		 std::vector<std::unique_ptr<AST::Token>> &macro,
+		 std::map<std::string, MatchedFragmentContainer> &fragments)
+    : input (input), macro (macro), fragments (fragments)
+  {}
+
+  /**
+   * Substitute a metavariable by its given fragment in a transcribing context,
+   * i.e. replacing $var with the associated fragment.
+   *
+   * @param metavar Metavariable to try and replace
+   *
+   * @return A token containing the associated fragment expanded into tokens if
+   * any, or the cloned token if no fragment was associated
+   */
+  std::vector<std::unique_ptr<AST::Token>>
+  substitute_metavar (std::unique_ptr<AST::Token> &metavar);
+
+  /**
+   * Substitute a macro repetition by its given fragments
+   *
+   * @param pattern_start Start index of the pattern tokens
+   * @param pattern_end End index of the patterns tokens
+   * @param separator Optional separator to include when expanding tokens
+   *
+   * @return A vector containing the repeated pattern
+   */
+  std::vector<std::unique_ptr<AST::Token>>
+  substitute_repetition (size_t pattern_start, size_t pattern_end,
+			 std::unique_ptr<AST::Token> separator);
+
+  /**
+   * Substitute a given token by its appropriate representation
+   *
+   * @param token_idx Current token to try and substitute
+   *
+   * @return A token containing the associated fragment expanded into tokens if
+   * any, or the cloned token if no fragment was associated, as well as the
+   * amount of tokens that should be skipped before the next invocation. Since
+   * this function may consume more than just one token, it is important to skip
+   * ahead of the input to avoid mis-substitutions
+   */
+  std::pair<std::vector<std::unique_ptr<AST::Token>>, size_t>
+  substitute_token (size_t token_idx);
+
+  /**
+   * Substitute all tokens by their appropriate representation
+   *
+   * @return A vector containing the substituted tokens
+   */
+  std::vector<std::unique_ptr<AST::Token>> substitute_tokens ();
+};
+} // namespace Rust
