diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index 634796dd..9578eaab 100644 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -6,7 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::{parse::*, spanned::Sp}; +use crate::{parse::*, spanned::Sp, ty::Ty}; use std::env; @@ -14,11 +14,7 @@ use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase}; use proc_macro2::{Span, TokenStream}; use proc_macro_error::span_error; use quote::{quote, quote_spanned, ToTokens}; -use syn::{ - self, ext::IdentExt, spanned::Spanned, AngleBracketedGenericArguments, Attribute, Expr, - GenericArgument, Ident, LitStr, MetaNameValue, PathArguments, PathSegment, Type::Path, - TypePath, -}; +use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Ident, LitStr, MetaNameValue}; #[derive(Clone)] pub enum Kind { @@ -28,16 +24,6 @@ pub enum Kind { Skip(Option), } -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Ty { - Bool, - Vec, - Option, - OptionOption, - OptionVec, - Other, -} - #[derive(Clone)] pub struct Method { name: Ident, @@ -424,32 +410,6 @@ impl Attrs { } } - fn ty_from_field(ty: &syn::Type) -> Sp { - let t = |kind| Sp::new(kind, ty.span()); - if let Path(TypePath { - path: syn::Path { ref segments, .. }, - .. - }) = *ty - { - match segments.iter().last().unwrap().ident.to_string().as_str() { - "bool" => t(Ty::Bool), - "Option" => sub_type(ty) - .map(Attrs::ty_from_field) - .map(|ty| match *ty { - Ty::Option => t(Ty::OptionOption), - Ty::Vec => t(Ty::OptionVec), - _ => t(Ty::Option), - }) - .unwrap_or(t(Ty::Option)), - - "Vec" => t(Ty::Vec), - _ => t(Ty::Other), - } - } else { - t(Ty::Other) - } - } - pub fn from_field(field: &syn::Field, struct_casing: Sp) -> Self { let name = field.ident.clone().unwrap(); let mut res = Self::new(field.span(), Name::Derived(name.clone()), struct_casing); @@ -485,7 +445,7 @@ impl Attrs { ); } - let ty = Self::ty_from_field(&field.ty); + let ty = Ty::from_syn_ty(&field.ty); match *ty { Ty::OptionOption => { span_error!( @@ -513,7 +473,7 @@ impl Attrs { } } Kind::Arg(orig_ty) => { - let mut ty = Self::ty_from_field(&field.ty); + let mut ty = Ty::from_syn_ty(&field.ty); if res.has_custom_parser { match *ty { Ty::Option | Ty::Vec | Ty::OptionVec => (), @@ -648,30 +608,6 @@ impl Attrs { } } -pub fn sub_type(t: &syn::Type) -> Option<&syn::Type> { - let segs = match *t { - Path(TypePath { - path: syn::Path { ref segments, .. }, - .. - }) => segments, - _ => return None, - }; - match *segs.iter().last().unwrap() { - PathSegment { - arguments: - PathArguments::AngleBracketed(AngleBracketedGenericArguments { ref args, .. }), - .. - } if args.len() == 1 => { - if let GenericArgument::Type(ref ty) = args[0] { - Some(ty) - } else { - None - } - } - _ => None, - } -} - /// replace all `:` with `, ` when not inside the `<>` /// /// `"author1:author2:author3" => "author1, author2, author3"` diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 80fbc9e8..70347813 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -15,10 +15,12 @@ extern crate proc_macro; mod attrs; mod parse; mod spanned; +mod ty; use crate::{ - attrs::{sub_type, Attrs, CasingStyle, Kind, Name, ParserKind, Ty}, + attrs::{Attrs, CasingStyle, Kind, Name, ParserKind}, spanned::Sp, + ty::{sub_type, Ty}, }; use proc_macro2::{Span, TokenStream}; diff --git a/structopt-derive/src/ty.rs b/structopt-derive/src/ty.rs new file mode 100644 index 00000000..06eb3ecf --- /dev/null +++ b/structopt-derive/src/ty.rs @@ -0,0 +1,108 @@ +//! Special types handling + +use crate::spanned::Sp; + +use syn::{ + spanned::Spanned, GenericArgument, Path, PathArguments, PathArguments::AngleBracketed, + PathSegment, Type, TypePath, +}; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Ty { + Bool, + Vec, + Option, + OptionOption, + OptionVec, + Other, +} + +impl Ty { + pub fn from_syn_ty(ty: &syn::Type) -> Sp { + use Ty::*; + let t = |kind| Sp::new(kind, ty.span()); + + if is_simple_ty(ty, "bool") { + t(Bool) + } else if is_generic_ty(ty, "Vec") { + t(Vec) + } else if let Some(subty) = subty_if_name(ty, "Option") { + if is_generic_ty(subty, "Option") { + t(OptionOption) + } else if is_generic_ty(subty, "Vec") { + t(OptionVec) + } else { + t(Option) + } + } else { + t(Other) + } + } +} + +pub fn sub_type(ty: &syn::Type) -> Option<&syn::Type> { + subty_if(ty, |_| true) +} + +fn only_last_segment(ty: &syn::Type) -> Option<&PathSegment> { + match ty { + Type::Path(TypePath { + qself: None, + path: + Path { + leading_colon: None, + segments, + }, + }) => only_one(segments.iter()), + + _ => None, + } +} + +fn subty_if(ty: &syn::Type, f: F) -> Option<&syn::Type> +where + F: FnOnce(&PathSegment) -> bool, +{ + only_last_segment(ty) + .filter(|segment| f(segment)) + .and_then(|segment| { + if let AngleBracketed(args) = &segment.arguments { + only_one(args.args.iter()).and_then(|genneric| { + if let GenericArgument::Type(ty) = genneric { + Some(ty) + } else { + None + } + }) + } else { + None + } + }) +} + +fn subty_if_name<'a>(ty: &'a syn::Type, name: &str) -> Option<&'a syn::Type> { + subty_if(ty, |seg| seg.ident == name) +} + +fn is_simple_ty(ty: &syn::Type, name: &str) -> bool { + only_last_segment(ty) + .map(|segment| { + if let PathArguments::None = segment.arguments { + segment.ident == name + } else { + false + } + }) + .unwrap_or(false) +} + +fn is_generic_ty(ty: &syn::Type, name: &str) -> bool { + subty_if_name(ty, name).is_some() +} + +fn only_one(mut iter: I) -> Option +where + I: Iterator, +{ + iter.next().filter(|_| iter.next().is_none()) +} diff --git a/tests/special_types.rs b/tests/special_types.rs new file mode 100644 index 00000000..ffed5e20 --- /dev/null +++ b/tests/special_types.rs @@ -0,0 +1,73 @@ +//! Checks that types like `::std::option::Option` are not special + +use structopt::StructOpt; + +#[rustversion::since(1.37)] +#[test] +fn special_types_bool() { + mod inner { + #[allow(non_camel_case_types)] + #[derive(PartialEq, Debug)] + pub struct bool(pub String); + + impl std::str::FromStr for self::bool { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(self::bool(s.into())) + } + } + }; + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + arg: inner::bool, + } + + assert_eq!( + Opt { + arg: inner::bool("success".into()) + }, + Opt::from_iter(&["test", "success"]) + ); +} + +#[test] +fn special_types_option() { + fn parser(s: &str) -> Option { + Some(s.to_string()) + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(parse(from_str = parser))] + arg: ::std::option::Option, + } + + assert_eq!( + Opt { + arg: Some("success".into()) + }, + Opt::from_iter(&["test", "success"]) + ); +} + +#[test] +fn special_types_vec() { + fn parser(s: &str) -> Vec { + vec![s.to_string()] + } + + #[derive(StructOpt, PartialEq, Debug)] + struct Opt { + #[structopt(parse(from_str = parser))] + arg: ::std::vec::Vec, + } + + assert_eq!( + Opt { + arg: vec!["success".into()] + }, + Opt::from_iter(&["test", "success"]) + ); +}