How do I emit a literal '::' in macro_rules?

Followup to Help w/ a macro rules - #10 by zeroexcuses

I almost have it working, except it seems something is wrong with the macro expansion of Nothing::default

Full code here:

Error of:

expected one of `;`, `where`, or `{`, found `:`
pub struct Nothing<T> {
    t: PhantomData<T>,
}

impl<T> Default for Nothing<T> {
    fn default() -> Self {
        Nothing {
            t: PhantomData::default(),
        }
    }
}

#[macro_export]
macro_rules! foo {
    {
        $vis:vis trait $trait_name:ident {
            $(fn $method_name:ident(&self, obj:
            $(Nothing<$obj_type_unit:ty>)?
            $(Rc<$obj_type_rc:ty>)?
            $(& $obj_type_ref:ty)?  $(, $arg:ident : $arg_type:ty)* $(,)?)
            $( -> $r_ty:ty)?
            ;)+
        }
    } => {
        $vis trait $trait_name { $(fn $method_name(&self, obj:
            $(Nothing<$obj_type_unit>)?
            $(Rc<$obj_type_rc>)?
            $(& $obj_type_ref)? , $($arg : $arg_type), *) $( -> $r_ty:ty)? ;)+
        }

        $(foo! { @expand_impl  $trait_name $method_name $(Nothing<$obj_type_unit>)? $(Rc<$obj_type_rc>)? $(& $obj_type_ref)? , $($arg: $arg_type), *,  $( -> $r_ty:ty)? })+
    };

    (@expand_impl $trait_name:ident $method_name:ident Rc<$obj_type:ty>, $($arg:ident : $arg_type:ty), *, $( -> $r_ty:ty)? ) => {
        impl $obj_type {
            fn $method_name(self: Rc<Self>, aux: &dyn $trait_name, $($arg: $arg_type), *) $( -> $r_ty:ty)? {
                aux.$method_name(self, $($arg), *)
            }
        }
    };

    (@expand_impl $trait_name:ident $method_name:ident Nothing<$obj_type:ty>, $($arg:ident : $arg_type:ty), *, $( -> $r_ty:ty)? ) => {
        impl $obj_type {
            fn $method_name(aux: &dyn $trait_name, $($arg: $arg_type), *) $( -> $r_ty:ty)? {
                let t = Nothing::default();
                aux.$method_name(t, $($arg), *)
            }
        }
    };


    (@expand_impl $trait_name:ident $method_name:ident & $obj_type:ty, $($arg:ident : $arg_type:ty), *, $( -> $r_ty:ty)? ) => {
        impl $obj_type {
            fn $method_name(&self, aux: &dyn $trait_name, $($arg: $arg_type), *) $( -> $r_ty:ty)? {
                aux.$method_name(self, $($arg), *)
            }
        }
    }
}

You're expanding to code including the expansion pattern $r_ty:ty. In 99.99% of cases, you don't want to put the fragment type specifier in your macro expansion, but that's an easy mistake to make. That's likely what's causing your "unexpected :" error.

You can use trace_macros! to help diagnose expansion issues (no need to use nightly, even; you'll get the macro trace as well as the nightly feature error) or try expanding the macro with the IDE. Either would show the (rough, modulo hygiene details) terminating expansion state that is causing the error.

2 Likes

I have no idea how you caught that, but thanks!

I was looking for the bug in the wrong place the whole time. The most frustrating part ? The "emit return type" and the "Nothing::default()" was in the same fn, so removing that one fn made it "work", adding it back in broken the code, further strengthening my belief that the "Nothing::default()" was at fault. LOL.

I've made the same mistake too many times to count, that's how. Also I got lucky while scrolling around the code textbox with a narrow width on my smartphone and saw essentially

: $arg_type:ty), *, $( -> $r_ty:ty)? ) => {

-> $r_ty:ty)? {

which then combined with having made the mistake myself so many times and the error being "unexpected :", immediately jumped as potentially being that same mistake. That, and I was looking for where isolated : tokens were present, since that was the token the error was complaining about.

Once you know what kinds of mistakes are common, diagnosing them is often straightforward. But just passing on a list of common things to look out for doesn't help much in the bigger picture, thus the nod to how you might go about diagnosing where the issue is yourself.

4 Likes

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.