Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle special types correctly #287

Merged
merged 1 commit into from
Nov 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"])
);
}