title: Fixing "no rules expected the token" macro error
body: <p>I'm trying to write a macro for destructuring BSON data which looks like this:</p>
  
  <pre><code>let bson: Document = ...;
  let (id, hash, name, path, modification_time, size, metadata, commit_data) = bson_destructure! {
      get id = from (bson), optional, name ("_id"), as ObjectId;
      get hash = from (bson), as String, through (|s| ContentHash::from_str(&amp;s));
      get name = from (bson), as String;
      get path = from (bson), as Bson, through (PathBuf::from_bson);
      get modification_time = from (bson), as UtcDatetime, through (FileTime);
      get size = from (bson), as I64, through (|n| n as u64);
      get metadata = from (bson), as Document, through (Metadata::from_bson);
      get commit_data = from (bson), optional, as Document, through (CommitData::from_bson);
      ret (id, hash, name, path, modification_time, size, metadata, commit_data)
  };
  </code></pre>
  
  <p>I've written the following macro (pretty large) for it:</p>
  
  <pre><code>macro_rules! bson_destructure {
      // required field
      (
          @collect req,
          [$target:ident, $source:expr, $field:expr, Bson, $f:expr],
          [];
          $($rest:tt)*
      ) =&gt; {{
          let $target = try!(match $source.remove($field) {
              Some(v) =&gt; $f(v),
              None =&gt; Err(BsonDestructureError::MissingField {
                  field_name: $field,
                  expected: "Bson"
              }),
          });
          bson_destructure!($($rest)*)
      }};
      (
          @collect req,
          [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr],
          [];
          $($rest:tt)*
      ) =&gt; {{
          let $target = try!(match $source.remove($field) {
              Some(v) =&gt; match v {
                  ::ejdb::bson::Bson::$variant(v) =&gt; $f(v),
                  v =&gt; Err(BsonDestructureError::InvalidType {
                      field_name: $field,
                      expected: stringify!($variant),
                      actual: v
                  })
              },
              None =&gt; Err(BsonDestructureError::MissingField {
                  field_name: $field,
                  expected: stringify!($variant)
              }),
          });
          bson_destructure!($($rest)*)
      }};
  
      // optional field
      (
          @collect opt,
          [$target:ident, $source:expr, $field:expr, Bson, $f:expr],
          [];
          $($rest:tt)*
      ) =&gt; {{
          let $target = try!(match $source.remove($field) {
              Some(v) =&gt; $f(v).map(Some),
              None =&gt; Ok(None),
          });
          bson_destructure!($($rest)*)
      }};
      (
          @collect opt,
          [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr],
          [];
          $($rest:tt)*
      ) =&gt; {{
          let $target = try!(match $source.remove($field) {
              Some(v) =&gt; match v {
                  ::ejdb::bson::Bson::$variant(v) =&gt; $f(v).map(Some),
                  v =&gt; Err(BsonDestructureError::InvalidType {
                      field_name: $field,
                      expected: stringify!($variant),
                      actual: v
                  })
              },
              None =&gt; Ok(None),
          });
          bson_destructure!($($rest)*)
      }};
  
      // change variant name
      (
          @collect $k:tt,
          [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr],
          [as $nv:ident, $($word:ident $arg:tt),*];
          $($rest:tt)*
      ) =&gt; {
          bson_destructure!(
              @collect $k,
              [$target, $source, $field, $nv, $f],
              [$($word $arg),*];
              $($rest)*
          )
      };
  
      // change final mapping function
      (
          @collect $k:tt,
          [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr],
          [through ($nf:expr), $($word:ident $arg:tt),*];
          $($rest:tt)*
      ) =&gt; {
          bson_destructure!(
              @collect $k,
              [$target, $source, $field, $variant, $nf],
              [$($word $arg),*];
              $($rest)*
          )
      };
  
      // change field name
      (
          @collect $k:tt,
          [$target:ident, $source:expr, $field:expr, $variant:ident, $f:expr],
          [name ($nn:expr), $($word:ident $arg:tt),*];
          $($rest:tt)*
      ) =&gt; {
          bson_destructure!(
              @collect $k,
              [$target, $source, $nn, $variant, $f],
              [$($word $arg),*];
              $($rest)*
          )
      };
  
      // main forms
      (get $target:ident = from ($source:expr), $($word:ident $arg:tt),*; $($rest:tt)*) =&gt; {
          bson_destructure!(
              @collect req,
              [$target, $source, stringify!($target), Bson, Ok],
              [$($word $arg),*];
              $($rest)*
          )
      };
      (get $target:ident = from ($source:expr), optional, $($word:ident $arg:tt),*; $($rest:tt)*) =&gt; {
          bson_destructure!(
              @collect opt,
              [$target, $source, stringify!($target), Bson, Ok],
              [$($word $arg),*];
              $($rest)*
          )
      };
  
      // final form
      (ret $e:expr) =&gt; { $e }
  }
  </code></pre>
  
  <p>However, the first example above results in the following compilation error:</p>
  
  <pre><code>src/db/data.rs:345:22: 345:25 error: no rules expected the token `opt`
  src/db/data.rs:345             @collect opt,
                                          ^~~
  </code></pre>
  
  <p>I'm somewhat surprised that it doesn't show the error location as usual (that is, there is no indication where expansion happens), however, the error vanishes when I comment the piece of code which uses the macro out.</p>
  
  <p>I can't see why it says that no rules expected this token because there is such a rule, but maybe I don't understand something.</p>
  
  <p>I'm pretty sure that this is possible because that's roughly what quick_error crate does, but it seems that my macro writing skills are still lacking.</p>
  
  <p>How should I fix the macro so it would work as I expect?</p>
  
  <p>For completeness, the following is the definition of <code>BsonDestructureError</code>:</p>
  
  <pre><code>#[derive(Debug, Clone)]
  pub enum BsonDestructureError {
      InvalidType {
          field_name: &amp;'static str,
          expected: &amp;'static str,
          actual: Bson
      },
      InvalidArrayItemType {
          index: usize,
          expected: &amp;'static str,
          actual: Bson
      },
      MissingField {
          field_name: &amp;'static str,
          expected: &amp;'static str
      }
  }
  </code></pre>
  
  <p>I'm also using <code>bson</code> crate reexported from <code>ejdb</code> crate. <a href="https://gist.github.com/netvl/11579e3b385ef2b52b6f">Here</a> is a minimal example, runnable with <a href="https://github.com/DanielKeep/cargo-script"><code>cargo script</code></a> on stable Rust.</p>