簡體   English   中英

如何處理過程宏中的枚舉/結構/字段屬性?

[英]How do I process enum/struct/field attributes in a procedural macro?

Serde 支持應用與#[derive(Serialize)]一起使用的自定義屬性:

#[derive(Serialize)]
struct Resource {
    // Always serialized.
    name: String,

    // Never serialized.
    #[serde(skip_serializing)]
    hash: String,

    // Use a method to decide whether the field should be skipped.
    #[serde(skip_serializing_if = "Map::is_empty")]
    metadata: Map<String, String>,
}

我了解如何實現過程宏(在此示例中為Serialize ),但我應該如何實現#[serde(skip_serializing)] 我無法在任何地方找到此信息。 文檔甚至沒有提到這一點。 我試圖查看serde-derive源代碼,但它對我來說非常復雜。

  1. 首先,您必須在注冊過程宏的同一位置注冊所有屬性。 假設我們要添加兩個屬性(我們仍然沒有討論它們屬於什么:結構或字段或兩者):

     #[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))] pub fn fxsm(input: TokenStream) -> TokenStream { // ... }

    之后,您可能已經使用以下內容編譯了您的用戶代碼:

     #[derive(Copy, Clone, Debug, FiniteStateMachine)] #[state_change(GameEvent, change_condition)] // optional enum GameState { #[state_transitions(NeedServer, Ready)] Prepare { players: u8 }, #[state_transitions(Prepare, Ready)] NeedServer, #[state_transitions(Prepare)] Ready, }

    如果沒有該編譯器,則會出現類似以下消息的錯誤:

    state_change不屬於任何已知屬性。

    這些屬性是可選的,我們所做的只是允許指定它們。 當你派生你的程序宏時,你可能會檢查你想要的一切(包括屬性存在)和panic! 在某些情況下,編譯器會告知有意義的消息。

  2. 現在我們將討論處理屬性! 讓我們忘記state_transitions屬性,因為它的處理不會與處理 struct/enum 屬性(實際上它只是多一點代碼)和討論state_change syn crate 為您提供了有關定義的所有所需信息(但不幸的是不是實現(我在這里談論的是impl ),但這當然足以處理屬性)。 更詳細地說,我們需要syn::DeriveInputsyn::Bodysyn::Variantsyn::Attribute和最后的syn::MetaItem

要處理字段的屬性,您需要從一個到另一個遍歷所有這些結構。 當您到達Vec<syn:: Attribute> - 這就是您想要的,一個字段的所有屬性的列表。 在這里可以找到我們的state_transitions 當你找到它時,你可能想要獲取它的內容,這可以通過使用匹配的syn::MetaItem枚舉來完成。 只需閱讀文檔 :) 這是一個簡單的示例代碼,當我們在某個字段上找到state_change屬性時會state_change恐慌,並且它會檢查我們的目標實體是否派生了CopyClone或兩者都不是:

    #[proc_macro_derive(FiniteStateMachine, attributes(state_transitions, state_change))]
    pub fn fxsm(input: TokenStream) -> TokenStream {
        // Construct a string representation of the type definition
        let s = input.to_string();

        // Parse the string representation
        let ast = syn::parse_derive_input(&s).unwrap();

        // Build the impl
        let gen = impl_fsm(&ast);

        // Return the generated impl
        gen.parse().unwrap()
    }

    fn impl_fsm(ast: &syn::DeriveInput) -> Tokens {
        const STATE_CHANGE_ATTR_NAME: &'static str = "state_change";

        if let syn::Body::Enum(ref variants) = ast.body {

            // Looks for state_change attriute (our attribute)
            if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == STATE_CHANGE_ATTR_NAME) {
                if let syn::MetaItem::List(_, ref nested) = a.value {
                    panic!("Found our attribute with contents: {:?}", nested);
                }
            }

            // Looks for derive impls (not our attribute)
            if let Some(ref a) = ast.attrs.iter().find(|a| a.name() == "derive") {
                if let syn::MetaItem::List(_, ref nested) = a.value {
                    if derives(nested, "Copy") {
                        return gen_for_copyable(&ast.ident, &variants, &ast.generics);
                    } else if derives(nested, "Clone") {
                        return gen_for_clonable(&ast.ident, &variants, &ast.generics);
                    } else {
                        panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
                    }
                } else {
                    panic!("Unable to produce Finite State Machine code on a enum which does not drive Copy nor Clone traits.");
                }
            } else {
                panic!("How have you been able to call me without derive!?!?");
            }
        } else {
            panic!("Finite State Machine must be derived on a enum.");
        }
    }

    fn derives(nested: &[syn::NestedMetaItem], trait_name: &str) -> bool {
        nested.iter().find(|n| {
            if let syn::NestedMetaItem::MetaItem(ref mt) = **n {
                if let syn::MetaItem::Word(ref id) = *mt {
                    return id == trait_name;
                }
                return false
            }
            false
        }).is_some()
    }

您可能有興趣閱讀serde_codegen_internalsserde_deriveserenity 的#[command] attr ,我的另一個小項目 - unique-type-idfxsm-derive 最后一個鏈接其實是我自己的項目,向自己解釋如何在 Rust 中使用過程宏。


在一些 Rust 1.15 和更新同步之后,不再可能檢查enums/structs派生,但是,其他一切正常。

您在字段上實現屬性作為結構派生宏的一部分(您只能為結構和枚舉實現派生宏)。

Serde 通過檢查syn提供的結構中的每個字段的屬性並相應地更改代碼生成來做到這一點。

您可以在此處找到相關代碼: https : //github.com/serde-rs/serde/blob/master/serde_derive/src/internals/attr.rs

在涉及state_transitions屬性時擴展 Victor Polevoy 的答案。 我提供了一個示例,說明如何在派生#[derive(FiniteStateMachine)]的枚舉上提取字段屬性#[state_transitions(NeedServer, Ready)] ] :

#[derive(FiniteStateMachine)]
enum GameState {
    #[state_transitions(NeedServer, Ready)] // <-- extract this
    Prepare { players: u8 },
    
    #[state_transitions(Prepare, Ready)]
    NeedServer,
   
    #[state_transitions(Prepare)]
    Ready,
}
use proc_macro::TokenStream;

#[proc_macro_derive(FiniteStateMachine, attributes(state_transitions))]
pub fn finite_state_machine(input: TokenStream) -> TokenStream {

    let ast = syn::parse(input).unwrap();

    // Extract the enum variants
    let variants: Vec<&syn::Variant> = match &ast.data {
        syn::Data::Enum(ref data_enum) => data_enum.variants.iter().collect(),
        other => panic!("#[derive(FiniteStateMachine)] expects enum, got {:#?}", other)
    };

    // For each variant, extract the attributes
    let _ = variants.iter().map(|variant| { 
        let attrs = variant.attrs.iter()
            // checks attribute named "state_transitions(...)"
            .find_map(|attr| match attr.path.is_ident("state_transitions") {
                true => Some(&attr.tokens),
                false => None,
            })
            .expect("#[derive(FiniteStateMachine)] expects attribute macros #[state_transitions(...)] on each variant, found none");

        // outputs: attr: "(NeedServer, Ready)"
        eprintln!("attr: {:#?}", attrs.to_string());

        // do something with the extracted attributes
        ...
    })
    .collect();

    ...
}

提取的attrs (類型為TokenStream )的內容如下所示:

TokenStream [
    Group {
        delimiter: Parenthesis,
        stream: TokenStream [
            Ident {
                ident: "NeedServer",
                span: #0 bytes(5511..5521),
            },
            Punct {
                ch: ',',
                spacing: Alone,
                span: #0 bytes(5521..5522),
            },
            Ident {
                ident: "Ready",
                span: #0 bytes(5523..5528),
            },
        ],
        span: #0 bytes(5510..5529),
    },
]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM