如何在过程宏中处理枚举/结构/字段属性?

时间:2017-02-27 11:00:05

标签: rust

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)]?我无法在任何地方找到这些信息。 docs甚至没有提到这一点。我试着查看serde-derive源代码,但这对我来说非常复杂。

2 个答案:

答案 0 :(得分:16)

  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_changesyn crate为您提供了有关定义的所有必要信息(但遗憾的是没有实现(我在这里谈论的是impl),但这足以处理属性当然)。为了更详细,我们需要syn::DeriveInputsyn::Bodysyn::Variantsyn::Attribute以及最后syn::MetaItem

    要处理字段的属性,您需要从一个到另一个遍历所有这些结构。当你到达Vec<syn:: Attribute>时 - 这就是你想要的,一个字段的所有属性的列表。在这里我们可以找到state_transitions。当您找到它时,您可能希望获得其内容,这可以通过使用匹配的syn::MetaItem枚举来完成。只需阅读文档:)这是一个简单的示例代码,当我们在某个字段上找到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()
    }
    
  3. 您可能对阅读 serde_codegen_internalsserde_derivefxsm-derive感兴趣。最后一个链接实际上是我自己的项目,向我自己解释如何在Rust中使用过程宏。

    在一些Rust 1.15之后并且更新了syn crate,不再可能检查enums/structs的派生,但是其他一切都可以正常工作。

答案 1 :(得分:3)

您在字段上实现属性作为struct的派生宏的一部分(您只能为结构和枚举实现派生宏)。

Serde通过检查syn提供的结构中的属性的每个字段并相应地更改代码生成来完成此操作。

您可以在此处找到相关代码:https://github.com/serde-rs/serde/blob/master/serde_codegen_internals/src/attr.rs#L149-L283