Skip to content

Commit

Permalink
Handle special types correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
CreepySkeleton committed Nov 29, 2019
1 parent 6271ee7 commit d1a50bf
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 69 deletions.
72 changes: 4 additions & 68 deletions structopt-derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@
// 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;

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 {
Expand All @@ -28,16 +24,6 @@ pub enum Kind {
Skip(Option<Expr>),
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Ty {
Bool,
Vec,
Option,
OptionOption,
OptionVec,
Other,
}

#[derive(Clone)]
pub struct Method {
name: Ident,
Expand Down Expand Up @@ -424,32 +410,6 @@ impl Attrs {
}
}

fn ty_from_field(ty: &syn::Type) -> Sp<Ty> {
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<CasingStyle>) -> Self {
let name = field.ident.clone().unwrap();
let mut res = Self::new(field.span(), Name::Derived(name.clone()), struct_casing);
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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 => (),
Expand Down Expand Up @@ -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"`
Expand Down
4 changes: 3 additions & 1 deletion structopt-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
108 changes: 108 additions & 0 deletions structopt-derive/src/ty.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<F>(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<I, T>(mut iter: I) -> Option<T>
where
I: Iterator<Item = T>,
{
iter.next().filter(|_| iter.next().is_none())
}
73 changes: 73 additions & 0 deletions tests/special_types.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Err> {
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<String> {
Some(s.to_string())
}

#[derive(StructOpt, PartialEq, Debug)]
struct Opt {
#[structopt(parse(from_str = parser))]
arg: ::std::option::Option<String>,
}

assert_eq!(
Opt {
arg: Some("success".into())
},
Opt::from_iter(&["test", "success"])
);
}

#[test]
fn special_types_vec() {
fn parser(s: &str) -> Vec<String> {
vec![s.to_string()]
}

#[derive(StructOpt, PartialEq, Debug)]
struct Opt {
#[structopt(parse(from_str = parser))]
arg: ::std::vec::Vec<String>,
}

assert_eq!(
Opt {
arg: vec!["success".into()]
},
Opt::from_iter(&["test", "success"])
);
}

0 comments on commit d1a50bf

Please sign in to comment.