diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index ba157b44..a74dbda1 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -18,6 +18,7 @@ syn = { version = "0.15.10", features = ["full"] } quote = "0.6" proc-macro2 = "0.4" heck = "^0.3.0" +proc-macro-error = "0.1" [features] nightly = ["proc-macro2/nightly"] diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index ca33a7a3..4f82eaf3 100755 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -5,23 +5,26 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. +use crate::spanned::Sp; use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase}; use proc_macro2::{Span, TokenStream}; +use proc_macro_error::span_error; use quote::quote; use std::{env, mem}; +use syn::spanned::Spanned as _; use syn::Type::Path; use syn::{ - self, AngleBracketedGenericArguments, Attribute, GenericArgument, Ident, MetaNameValue, + self, AngleBracketedGenericArguments, Attribute, GenericArgument, Ident, LitStr, MetaNameValue, PathArguments, PathSegment, TypePath, }; use crate::parse::*; -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Clone, Debug)] pub enum Kind { - Arg(Ty), - Subcommand(Ty), + Arg(Sp), + Subcommand(Sp), FlattenStruct, Skip, } @@ -36,20 +39,8 @@ pub enum Ty { Other, } -#[derive(Debug)] -pub struct Attrs { - name: String, - cased_name: String, - casing: CasingStyle, - methods: Vec, - parser: (Parser, TokenStream), - has_custom_parser: bool, - kind: Kind, -} - -#[derive(Debug)] -struct Method { - name: String, +pub struct Method { + name: Ident, args: TokenStream, } @@ -79,55 +70,66 @@ pub enum CasingStyle { Verbatim, } -impl ::std::str::FromStr for Parser { - type Err = String; - fn from_str(s: &str) -> Result { - match s { - "from_str" => Ok(Parser::FromStr), - "try_from_str" => Ok(Parser::TryFromStr), - "from_os_str" => Ok(Parser::FromOsStr), - "try_from_os_str" => Ok(Parser::TryFromOsStr), - "from_occurrences" => Ok(Parser::FromOccurrences), - _ => Err(format!("unsupported parser {}", s)), +pub struct Attrs { + name: Sp, + cased_name: String, + casing: Sp, + methods: Vec, + parser: Sp<(Sp, TokenStream)>, + has_custom_parser: bool, + kind: Sp, +} + +impl Parser { + fn from_ident(ident: Ident) -> Sp { + use Parser::*; + + let p = |kind| Sp::new(kind, ident.span()); + match &*ident.to_string() { + "from_str" => p(FromStr), + "try_from_str" => p(TryFromStr), + "from_os_str" => p(FromOsStr), + "try_from_os_str" => p(TryFromOsStr), + "from_occurrences" => p(FromOccurrences), + s => span_error!(ident.span(), "unsupported parser `{}`", s), } } } impl CasingStyle { - fn translate(self, input: &str) -> String { + fn translate(&self, input: &str) -> String { + use CasingStyle::*; + match self { - CasingStyle::Pascal => input.to_camel_case(), - CasingStyle::Kebab => input.to_kebab_case(), - CasingStyle::Camel => input.to_mixed_case(), - CasingStyle::ScreamingSnake => input.to_shouty_snake_case(), - CasingStyle::Snake => input.to_snake_case(), - CasingStyle::Verbatim => String::from(input), + Pascal => input.to_camel_case(), + Kebab => input.to_kebab_case(), + Camel => input.to_mixed_case(), + ScreamingSnake => input.to_shouty_snake_case(), + Snake => input.to_snake_case(), + Verbatim => String::from(input), } } -} -impl ::std::str::FromStr for CasingStyle { - type Err = String; + fn from_lit(name: LitStr) -> Sp { + use CasingStyle::*; - fn from_str(name: &str) -> Result { - let name = name.to_camel_case().to_lowercase(); + let normalized = name.value().to_camel_case().to_lowercase(); + let cs = |kind| Sp::new(kind, name.span()); - let case = match name.as_ref() { - "camel" | "camelcase" => CasingStyle::Camel, - "kebab" | "kebabcase" => CasingStyle::Kebab, - "pascal" | "pascalcase" => CasingStyle::Pascal, - "screamingsnake" | "screamingsnakecase" => CasingStyle::ScreamingSnake, - "snake" | "snakecase" => CasingStyle::Snake, - "verbatim" | "verbatimcase" => CasingStyle::Verbatim, - _ => return Err(format!("unsupported casing: {}", name)), - }; - - Ok(case) + match normalized.as_ref() { + "camel" | "camelcase" => cs(Camel), + "kebab" | "kebabcase" => cs(Kebab), + "pascal" | "pascalcase" => cs(Pascal), + "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake), + "snake" | "snakecase" => cs(Snake), + "verbatim" | "verbatimcase" => cs(Verbatim), + s => span_error!(name.span(), "unsupported casing: `{}`", s), + } } } impl Attrs { - fn new(name: String, casing: CasingStyle) -> Self { + fn new(name: Sp, casing: Sp) -> Self { let cased_name = casing.translate(&name); Self { @@ -135,23 +137,27 @@ impl Attrs { cased_name, casing, methods: vec![], - parser: (Parser::TryFromStr, quote!(::std::str::FromStr::from_str)), + parser: Sp::call_site(( + Sp::call_site(Parser::TryFromStr), + quote!(::std::str::FromStr::from_str), + )), has_custom_parser: false, - kind: Kind::Arg(Ty::Other), + kind: Sp::call_site(Kind::Arg(Sp::call_site(Ty::Other))), } } - fn push_str_method(&mut self, name: &str, arg: &str) { - match (name, arg) { + + fn push_str_method(&mut self, name: Sp, arg: Sp) { + match (&**name, &**arg) { ("about", "") | ("version", "") | ("author", "") => { let methods = mem::replace(&mut self.methods, vec![]); self.methods = methods.into_iter().filter(|m| m.name != name).collect(); } - ("name", new_name) => { - self.name = new_name.into(); - self.cased_name = self.casing.translate(new_name); + ("name", _) => { + self.cased_name = self.casing.translate(&arg); + self.name = arg; } - (name, arg) => self.methods.push(Method { - name: name.to_string(), + _ => self.methods.push(Method { + name: name.as_ident(), args: quote!(#arg), }), } @@ -162,74 +168,81 @@ impl Attrs { for attr in parse_structopt_attributes(attrs) { match attr { - Short => { - let cased_name = &self.cased_name.clone(); - self.push_str_method("short", cased_name); + Short(ident) => { + let cased_name = Sp::call_site(self.cased_name.clone()); + self.push_str_method(ident.into(), cased_name); } - Long => { - let cased_name = &self.cased_name.clone(); - self.push_str_method("long", cased_name); + Long(ident) => { + let cased_name = Sp::call_site(self.cased_name.clone()); + self.push_str_method(ident.into(), cased_name); } - Subcommand => { - self.set_kind(Kind::Subcommand(Ty::Other)); + Subcommand(ident) => { + let ty = Sp::call_site(Ty::Other); + let kind = Sp::new(Kind::Subcommand(ty), ident.span()); + self.set_kind(kind); } - Flatten => { - self.set_kind(Kind::FlattenStruct); + Flatten(ident) => { + let kind = Sp::new(Kind::FlattenStruct, ident.span()); + self.set_kind(kind); } - Skip => { - self.set_kind(Kind::Skip); + Skip(ident) => { + let kind = Sp::new(Kind::Skip, ident.span()); + self.set_kind(kind); } NameLitStr(name, lit) => { - self.push_str_method(&name.to_string(), &lit.value()); + self.push_str_method(name.into(), lit.into()); } NameExpr(name, expr) => self.methods.push(Method { - name: name.to_string(), + name: name.into(), args: quote!(#expr), }), MethodCall(name, args) => self.methods.push(Method { - name: name.to_string(), + name: name.into(), args: quote!(#args), }), - RenameAll(casing_lit) => { - let casing: CasingStyle = { - ::std::str::FromStr::from_str(&casing_lit.value()) - .unwrap_or_else(|error| panic!("{}", error)) - }; - - self.casing = casing; + RenameAll(_, casing_lit) => { + self.casing = CasingStyle::from_lit(casing_lit); self.cased_name = self.casing.translate(&self.name); } - Parse(spec) => { + Parse(ident, spec) => { self.has_custom_parser = true; + self.parser = match spec.parse_func { None => { - use crate::Parser::*; - let parser = spec.kind.to_string().parse().unwrap(); - let function = match parser { + use crate::attrs::Parser::*; + + let parser: Sp<_> = Parser::from_ident(spec.kind).into(); + let function = match *parser { FromStr | FromOsStr => quote!(::std::convert::From::from), TryFromStr => quote!(::std::str::FromStr::from_str), - TryFromOsStr => panic!( + TryFromOsStr => span_error!( + parser.span(), "cannot omit parser function name with `try_from_os_str`" ), FromOccurrences => quote!({ |v| v as _ }), }; - (parser, function) + Sp::new((parser, function), ident.span()) } Some(func) => { - let parser = spec.kind.to_string().parse().unwrap(); + let parser: Sp<_> = Parser::from_ident(spec.kind).into(); match func { - syn::Expr::Path(_) => (parser, quote!(#func)), - _ => panic!("`parse` argument must be a function path"), + syn::Expr::Path(_) => { + Sp::new((parser, quote!(#func)), ident.span()) + } + _ => span_error!( + func.span(), + "`parse` argument must be a function path" + ), } } } @@ -242,8 +255,7 @@ impl Attrs { let doc_comments = attrs .iter() .filter_map(|attr| { - let path = &attr.path; - if quote!(#path).to_string() == "doc" { + if attr.path.is_ident("doc") { attr.interpret_meta() } else { None @@ -297,10 +309,10 @@ impl Attrs { }; if expected_doc_comment_split { - let long_name = String::from("long_") + name; + let long_name = Sp::call_site(format!("long_{}", name)); self.methods.push(Method { - name: long_name, + name: long_name.as_ident(), args: quote!(#merged_lines), }); @@ -309,22 +321,25 @@ impl Attrs { // typically omits the trailing period. let short_arg = doc_comments .first() - .map(String::as_ref) - .map(str::trim) + .map(|s| s.trim()) .map_or("", |s| s.trim_end_matches('.')); self.methods.push(Method { - name: name.to_string(), + name: Ident::new(name, Span::call_site()), args: quote!(#short_arg), }); } else { self.methods.push(Method { - name: name.to_string(), + name: Ident::new(name, Span::call_site()), args: quote!(#merged_lines), }); } } - pub fn from_struct(attrs: &[Attribute], name: String, argument_casing: CasingStyle) -> Self { + pub fn from_struct( + attrs: &[Attribute], + name: Sp, + argument_casing: Sp, + ) -> Self { let mut res = Self::new(name, argument_casing); let attrs_with_env = [ ("version", "CARGO_PKG_VERSION"), @@ -341,155 +356,208 @@ impl Attrs { } else { arg }; - res.push_str_method(name, &new_arg); + let name = Sp::call_site(name.to_string()); + let new_arg = Sp::call_site(new_arg.to_string()); + res.push_str_method(name, new_arg); }); res.push_doc_comment(attrs, "about"); res.push_attrs(attrs); if res.has_custom_parser { - panic!("parse attribute is only allowed on fields"); + span_error!( + res.parser.span(), + "parse attribute is only allowed on fields" + ); } - match res.kind { - Kind::Subcommand(_) => panic!("subcommand is only allowed on fields"), - Kind::FlattenStruct => panic!("flatten is only allowed on fields"), - Kind::Skip => panic!("skip is only allowed on fields"), + match &*res.kind { + Kind::Subcommand(_) => { + span_error!(res.kind.span(), "subcommand is only allowed on fields") + } + Kind::FlattenStruct => { + span_error!(res.kind.span(), "flatten is only allowed on fields") + } + Kind::Skip => span_error!(res.kind.span(), "skip is only allowed on fields"), Kind::Arg(_) => res, } } - fn ty_from_field(ty: &syn::Type) -> Ty { + 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" => Ty::Bool, - "Option" => match sub_type(ty).map(Attrs::ty_from_field) { - Some(Ty::Option) => Ty::OptionOption, - Some(Ty::Vec) => Ty::OptionVec, - _ => Ty::Option, - }, - "Vec" => Ty::Vec, - _ => Ty::Other, + "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 { - Ty::Other + t(Ty::Other) } } - pub fn from_field(field: &syn::Field, struct_casing: CasingStyle) -> Self { - let name = field.ident.as_ref().unwrap().to_string(); - let mut res = Self::new(name, struct_casing); + pub fn from_field(field: &syn::Field, struct_casing: Sp) -> Self { + let name = field.ident.clone().unwrap(); + let mut res = Self::new(name.into(), struct_casing); res.push_doc_comment(&field.attrs, "help"); res.push_attrs(&field.attrs); - match res.kind { + match &*res.kind { Kind::FlattenStruct => { if res.has_custom_parser { - panic!("parse attribute is not allowed for flattened entry"); + span_error!( + res.parser.span(), + "parse attribute is not allowed for flattened entry" + ); } if !res.methods.is_empty() { - panic!("methods and doc comments are not allowed for flattened entry"); + span_error!( + res.kind.span(), + "methods and doc comments are not allowed for flattened entry" + ); } } Kind::Subcommand(_) => { if res.has_custom_parser { - panic!("parse attribute is not allowed for subcommand"); + span_error!( + res.parser.span(), + "parse attribute is not allowed for subcommand" + ); } - if !res.methods.iter().all(|m| m.name == "help") { - panic!("methods in attributes are not allowed for subcommand"); + if let Some(m) = res.methods.iter().find(|m| m.name != "help") { + span_error!( + m.name.span(), + "methods in attributes are not allowed for subcommand" + ); } let ty = Self::ty_from_field(&field.ty); - match ty { + match *ty { Ty::OptionOption => { - panic!("Option> type is not allowed for subcommand"); + span_error!( + ty.span(), + "Option> type is not allowed for subcommand" + ); } Ty::OptionVec => { - panic!("Option> type is not allowed for subcommand"); + span_error!( + ty.span(), + "Option> type is not allowed for subcommand" + ); } _ => (), } - res.kind = Kind::Subcommand(ty); + res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span()); } Kind::Skip => { - if !res.methods.iter().all(|m| m.name == "help") { - panic!("methods are not allowed for skipped fields"); + if let Some(m) = res.methods.iter().find(|m| m.name != "help") { + span_error!(m.name.span(), "methods are not allowed for skipped fields"); } } Kind::Arg(_) => { let mut ty = Self::ty_from_field(&field.ty); if res.has_custom_parser { - match ty { + match *ty { Ty::Option | Ty::Vec => (), - _ => ty = Ty::Other, + _ => ty = Sp::new(Ty::Other, ty.span()), } } - match ty { + + match *ty { Ty::Bool => { - if res.has_method("default_value") { - panic!("default_value is meaningless for bool") + if let Some(m) = res.find_method("default_value") { + span_error!(m.name.span(), "default_value is meaningless for bool") } - if res.has_method("required") { - panic!("required is meaningless for bool") + if let Some(m) = res.find_method("required") { + span_error!(m.name.span(), "required is meaningless for bool") } } Ty::Option => { - if res.has_method("default_value") { - panic!("default_value is meaningless for Option") + if let Some(m) = res.find_method("default_value") { + span_error!(m.name.span(), "default_value is meaningless for Option") } - if res.has_method("required") { - panic!("required is meaningless for Option") + if let Some(m) = res.find_method("required") { + span_error!(m.name.span(), "required is meaningless for Option") } } Ty::OptionOption => { // If it's a positional argument. if !(res.has_method("long") || res.has_method("short")) { - panic!("Option> type is meaningless for positional argument") + span_error!( + ty.span(), + "Option> type is meaningless for positional argument" + ) } } Ty::OptionVec => { // If it's a positional argument. if !(res.has_method("long") || res.has_method("short")) { - panic!("Option> type is meaningless for positional argument") + span_error!( + ty.span(), + "Option> type is meaningless for positional argument" + ) } } _ => (), } - res.kind = Kind::Arg(ty); + res.kind = Sp::call_site(Kind::Arg(ty)); } } res } - fn set_kind(&mut self, kind: Kind) { - if let Kind::Arg(_) = self.kind { + + fn set_kind(&mut self, kind: Sp) { + if let Kind::Arg(_) = *self.kind { self.kind = kind; } else { - panic!("subcommand, flatten and skip cannot be used together"); + span_error!( + kind.span(), + "subcommand, flatten and skip cannot be used together" + ); } } - pub fn has_method(&self, method: &str) -> bool { - self.methods.iter().any(|m| m.name == method) + + pub fn has_method(&self, name: &str) -> bool { + self.find_method(name).is_some() + } + + pub fn find_method(&self, name: &str) -> Option<&Method> { + self.methods.iter().find(|m| m.name == name) } + pub fn methods(&self) -> TokenStream { - let methods = self.methods.iter().map(|&Method { ref name, ref args }| { - let name = Ident::new(name, Span::call_site()); - quote!( .#name(#args) ) - }); + let methods = self + .methods + .iter() + .map(|&Method { ref name, ref args }| quote!( .#name(#args) )); quote!( #(#methods)* ) } + pub fn cased_name(&self) -> &str { &self.cased_name } - pub const fn parser(&self) -> &(Parser, TokenStream) { + + pub fn parser(&self) -> &(Sp, TokenStream) { &self.parser } - pub const fn kind(&self) -> Kind { - self.kind + + pub fn kind(&self) -> Sp { + self.kind.clone() } - pub const fn casing(&self) -> CasingStyle { - self.casing + + pub fn casing(&self) -> Sp { + self.casing.clone() } } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index d7a2b380..21716c83 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -14,11 +14,15 @@ extern crate proc_macro; mod attrs; mod parse; +mod spanned; use crate::attrs::{sub_type, Attrs, CasingStyle, Kind, Parser, Ty}; +use crate::spanned::Sp; use proc_macro2::{Span, TokenStream}; -use quote::quote; +use proc_macro_error::{call_site_error, filter_macro_errors, span_error}; +use quote::{quote, quote_spanned}; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::token::Comma; use syn::*; @@ -38,9 +42,11 @@ struct GenOutput { /// Generates the `StructOpt` impl. #[proc_macro_derive(StructOpt, attributes(structopt))] pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input: DeriveInput = syn::parse(input).unwrap(); - let gen = impl_structopt(&input); - gen.into() + filter_macro_errors! { + let input: DeriveInput = syn::parse(input).unwrap(); + let gen = impl_structopt(&input); + gen.into() + } } /// Generate a block of code to add arguments/subcommands corresponding to @@ -50,43 +56,45 @@ fn gen_augmentation( app_var: &Ident, parent_attribute: &Attrs, ) -> TokenStream { - let subcmds: Vec<_> = fields - .iter() - .filter_map(|field| { - let attrs = Attrs::from_field(field, parent_attribute.casing()); - if let Kind::Subcommand(ty) = attrs.kind() { - let subcmd_type = match (ty, sub_type(&field.ty)) { - (Ty::Option, Some(sub_type)) => sub_type, - _ => &field.ty, - }; - let required = if ty == Ty::Option { - quote!() - } else { - quote! { - let #app_var = #app_var.setting( - ::structopt::clap::AppSettings::SubcommandRequiredElseHelp - ); - } - }; - - Some(quote! { - let #app_var = <#subcmd_type>::augment_clap( #app_var ); - #required - }) + let mut subcmds = fields.iter().filter_map(|field| { + let attrs = Attrs::from_field(field, parent_attribute.casing()); + if let Kind::Subcommand(ty) = &*attrs.kind() { + let subcmd_type = match (**ty, sub_type(&field.ty)) { + (Ty::Option, Some(sub_type)) => sub_type, + _ => &field.ty, + }; + let required = if **ty == Ty::Option { + quote!() } else { - None - } - }) - .collect(); + quote! { + let #app_var = #app_var.setting( + ::structopt::clap::AppSettings::SubcommandRequiredElseHelp + ); + } + }; + + let span = field.span(); + let ts = quote! { + let #app_var = <#subcmd_type>::augment_clap( #app_var ); + #required + }; + Some((span, ts)) + } else { + None + } + }); - assert!( - subcmds.len() <= 1, - "cannot have more than one nested subcommand" - ); + let subcmd = subcmds.next().map(|(_, ts)| ts); + if let Some((span, _)) = subcmds.next() { + span_error!( + span, + "nested subcommands are not allowed, that's the second" + ); + } let args = fields.iter().filter_map(|field| { let attrs = Attrs::from_field(field, parent_attribute.casing()); - match attrs.kind() { + match &*attrs.kind() { Kind::Subcommand(_) | Kind::Skip => None, Kind::FlattenStruct => { let ty = &field.ty; @@ -100,36 +108,37 @@ fn gen_augmentation( }) } Kind::Arg(ty) => { - let convert_type = match ty { + let convert_type = match **ty { Ty::Vec | Ty::Option => sub_type(&field.ty).unwrap_or(&field.ty), Ty::OptionOption | Ty::OptionVec => sub_type(&field.ty).and_then(sub_type).unwrap_or(&field.ty), _ => &field.ty, }; - let occurrences = attrs.parser().0 == Parser::FromOccurrences; + let occurrences = *attrs.parser().0 == Parser::FromOccurrences; - let validator = match *attrs.parser() { + let (parser, f) = attrs.parser(); + let validator = match **parser { // clippy v0.0.212 (1fac380 2019-02-20) produces `redundant_closure` warnings // for `.map_err(|e| e.to_string())` when `e` is a reference // (e.g. `&'static str`). To suppress the warning, we have to write // `|e| (&e).to_string()` since `e` may be a reference or non-reference. - // When Rust 1.35 is released, this hack will be obsolute because the next + // When Rust 1.35 is released, this hack will be obsolete because the next // stable clippy is going to stop triggering the warning for macros. // https://github.com/rust-lang/rust-clippy/pull/3816 - (Parser::TryFromStr, ref f) => quote! { + Parser::TryFromStr => quote! { .validator(|s| { #f(&s) .map(|_: #convert_type| ()) .map_err(|e| (&e).to_string()) }) }, - (Parser::TryFromOsStr, ref f) => quote! { + Parser::TryFromOsStr => quote! { .validator_os(|s| #f(&s).map(|_: #convert_type| ())) }, _ => quote!(), }; - let modifier = match ty { + let modifier = match **ty { Ty::Bool => quote!( .takes_value(false).multiple(false) ), Ty::Option => quote!( .takes_value(true).multiple(false) #validator ), Ty::OptionOption => { @@ -161,7 +170,7 @@ fn gen_augmentation( quote! {{ #( #args )* - #( #subcmds )* + #subcmd #app_var }} } @@ -170,41 +179,43 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) let fields = fields.iter().map(|field| { let attrs = Attrs::from_field(field, parent_attribute.casing()); let field_name = field.ident.as_ref().unwrap(); - match attrs.kind() { + let kind = attrs.kind(); + match &*kind { Kind::Subcommand(ty) => { - let subcmd_type = match (ty, sub_type(&field.ty)) { + let subcmd_type = match (**ty, sub_type(&field.ty)) { (Ty::Option, Some(sub_type)) => sub_type, _ => &field.ty, }; - let unwrapper = match ty { + let unwrapper = match **ty { Ty::Option => quote!(), _ => quote!( .unwrap() ), }; quote!(#field_name: <#subcmd_type>::from_subcommand(matches.subcommand())#unwrapper) } Kind::FlattenStruct => quote!(#field_name: ::structopt::StructOpt::from_clap(matches)), - Kind::Skip => quote!(#field_name: Default::default()), + Kind::Skip => quote_spanned!(kind.span()=> #field_name: Default::default()), Kind::Arg(ty) => { - use crate::Parser::*; - let (value_of, values_of, parse) = match *attrs.parser() { - (FromStr, ref f) => (quote!(value_of), quote!(values_of), f.clone()), - (TryFromStr, ref f) => ( + use crate::attrs::Parser::*; + let (parser, f) = attrs.parser(); + let (value_of, values_of, parse) = match **parser { + FromStr => (quote!(value_of), quote!(values_of), f.clone()), + TryFromStr => ( quote!(value_of), quote!(values_of), quote!(|s| #f(s).unwrap()), ), - (FromOsStr, ref f) => (quote!(value_of_os), quote!(values_of_os), f.clone()), - (TryFromOsStr, ref f) => ( + FromOsStr => (quote!(value_of_os), quote!(values_of_os), f.clone()), + TryFromOsStr => ( quote!(value_of_os), quote!(values_of_os), quote!(|s| #f(s).unwrap()), ), - (FromOccurrences, ref f) => (quote!(occurrences_of), quote!(), f.clone()), + FromOccurrences => (quote!(occurrences_of), quote!(), f.clone()), }; - let occurrences = attrs.parser().0 == Parser::FromOccurrences; + let occurrences = *attrs.parser().0 == Parser::FromOccurrences; let name = attrs.cased_name(); - let field_value = match ty { + let field_value = match **ty { Ty::Bool => quote!(matches.is_present(#name)), Ty::Option => quote! { matches.#value_of(#name) @@ -266,11 +277,9 @@ fn gen_from_clap( } fn gen_clap(attrs: &[Attribute]) -> GenOutput { - let name = std::env::var("CARGO_PKG_NAME") - .ok() - .unwrap_or_else(String::default); + let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); - let attrs = Attrs::from_struct(attrs, name, DEFAULT_CASING); + let attrs = Attrs::from_struct(attrs, Sp::call_site(name), Sp::call_site(DEFAULT_CASING)); let tokens = { let name = attrs.cased_name(); let methods = attrs.methods(); @@ -335,8 +344,11 @@ fn gen_augment_clap_enum( use syn::Fields::*; let subcommands = variants.iter().map(|variant| { - let name = variant.ident.to_string(); - let attrs = Attrs::from_struct(&variant.attrs, name, parent_attribute.casing()); + let attrs = Attrs::from_struct( + &variant.attrs, + variant.ident.clone().into(), + parent_attribute.casing(), + ); let app_var = Ident::new("subcommand", Span::call_site()); let arg_block = match variant.fields { Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs), @@ -356,7 +368,7 @@ fn gen_augment_clap_enum( } } } - Unnamed(..) => panic!("{}: tuple enums are not supported", variant.ident), + Unnamed(..) => call_site_error!("{}: tuple enums are not supported", variant.ident), }; let name = attrs.cased_name(); @@ -398,7 +410,7 @@ fn gen_from_subcommand( let match_arms = variants.iter().map(|variant| { let attrs = Attrs::from_struct( &variant.attrs, - variant.ident.to_string(), + variant.ident.clone().into(), parent_attribute.casing(), ); let sub_name = attrs.cased_name(); @@ -410,7 +422,7 @@ fn gen_from_subcommand( let ty = &fields.unnamed[0]; quote!( ( <#ty as ::structopt::StructOpt>::from_clap(matches) ) ) } - Unnamed(..) => panic!("{}: tuple enums are not supported", variant.ident), + Unnamed(..) => call_site_error!("{}: tuple enums are not supported", variant.ident), }; quote! { @@ -512,14 +524,12 @@ fn impl_structopt(input: &DeriveInput) -> TokenStream { use syn::Data::*; let struct_name = &input.ident; - let inner_impl = match input.data { + match input.data { Struct(DataStruct { fields: syn::Fields::Named(ref fields), .. }) => impl_structopt_for_struct(struct_name, &fields.named, &input.attrs), Enum(ref e) => impl_structopt_for_enum(struct_name, &e.variants, &input.attrs), - _ => panic!("structopt only supports non-tuple structs and enums"), - }; - - quote!(#inner_impl) + _ => call_site_error!("structopt only supports non-tuple structs and enums"), + } } diff --git a/structopt-derive/src/parse.rs b/structopt-derive/src/parse.rs index 916c97d7..df7c7eaf 100755 --- a/structopt-derive/src/parse.rs +++ b/structopt-derive/src/parse.rs @@ -1,7 +1,8 @@ -use quote::quote; +use proc_macro_error::{span_error, ResultExt}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::{self, parenthesized, Attribute, Expr, Ident, LitStr, Token}; +use syn::spanned::Spanned; +use syn::{self, parenthesized, parse2, Attribute, Expr, Ident, LitStr, Token}; pub struct StructOptAttributes { pub paren_token: syn::token::Paren, @@ -11,6 +12,7 @@ pub struct StructOptAttributes { impl Parse for StructOptAttributes { fn parse(input: ParseStream<'_>) -> syn::Result { let content; + Ok(StructOptAttributes { paren_token: parenthesized!(content in input), attrs: content.parse_terminated(StructOptAttr::parse)?, @@ -19,13 +21,13 @@ impl Parse for StructOptAttributes { } pub enum StructOptAttr { - Short, - Long, - Flatten, - Subcommand, - Skip, - Parse(ParserSpec), - RenameAll(LitStr), + Short(Ident), + Long(Ident), + Flatten(Ident), + Subcommand(Ident), + Skip(Ident), + Parse(Ident, ParserSpec), + RenameAll(Ident, LitStr), NameLitStr(Ident, LitStr), NameExpr(Ident, Expr), MethodCall(Ident, Punctuated), @@ -45,7 +47,7 @@ impl Parse for StructOptAttr { match name_str.as_ref() { "rename_all" => { let casing_lit: LitStr = input.parse()?; - Ok(RenameAll(casing_lit)) + Ok(RenameAll(name, casing_lit)) } _ => { @@ -69,17 +71,9 @@ impl Parse for StructOptAttr { nested.parse_terminated(ParserSpec::parse)?; if parser_specs.len() == 1 { - Ok(Parse(parser_specs[0].clone())) + Ok(Parse(name, parser_specs[0].clone())) } else { - // Use `Error::new` instead of `input.error(...)` - // because when `input.error` tries to locate current span - // and sees that there is no tokens left to parse it adds - // 'unexpected end of input` to the error message, which is - // undesirable and misleading. - Err(syn::Error::new( - nested.cursor().span(), - "parse must have exactly one argument", - )) + span_error!(name.span(), "parse must have exactly one argument") } } @@ -91,15 +85,12 @@ impl Parse for StructOptAttr { } else { // Attributes represented with a sole identifier. match name_str.as_ref() { - "long" => Ok(Long), - "short" => Ok(Short), - "flatten" => Ok(Flatten), - "subcommand" => Ok(Subcommand), - "skip" => Ok(Skip), - _ => { - let msg = format!("unexpected attribute: {}", name_str); - Err(input.error(&msg)) - } + "long" => Ok(Long(name)), + "short" => Ok(Short(name)), + "flatten" => Ok(Flatten(name)), + "subcommand" => Ok(Subcommand(name)), + "skip" => Ok(Skip(name)), + _ => span_error!(name.span(), "unexpected attribute: {}", name_str), } } } @@ -114,8 +105,9 @@ pub struct ParserSpec { impl Parse for ParserSpec { fn parse(input: ParseStream<'_>) -> syn::Result { - let err_msg = "unknown value parser specification"; - let kind = input.parse().map_err(|_| input.error(err_msg))?; + let kind = input + .parse() + .map_err(|_| input.error("parser specification must start with identifier"))?; let eq_token = input.parse()?; let parse_func = match eq_token { None => None, @@ -130,22 +122,23 @@ impl Parse for ParserSpec { } pub fn parse_structopt_attributes(all_attrs: &[Attribute]) -> Vec { - let mut s_opt_attrs: Vec = vec![]; - for attr in all_attrs { - let path = &attr.path; - if let "structopt" = quote!(#path).to_string().as_ref() { - let tokens = attr.tts.clone(); - let is_empty = tokens.is_empty(); - let so_attrs: StructOptAttributes = syn::parse2(tokens).unwrap_or_else(|err| { - let tokens_str = if is_empty { - String::new() - } else { - format!("problematic tokens: {}", &attr.tts) - }; - panic!("{}, {}", err.to_string(), tokens_str) - }); - s_opt_attrs.extend(so_attrs.attrs); - } - } - s_opt_attrs + all_attrs + .iter() + .filter(|attr| attr.path.is_ident("structopt")) + .flat_map(|attr| { + let attrs: StructOptAttributes = parse2(attr.tts.clone()) + .map_err(|e| match &*e.to_string() { + // this error message is misleading and points to Span::call_site() + // so we patch it with something meaningful + "unexpected end of input, expected parentheses" => { + let span = attr.path.span(); + let patch_msg = "expected parentheses after `structopt`"; + syn::Error::new(span, patch_msg) + } + _ => e, + }) + .unwrap_or_exit(); + attrs.attrs + }) + .collect() } diff --git a/structopt-derive/src/spanned.rs b/structopt-derive/src/spanned.rs new file mode 100644 index 00000000..5b781f18 --- /dev/null +++ b/structopt-derive/src/spanned.rs @@ -0,0 +1,86 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote_spanned, ToTokens}; +use std::ops::{Deref, DerefMut}; +use syn::LitStr; + +/// An entity with a span attached. +#[derive(Debug, Clone)] +pub struct Sp { + span: Span, + val: T, +} + +impl Sp { + pub fn new(val: T, span: Span) -> Self { + Sp { val, span } + } + + pub fn call_site(val: T) -> Self { + Sp { + val, + span: Span::call_site(), + } + } + + pub fn span(&self) -> Span { + self.span.clone() + } +} + +impl Sp { + pub fn as_ident(&self) -> Ident { + Ident::new(&self.to_string(), self.span.clone()) + } +} + +impl Deref for Sp { + type Target = T; + + fn deref(&self) -> &T { + &self.val + } +} + +impl DerefMut for Sp { + fn deref_mut(&mut self) -> &mut T { + &mut self.val + } +} + +impl From for Sp { + fn from(ident: Ident) -> Self { + Sp { + val: ident.to_string(), + span: ident.span(), + } + } +} + +impl From for Sp { + fn from(lit: LitStr) -> Self { + Sp { + val: lit.value(), + span: lit.span(), + } + } +} + +impl PartialEq for Sp { + fn eq(&self, other: &Sp) -> bool { + self.val == other.val + } +} + +impl> AsRef for Sp { + fn as_ref(&self) -> &str { + self.val.as_ref() + } +} + +impl ToTokens for Sp { + fn to_tokens(&self, stream: &mut TokenStream) { + let val = &self.val; + let quoted = quote_spanned!(self.span=> #val); + stream.extend(quoted); + } +} diff --git a/tests/ui/bool_default_value.rs b/tests/ui/bool_default_value.rs index 9bdb0c94..86396901 100644 --- a/tests/ui/bool_default_value.rs +++ b/tests/ui/bool_default_value.rs @@ -15,7 +15,4 @@ struct Opt { b: bool, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/bool_default_value.stderr b/tests/ui/bool_default_value.stderr index f7aa64b7..1e26a2dc 100644 --- a/tests/ui/bool_default_value.stderr +++ b/tests/ui/bool_default_value.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/bool_default_value.rs:11:10 +error: default_value is meaningless for bool + --> $DIR/bool_default_value.rs:14:24 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: default_value is meaningless for bool +14 | #[structopt(short, default_value = true)] + | ^^^^^^^^^^^^^ diff --git a/tests/ui/bool_required.rs b/tests/ui/bool_required.rs index 018223c2..6c612d4f 100644 --- a/tests/ui/bool_required.rs +++ b/tests/ui/bool_required.rs @@ -15,7 +15,4 @@ struct Opt { b: bool, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/bool_required.stderr b/tests/ui/bool_required.stderr index 0ca45b7d..0b80d481 100644 --- a/tests/ui/bool_required.stderr +++ b/tests/ui/bool_required.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/bool_required.rs:11:10 +error: required is meaningless for bool + --> $DIR/bool_required.rs:14:24 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: required is meaningless for bool +14 | #[structopt(short, required = true)] + | ^^^^^^^^ diff --git a/tests/ui/flatten_and_doc.rs b/tests/ui/flatten_and_doc.rs index 6a7fe6b2..6e64cbf5 100644 --- a/tests/ui/flatten_and_doc.rs +++ b/tests/ui/flatten_and_doc.rs @@ -8,7 +8,7 @@ use structopt::StructOpt; -#[derive(StructOpt)] +#[derive(StructOpt, Debug)] struct DaemonOpts { #[structopt(short)] user: String, @@ -24,7 +24,4 @@ struct Opt { opts: DaemonOpts, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/flatten_and_doc.stderr b/tests/ui/flatten_and_doc.stderr index 4a359a4b..2724dbbb 100644 --- a/tests/ui/flatten_and_doc.stderr +++ b/tests/ui/flatten_and_doc.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/flatten_and_doc.rs:19:10 +error: methods and doc comments are not allowed for flattened entry + --> $DIR/flatten_and_doc.rs:23:17 | -19 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: methods and doc comments are not allowed for flattened entry +23 | #[structopt(flatten)] + | ^^^^^^^ diff --git a/tests/ui/flatten_and_methods.rs b/tests/ui/flatten_and_methods.rs index 4be47954..cdea4e74 100644 --- a/tests/ui/flatten_and_methods.rs +++ b/tests/ui/flatten_and_methods.rs @@ -8,7 +8,7 @@ use structopt::StructOpt; -#[derive(StructOpt)] +#[derive(StructOpt, Debug)] struct DaemonOpts { #[structopt(short)] user: String, @@ -23,7 +23,4 @@ struct Opt { opts: DaemonOpts, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/flatten_and_methods.stderr b/tests/ui/flatten_and_methods.stderr index caff8dbc..f058eb3f 100644 --- a/tests/ui/flatten_and_methods.stderr +++ b/tests/ui/flatten_and_methods.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/flatten_and_methods.rs:19:10 +error: methods and doc comments are not allowed for flattened entry + --> $DIR/flatten_and_methods.rs:22:24 | -19 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: methods and doc comments are not allowed for flattened entry +22 | #[structopt(short, flatten)] + | ^^^^^^^ diff --git a/tests/ui/flatten_and_parse.rs b/tests/ui/flatten_and_parse.rs index e0570212..a19f8d58 100644 --- a/tests/ui/flatten_and_parse.rs +++ b/tests/ui/flatten_and_parse.rs @@ -8,7 +8,7 @@ use structopt::StructOpt; -#[derive(StructOpt)] +#[derive(StructOpt, Debug)] struct DaemonOpts { #[structopt(short)] user: String, @@ -23,7 +23,4 @@ struct Opt { opts: DaemonOpts, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/flatten_and_parse.stderr b/tests/ui/flatten_and_parse.stderr index 8847d764..e217a840 100644 --- a/tests/ui/flatten_and_parse.stderr +++ b/tests/ui/flatten_and_parse.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/flatten_and_parse.rs:19:10 +error: parse attribute is not allowed for flattened entry + --> $DIR/flatten_and_parse.rs:22:26 | -19 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: parse attribute is not allowed for flattened entry +22 | #[structopt(flatten, parse(from_occurrences))] + | ^^^^^ diff --git a/tests/ui/non_existent_attr.rs b/tests/ui/non_existent_attr.rs index 96daf45c..2c02cc99 100644 --- a/tests/ui/non_existent_attr.rs +++ b/tests/ui/non_existent_attr.rs @@ -15,7 +15,4 @@ struct Opt { debug: bool, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/non_existent_attr.stderr b/tests/ui/non_existent_attr.stderr index 3f35373a..6592154b 100644 --- a/tests/ui/non_existent_attr.stderr +++ b/tests/ui/non_existent_attr.stderr @@ -1,7 +1,7 @@ error[E0599]: no method named `non_existing_attribute` found for type `clap::args::arg::Arg<'_, '_>` in the current scope - --> $DIR/non_existent_attr.rs:11:10 + --> $DIR/non_existent_attr.rs:14:24 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ +14 | #[structopt(short, non_existing_attribute = 1)] + | ^^^^^^^^^^^^^^^^^^^^^^ For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui/opt_opt_nonpositional.rs b/tests/ui/opt_opt_nonpositional.rs index 2a08105b..97b5efcc 100644 --- a/tests/ui/opt_opt_nonpositional.rs +++ b/tests/ui/opt_opt_nonpositional.rs @@ -14,7 +14,4 @@ struct Opt { n: Option>, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/opt_opt_nonpositional.stderr b/tests/ui/opt_opt_nonpositional.stderr index 439781d1..586bf7a5 100644 --- a/tests/ui/opt_opt_nonpositional.stderr +++ b/tests/ui/opt_opt_nonpositional.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/opt_opt_nonpositional.rs:11:10 +error: Option> type is meaningless for positional argument + --> $DIR/opt_opt_nonpositional.rs:14:8 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: Option> type is meaningless for positional argument +14 | n: Option>, + | ^^^^^^ diff --git a/tests/ui/opt_vec_nonpositional.rs b/tests/ui/opt_vec_nonpositional.rs index 0f6f0784..9c4fec13 100644 --- a/tests/ui/opt_vec_nonpositional.rs +++ b/tests/ui/opt_vec_nonpositional.rs @@ -14,7 +14,4 @@ struct Opt { n: Option>, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/opt_vec_nonpositional.stderr b/tests/ui/opt_vec_nonpositional.stderr index 79d59ede..6f01de56 100644 --- a/tests/ui/opt_vec_nonpositional.stderr +++ b/tests/ui/opt_vec_nonpositional.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/opt_vec_nonpositional.rs:11:10 +error: Option> type is meaningless for positional argument + --> $DIR/opt_vec_nonpositional.rs:14:8 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: Option> type is meaningless for positional argument +14 | n: Option>, + | ^^^^^^ diff --git a/tests/ui/option_default_value.rs b/tests/ui/option_default_value.rs index a86bc0ea..3043fa45 100644 --- a/tests/ui/option_default_value.rs +++ b/tests/ui/option_default_value.rs @@ -15,7 +15,4 @@ struct Opt { n: Option, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/option_default_value.stderr b/tests/ui/option_default_value.stderr index 53533d35..2215497a 100644 --- a/tests/ui/option_default_value.stderr +++ b/tests/ui/option_default_value.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/option_default_value.rs:11:10 +error: default_value is meaningless for Option + --> $DIR/option_default_value.rs:14:24 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: default_value is meaningless for Option +14 | #[structopt(short, default_value = 1)] + | ^^^^^^^^^^^^^ diff --git a/tests/ui/option_required.rs b/tests/ui/option_required.rs index d91afbf2..bd03133a 100644 --- a/tests/ui/option_required.rs +++ b/tests/ui/option_required.rs @@ -15,7 +15,4 @@ struct Opt { n: Option, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/option_required.stderr b/tests/ui/option_required.stderr index 93a4d8db..0230d57d 100644 --- a/tests/ui/option_required.stderr +++ b/tests/ui/option_required.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/option_required.rs:11:10 +error: required is meaningless for Option + --> $DIR/option_required.rs:14:24 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: required is meaningless for Option +14 | #[structopt(short, required = true)] + | ^^^^^^^^ diff --git a/tests/ui/parse_empty_try_from_os.rs b/tests/ui/parse_empty_try_from_os.rs index acfef0b2..b559efe4 100644 --- a/tests/ui/parse_empty_try_from_os.rs +++ b/tests/ui/parse_empty_try_from_os.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/parse_empty_try_from_os.stderr b/tests/ui/parse_empty_try_from_os.stderr index 45295593..004881c6 100644 --- a/tests/ui/parse_empty_try_from_os.stderr +++ b/tests/ui/parse_empty_try_from_os.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/parse_empty_try_from_os.rs:11:10 +error: cannot omit parser function name with `try_from_os_str` + --> $DIR/parse_empty_try_from_os.rs:14:23 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: cannot omit parser function name with `try_from_os_str` +14 | #[structopt(parse(try_from_os_str))] + | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/parse_function_is_not_path.rs b/tests/ui/parse_function_is_not_path.rs index 5eebc57f..eb663830 100644 --- a/tests/ui/parse_function_is_not_path.rs +++ b/tests/ui/parse_function_is_not_path.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/parse_function_is_not_path.stderr b/tests/ui/parse_function_is_not_path.stderr index c89d39f2..7cf74445 100644 --- a/tests/ui/parse_function_is_not_path.stderr +++ b/tests/ui/parse_function_is_not_path.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/parse_function_is_not_path.rs:11:10 +error: `parse` argument must be a function path + --> $DIR/parse_function_is_not_path.rs:14:34 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: `parse` argument must be a function path +14 | #[structopt(parse(from_str = "2"))] + | ^^^ diff --git a/tests/ui/parse_literal_spec.rs b/tests/ui/parse_literal_spec.rs index b6f125a1..160b80a4 100644 --- a/tests/ui/parse_literal_spec.rs +++ b/tests/ui/parse_literal_spec.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/parse_literal_spec.stderr b/tests/ui/parse_literal_spec.stderr index 8d2c8534..6e99e8b7 100644 --- a/tests/ui/parse_literal_spec.stderr +++ b/tests/ui/parse_literal_spec.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/parse_literal_spec.rs:11:10 +error: parser specification must start with identifier + --> $DIR/parse_literal_spec.rs:14:23 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: unknown value parser specification, problematic tokens: ( parse ( "from_str" ) ) +14 | #[structopt(parse("from_str"))] + | ^^^^^^^^^^ diff --git a/tests/ui/parse_not_zero_args.rs b/tests/ui/parse_not_zero_args.rs index 87291789..b727a07c 100644 --- a/tests/ui/parse_not_zero_args.rs +++ b/tests/ui/parse_not_zero_args.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/parse_not_zero_args.stderr b/tests/ui/parse_not_zero_args.stderr index 8af3e259..11abe06f 100644 --- a/tests/ui/parse_not_zero_args.stderr +++ b/tests/ui/parse_not_zero_args.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/parse_not_zero_args.rs:11:10 +error: parse must have exactly one argument + --> $DIR/parse_not_zero_args.rs:14:17 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: parse must have exactly one argument, problematic tokens: ( parse ( from_str , from_str ) ) +14 | #[structopt(parse(from_str, from_str))] + | ^^^^^ diff --git a/tests/ui/rename_all_wrong_casing.rs b/tests/ui/rename_all_wrong_casing.rs index 4dabe14b..529b1d4d 100644 --- a/tests/ui/rename_all_wrong_casing.rs +++ b/tests/ui/rename_all_wrong_casing.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/rename_all_wrong_casing.stderr b/tests/ui/rename_all_wrong_casing.stderr index 3feb2fa3..2a720800 100644 --- a/tests/ui/rename_all_wrong_casing.stderr +++ b/tests/ui/rename_all_wrong_casing.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/rename_all_wrong_casing.rs:11:10 +error: unsupported casing: `fail` + --> $DIR/rename_all_wrong_casing.rs:12:42 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: unsupported casing: fail +12 | #[structopt(name = "basic", rename_all = "fail")] + | ^^^^^^ diff --git a/tests/ui/skip_flatten.rs b/tests/ui/skip_flatten.rs index 5c14e5f9..96d5c0ae 100644 --- a/tests/ui/skip_flatten.rs +++ b/tests/ui/skip_flatten.rs @@ -32,11 +32,8 @@ enum Command { impl Default for Command { fn default() -> Self { - Pound { acorns: 0 } + Command::Pound { acorns: 0 } } } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/skip_flatten.stderr b/tests/ui/skip_flatten.stderr index d09320b6..76477a3c 100644 --- a/tests/ui/skip_flatten.stderr +++ b/tests/ui/skip_flatten.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/skip_flatten.rs:11:10 +error: subcommand, flatten and skip cannot be used together + --> $DIR/skip_flatten.rs:17:23 | -11 | #[derive(StructOpt)] - | ^^^^^^^^^ - | - = help: message: subcommand, flatten and skip cannot be used together +17 | #[structopt(skip, flatten)] + | ^^^^^^^ diff --git a/tests/ui/skip_subcommand.rs b/tests/ui/skip_subcommand.rs index e512290b..af08a6d2 100644 --- a/tests/ui/skip_subcommand.rs +++ b/tests/ui/skip_subcommand.rs @@ -32,11 +32,8 @@ enum Command { impl Default for Command { fn default() -> Self { - Pound { acorns: 0 } + Command::Pound { acorns: 0 } } } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/skip_subcommand.stderr b/tests/ui/skip_subcommand.stderr index 9156b11f..aba2d69f 100644 --- a/tests/ui/skip_subcommand.stderr +++ b/tests/ui/skip_subcommand.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/skip_subcommand.rs:11:10 +error: subcommand, flatten and skip cannot be used together + --> $DIR/skip_subcommand.rs:17:29 | -11 | #[derive(StructOpt)] - | ^^^^^^^^^ - | - = help: message: subcommand, flatten and skip cannot be used together +17 | #[structopt(subcommand, skip)] + | ^^^^ diff --git a/tests/ui/skip_with_other_options.rs b/tests/ui/skip_with_other_options.rs index 464bfd22..fe135937 100644 --- a/tests/ui/skip_with_other_options.rs +++ b/tests/ui/skip_with_other_options.rs @@ -9,7 +9,4 @@ pub struct Opts { b: u32, } -fn main() { - let opts = Opts::from_args(); - println!("{:?}", opts); -} +fn main() {} diff --git a/tests/ui/skip_with_other_options.stderr b/tests/ui/skip_with_other_options.stderr index 5744e5c7..7519eadc 100644 --- a/tests/ui/skip_with_other_options.stderr +++ b/tests/ui/skip_with_other_options.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/skip_with_other_options.rs:3:10 +error: methods are not allowed for skipped fields + --> $DIR/skip_with_other_options.rs:8:23 | -3 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: methods are not allowed for skipped fields +8 | #[structopt(skip, long)] + | ^^^^ diff --git a/tests/ui/skip_without_default.rs b/tests/ui/skip_without_default.rs index 44ab5a52..f8ce8a36 100644 --- a/tests/ui/skip_without_default.rs +++ b/tests/ui/skip_without_default.rs @@ -23,7 +23,4 @@ pub struct Opts { k: Kind, } -fn main() { - let opts = Opts::from_args(); - println!("{:?}", opts); -} +fn main() {} diff --git a/tests/ui/skip_without_default.stderr b/tests/ui/skip_without_default.stderr index 62d976ef..330898f6 100644 --- a/tests/ui/skip_without_default.stderr +++ b/tests/ui/skip_without_default.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `Kind: std::default::Default` is not satisfied - --> $DIR/skip_without_default.rs:17:10 + --> $DIR/skip_without_default.rs:22:17 | -17 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ the trait `std::default::Default` is not implemented for `Kind` +22 | #[structopt(skip)] + | ^^^^ the trait `std::default::Default` is not implemented for `Kind` | = note: required by `std::default::Default::default` diff --git a/tests/ui/struct_flatten.rs b/tests/ui/struct_flatten.rs index 2b205f16..b20b87b5 100644 --- a/tests/ui/struct_flatten.rs +++ b/tests/ui/struct_flatten.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/struct_flatten.stderr b/tests/ui/struct_flatten.stderr index b7bb5975..7f0fc6d6 100644 --- a/tests/ui/struct_flatten.stderr +++ b/tests/ui/struct_flatten.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/struct_flatten.rs:11:10 +error: flatten is only allowed on fields + --> $DIR/struct_flatten.rs:12:29 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: flatten is only allowed on fields +12 | #[structopt(name = "basic", flatten)] + | ^^^^^^^ diff --git a/tests/ui/struct_parse.rs b/tests/ui/struct_parse.rs index e428b23f..a46c4d57 100644 --- a/tests/ui/struct_parse.rs +++ b/tests/ui/struct_parse.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/struct_parse.stderr b/tests/ui/struct_parse.stderr index 3872dbf4..ac78b3ee 100644 --- a/tests/ui/struct_parse.stderr +++ b/tests/ui/struct_parse.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/struct_parse.rs:11:10 +error: parse attribute is only allowed on fields + --> $DIR/struct_parse.rs:12:29 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: parse attribute is only allowed on fields +12 | #[structopt(name = "basic", parse(from_str))] + | ^^^^^ diff --git a/tests/ui/struct_subcommand.rs b/tests/ui/struct_subcommand.rs index ac0b1457..b10bbc73 100644 --- a/tests/ui/struct_subcommand.rs +++ b/tests/ui/struct_subcommand.rs @@ -15,7 +15,4 @@ struct Opt { s: String, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/struct_subcommand.stderr b/tests/ui/struct_subcommand.stderr index f7430741..438f6f80 100644 --- a/tests/ui/struct_subcommand.stderr +++ b/tests/ui/struct_subcommand.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/struct_subcommand.rs:11:10 +error: subcommand is only allowed on fields + --> $DIR/struct_subcommand.rs:12:29 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: subcommand is only allowed on fields +12 | #[structopt(name = "basic", subcommand)] + | ^^^^^^^^^^ diff --git a/tests/ui/structopt_empty_attr.rs b/tests/ui/structopt_empty_attr.rs index a7fc0b99..833d214d 100644 --- a/tests/ui/structopt_empty_attr.rs +++ b/tests/ui/structopt_empty_attr.rs @@ -15,8 +15,5 @@ struct Opt { debug: bool, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/structopt_empty_attr.stderr b/tests/ui/structopt_empty_attr.stderr index cdb3320c..dde36309 100644 --- a/tests/ui/structopt_empty_attr.stderr +++ b/tests/ui/structopt_empty_attr.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/structopt_empty_attr.rs:11:10 +error: expected parentheses after `structopt` + --> $DIR/structopt_empty_attr.rs:14:7 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: unexpected end of input, expected parentheses, +14 | #[structopt] + | ^^^^^^^^^ diff --git a/tests/ui/structopt_name_value_attr.rs b/tests/ui/structopt_name_value_attr.rs index 3d9388f2..a85b27df 100644 --- a/tests/ui/structopt_name_value_attr.rs +++ b/tests/ui/structopt_name_value_attr.rs @@ -15,8 +15,5 @@ struct Opt { debug: bool, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/structopt_name_value_attr.stderr b/tests/ui/structopt_name_value_attr.stderr index ccfe3369..f681978d 100644 --- a/tests/ui/structopt_name_value_attr.stderr +++ b/tests/ui/structopt_name_value_attr.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/structopt_name_value_attr.rs:11:10 +error: expected parentheses + --> $DIR/structopt_name_value_attr.rs:14:17 | -11 | #[derive(StructOpt, Debug)] - | ^^^^^^^^^ - | - = help: message: expected parentheses, problematic tokens: = "short" +14 | #[structopt = "short"] + | ^ diff --git a/tests/ui/subcommand_and_flatten.rs b/tests/ui/subcommand_and_flatten.rs index 4ffb74fa..9716447c 100644 --- a/tests/ui/subcommand_and_flatten.rs +++ b/tests/ui/subcommand_and_flatten.rs @@ -30,7 +30,4 @@ enum Command { }, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/subcommand_and_flatten.stderr b/tests/ui/subcommand_and_flatten.stderr index f2f835a6..cacea5ed 100644 --- a/tests/ui/subcommand_and_flatten.stderr +++ b/tests/ui/subcommand_and_flatten.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/subcommand_and_flatten.rs:11:10 +error: subcommand, flatten and skip cannot be used together + --> $DIR/subcommand_and_flatten.rs:17:29 | -11 | #[derive(StructOpt)] - | ^^^^^^^^^ - | - = help: message: subcommand, flatten and skip cannot be used together +17 | #[structopt(subcommand, flatten)] + | ^^^^^^^ diff --git a/tests/ui/subcommand_and_methods.rs b/tests/ui/subcommand_and_methods.rs index 71cfd9c3..5cc8c7fd 100644 --- a/tests/ui/subcommand_and_methods.rs +++ b/tests/ui/subcommand_and_methods.rs @@ -30,7 +30,4 @@ enum Command { }, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/subcommand_and_methods.stderr b/tests/ui/subcommand_and_methods.stderr index e42933ff..1d7f95cf 100644 --- a/tests/ui/subcommand_and_methods.stderr +++ b/tests/ui/subcommand_and_methods.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/subcommand_and_methods.rs:11:10 +error: methods in attributes are not allowed for subcommand + --> $DIR/subcommand_and_methods.rs:17:29 | -11 | #[derive(StructOpt)] - | ^^^^^^^^^ - | - = help: message: methods in attributes are not allowed for subcommand +17 | #[structopt(subcommand, long)] + | ^^^^ diff --git a/tests/ui/subcommand_and_parse.rs b/tests/ui/subcommand_and_parse.rs index 1f1d2e92..6eb1874f 100644 --- a/tests/ui/subcommand_and_parse.rs +++ b/tests/ui/subcommand_and_parse.rs @@ -30,7 +30,4 @@ enum Command { }, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/subcommand_and_parse.stderr b/tests/ui/subcommand_and_parse.stderr index 593169cc..40700565 100644 --- a/tests/ui/subcommand_and_parse.stderr +++ b/tests/ui/subcommand_and_parse.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/subcommand_and_parse.rs:11:10 +error: parse attribute is not allowed for subcommand + --> $DIR/subcommand_and_parse.rs:17:29 | -11 | #[derive(StructOpt)] - | ^^^^^^^^^ - | - = help: message: parse attribute is not allowed for subcommand +17 | #[structopt(subcommand, parse(from_occurrences))] + | ^^^^^ diff --git a/tests/ui/subcommand_opt_opt.rs b/tests/ui/subcommand_opt_opt.rs index 8d691d20..760db803 100644 --- a/tests/ui/subcommand_opt_opt.rs +++ b/tests/ui/subcommand_opt_opt.rs @@ -30,7 +30,4 @@ enum Command { }, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/subcommand_opt_opt.stderr b/tests/ui/subcommand_opt_opt.stderr index 21b8d403..daad02fa 100644 --- a/tests/ui/subcommand_opt_opt.stderr +++ b/tests/ui/subcommand_opt_opt.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/subcommand_opt_opt.rs:11:10 +error: Option> type is not allowed for subcommand + --> $DIR/subcommand_opt_opt.rs:18:10 | -11 | #[derive(StructOpt)] - | ^^^^^^^^^ - | - = help: message: Option> type is not allowed for subcommand +18 | cmd: Option>, + | ^^^^^^ diff --git a/tests/ui/subcommand_opt_vec.rs b/tests/ui/subcommand_opt_vec.rs index e3e02f0a..4d8d3d5e 100644 --- a/tests/ui/subcommand_opt_vec.rs +++ b/tests/ui/subcommand_opt_vec.rs @@ -30,7 +30,4 @@ enum Command { }, } -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/subcommand_opt_vec.stderr b/tests/ui/subcommand_opt_vec.stderr index bf8ab276..a9833a60 100644 --- a/tests/ui/subcommand_opt_vec.stderr +++ b/tests/ui/subcommand_opt_vec.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked - --> $DIR/subcommand_opt_vec.rs:11:10 +error: Option> type is not allowed for subcommand + --> $DIR/subcommand_opt_vec.rs:18:10 | -11 | #[derive(StructOpt)] - | ^^^^^^^^^ - | - = help: message: Option> type is not allowed for subcommand +18 | cmd: Option>, + | ^^^^^^ diff --git a/tests/ui/tuple_struct.rs b/tests/ui/tuple_struct.rs index af9b1d5c..f5f363a9 100644 --- a/tests/ui/tuple_struct.rs +++ b/tests/ui/tuple_struct.rs @@ -12,7 +12,4 @@ use structopt::StructOpt; #[structopt(name = "basic")] struct Opt(u32); -fn main() { - let opt = Opt::from_args(); - println!("{:?}", opt); -} +fn main() {} diff --git a/tests/ui/tuple_struct.stderr b/tests/ui/tuple_struct.stderr index bbce69c5..9f2876f6 100644 --- a/tests/ui/tuple_struct.stderr +++ b/tests/ui/tuple_struct.stderr @@ -1,7 +1,5 @@ -error: proc-macro derive panicked +error: structopt only supports non-tuple structs and enums --> $DIR/tuple_struct.rs:11:10 | 11 | #[derive(StructOpt, Debug)] | ^^^^^^^^^ - | - = help: message: structopt only supports non-tuple structs and enums