Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Special-case quote! macro #3406

Open
RReverser opened this issue Feb 18, 2019 · 8 comments
Open

Special-case quote! macro #3406

RReverser opened this issue Feb 18, 2019 · 8 comments

Comments

@RReverser
Copy link
Contributor

quote! is fairly popular for code generation, and more often than not its argument could be passed and formatted as valid Rust code.

The only thing that prevents that from happening right now is parser stumbling on #name substitutions, but if they're temporarily replaced with regular identifiers as we already do for macro definitions, this shouldn't be a problem.

@scampi
Copy link
Contributor

scampi commented Feb 18, 2019

Can you provide an example ?

@RReverser
Copy link
Contributor Author

I think the first example from the README would do:

let tokens = quote! {
    struct SerializeWith #generics #where_clause {
        value: &'a #field_ty,
        phantom: core::marker::PhantomData<#item_ty>,
    }

    impl #generics serde::Serialize for SerializeWith #generics #where_clause {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            #path(self.value, serializer)
        }
    }

    SerializeWith {
        value: #value,
        phantom: core::marker::PhantomData::<#item_ty>,
    }
};

E.g. if it would be copy-pasted without indentation:

fn main() {
let tokens = quote! {
struct SerializeWith #generics #where_clause {
value: &'a #field_ty,
phantom: core::marker::PhantomData<#item_ty>,
}

impl #generics serde::Serialize for SerializeWith #generics #where_clause {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#path(self.value, serializer)
}
}

SerializeWith {
value: #value,
phantom: core::marker::PhantomData::<#item_ty>,
}
};
}

I'd expect rustfmt to restore the formatting as above.

Just like with macro definitions, there are edge cases like repetitions or substitutions that don't make much sense when replaced with identifier, but even best-effort formatting would be already good to have.

@RReverser
Copy link
Contributor Author

Actually after writing realised that example above already contains substitutions that make formatting hard to do, but something simpler would do.

@jhpratt
Copy link
Member

jhpratt commented Feb 21, 2019

I'd very much like to see this. Currently working on a project (not mine) that uses proc macros, and there are tons of things that could use fixing. Needless to say it's quite tedious to do it by hand.

@Dushistov
Copy link

if it would be copy-pasted without indentation:

Actually, now (rustfmt 1.4.11-stable (1838235 2019-12-03)) it is rustfmt who broke indentation:

Before:

use quote::quote;

fn somehting() {
    run_compare_test(
        quote! {         
            struct NavPosLLH {
		itow: u32,		
            }
        },
        quote! {
            pub struct NavPosLLHRef<'a>(&'a [u8]);
            impl<'a> NavPosLLHRef<'a> {
		#[inline]
		pub fn itow(&self) -> u32 {
		    u32::from_le_bytes([self.0[0], self.0[1], self.0[2], self.0[3]])
		}
            }
        },
    );
}

fn run_compare_test(input: proc_macro2::TokenStream, expect_output: proc_macro2::TokenStream) {}

after:

use quote::quote;

fn somehting() {
    run_compare_test(
        quote! {
            struct NavPosLLH {
        itow: u32,
            }
        },
        quote! {
            pub struct NavPosLLHRef<'a>(&'a [u8]);
            impl<'a> NavPosLLHRef<'a> {
        #[inline]
        pub fn itow(&self) -> u32 {
            u32::from_le_bytes([self.0[0], self.0[1], self.0[2], self.0[3]])
        }
            }
        },
    );
}

fn run_compare_test(input: proc_macro2::TokenStream, expect_output: proc_macro2::TokenStream) {}

@calebcartwright
Copy link
Member

@Dushistov - I can't reproduce that. On rustmft 1.4.12 (which is the same as 1.4.11 plus a dependency version bump on the compiler internals) the original formatting remains/rustfmt does not break indentation. Are you using any configuration options?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=69f9aa884effc8b0941704bc9614a66a

@Dushistov
Copy link

@calebcartwright

Actually, I have no idea why you can not reproduce. I use plain standard rustfmt from rustup installation. I attached "hello world" crate with exactly this lines.
foo.zip

 ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rustfmt  --edition 2018 --emit stdout src/lib.rs 
/tmp/foo/src/lib.rs:

use quote::quote;

fn somehting() {
    run_compare_test(
        quote! {
            struct NavPosLLH {
        itow: u32,
            }
        },
        quote! {
            pub struct NavPosLLHRef<'a>(&'a [u8]);
            impl<'a> NavPosLLHRef<'a> {
        #[inline]
        pub fn itow(&self) -> u32 {
            u32::from_le_bytes([self.0[0], self.0[1], self.0[2], self.0[3]])
        }
            }
        },
    );
}

fn run_compare_test(input: proc_macro2::TokenStream, expect_output: proc_macro2::TokenStream) {}

@calebcartwright
Copy link
Member

That helped thanks! The problem with your original/before snippet @Dushistov is that you have a mixture of hard tabs and spaces in your indentation, not related to this issue.

When you run rustfmt, the hard tabs are converted to spaces (default behaviour is 4 spaces). Certain renderers (including GH) use 8 spaces for tabs which is why your snippet looks aligned differently in different places (GH vs. Rust Playground).

In order to resolve this, you'll want to use tabs or spaces consistently. Specifically,

on line 7 (itow: u32) you have two tabs
lines 13-14 have 2 tabs (the pub fn itow(&self) -> u32 def and attribute)
line 15 has a mix of 2 tabs and 4 spaces
and line 16 has 2 tabs
all other lines use spaces for indentation

original snippet with trailing comments added to highlight hard tab usage

use quote::quote;

fn somehting() {
    run_compare_test(
        quote! {
            struct NavPosLLH {
		itow: u32, // 2 hard tabs
            }
        },
        quote! {
            pub struct NavPosLLHRef<'a>(&'a [u8]);
            impl<'a> NavPosLLHRef<'a> {
		#[inline] // 2 hard tabs
		pub fn itow(&self) -> u32 { // 2 hard tabs
		    u32::from_le_bytes([self.0[0], self.0[1], self.0[2], self.0[3]]) // 2 hard tabs and 4 spaces
		} // 2 hard tabs
            }
        },
    );
}

fn run_compare_test(input: proc_macro2::TokenStream, expect_output: proc_macro2::TokenStream) {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants