How to generate a doctest inside a macro_rules!

I'm wondering if there is a way to use the $parameters of a macro_rules! invocation in a doctest generated by that macro? I'm trying to generate identical implementations for several types by using a macro_rules!, but the comments that are creating a doctest aren't being modified like I want. My code looks something like:


macro_rules! impl_stuff {
    ($ty: ty) => {
impl<'a, 'b> std::ops::Sub<&'b $ty> for &'a $ty {
    type Output = $ty;

    /// Returns the difference of `self` and `rhs` as a new `$ty`.
    ///
    /// # Examples
    ///
    /// ```
    /// use tinyset::$ty;
    ///
    /// let a: $ty = vec![1, 2, 3].into_iter().collect();
    /// let b: $ty = vec![3, 4, 5].into_iter().collect();
    ///
    /// let set = &a - &b;
    ///
    /// let mut i = 0;
    /// let expected = [1, 2];
    /// for x in set {
    ///     assert!(expected.contains(&x));
    ///     i += 1;
    /// }
    /// assert_eq!(i, expected.len());
    /// ```
    fn sub(self, rhs: &$ty) -> $ty {
        let mut s = <$ty>::with_capacity(self.len());
        for v in self.iter() {
            if !rhs.contains(v) {
                s.insert(v);
            }
        }
        s
    }
}
}

and I was hoping to see nice documentation generated with doctests that are runnable. Instead I get errors like:

Couldn't compile the test.
---- src/setu32.rs - setu32::&'a SetU32::sub (line 64) stdout ----
error: expected identifier, found `$`
 --> src/setu32.rs:65:14
  |
4 | use tinyset::$ty;
  |              ^ expected identifier

error: aborting due to previous error

Any suggestions? I could just copy and paste the code, but I'd rather not have to manage to ensure that three separate implementations of several bits of code (for usize, u32, and u64, in case you're curious) are all identical and well-optimized. I foresee making a change to one of them down the road and forgetting to change the others.

Maybe you could generate a #[doc = "..."] attribute instead of the /// form? E.g. this toy example seems to do the desired thing

macro_rules! impl_stuff {
    ( $ty:ty ) => {
        #[doc = concat!("\
Blah blah blah.

```
let x: ", stringify!($ty), ";
```
",
        )]
        impl std::ops::Sub<$ty> for $ty {
            type Output = $ty;

            fn sub(self, other: Self) -> Self::Output {
                unimplemented!()
            }
        }
    };
}
3 Likes

You might find macropol useful. (It replaces all string literals, including doc comments, with concat! to embed macro metavariables and other things.)

#[macropol::macropol]
macro_rules! impl_stuff {
    ($ty: ty) => {
impl<'a, 'b> std::ops::Sub<&'b $ty> for &'a $ty {
    type Output = $ty;

    /// Returns the difference of `self` and `rhs` as a new `$&ty`.
    ///
    /// # Examples
    ///
    /// ```
    /// use tinyset::$&ty;
    ///
    /// let a: $&ty = vec![1, 2, 3].into_iter().collect();
    /// let b: $&ty = vec![3, 4, 5].into_iter().collect();
    ///
    /// let set = &a - &b;
    ///
    /// let mut i = 0;
    /// let expected = [1, 2];
    /// for x in set {
    ///     assert!(expected.contains(&x));
    ///     i += 1;
    /// }
    /// assert_eq!(i, expected.len());
    /// ```
    fn sub(self, rhs: &$ty) -> $ty {
        let mut s = <$ty>::with_capacity(self.len());
        for v in self.iter() {
            if !rhs.contains(v) {
                s.insert(v);
            }
        }
        s
    }
}
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.