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

Allow user to set the crate path of num_enum #77

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

yshui
Copy link

@yshui yshui commented Sep 12, 2022

Add a #[num_enum(crate = ..)] attribute, which lets the user set where the num_enum crate is located.

@yshui
Copy link
Author

yshui commented Sep 13, 2022

This closes #76

Copy link
Collaborator

@danielhenrymantilla danielhenrymantilla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks for the quick PR! 🙏

I have some nits here and there, but otherwise LGTM ✅

Comment on lines 11 to 13
mod reexport {
pub use ::num_enum;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you have this on its own tests/file.rs, and then, use, instead:

Suggested change
mod reexport {
pub use ::num_enum;
}
extern crate std as num_enum; // ensure `::num_enum` is a bad path
mod reexport {
pub extern crate num_enum;
}

This way if the macro decided to ignore the crate parameter, we will detect it and fail 🙂

Comment on lines 49 to 50
let s: syn::LitStr = input.parse()?;
let path = s.parse_with(syn::Path::parse_mod_style)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let s: syn::LitStr = input.parse()?;
let path = s.parse_with(syn::Path::parse_mod_style)?;
let path = syn::Path::parse_mod_style(input)?;

The fact serde uses a literal on the rhs of crate = … is a historical artifact (crate = ::some_path was not legal at the beginning), but nowadays not requiring code to be stringified is better for spans and IDEs 🙂

Comment on lines 289 to 299
for attr in attrs {
if attr.path.is_ident("num_enum") {
match attr.parse_args_with(NumEnumAttributes::parse) {
Ok(NumEnumAttributes { items }) => {
if items.len() != 1 || crate_path.is_some() {
die!(attr.tokens => "Expected exactly one `crate` argument");
} else {
let NumEnumAttributeItem::Crate(item) =
items.into_iter().next().unwrap();
crate_path = Some(item.path);
}
}
Err(err) => {
die!(attr =>
format!("Invalid attribute: {}", err)
);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't "scale well" if other attributes are added. I'd rather suggest an approach along the lines of .flat_map()ing / nested-for-style the attributes (since #[num_enum(foo)] #[num_enum(bar)] is the same as #[num_enum(foo, bar)]:

for attr in attrs {
    if attr.path.is_ident("num_enum") {
        for item in attr.parse_args_with(NumEnumAttributes::parse)? {
            match item {
                // when written like this, it's easier to extend with more attrs.
                NumEnumAttributeItem::Crate(item) => {
                    if crate_path.is_some() {
                        die!(item.path => "duplicate `crate` annotation");
                    }
                    crate_path = Some(item.path);
                },
            }
        }
    }}

Comment on lines 558 to 560
syn::LitStr::new(&format!("::{}", get_crate_name()), Span::call_site())
.parse_with(syn::Path::parse_mod_style)
.unwrap()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
syn::LitStr::new(&format!("::{}", get_crate_name()), Span::call_site())
.parse_with(syn::Path::parse_mod_style)
.unwrap()
let krate = get_crate_name();
parse_quote!( :: #krate )

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this parse_quote! would generate :: "num_enum" because krate is a String

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe let krate = format_ident!("{}", get_crate_name());?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah true, yeah what you've just suggested would be the right thing.

Although at that point, I guess we could go back to something closer to your original suggestion:

syn::parse_str(&format!("::{}", get_crate_name())).unwrap()
  • (it just sidesteps the intermediary LitStr)

Comment on lines 667 to 671
let krate = enum_info.crate_path.clone().unwrap_or_else(|| {
syn::LitStr::new(&format!("::{}", get_crate_name()), Span::call_site())
.parse_with(syn::Path::parse_mod_style)
.unwrap()
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, given the code duplication, I wonder if get_crate_name() shouldn't be taking a Option<Path> parameter and being the one emitting the actual path?

Add a `#[num_enum(crate = ..)]` attribute, which lets the user set where
the num_enum crate is located.
@yshui
Copy link
Author

yshui commented Jan 11, 2023

@danielhenrymantilla hey, sorry for taking a while to get back to this. i think i have now addressed most of the points.

Copy link
Collaborator

@danielhenrymantilla danielhenrymantilla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

Copy link
Owner

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! I have suggestions for a few very small tweaks, then let's merge!

@@ -0,0 +1,5 @@
error: Expected exactly one `crate` argument
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect this error message to be "at most one" rather than "exactly one" - it's allowed to not specify a crate argument.

@@ -1,16 +1,16 @@
error: `num_enum` only supports unit variants (with no associated data), but `Number::NonZero` was not a unit variant.
error: `:: num_enum` only supports unit variants (with no associated data), but `Number::NonZero` was not a unit variant.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't expect a space between :: and num_enum here?

match item {
NumEnumAttributeItem::Crate(item) => {
if crate_path.is_some() {
die!(attr.tokens => "Expected exactly one `crate` argument");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
die!(attr.tokens => "Expected exactly one `crate` argument");
die!(attr.tokens => "Expected at most one `crate` argument");

proc_macro_crate::FoundCrate::Itself => parse_quote!(::num_enum),
proc_macro_crate::FoundCrate::Name(name) => {
let krate = format_ident!("{}", name);
parse_quote!( :: #krate )
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
parse_quote!( :: #krate )
parse_quote!( ::#krate )

// Don't depend on proc-macro-crate in no_std environments because it causes an awkward dependency
// on serde with std.
//
// no_std dependees on num_enum cannot rename the num_enum crate when they depend on it. Sorry.
//
// See https://github.com/illicitonion/num_enum/issues/18
#[cfg(not(feature = "proc-macro-crate"))]
fn get_crate_name() -> String {
String::from("num_enum")
fn get_crate_name(path: Option<syn::Path>) -> syn::Path {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn get_crate_name(path: Option<syn::Path>) -> syn::Path {
fn get_crate_path(path: Option<syn::Path>) -> syn::Path {

Let's rename this (and the other #[cfg-gated one, and its call sites) to make more clear that it has the :: prefix (and that callers don't need to add it)

@diondokter
Copy link

Can I help in any way to get this over the finish line and get this feature released?

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

Successfully merging this pull request may close these issues.

None yet

4 participants