A procedural macro that could count the occurrences of a particular enum variant in a collection.

huangapple go评论56阅读模式
英文:

A procedural macro that could count the occurrences of a particular enum variant in a collection

问题

我尝试制作一个过程宏 (CountOf),用于计算集合中特定枚举变体的出现次数,本例中为向量。

这是我用于测试宏的 src/macros.rs 文件

#[derive(count_of::CountOf, Clone)]
pub enum SomeEnum {
	Variant1,
	Variant2,
	Variant3,
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn count_of_works() {
		use SomeEnum::*;
		let enums = vec![
			Variant1,
			Variant1,
			Variant2,
			Variant3,
			Variant2,
			Variant2,
		];

		assert_eq!(enums.variant1_count(), 2);
		assert_eq!(enums.variant2_count(), 3);
		assert_eq!(enums.variant3_count(), 1);
	}
}

这是 count_of crate 中的 lib.rs 文件:

use inflector::Inflector;
use quote::quote;

#[proc_macro_derive(CountOf)]
pub fn count_of(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
	let input = syn::parse_macro_input!(input as syn::ItemEnum);
	let name = input.ident;

	let variants = input.variants.iter().map(|variant| {
		let variant_name = &variant.ident;
		let variant_count = variant_name.to_string().to_lowercase();

		quote! {
			pub fn #variant_count(&self) -> usize {
				self.iter().filter(|&&x| x == &#name::#variant_name).count()
			}
		}
	});

	let output = quote! {
		impl #name {
			#(#variants)*
		}
	};

	proc_macro::TokenStream::from(output)
}

这是我的 Cargo.toml 中的依赖项:

[dependencies]
count-of           = { path = "./count-of" }

还有这些是:

[package]
name    = "count-of"
version = "0.1.0"

[lib]
proc-macro = true

[dependencies]
syn         = { version = "2.0.15", features = ["full"] }
quote       = "1.0.26"
Inflector   = "0.11.4"
proc-macro2 = "1.0.56"

遇到这些错误:

error: expected identifier, found `"Variant1"`
  --> src/macros.rs:92:10
error: proc-macro derive produced unparseable tokens
  --> src/macros.rs:92:10
error[E0599]: no method named `variant1_count` found for struct `Vec<SomeEnum>` in the current scope
   --> src/macros.rs:135:23
error[E0599]: no method named `variant2_count` found for struct `Vec<SomeEnum>` in the current scope
   --> src/macros.rs:136:23
error[E0599]: no method named `variant3_count` found for struct `Vec<SomeEnum>` in the current scope
   --> src/macros.rs:137:23
英文:

I'm trying to make a procedural macro (CountOf) that counts the ocurrences of a particular enum variant in a collection, in this case a vector.

This src/macros.rs is what I'm using for testing the macro

#[derive(count_of::CountOf, Clone)]
pub enum SomeEnum {
	Variant1,
	Variant2,
	Variant3,
}

#[cfg(test)]
mod tests {
	use super::*;
#[test]
	fn count_of_works() {
		use SomeEnum::*;
		let enums = vec![
			Variant1,
			Variant1,
			Variant2,
			Variant3,
			Variant2,
			Variant2,
		];

		assert_eq!(enums.variant1_count(), 2);
		assert_eq!(enums.variant2_count(), 3);
		assert_eq!(enums.variant3_count(), 1);
	}
}

This is my lib.rs in the count_of crate:

use inflector::Inflector;
use quote::quote;

#[proc_macro_derive(CountOf)]
pub fn count_of(input: proc_macro::TokenStream) -&gt; proc_macro::TokenStream {
	let input = syn::parse_macro_input!(input as syn::ItemEnum);
	let name = input.ident;

	let variants = input.variants.iter().map(|variant| {
		let variant_name = &amp;variant.ident;
		let variant_count = variant_name.to_string().to_lowercase();

		quote! {
			pub fn #variant_count(&amp;self) -&gt; usize {
				self.iter().filter(|&amp;&amp;x| x == &amp;#name::#variant_name).count()
			}
		}
	});

	let output = quote! {
		impl #name {
			#(#variants)*
		}
	};

	proc_macro::TokenStream::from(output)
}

This are my dependencies in my Cargo.toml:

[dependencies]
count-of           = { path = &quot;./count-of&quot; }

An this is the
:

[package]
name    = &quot;count-of&quot;
version = &quot;0.1.0&quot;

[lib]
proc-macro = true

[dependencies]
syn         = { version = &quot;2.0.15&quot;, features = [&quot;full&quot;] }
quote       = &quot;1.0.26&quot;
Inflector   = &quot;0.11.4&quot;
proc-macro2 = &quot;1.0.56&quot;

Getting this errors:

error: expected identifier, found `&quot;Variant1&quot;`
  --&gt; src/macros.rs:92:10
   |
92 | #[derive(count_of::CountOf, Clone)]
   |          ^^^^^^^^^^^^^^^^^
   |          |
   |          expected identifier
   |          while parsing this item list starting here
   |          the item list ends here
   |
   = note: this error originates in the derive macro `count_of::CountOf` (in Nightly builds, run with -Z macro-backtrace for more info)

error: proc-macro derive produced unparseable tokens
  --&gt; src/macros.rs:92:10
   |
92 | #[derive(count_of::CountOf, Clone)]
   |          ^^^^^^^^^^^^^^^^^
error[E0599]: no method named `variant1_count` found for struct `Vec&lt;SomeEnum&gt;` in the current scope
   --&gt; src/macros.rs:135:23
    |
135 |         assert_eq!(enums.variant1_count(), 2);
    |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `Vec&lt;SomeEnum&gt;`

error[E0599]: no method named `variant2_count` found for struct `Vec&lt;SomeEnum&gt;` in the current scope
   --&gt; src/macros.rs:136:23
    |
136 |         assert_eq!(enums.variant2_count(), 3);
    |                             ^^^^^^^^^^^^^^^^^ method not found in `Vec&lt;SomeEnum&gt;`

error[E0599]: no method named `variant3_count` found for struct `Vec&lt;SomeEnum&gt;` in the current scope
   --&gt; src/macros.rs:137:23
    |
137 |         assert_eq!(enums.variant3_count(), 1);
    |                             ^^^^^^^^ method not found in `Vec&lt;SomeEnum&gt;`

I'm thinking my procedural macro is the issue but can't find how to fix it. If someone has a solution would really appreciate it.

答案1

得分: 2

以下是您提供的代码的翻译部分:

你的代码中有一些错误。

- 不要在 `quote!` 内使用 `String`,应该使用正确的类型,这里是 `Ident`。
- 如果要在 `Vec` 上调用函数,不要在类型本身上实现这些函数。

第二个问题比较难解决,因为你不能在外部类型上实现方法。

解决方案是创建一个新的 trait,然后为外部类型(这里是 `Vec&lt;SomeEnum&gt;`)实现该 trait

下面是一些有效的代码:

```rust
use proc_macro2::Ident;
use quote::{format_ident, quote};

#[proc_macro_derive(CountOf)]
pub fn count_of(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::ItemEnum);
    let name = input.ident;

    let trait_name = format_ident!("{}VecExt", name);

    let variants = input.variants.iter().map(|variant| {
        let variant_name = &variant.ident;
        let variant_count = variant_name.to_string().to_lowercase() + "_count";
        let variant_count_ident = Ident::new(&variant_count, variant_name.span());

        quote! {
            fn #variant_count_ident(&self) -> usize {
                self.as_ref().iter().filter(|&x| x == &#name::#variant_name).count()
            }
        }
    });

    let output = quote! {
        pub trait #trait_name: AsRef<[#name]> {
            #(#variants)*
        }
        impl<T> #trait_name for T where T: AsRef<[#name]> {}
    };

    proc_macro::TokenStream::from(output)
}
use rust_playground::CountOf;

#[derive(CountOf, Clone, Eq, PartialEq)]
pub enum SomeEnum {
    Variant1,
    Variant2,
    Variant3,
}

fn main() {
    use SomeEnum::*;

    let enums = vec![Variant1, Variant1, Variant2, Variant3, Variant2, Variant2];

    println!("Variant 1: {}", enums.variant1_count());
    println!("Variant 2: {}", enums.variant2_count());
    println!("Variant 3: {}", enums.variant3_count());
}
Variant 1: 2
Variant 2: 3
Variant 3: 1

当将代码通过 cargo expand 运行时,你可以看到它展开为:

pub enum SomeEnum {
    Variant1,
    Variant2,
    Variant3,
}
pub trait SomeEnumVecExt: AsRef<[SomeEnum]> {
    fn variant1_count(&self) -> usize {
        self.as_ref().iter().filter(|&x| x == &SomeEnum::Variant1).count()
    }
    fn variant2_count(&self) -> usize {
        self.as_ref().iter().filter(|&x| x == &SomeEnum::Variant2).count()
    }
    fn variant3_count(&self) -> usize {
        self.as_ref().iter().filter(|&x| x == &SomeEnum::Variant3).count()
    }
}
impl<T> SomeEnumVecExt for T
where
    T: AsRef<[SomeEnum]>,
{}

还要注意 #[derive(Eq, PartialEq)],否则无法比较枚举类型。

英文:

There are a couple of errors in your code.

  • Don't use Strings inside of quote!. Use the proper type instead, in this case Ident.
  • Don't implement the functions for the type itself, if you want to call them on a Vec.

The second one is the hard one - because you cannot implement methods on a foreign type.

The solution is to create a new trait and then implement the trait for the foreign type, here Vec&lt;SomeEnum&gt;.

Here's some working code:

use proc_macro2::Ident;
use quote::{format_ident, quote};

#[proc_macro_derive(CountOf)]
pub fn count_of(input: proc_macro::TokenStream) -&gt; proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::ItemEnum);
    let name = input.ident;

    let trait_name = format_ident!(&quot;{}VecExt&quot;, name);

    let variants = input.variants.iter().map(|variant| {
        let variant_name = &amp;variant.ident;
        let variant_count = variant_name.to_string().to_lowercase() + &quot;_count&quot;;
        let variant_count_ident = Ident::new(&amp;variant_count, variant_name.span());

        quote! {
            fn #variant_count_ident(&amp;self) -&gt; usize {
                self.as_ref().iter().filter(|&amp;x| x == &amp;#name::#variant_name).count()
            }
        }
    });

    let output = quote! {
        pub trait #trait_name: AsRef&lt;[#name]&gt; {
            #(#variants)*
        }
        impl&lt;T&gt; #trait_name for T where T: AsRef&lt;[#name]&gt; {}
    };

    proc_macro::TokenStream::from(output)
}
use rust_playground::CountOf;

#[derive(CountOf, Clone, Eq, PartialEq)]
pub enum SomeEnum {
    Variant1,
    Variant2,
    Variant3,
}

fn main() {
    use SomeEnum::*;

    let enums = vec![Variant1, Variant1, Variant2, Variant3, Variant2, Variant2];

    println!(&quot;Variant 1: {}&quot;, enums.variant1_count());
    println!(&quot;Variant 2: {}&quot;, enums.variant2_count());
    println!(&quot;Variant 3: {}&quot;, enums.variant3_count());
}
Variant 1: 2
Variant 2: 3
Variant 3: 1

When putting the code through cargo expand, you can see what it exands to:

pub enum SomeEnum {
    Variant1,
    Variant2,
    Variant3,
}
pub trait SomeEnumVecExt: AsRef&lt;[SomeEnum]&gt; {
    fn variant1_count(&amp;self) -&gt; usize {
        self.as_ref().iter().filter(|&amp;x| x == &amp;SomeEnum::Variant1).count()
    }
    fn variant2_count(&amp;self) -&gt; usize {
        self.as_ref().iter().filter(|&amp;x| x == &amp;SomeEnum::Variant2).count()
    }
    fn variant3_count(&amp;self) -&gt; usize {
        self.as_ref().iter().filter(|&amp;x| x == &amp;SomeEnum::Variant3).count()
    }
}
impl&lt;T&gt; SomeEnumVecExt for T
where
    T: AsRef&lt;[SomeEnum]&gt;,
{}

Also note the #[derive(Eq, PartialEq)], otherwise you cannot compare enums.

Maybe a little explanation:

  • I create the trait MyEnumVecExt
  • I implement the trait for all types that implement AsRef([SomeEnum]), which is all the types that can be referenced as &amp;[SomeEnum], like Vec&lt;SomeEnum&gt;, [SomeEnum; 10], &amp;mut [SomeEnum], etc.

huangapple
  • 本文由 发表于 2023年6月11日 23:24:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76451168.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定