使用syn时如何从Option <t>中获得T?

时间:2019-03-20 23:56:45

标签: rust metaprogramming rust-proc-macros

我正在使用syn解析Rust代码。当我使用field.ty读取命名字段的类型时,会得到syn::Type。使用quote!{#ty}.to_string()打印时,我得到"Option<String>"

我如何只得到"String"?我想使用#ty中的quote!来打印"String"而不是"Option<String>"

我想生成如下代码:

impl Foo {
    pub set_bar(&mut self, v: String) {
        self.bar = Some(v);
    }
}

开始
struct Foo {
    bar: Option<String>
}

我的尝试

let ast: DeriveInput = parse_macro_input!(input as DeriveInput);

let data: Data = ast.data;

match data {
    Data::Struct(ref data) => match data.fields {
        Fields::Named(ref fields) => {

            fields.named.iter().for_each(|field| {
                let name = &field.ident.clone().unwrap();

                let ty = &field.ty;
                quote!{
                    impl Foo {
                        pub set_bar(&mut self, v: #ty) {
                            self.bar = Some(v);
                        }
                    }
                };      
            });
        }
        _ => {}
    },
    _ => panic!("You can derive it only from struct"),
}

2 个答案:

答案 0 :(得分:4)

您应该执行以下未经测试的示例:

use syn::{GenericArgument, PathArguments, Type};

fn extract_type_from_option(ty: &Type) -> Type {
    fn path_is_option(path: &Path) -> bool {
        leading_colon.is_none()
            && path.segments.len() == 1
            && path.segments.iter().next().unwrap().ident == "Option"
    }

    match ty {
        Type::Path(typepath) if typepath.qself.is_none() && path_is_option(typepath.path) => {
            // Get the first segment of the path (there is only one, in fact: "Option"):
            let type_params = typepath.path.segments.iter().first().unwrap().arguments;
            // It should have only on angle-bracketed param ("<String>"):
            let generic_arg = match type_params {
                PathArguments::AngleBracketed(params) => params.args.iter().first().unwrap(),
                _ => panic!("TODO: error handling"),
            };
            // This argument must be a type:
            match generic_arg {
                GenericArgument::Type(ty) => ty,
                _ => panic!("TODO: error handling"),
            }
        }
        _ => panic!("TODO: error handling"),
    }
}

没有太多要解释的东西,它只是“展开”类型的各种组成部分:

Type-> TypePath-> Path-> PathSegment-> PathArguments-> AngleBracketedGenericArguments-> GenericArgument -> Type

如果有更简单的方法可以做到这一点,我将很高兴知道它。


请注意,由于syn是解析器,因此它可以处理令牌。您无法确定这是Option。例如,用户可以键入std::option::Option或输入type MaybeString = std::option::Option<String>;。您无法处理这些任意名称。

答案 1 :(得分:0)

我的the response from @French Boiethios的更新版本经过测试并在公共包装箱中使用,并支持Option的几种语法:

  • std::option::Option
  • ::std::option::Option
  • core::option::Option
  • ::core::option::Option
  • fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> { use syn::punctuated::Pair; use syn::token::Colon2; use syn::{GenericArgument, Path, PathArguments, PathSegment}; fn extract_type_path(ty: &syn::Type) -> Option<&Path> { match *ty { syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path), _ => None, } } // TODO store (with lazy static) the vec of string // TODO maybe optimization, reverse the order of segments fn extract_option_segment(path: &Path) -> Option<Pair<&PathSegment, &Colon2>> { let idents_of_path = path .segments .iter() .into_iter() .fold(String::new(), |mut acc, v| { acc.push_str(&v.ident.to_string()); acc.push('|'); acc }); vec!["Option|", "std|option|Option|", "core|option|Option|"] .into_iter() .find(|s| &idents_of_path == *s) .and_then(|_| path.segments.last()) } extract_type_path(ty) .and_then(|path| extract_option_segment(path)) .and_then(|pair_path_segment| { let type_params = &pair_path_segment.into_value().arguments; // It should have only on angle-bracketed param ("<String>"): match *type_params { PathArguments::AngleBracketed(ref params) => params.args.first(), _ => None, } }) .and_then(|generic_arg| match *generic_arg.into_value() { GenericArgument::Type(ref ty) => Some(ty), _ => None, }) }
<img class="container" id="c1" > 
<img class="container" id="c2"> 
<img class="container" id="c3">
<p class="dot" id="d3"></p>
<p class="dot" id="d2"></p>
<p class="dot" id="d1"></p>


var i;
let slide;
let dot;

function plusSlide()
{
    for (i = 3; i >= 0; i--)
    {
      slide = document.getElementById("c" + i);
      dot   = document.getElementById("d" + i);

      if (i == 3)
      {
        slide.id = "c" + 0;
        dot.id   = "d" + 0;
      }
      else
      {
        slide.id = "c" + (i + 1);
        dot.id   = "d" + (i + 1);
      }
    }

  setTimeout(plusSlide, 5000);
}

plusSlide();