lofty_attr-0.9.0/.cargo_vcs_info.json0000644000000001500000000000100132250ustar { "git": { "sha1": "242ab33fe6e35d506cf519e0ba9644580917e80d" }, "path_in_vcs": "lofty_attr" }lofty_attr-0.9.0/Cargo.toml0000644000000017430000000000100112340ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "lofty_attr" version = "0.9.0" authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"] include = [ "src", "Cargo.toml", "../LICENSE-*", ] description = "Macros for Lofty" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/Serial-ATA/lofty-rs" [lib] proc-macro = true [dependencies.proc-macro2] version = "1.0.64" [dependencies.quote] version = "1.0.29" [dependencies.syn] version = "2.0.25" features = [ "full", "parsing", ] lofty_attr-0.9.0/Cargo.toml.orig000064400000000000000000000007151046102023000147130ustar 00000000000000[package] name = "lofty_attr" version = "0.9.0" authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"] edition = "2021" license = "MIT OR Apache-2.0" description = "Macros for Lofty" repository = "https://github.com/Serial-ATA/lofty-rs" readme = "README.md" include = ["src", "Cargo.toml", "../LICENSE-*"] [dependencies] syn = { version = "2.0.25", features = ["full", "parsing"] } quote = "1.0.29" proc-macro2 = "1.0.64" [lib] proc-macro = true lofty_attr-0.9.0/README.md000064400000000000000000000001071046102023000132760ustar 00000000000000This crate provides macros for [Lofty](https://crates.io/crates/lofty).lofty_attr-0.9.0/src/internal.rs000064400000000000000000000057111046102023000147760ustar 00000000000000// Items that only pertain to internal usage of lofty_attr use crate::lofty_file::FieldContents; use std::collections::HashMap; use quote::quote; pub(crate) fn opt_internal_file_type( struct_name: String, ) -> Option<(proc_macro2::TokenStream, bool)> { const LOFTY_FILE_TYPES: [&str; 12] = [ "Aac", "Aiff", "Ape", "Flac", "Mpeg", "Mp4", "Mpc", "Opus", "Vorbis", "Speex", "Wav", "WavPack", ]; const ID3V2_STRIPPABLE: [&str; 2] = ["Flac", "Ape"]; let stripped = struct_name.strip_suffix("File"); if let Some(prefix) = stripped { if let Some(pos) = LOFTY_FILE_TYPES .iter() .position(|p| p.eq_ignore_ascii_case(prefix)) { let file_ty = LOFTY_FILE_TYPES[pos]; let tt = file_ty.parse::().unwrap(); return Some((tt, ID3V2_STRIPPABLE.contains(&file_ty))); } } None } pub(crate) fn init_write_lookup( id3v2_strippable: bool, ) -> HashMap<&'static str, proc_macro2::TokenStream> { let mut map = HashMap::new(); macro_rules! insert { ($map:ident, $key:path, $val:block) => { $map.insert(stringify!($key), quote! { $val }) }; } insert!(map, Ape, { lofty::ape::tag::ApeTagRef { read_only: false, items: lofty::ape::tag::tagitems_into_ape(tag), } .write_to(data) }); insert!(map, Id3v1, { Into::>::into(tag).write_to(data) }); if id3v2_strippable { insert!(map, Id3v2, { lofty::id3::v2::tag::Id3v2TagRef::empty().write_to(data) }); } else { insert!(map, Id3v2, { lofty::id3::v2::tag::Id3v2TagRef { flags: lofty::id3::v2::Id3v2TagFlags::default(), frames: lofty::id3::v2::tag::tag_frames(tag), } .write_to(data) }); } insert!(map, RiffInfo, { lofty::iff::wav::tag::RIFFInfoListRef::new(lofty::iff::wav::tag::tagitems_into_riff( tag.items(), )) .write_to(data) }); insert!(map, AiffText, { lofty::iff::aiff::tag::AiffTextChunksRef { name: tag.get_string(&lofty::tag::item::ItemKey::TrackTitle), author: tag.get_string(&lofty::tag::item::ItemKey::TrackArtist), copyright: tag.get_string(&lofty::tag::item::ItemKey::CopyrightMessage), annotations: Some(tag.get_strings(&lofty::tag::item::ItemKey::Comment)), comments: None, } .write_to(data) }); map } pub(crate) fn write_module( fields: &[FieldContents], lookup: HashMap<&'static str, proc_macro2::TokenStream>, ) -> proc_macro2::TokenStream { let applicable_formats = fields.iter().map(|f| { let tag_ty = syn::parse_str::(&format!("::lofty::TagType::{}", &f.tag_type)).unwrap(); let features = f.cfg_features.iter(); let block = lookup.get(&*tag_ty.segments[2].ident.to_string()).unwrap(); quote! { #( #features )* #tag_ty => #block, } }); quote! { pub(crate) mod write { #[allow(unused_variables)] pub(crate) fn write_to(data: &mut ::std::fs::File, tag: &::lofty::Tag) -> ::lofty::error::Result<()> { match tag.tag_type() { #( #applicable_formats )* _ => crate::macros::err!(UnsupportedTag), } } } } } lofty_attr-0.9.0/src/lib.rs000064400000000000000000000050301046102023000137220ustar 00000000000000//! Macros for [Lofty](https://crates.io/crates/lofty) #![allow( unknown_lints, clippy::too_many_lines, clippy::cast_precision_loss, clippy::cast_sign_loss, clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::module_name_repetitions, clippy::must_use_candidate, clippy::doc_markdown, let_underscore_drop, clippy::match_wildcard_for_single_variants, clippy::semicolon_if_nothing_returned, clippy::new_without_default, clippy::from_over_into, clippy::upper_case_acronyms, clippy::single_match_else, clippy::similar_names, clippy::tabs_in_doc_comments, clippy::len_without_is_empty, clippy::needless_late_init, clippy::type_complexity, clippy::type_repetition_in_bounds, unused_qualifications, clippy::return_self_not_must_use, clippy::bool_to_int_with_if, clippy::uninlined_format_args, /* This should be changed for any normal "{}", but I'm not a fan of it for any debug or width specific formatting */ clippy::manual_let_else, clippy::struct_excessive_bools, clippy::match_bool, clippy::needless_pass_by_value )] mod internal; mod lofty_file; mod lofty_tag; mod util; use lofty_tag::LoftyTagAttribute; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, ItemStruct}; /// Creates a file usable by Lofty /// /// See [here](https://github.com/Serial-ATA/lofty-rs/tree/main/examples/custom_resolver) for an example of how to use it. #[proc_macro_derive(LoftyFile, attributes(lofty))] pub fn lofty_file(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let data_struct = match input.data { Data::Struct( ref data_struct @ DataStruct { fields: Fields::Named(_), .. }, ) => data_struct, _ => { return TokenStream::from( util::err( input.ident.span(), "This macro can only be used on structs with named fields", ) .to_compile_error(), ); }, }; let mut errors = Vec::new(); let ret = lofty_file::parse(&input, data_struct, &mut errors); finish(&ret, &errors) } #[proc_macro_attribute] #[doc(hidden)] pub fn tag(args_input: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args_input as LoftyTagAttribute); let input = parse_macro_input!(input as ItemStruct); let ret = lofty_tag::create(args, input); finish(&ret, &[]) } fn finish(ret: &proc_macro2::TokenStream, errors: &[syn::Error]) -> TokenStream { let compile_errors = errors.iter().map(syn::Error::to_compile_error); TokenStream::from(quote! { #(#compile_errors)* #ret }) } lofty_attr-0.9.0/src/lofty_file.rs000064400000000000000000000251131046102023000153140ustar 00000000000000use crate::internal; use crate::util::{self, bail}; use proc_macro2::{Ident, Span}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::spanned::Spanned; use syn::{Attribute, DataStruct, DeriveInput, Field, Type}; pub(crate) fn parse( input: &DeriveInput, data_struct: &DataStruct, errors: &mut Vec, ) -> proc_macro2::TokenStream { let impl_audiofile = should_impl_audiofile(&input.attrs); let impl_into_taggedfile = should_impl_into_taggedfile(&input.attrs); let struct_name = input.ident.clone(); let read_fn = match util::get_attr("read_fn", &input.attrs) { Some(rfn) => rfn, _ if impl_audiofile => { bail!( errors, input.ident.span(), "Expected a #[read_fn] attribute" ); }, _ => proc_macro2::TokenStream::new(), }; let write_fn = match util::get_attr("write_fn", &input.attrs) { Some(wfn) => wfn, _ => proc_macro2::TokenStream::new(), }; // TODO: This is not readable in the slightest let opt_file_type = internal::opt_internal_file_type(struct_name.to_string()); let has_internal_file_type = opt_file_type.is_some(); let is_internal = input .attrs .iter() .any(|attr| util::has_path_attr(attr, "internal_write_module_do_not_use_anywhere_else")); if !has_internal_file_type && is_internal { // TODO: This is the best check we can do for now I think? // Definitely needs some work when a better solution comes out. bail!( errors, input.ident.span(), "Attempted to use an internal attribute externally" ); } let mut id3v2_strippable = false; let file_type = match opt_file_type { Some((ft, id3v2_strip)) => { id3v2_strippable = id3v2_strip; ft }, _ => match util::get_attr("file_type", &input.attrs) { Some(rfn) => rfn, _ => { bail!( errors, input.ident.span(), "Expected a #[file_type] attribute" ); }, }, }; let (tag_fields, properties_field) = match get_fields(errors, data_struct) { Some(fields) => fields, None => return proc_macro2::TokenStream::new(), }; if tag_fields.is_empty() { errors.push(util::err(input.ident.span(), "Struct has no tag fields")); } let assert_tag_impl_into = tag_fields.iter().enumerate().map(|(i, f)| { let name = format_ident!("_AssertTagExt{}", i); let field_ty = &f.ty; quote_spanned! {field_ty.span()=> struct #name where #field_ty: lofty::TagExt; } }); let mut audiofile_impl = proc_macro2::TokenStream::new(); if impl_audiofile { let properties_field = if let Some(field) = properties_field { field } else { bail!(errors, input.ident.span(), "Struct has no properties field"); }; audiofile_impl = generate_audiofile_impl( &struct_name, &tag_fields, properties_field, read_fn, write_fn, ); } let mut from_taggedfile_impl = proc_macro2::TokenStream::new(); if impl_into_taggedfile { from_taggedfile_impl = generate_from_taggedfile_impl( &struct_name, &tag_fields, file_type, has_internal_file_type, ); } let getters = get_getters(&tag_fields, &struct_name); let mut ret = quote! { #( #assert_tag_impl_into )* #audiofile_impl #from_taggedfile_impl #( #getters )* }; // Create `write` module if internal if is_internal { let lookup = internal::init_write_lookup(id3v2_strippable); let write_mod = internal::write_module(&tag_fields, lookup); ret = quote! { #ret use crate::_this_is_internal; #write_mod } } ret } pub(crate) struct FieldContents { name: Ident, pub(crate) cfg_features: Vec, needs_option: bool, getter_name: Option, ty: Type, pub(crate) tag_type: proc_macro2::TokenStream, } fn get_fields<'a>( errors: &mut Vec, data: &'a DataStruct, ) -> Option<(Vec, Option<&'a syn::Field>)> { let mut tag_fields = Vec::new(); let mut properties_field = None; for field in &data.fields { let name = field.ident.clone().unwrap(); if name.to_string().ends_with("_tag") { let tag_type = match util::get_attr("tag_type", &field.attrs) { Some(tt) => tt, _ => { errors.push(util::err(field.span(), "Field has no `tag_type` attribute")); return None; }, }; let cfg = field .attrs .iter() .cloned() .filter_map(|a| util::get_attr_list("cfg", &a).map(|_| a)) .collect::>(); let option_unwrapped = util::extract_type_from_option(&field.ty); // `option_unwrapped` will be `Some` if the type was wrapped in an `Option` let needs_option = option_unwrapped.is_some(); let contents = FieldContents { name, getter_name: util::get_attr("getter", &field.attrs), ty: option_unwrapped.unwrap_or_else(|| field.ty.clone()), tag_type, needs_option, cfg_features: cfg, }; tag_fields.push(contents); continue; } if name == "properties" { properties_field = Some(field); } } Some((tag_fields, properties_field)) } fn should_impl_audiofile(attrs: &[Attribute]) -> bool { for attr in attrs { if util::has_path_attr(attr, "no_audiofile_impl") { return false; } } true } fn should_impl_into_taggedfile(attrs: &[Attribute]) -> bool { for attr in attrs { if util::has_path_attr(attr, "no_into_taggedfile_impl") { return false; } } true } fn get_getters<'a>( tag_fields: &'a [FieldContents], struct_name: &'a Ident, ) -> impl Iterator + 'a { tag_fields.iter().map(move |f| { let name = f.getter_name.clone().unwrap_or_else(|| { let name = f.name.to_string().strip_suffix("_tag").unwrap().to_string(); Ident::new(&name, f.name.span()).into_token_stream() }); let (ty_prefix, ty_suffix) = if f.needs_option { (quote! {Option<}, quote! {>}) } else { (quote! {}, quote! {}) }; let field_name = &f.name; let field_ty = &f.ty; let ref_access = if f.needs_option { quote! {self.#field_name.as_ref()} } else { quote! {&self.#field_name} }; let mut_ident = Ident::new(&format!("{}_mut", name), Span::call_site()); let mut_access = if f.needs_option { quote! {self.#field_name.as_mut()} } else { quote! {&mut self.#field_name} }; let set_ident = Ident::new(&format!("set_{}", name), Span::call_site()); let setter = if f.needs_option { quote! { let ret = self.#field_name.take(); self.#field_name = Some(tag); return ret; } } else { quote! { Some(::core::mem::replace(&mut self.#field_name, tag)) } }; let remove_ident = Ident::new(&format!("remove_{}", name), Span::call_site()); let remover = if f.needs_option { quote! { self.#field_name.take() } } else { let assert_field_ty_default = quote_spanned! {f.name.span()=> struct _AssertDefault where #field_ty: core::default::Default; }; quote! { #assert_field_ty_default ::core::mem::take(&mut self.#field_name) } }; let cfg = &f.cfg_features; quote! { #( #cfg )* impl #struct_name { /// Returns a reference to the tag pub fn #name(&self) -> #ty_prefix &#field_ty #ty_suffix { #ref_access } /// Returns a mutable reference to the tag pub fn #mut_ident(&mut self) -> #ty_prefix &mut #field_ty #ty_suffix { #mut_access } /// Sets the tag, returning the old one pub fn #set_ident(&mut self, tag: #field_ty) -> Option<#field_ty> { #setter } /// Removes the tag pub fn #remove_ident(&mut self) -> #ty_prefix #field_ty #ty_suffix { #remover } } } }) } fn generate_audiofile_impl( struct_name: &Ident, tag_fields: &[FieldContents], properties_field: &Field, read_fn: proc_macro2::TokenStream, write_fn: proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let save_to_body = get_save_to_body(write_fn, tag_fields); let tag_exists = tag_exists_iter(tag_fields); let tag_exists_2 = tag_exists_iter(tag_fields); let tag_type = tag_fields.iter().map(|f| &f.tag_type); let properties_field_ty = &properties_field.ty; let assert_properties_impl = quote_spanned! {properties_field_ty.span()=> struct _AssertIntoFileProperties where #properties_field_ty: ::std::convert::Into<::lofty::FileProperties>; }; quote! { #assert_properties_impl impl ::lofty::AudioFile for #struct_name { type Properties = #properties_field_ty; fn read_from(reader: &mut R, parse_options: ::lofty::ParseOptions) -> ::lofty::error::Result where R: std::io::Read + std::io::Seek, { #read_fn(reader, parse_options) } fn save_to(&self, file: &mut ::std::fs::File) -> ::lofty::error::Result<()> { use ::lofty::TagExt as _; use ::std::io::Seek as _; #save_to_body } fn properties(&self) -> &Self::Properties { &self.properties } #[allow(unreachable_code)] fn contains_tag(&self) -> bool { #( #tag_exists )||* } #[allow(unreachable_code, unused_variables)] fn contains_tag_type(&self, tag_type: ::lofty::TagType) -> bool { match tag_type { #( ::lofty::TagType::#tag_type => { #tag_exists_2 } ),* _ => false } } } } } fn get_save_to_body( write_fn: proc_macro2::TokenStream, tag_fields: &[FieldContents], ) -> proc_macro2::TokenStream { if !write_fn.is_empty() { return quote! { #write_fn(&self, file) }; } let tag_field_save = tag_fields.iter().map(|f| { let name = &f.name; if f.needs_option { quote! { file.rewind()?; if let Some(ref tag) = self.#name { tag.save_to(file)?; } } } else { quote! { file.rewind()?; self.#name.save_to(file)?; } } }); quote! { #(#tag_field_save)* Ok(()) } } fn tag_exists_iter( tag_fields: &[FieldContents], ) -> impl Iterator + '_ { tag_fields.iter().map(|f| { let name = &f.name; if f.needs_option { quote! { self.#name.is_some() } } else { quote! { true } } }) } fn generate_from_taggedfile_impl( struct_name: &Ident, tag_fields: &[FieldContents], file_type: proc_macro2::TokenStream, has_internal_file_type: bool, ) -> proc_macro2::TokenStream { let conditions = tag_fields.iter().map(|f| { let name = &f.name; if f.needs_option { quote! { if let Some(t) = input.#name { tags.push(t.into()); } } } else { quote! { tags.push(input.#name.into()); } } }); let file_type_variant = if has_internal_file_type { quote! { ::lofty::FileType::#file_type } } else { let file_ty_str = file_type.to_string(); quote! { ::lofty::FileType::Custom(#file_ty_str) } }; quote! { impl ::std::convert::From<#struct_name> for ::lofty::TaggedFile { fn from(input: #struct_name) -> Self { use ::lofty::TaggedFileExt as _; ::lofty::TaggedFile::new( #file_type_variant, ::lofty::FileProperties::from(input.properties), { let mut tags: Vec<::lofty::Tag> = Vec::new(); #( #conditions )* tags } ) } } } } lofty_attr-0.9.0/src/lofty_tag.rs000064400000000000000000000072621046102023000151550ustar 00000000000000use quote::quote; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{ Error, Expr, ExprLit, ItemStruct, Lit, LitStr, Meta, MetaList, MetaNameValue, Path, Result, Token, }; enum SupportedFormat { Full(Path), ReadOnly(Path), } pub(crate) struct LoftyTagAttribute { description: LitStr, supported_formats: Vec, } impl Parse for LoftyTagAttribute { fn parse(input: ParseStream) -> Result { let mut description = None; let mut supported_formats = Vec::new(); let start_span = input.span(); let args = Punctuated::::parse_separated_nonempty(input)?; for nested_meta in args { match nested_meta { Meta::NameValue(mnv) if mnv.path.is_ident("description") => { if description.is_some() { return Err(Error::new(mnv.span(), "Duplicate `description` entry")); } description = Some(parse_description(mnv)?); }, Meta::List(list) if list.path.is_ident("supported_formats") => { parse_supported_formats(list, &mut supported_formats)?; }, _ => { return Err(Error::new( nested_meta.span(), "Unexpected input, check the format of the arguments", )) }, } } if description.is_none() { return Err(Error::new(start_span, "No description provided")); } Ok(Self { description: description.unwrap(), supported_formats, }) } } pub(crate) fn create( lofty_tag_attribute: LoftyTagAttribute, input: ItemStruct, ) -> proc_macro2::TokenStream { let ident = &input.ident; let desc = lofty_tag_attribute.description; let supported_types_iter = lofty_tag_attribute .supported_formats .iter() .map(|format| match format { SupportedFormat::Full(path) => format!( "* [`FileType::{ft}`](crate::FileType::{ft})\n", ft = path.get_ident().unwrap() ), SupportedFormat::ReadOnly(path) => format!( "* [`FileType::{ft}`](crate::FileType::{ft}) **(READ ONLY)**\n", ft = path.get_ident().unwrap() ), }); let flattened_file_types = lofty_tag_attribute .supported_formats .iter() .map(|format| match format { SupportedFormat::Full(path) | SupportedFormat::ReadOnly(path) => path, }); let read_only_file_types = lofty_tag_attribute .supported_formats .iter() .filter_map(|format| match format { SupportedFormat::ReadOnly(path) => Some(path), _ => None, }); quote! { use crate::_this_is_internal; #[doc = #desc] #[doc = "\n"] #[doc = "## Supported file types\n\n"] #( #[doc = #supported_types_iter] )* #[doc = "\n"] #input impl #ident { pub(crate) const SUPPORTED_FORMATS: &'static [::lofty::FileType] = &[ #( ::lofty::FileType:: #flattened_file_types ),* ]; pub(crate) const READ_ONLY_FORMATS: &'static [::lofty::FileType] = &[ #( ::lofty::FileType:: #read_only_file_types ),* ]; } } } fn parse_description(name_value: MetaNameValue) -> Result { match name_value.value { Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) => Ok(lit_str), _ => Err(Error::new( name_value.span(), "Invalid `description` entry, expected string value", )), } } fn parse_supported_formats( meta_list: MetaList, supported_formats: &mut Vec, ) -> Result<()> { let mut read_only_encountered = false; meta_list.parse_nested_meta(|meta| { if meta.path.is_ident("read_only") { if read_only_encountered { return Err(meta.error("Duplicate `read_only` entry")); } read_only_encountered = true; meta.parse_nested_meta(|nested_meta| { supported_formats.push(SupportedFormat::ReadOnly(nested_meta.path)); Ok(()) })?; } else { supported_formats.push(SupportedFormat::Full(meta.path)); } Ok(()) }) } lofty_attr-0.9.0/src/util.rs000064400000000000000000000051251046102023000141360ustar 00000000000000use std::fmt::Display; use proc_macro2::Span; use syn::{Attribute, Error, LitStr, Meta, MetaList, Type}; macro_rules! bail { ($errors:ident, $span:expr, $msg:expr) => { $errors.push(crate::util::err($span, $msg)); return proc_macro2::TokenStream::new(); }; } pub(crate) use bail; pub(crate) fn get_attr(name: &str, attrs: &[Attribute]) -> Option { let mut found = None; for attr in attrs { if let Some(list) = get_attr_list("lofty", attr) { let res = list.parse_nested_meta(|meta| { if meta.path.is_ident(name) { let value = meta.value()?; let value_str: LitStr = value.parse()?; found = Some(value_str.parse::().unwrap()); return Ok(()); } Err(meta.error("")) }); if res.is_ok() { return found; } } } found } pub(crate) fn has_path_attr(attr: &Attribute, name: &str) -> bool { if let Some(list) = get_attr_list("lofty", attr) { let res = list.parse_nested_meta(|meta| { if meta.path.is_ident(name) { return Ok(()); } Err(Error::new(Span::call_site(), "")) }); return res.is_ok(); } false } pub(crate) fn get_attr_list(path: &str, attr: &Attribute) -> Option { if attr.path().is_ident(path) { if let Meta::List(list) = &attr.meta { return Some(list.clone()); } } None } // https://stackoverflow.com/questions/55271857/how-can-i-get-the-t-from-an-optiont-when-using-syn pub(crate) fn extract_type_from_option(ty: &Type) -> Option { use syn::{GenericArgument, Path, PathArguments, PathSegment}; fn extract_type_path(ty: &Type) -> Option<&Path> { match *ty { Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path), _ => None, } } fn extract_option_segment(path: &Path) -> Option<&PathSegment> { let idents_of_path = path.segments.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(extract_option_segment) .and_then(|path_seg| { let type_params = &path_seg.arguments; // It should have only on angle-bracketed param (""): match *type_params { PathArguments::AngleBracketed(ref params) => params.args.first(), _ => None, } }) .and_then(|generic_arg| match *generic_arg { GenericArgument::Type(ref ty) => Some(ty.clone()), _ => None, }) } pub(crate) fn err(span: Span, error: T) -> syn::Error { syn::Error::new(span, error) }