diff --git a/src/lib.rs b/src/lib.rs index 92d93ab..c3e0ec0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1202,7 +1202,7 @@ pub trait StructOptInternal: StructOpt { false } - fn from_subcommand<'a, 'b>(_sub: (&'b str, Option<&'b clap::ArgMatches<'a>>)) -> Option + fn from_subcommand<'a>(_sub: (&'a str, Option<&'a clap::ArgMatches<'_>>)) -> Option where Self: std::marker::Sized, { @@ -1227,7 +1227,7 @@ impl StructOptInternal for Box { } #[doc(hidden)] - fn from_subcommand<'a, 'b>(sub: (&'b str, Option<&'b clap::ArgMatches<'a>>)) -> Option { + fn from_subcommand<'a>(sub: (&'a str, Option<&'a clap::ArgMatches<'_>>)) -> Option { ::from_subcommand(sub).map(Box::new) } diff --git a/structopt-derive/Cargo.toml b/structopt-derive/Cargo.toml index 43628e0..8782016 100644 --- a/structopt-derive/Cargo.toml +++ b/structopt-derive/Cargo.toml @@ -14,11 +14,11 @@ license = "Apache-2.0/MIT" travis-ci = { repository = "TeXitoi/structopt" } [dependencies] -syn = { version = "1", features = ["full"] } +syn = { version = "2", features = ["full"] } quote = "1" proc-macro2 = "1" heck = "0.4.0" -proc-macro-error = "1.0.0" +proc-macro2-diagnostics = "0.10" [features] paw = [] diff --git a/structopt-derive/src/attrs.rs b/structopt-derive/src/attrs.rs index 95ed4a5..4fa6bf4 100644 --- a/structopt-derive/src/attrs.rs +++ b/structopt-derive/src/attrs.rs @@ -13,10 +13,11 @@ use std::env; use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{Span, TokenStream}; -use proc_macro_error::abort; +use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt as _}; use quote::{quote, quote_spanned, ToTokens}; use syn::{ - self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Ident, LitStr, MetaNameValue, Type, + self, ext::IdentExt, spanned::Spanned, Attribute, Expr, ExprLit, Ident, LitStr, MetaNameValue, + Type, }; #[derive(Clone)] @@ -100,18 +101,32 @@ impl Method { Method { name, args } } - fn from_lit_or_env(ident: Ident, lit: Option, env_var: &str) -> Self { + fn from_lit_or_env( + ident: Ident, + lit: Option, + env_var: &str, + ) -> Result { let mut lit = match lit { Some(lit) => lit, None => match env::var(env_var) { Ok(val) => LitStr::new(&val, ident.span()), - Err(_) => { - abort!(ident, - "cannot derive `{}` from Cargo.toml", ident; - note = "`{}` environment variable is not set", env_var; - help = "use `{} = \"...\"` to set {} manually", ident, ident; - ); + Err(error) => { + let diagnostic = ident + .span() + .error(format!("cannot derive `{}` from Cargo.toml", ident)) + .help(format!( + "use `{} = \"...\"` to set {} manually", + ident, ident + )); + return Err(match error { + env::VarError::NotPresent => diagnostic + .note(format!("`{}` environment variable is not set", env_var)), + env::VarError::NotUnicode(val) => diagnostic.note(format!( + "`{}` environment variable contains non-unicode data `{}`", + env_var, val.to_string_lossy(), + )), + }); } }, }; @@ -121,7 +136,7 @@ impl Method { lit = LitStr::new(&edited, lit.span()); } - Method::new(ident, quote!(#lit)) + Ok(Self::new(ident, quote!(#lit))) } } @@ -139,7 +154,7 @@ impl Parser { Sp::new(Parser { kind, func }, span) } - fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp { + fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Result, Diagnostic> { use ParserKind::*; let kind = match &*spec.kind.to_string() { @@ -149,7 +164,12 @@ impl Parser { "try_from_os_str" => TryFromOsStr, "from_occurrences" => FromOccurrences, "from_flag" => FromFlag, - s => abort!(spec.kind, "unsupported parser `{}`", s), + s => { + return Err(spec + .kind + .span() + .error(format!("unsupported parser `{}`", s))) + } }; let func = match spec.parse_func { @@ -158,44 +178,49 @@ impl Parser { quote_spanned!(spec.kind.span()=> ::std::convert::From::from) } TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str), - TryFromOsStr => abort!( - spec.kind, - "you must set parser for `try_from_os_str` explicitly" - ), + TryFromOsStr => { + return Err(spec + .kind + .span() + .error("you must set parser for `try_from_os_str` explicitly")) + } FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }), FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from), }, Some(func) => match func { syn::Expr::Path(_) => quote!(#func), - _ => abort!(func, "`parse` argument must be a function path"), + _ => { + return Err(func + .span() + .error("`parse` argument must be a function path")) + } }, }; let kind = Sp::new(kind, spec.kind.span()); let parser = Parser { kind, func }; - Sp::new(parser, parse_ident.span()) + Ok(Sp::new(parser, parse_ident.span())) } } impl CasingStyle { - fn from_lit(name: LitStr) -> Sp { + fn from_lit(name: LitStr) -> Result, Diagnostic> { use CasingStyle::*; let normalized = name.value().to_upper_camel_case().to_lowercase(); - let cs = |kind| Sp::new(kind, name.span()); - - 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), - "lower" | "lowercase" => cs(Lower), - "upper" | "uppercase" => cs(Upper), - s => abort!(name, "unsupported casing: `{}`", s), - } + let kind = match normalized.as_ref() { + "camel" | "camelcase" => Camel, + "kebab" | "kebabcase" => Kebab, + "pascal" | "pascalcase" => Pascal, + "screamingsnake" | "screamingsnakecase" => ScreamingSnake, + "snake" | "snakecase" => Snake, + "verbatim" | "verbatimcase" => Verbatim, + "lower" | "lowercase" => Lower, + "upper" | "uppercase" => Upper, + s => return Err(name.span().error(format!("unsupported casing: `{}`", s))), + }; + Ok(Sp::new(kind, name.span())) } } @@ -266,10 +291,10 @@ impl Attrs { } } - fn push_attrs(&mut self, attrs: &[Attribute]) { + fn push_attrs(&mut self, attrs: &[Attribute]) -> Result<(), Diagnostic> { use crate::parse::StructOptAttr::*; - for attr in parse_structopt_attributes(attrs) { + for attr in parse_structopt_attributes(attrs)? { match attr { Short(ident) | Long(ident) => { self.push_method(ident, self.name.clone().translate(*self.casing)); @@ -282,7 +307,7 @@ impl Attrs { Subcommand(ident) => { let ty = Sp::call_site(Ty::Other); let kind = Sp::new(Kind::Subcommand(ty), ident.span()); - self.set_kind(kind); + self.set_kind(kind)?; } ExternalSubcommand(ident) => { @@ -291,12 +316,12 @@ impl Attrs { Flatten(ident) => { let kind = Sp::new(Kind::Flatten, ident.span()); - self.set_kind(kind); + self.set_kind(kind)?; } Skip(ident, expr) => { let kind = Sp::new(Kind::Skip(expr), ident.span()); - self.set_kind(kind); + self.set_kind(kind)?; } NoVersion(ident) => self.no_version = Some(ident), @@ -310,13 +335,10 @@ impl Attrs { let ty = if let Some(ty) = self.ty.as_ref() { ty } else { - abort!( - ident, - "#[structopt(default_value)] (without an argument) can be used \ - only on field level"; - - note = "see \ - https://docs.rs/structopt/0.3.5/structopt/#magical-methods") + return Err( + ident.span().error( + "#[structopt(default_value)] (without an argument) can be used only on field level", + ).note("see https://docs.rs/structopt/0.3.5/structopt/#magical-methods")); }; quote_spanned!(ident.span()=> { @@ -335,15 +357,13 @@ impl Attrs { } About(ident, about) => { - self.about = Some(Method::from_lit_or_env( - ident, - about, - "CARGO_PKG_DESCRIPTION", - )); + let about = Method::from_lit_or_env(ident, about, "CARGO_PKG_DESCRIPTION")?; + self.about = Some(about); } Author(ident, author) => { - self.author = Some(Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS")); + let author = Method::from_lit_or_env(ident, author, "CARGO_PKG_AUTHORS")?; + self.author = Some(author); } Version(ident, version) => { @@ -361,35 +381,40 @@ impl Attrs { MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)), RenameAll(_, casing_lit) => { - self.casing = CasingStyle::from_lit(casing_lit); + self.casing = CasingStyle::from_lit(casing_lit)?; } RenameAllEnv(_, casing_lit) => { - self.env_casing = CasingStyle::from_lit(casing_lit); + self.env_casing = CasingStyle::from_lit(casing_lit)?; } Parse(ident, spec) => { self.has_custom_parser = true; - self.parser = Parser::from_spec(ident, spec); + self.parser = Parser::from_spec(ident, spec)?; } } } + Ok(()) } fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) { - use crate::Lit::*; - use crate::Meta::*; + use syn::Lit::*; + use syn::Meta::*; let comment_parts: Vec<_> = attrs .iter() - .filter(|attr| attr.path.is_ident("doc")) + .filter(|attr| attr.path().is_ident("doc")) .filter_map(|attr| { - if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() { - Some(s.value()) - } else { - // non #[doc = "..."] attributes are not our concern - // we leave them for rustc to handle - None + match &attr.meta { + NameValue(MetaNameValue { + value: Expr::Lit(ExprLit { lit: Str(s), .. }), + .. + }) => Some(s.value()), + _ => { + // non #[doc = "..."] attributes are not our concern + // we leave them for rustc to handle + None + } } }) .collect(); @@ -406,23 +431,26 @@ impl Attrs { argument_casing: Sp, env_casing: Sp, allow_skip: bool, - ) -> Self { + ) -> Result { let mut res = Self::new(span, name, parent_attrs, None, argument_casing, env_casing); - res.push_attrs(attrs); + res.push_attrs(attrs)?; res.push_doc_comment(attrs, "about"); if res.has_custom_parser { - abort!( - res.parser.span(), - "`parse` attribute is only allowed on fields" - ); + return Err(res + .parser + .span() + .error("`parse` attribute is only allowed on fields")); } match &*res.kind { - Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"), + Kind::Subcommand(_) => Err(res + .kind + .span() + .error("subcommand is only allowed on fields")), Kind::Skip(_) if !allow_skip => { - abort!(res.kind.span(), "skip is only allowed on fields") + Err(res.kind.span().error("skip is only allowed on fields")) } - Kind::Arg(_) | Kind::ExternalSubcommand | Kind::Flatten | Kind::Skip(_) => res, + Kind::Arg(_) | Kind::ExternalSubcommand | Kind::Flatten | Kind::Skip(_) => Ok(res), } } @@ -431,7 +459,7 @@ impl Attrs { parent_attrs: Option<&Attrs>, struct_casing: Sp, env_casing: Sp, - ) -> Self { + ) -> Result { let name = field.ident.clone().unwrap(); let mut res = Self::new( field.span(), @@ -441,22 +469,22 @@ impl Attrs { struct_casing, env_casing, ); - res.push_attrs(&field.attrs); + res.push_attrs(&field.attrs)?; res.push_doc_comment(&field.attrs, "help"); match &*res.kind { Kind::Flatten => { if res.has_custom_parser { - abort!( - res.parser.span(), - "parse attribute is not allowed for flattened entry" - ); + return Err(res + .parser + .span() + .error("parse attribute is not allowed for flattened entry")); } if res.has_explicit_methods() { - abort!( - res.kind.span(), - "methods are not allowed for flattened entry" - ); + return Err(res + .kind + .span() + .error("methods are not allowed for flattened entry")); } if res.has_doc_methods() { @@ -468,31 +496,31 @@ impl Attrs { Kind::Subcommand(_) => { if res.has_custom_parser { - abort!( - res.parser.span(), - "parse attribute is not allowed for subcommand" - ); + return Err(res + .parser + .span() + .error("parse attribute is not allowed for subcommand")); } if res.has_explicit_methods() { - abort!( - res.kind.span(), - "methods in attributes are not allowed for subcommand" - ); + return Err(res + .kind + .span() + .error("methods in attributes are not allowed for subcommand")); } let ty = Ty::from_syn_ty(&field.ty); match *ty { Ty::OptionOption => { - abort!( - field.ty, - "Option> type is not allowed for subcommand" - ); + return Err(field + .ty + .span() + .error("Option> type is not allowed for subcommand")); } Ty::OptionVec => { - abort!( - field.ty, - "Option> type is not allowed for subcommand" - ); + return Err(field + .ty + .span() + .error("Option> type is not allowed for subcommand")); } _ => (), } @@ -501,10 +529,10 @@ impl Attrs { } Kind::Skip(_) => { if res.has_explicit_methods() { - abort!( - res.kind.span(), - "methods are not allowed for skipped fields" - ); + return Err(res + .kind + .span() + .error("methods are not allowed for skipped fields")); } } Kind::Arg(orig_ty) => { @@ -519,43 +547,46 @@ impl Attrs { match *ty { Ty::Bool => { if res.is_positional() && !res.has_custom_parser { - abort!(field.ty, - "`bool` cannot be used as positional parameter with default parser"; - help = "if you want to create a flag add `long` or `short`"; - help = "If you really want a boolean parameter \ - add an explicit parser, for example `parse(try_from_str)`"; - note = "see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs"; - ) + return Err(field.ty.span().error( + "`bool` cannot be used as positional parameter with default parser").help( + "if you want to create a flag add `long` or `short`").help( + "If you really want a boolean parameter \ + add an explicit parser, for example `parse(try_from_str)`").note( + "see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs")); } if let Some(m) = res.find_method("default_value") { - abort!(m.name, "default_value is meaningless for bool") + return Err(m + .name + .span() + .error("default_value is meaningless for bool")); } if let Some(m) = res.find_method("required") { - abort!(m.name, "required is meaningless for bool") + return Err(m.name.span().error("required is meaningless for bool")); } } Ty::Option => { if let Some(m) = res.find_method("default_value") { - abort!(m.name, "default_value is meaningless for Option") + return Err(m + .name + .span() + .error("default_value is meaningless for Option")); } if let Some(m) = res.find_method("required") { - abort!(m.name, "required is meaningless for Option") + return Err(m.name.span().error("required is meaningless for Option")); } } Ty::OptionOption => { if res.is_positional() { - abort!( - field.ty, - "Option> type is meaningless for positional argument" - ) + return Err(field.ty.span().error( + "Option> type is meaningless for positional argument", + )); } } Ty::OptionVec => { if res.is_positional() { - abort!( - field.ty, - "Option> type is meaningless for positional argument" - ) + return Err(field.ty.span().error( + "Option> type is meaningless for positional argument", + )); } } @@ -565,17 +596,17 @@ impl Attrs { } } - res + Ok(res) } - fn set_kind(&mut self, kind: Sp) { + fn set_kind(&mut self, kind: Sp) -> Result<(), Diagnostic> { if let Kind::Arg(_) = *self.kind { self.kind = kind; + Ok(()) } else { - abort!( - kind.span(), - "subcommand, flatten and skip cannot be used together" - ); + Err(kind + .span() + .error("subcommand, flatten and skip cannot be used together")) } } diff --git a/structopt-derive/src/lib.rs b/structopt-derive/src/lib.rs index 0838f50..9230f53 100644 --- a/structopt-derive/src/lib.rs +++ b/structopt-derive/src/lib.rs @@ -30,9 +30,13 @@ use crate::{ }; use proc_macro2::{Span, TokenStream}; -use proc_macro_error::{abort, abort_call_site, proc_macro_error, set_dummy}; +use proc_macro2_diagnostics::{Diagnostic, SpanDiagnosticExt as _}; use quote::{format_ident, quote, quote_spanned}; -use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, *}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DataStruct, DeriveInput, + Field, FieldsUnnamed, GenericParam, Generics, Ident, ImplGenerics, Path, TypeGenerics, + TypeParamBound, Variant, WhereClause, WherePredicate, +}; /// Default casing style for generated arguments. const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab; @@ -52,11 +56,29 @@ struct GenOutput { /// Generates the `StructOpt` impl. #[proc_macro_derive(StructOpt, attributes(structopt))] -#[proc_macro_error] pub fn structopt(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input: DeriveInput = syn::parse(input).unwrap(); - let gen = impl_structopt(&input); - gen.into() + match impl_structopt(&input) { + Ok(tokens) => tokens.into(), + Err(diag) => { + let struct_name = &input.ident; + let diag = diag.emit_as_item_tokens(); + quote! { + #diag + + impl ::structopt::StructOpt for #struct_name { + fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { + unimplemented!() + } + fn from_clap(_matches: &::structopt::clap::ArgMatches) -> Self { + unimplemented!() + } + } + + impl ::structopt::StructOptInternal for #struct_name {} + }.into() + } + } } /// Generate a block of code to add arguments/subcommands corresponding to @@ -65,14 +87,17 @@ fn gen_augmentation( fields: &Punctuated, app_var: &Ident, parent_attribute: &Attrs, -) -> TokenStream { +) -> Result { let mut subcmds = fields.iter().filter_map(|field| { - let attrs = Attrs::from_field( + let attrs = match Attrs::from_field( field, Some(parent_attribute), parent_attribute.casing(), parent_attribute.env_casing(), - ); + ) { + Ok(attrs) => attrs, + Err(err) => return Some(Err(err)), + }; let kind = attrs.kind(); if let Kind::Subcommand(ty) = &*kind { let subcmd_type = match (**ty, sub_type(&field.ty)) { @@ -96,44 +121,43 @@ fn gen_augmentation( ); #required }; - Some((span, ts)) + Some(Ok((span, ts))) } else { None } }); - let subcmd = subcmds.next().map(|(_, ts)| ts); - if let Some((span, _)) = subcmds.next() { - abort!( - span, - "multiple subcommand sets are not allowed, that's the second" - ); + let subcmd = subcmds.next().transpose()?.map(|(_, ts)| ts); + if let Some((span, _)) = subcmds.next().transpose()? { + return Err(span.error("multiple subcommand sets are not allowed, that's the second")); } let args = fields.iter().filter_map(|field| { - let attrs = Attrs::from_field( + let attrs = match Attrs::from_field( field, Some(parent_attribute), parent_attribute.casing(), parent_attribute.env_casing(), - ); + ) { + Ok(attrs) => attrs, + Err(err) => return Some(Err(err)), + }; let kind = attrs.kind(); match &*kind { - Kind::ExternalSubcommand => abort!( - kind.span(), + Kind::ExternalSubcommand => return Some(Err(kind.span().error( "`external_subcommand` is only allowed on enum variants" - ), + ))), Kind::Subcommand(_) | Kind::Skip(_) => None, Kind::Flatten => { let ty = &field.ty; - Some(quote_spanned! { kind.span()=> + Some(Ok(quote_spanned! { kind.span()=> let #app_var = <#ty as ::structopt::StructOptInternal>::augment_clap(#app_var); let #app_var = if <#ty as ::structopt::StructOptInternal>::is_subcommand() { #app_var.setting(::structopt::clap::AppSettings::SubcommandRequiredElseHelp) } else { #app_var }; - }) + })) } Kind::Arg(ty) => { let convert_type = match **ty { @@ -220,28 +244,31 @@ fn gen_augmentation( let name = attrs.cased_name(); let methods = attrs.field_methods(); - Some(quote_spanned! { field.span()=> + Some(Ok(quote_spanned! { field.span()=> let #app_var = #app_var.arg( ::structopt::clap::Arg::with_name(#name) #modifier #methods ); - }) + })) } } - }); + }).collect::, _>>()?; let app_methods = parent_attribute.top_level_methods(); let version = parent_attribute.version(); - quote! {{ + Ok(quote! {{ let #app_var = #app_var#app_methods; #( #args )* #subcmd #app_var#version - }} + }}) } -fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) -> TokenStream { +fn gen_constructor( + fields: &Punctuated, + parent_attribute: &Attrs, +) -> Result { // This ident is used in several match branches below, // and the `quote[_spanned]` invocations have different spans. // @@ -258,14 +285,13 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) Some(parent_attribute), parent_attribute.casing(), parent_attribute.env_casing(), - ); + )?; let field_name = field.ident.as_ref().unwrap(); let kind = attrs.kind(); - match &*kind { - Kind::ExternalSubcommand => abort!( - kind.span(), + Ok(match &*kind { + Kind::ExternalSubcommand => return Err(kind.span().error( "`external_subcommand` is allowed only on enum variants" - ), + )), Kind::Subcommand(ty) => { let subcmd_type = match (**ty, sub_type(&field.ty)) { @@ -384,29 +410,29 @@ fn gen_constructor(fields: &Punctuated, parent_attribute: &Attrs) quote_spanned!(field.span()=> #field_name: #field_value ) } - } - }); + }) + }).collect::, _>>()?; - quote! {{ + Ok(quote! {{ #( #fields ),* - }} + }}) } fn gen_from_clap( struct_name: &Ident, fields: &Punctuated, parent_attribute: &Attrs, -) -> TokenStream { - let field_block = gen_constructor(fields, parent_attribute); +) -> Result { + let field_block = gen_constructor(fields, parent_attribute)?; - quote! { + Ok(quote! { fn from_clap(matches: &::structopt::clap::ArgMatches) -> Self { #struct_name #field_block } - } + }) } -fn gen_clap(attrs: &[Attribute]) -> GenOutput { +fn gen_clap(attrs: &[Attribute]) -> Result { let name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); let attrs = Attrs::from_struct( @@ -417,17 +443,17 @@ fn gen_clap(attrs: &[Attribute]) -> GenOutput { Sp::call_site(DEFAULT_CASING), Sp::call_site(DEFAULT_ENV_CASING), false, - ); + )?; let tokens = { let name = attrs.cased_name(); quote!(::structopt::clap::App::new(#name)) }; - GenOutput { tokens, attrs } + Ok(GenOutput { tokens, attrs }) } -fn gen_clap_struct(struct_attrs: &[Attribute]) -> GenOutput { - let initial_clap_app_gen = gen_clap(struct_attrs); +fn gen_clap_struct(struct_attrs: &[Attribute]) -> Result { + let initial_clap_app_gen = gen_clap(struct_attrs)?; let clap_tokens = initial_clap_app_gen.tokens; let augmented_tokens = quote! { @@ -437,26 +463,29 @@ fn gen_clap_struct(struct_attrs: &[Attribute]) -> GenOutput { } }; - GenOutput { + Ok(GenOutput { tokens: augmented_tokens, attrs: initial_clap_app_gen.attrs, - } + }) } -fn gen_augment_clap(fields: &Punctuated, parent_attribute: &Attrs) -> TokenStream { +fn gen_augment_clap( + fields: &Punctuated, + parent_attribute: &Attrs, +) -> Result { let app_var = Ident::new("app", Span::call_site()); - let augmentation = gen_augmentation(fields, &app_var, parent_attribute); - quote! { + let augmentation = gen_augmentation(fields, &app_var, parent_attribute)?; + Ok(quote! { fn augment_clap<'a, 'b>( #app_var: ::structopt::clap::App<'a, 'b> ) -> ::structopt::clap::App<'a, 'b> { #augmentation } - } + }) } -fn gen_clap_enum(enum_attrs: &[Attribute]) -> GenOutput { - let initial_clap_app_gen = gen_clap(enum_attrs); +fn gen_clap_enum(enum_attrs: &[Attribute]) -> Result { + let initial_clap_app_gen = gen_clap(enum_attrs)?; let clap_tokens = initial_clap_app_gen.tokens; let tokens = quote! { @@ -467,20 +496,20 @@ fn gen_clap_enum(enum_attrs: &[Attribute]) -> GenOutput { } }; - GenOutput { + Ok(GenOutput { tokens, attrs: initial_clap_app_gen.attrs, - } + }) } fn gen_augment_clap_enum( variants: &Punctuated, parent_attribute: &Attrs, -) -> TokenStream { +) -> Result { use syn::Fields::*; let subcommands = variants.iter().filter_map(|variant| { - let attrs = Attrs::from_struct( + let attrs = match Attrs::from_struct( variant.span(), &variant.attrs, Name::Derived(variant.ident.clone()), @@ -488,7 +517,10 @@ fn gen_augment_clap_enum( parent_attribute.casing(), parent_attribute.env_casing(), true, - ); + ) { + Ok(attrs) => attrs, + Err(err) => return Some(Err(err)), + }; let kind = attrs.kind(); match &*kind { @@ -496,25 +528,24 @@ fn gen_augment_clap_enum( Kind::ExternalSubcommand => { let app_var = Ident::new("app", Span::call_site()); - Some(quote_spanned! { attrs.kind().span()=> + Some(Ok(quote_spanned! { attrs.kind().span()=> let #app_var = #app_var.setting( ::structopt::clap::AppSettings::AllowExternalSubcommands ); - }) + })) }, Kind::Flatten => { match variant.fields { Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; - Some(quote! { + Some(Ok(quote! { let app = <#ty as ::structopt::StructOptInternal>::augment_clap(app); - }) + })) }, - _ => abort!( - variant, + _ => return Some(Err(variant.span().error( "`flatten` is usable only with single-typed tuple variants" - ), + ))), } }, @@ -526,7 +557,10 @@ fn gen_augment_clap_enum( let arg_block = match variant.fields { // If the variant is named, then gen_augmentation already generates the // top level methods (#from_attrs) and version. - Named(ref fields) => gen_augmentation(&fields.named, &app_var, &attrs), + Named(ref fields) => match gen_augmentation(&fields.named, &app_var, &attrs) { + Ok(ts) => ts, + Err(err) => return Some(Err(err)), + }, Unit => quote!( #app_var#from_attrs#version ), Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0]; @@ -545,23 +579,23 @@ fn gen_augment_clap_enum( } } } - Unnamed(..) => abort!(variant, "non single-typed tuple enums are not supported"), + Unnamed(..) => return Some(Err(variant.span().error("non single-typed tuple enums are not supported"))), }; let name = attrs.cased_name(); - Some(quote! { + Some(Ok(quote! { let app = app.subcommand({ let #app_var = ::structopt::clap::SubCommand::with_name(#name); #arg_block }); - }) + })) }, } - }); + }).collect::, _>>()?; let app_methods = parent_attribute.top_level_methods(); let version = parent_attribute.version(); - quote! { + Ok(quote! { fn augment_clap<'a, 'b>( app: ::structopt::clap::App<'a, 'b> ) -> ::structopt::clap::App<'a, 'b> { @@ -569,7 +603,7 @@ fn gen_augment_clap_enum( #( #subcommands )*; app #version } - } + }) } fn gen_from_clap_enum() -> TokenStream { @@ -586,7 +620,7 @@ fn gen_from_subcommand( name: &Ident, variants: &Punctuated, parent_attribute: &Attrs, -) -> TokenStream { +) -> Result { use syn::Fields::*; let mut ext_subcmd = None; @@ -594,7 +628,7 @@ fn gen_from_subcommand( let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants .iter() .filter_map(|variant| { - let attrs = Attrs::from_struct( + let attrs = match Attrs::from_struct( variant.span(), &variant.attrs, Name::Derived(variant.ident.clone()), @@ -602,29 +636,32 @@ fn gen_from_subcommand( parent_attribute.casing(), parent_attribute.env_casing(), true, - ); + ) { + Ok(attrs) => attrs, + Err(err) => return Some(Err(err)), + }; let variant_name = &variant.ident; match *attrs.kind() { Kind::ExternalSubcommand => { if ext_subcmd.is_some() { - abort!( - attrs.kind().span(), + return Some(Err(attrs.kind().span().error( "Only one variant can be marked with `external_subcommand`, \ - this is the second" - ); + this is the second", + ))); } let ty = match variant.fields { Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, - _ => abort!( - variant, - "The enum variant marked with `external_attribute` must be \ + _ => { + return Some(Err(variant.span().error( + "The enum variant marked with `external_attribute` must be \ a single-typed tuple, and the type must be either `Vec` \ - or `Vec`." - ), + or `Vec`.", + ))) + } }; let (span, str_ty, values_of) = match subty_if_name(ty, "Vec") { @@ -644,20 +681,23 @@ fn gen_from_subcommand( } } - None => abort!( - ty, - "The type must be either `Vec` or `Vec` \ - to be used with `external_subcommand`." - ), + None => { + return Some(Err(ty.span().error( + "The type must be either `Vec` or `Vec` \ + to be used with `external_subcommand`.", + ))) + } }; ext_subcmd = Some((span, variant_name, str_ty, values_of)); None } Kind::Skip(_) => None, - _ => Some((variant, attrs)), + _ => Some(Ok((variant, attrs))), } }) + .collect::, _>>()? + .into_iter() .partition(|(_, attrs)| match &*attrs.kind() { Kind::Flatten => true, _ => false, @@ -693,50 +733,57 @@ fn gen_from_subcommand( None => quote!(None), }; - let match_arms = variants.iter().map(|(variant, attrs)| { - let sub_name = attrs.cased_name(); - let variant_name = &variant.ident; - let constructor_block = match variant.fields { - Named(ref fields) => gen_constructor(&fields.named, &attrs), - Unit => quote!(), - Unnamed(ref fields) if fields.unnamed.len() == 1 => { - let ty = &fields.unnamed[0]; - quote!( ( <#ty as ::structopt::StructOpt>::from_clap(#matches) ) ) - } - Unnamed(..) => abort!( - variant.ident, - "non single-typed tuple enums are not supported" - ), - }; + let match_arms = variants + .iter() + .map(|(variant, attrs)| { + let sub_name = attrs.cased_name(); + let variant_name = &variant.ident; + let constructor_block = match variant.fields { + Named(ref fields) => gen_constructor(&fields.named, &attrs)?, + Unit => quote!(), + Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed[0]; + quote!( ( <#ty as ::structopt::StructOpt>::from_clap(#matches) ) ) + } + Unnamed(..) => { + return Err(variant + .ident + .span() + .error("non single-typed tuple enums are not supported")) + } + }; - quote! { - (#sub_name, Some(#matches)) => { - Some(#name :: #variant_name #constructor_block) - } - } - }); + Ok(quote! { + (#sub_name, Some(#matches)) => { + Some(#name :: #variant_name #constructor_block) + } + }) + }) + .collect::, _>>()?; - let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| { - let variant_name = &variant.ident; - match variant.fields { - Unnamed(ref fields) if fields.unnamed.len() == 1 => { - let ty = &fields.unnamed[0]; - quote! { - if let Some(res) = - <#ty as ::structopt::StructOptInternal>::from_subcommand(#other) - { - return Some(#name :: #variant_name (res)); - } + let child_subcommands = flatten_variants + .iter() + .map(|(variant, _attrs)| { + let variant_name = &variant.ident; + match variant.fields { + Unnamed(ref fields) if fields.unnamed.len() == 1 => { + let ty = &fields.unnamed[0]; + Ok(quote! { + if let Some(res) = + <#ty as ::structopt::StructOptInternal>::from_subcommand(#other) + { + return Some(#name :: #variant_name (res)); + } + }) } + _ => Err(variant + .span() + .error("`flatten` is usable only with single-typed tuple variants")), } - _ => abort!( - variant, - "`flatten` is usable only with single-typed tuple variants" - ), - } - }); + }) + .collect::, _>>()?; - quote! { + Ok(quote! { fn from_subcommand<'a, 'b>( sub: (&'b str, Option<&'b ::structopt::clap::ArgMatches<'a>>) ) -> Option { @@ -748,7 +795,7 @@ fn gen_from_subcommand( } } } - } + }) } #[cfg(feature = "paw")] @@ -776,13 +823,13 @@ fn gen_paw_impl(_: &ImplGenerics, _: &Ident, _: &TypeGenerics, _: &TokenStream) fn split_structopt_generics_for_impl( generics: &Generics, ) -> (ImplGenerics, TypeGenerics, TokenStream) { - use syn::{token::Add, TypeParamBound::Trait}; + use syn::{token::Plus, TypeParamBound::Trait}; fn path_ends_with(path: &Path, ident: &str) -> bool { path.segments.last().unwrap().ident == ident } - fn type_param_bounds_contains(bounds: &Punctuated, ident: &str) -> bool { + fn type_param_bounds_contains(bounds: &Punctuated, ident: &str) -> bool { for bound in bounds { if let Trait(bound) = bound { if path_ends_with(&bound.path, ident) { @@ -873,16 +920,16 @@ fn impl_structopt_for_struct( fields: &Punctuated, attrs: &[Attribute], generics: &Generics, -) -> TokenStream { +) -> Result { let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics); - let basic_clap_app_gen = gen_clap_struct(attrs); - let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs); - let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs); + let basic_clap_app_gen = gen_clap_struct(attrs)?; + let augment_clap = gen_augment_clap(fields, &basic_clap_app_gen.attrs)?; + let from_clap = gen_from_clap(name, fields, &basic_clap_app_gen.attrs)?; let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause); let clap_tokens = basic_clap_app_gen.tokens; - quote! { + Ok(quote! { #[allow(unused_variables)] #[allow(unknown_lints)] #[allow( @@ -922,7 +969,7 @@ fn impl_structopt_for_struct( } #paw_impl - } + }) } fn impl_structopt_for_enum( @@ -930,19 +977,19 @@ fn impl_structopt_for_enum( variants: &Punctuated, attrs: &[Attribute], generics: &Generics, -) -> TokenStream { +) -> Result { let (impl_generics, ty_generics, where_clause) = split_structopt_generics_for_impl(&generics); - let basic_clap_app_gen = gen_clap_enum(attrs); + let basic_clap_app_gen = gen_clap_enum(attrs)?; let clap_tokens = basic_clap_app_gen.tokens; let attrs = basic_clap_app_gen.attrs; - let augment_clap = gen_augment_clap_enum(variants, &attrs); + let augment_clap = gen_augment_clap_enum(variants, &attrs)?; let from_clap = gen_from_clap_enum(); - let from_subcommand = gen_from_subcommand(name, variants, &attrs); + let from_subcommand = gen_from_subcommand(name, variants, &attrs)?; let paw_impl = gen_paw_impl(&impl_generics, name, &ty_generics, &where_clause); - quote! { + Ok(quote! { #[allow(unknown_lints)] #[allow(unused_variables, dead_code, unreachable_code)] #[allow( @@ -982,27 +1029,14 @@ fn impl_structopt_for_enum( } #paw_impl - } + }) } -fn impl_structopt(input: &DeriveInput) -> TokenStream { +fn impl_structopt(input: &DeriveInput) -> Result { use syn::Data::*; let struct_name = &input.ident; - set_dummy(quote! { - impl ::structopt::StructOpt for #struct_name { - fn clap<'a, 'b>() -> ::structopt::clap::App<'a, 'b> { - unimplemented!() - } - fn from_clap(_matches: &::structopt::clap::ArgMatches) -> Self { - unimplemented!() - } - } - - impl ::structopt::StructOptInternal for #struct_name {} - }); - match input.data { Struct(DataStruct { fields: syn::Fields::Named(ref fields), @@ -1011,6 +1045,6 @@ fn impl_structopt(input: &DeriveInput) -> TokenStream { Enum(ref e) => { impl_structopt_for_enum(struct_name, &e.variants, &input.attrs, &input.generics) } - _ => abort_call_site!("structopt only supports non-tuple structs and enums"), + _ => Err(Span::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 11511a1..682cbae 100644 --- a/structopt-derive/src/parse.rs +++ b/structopt-derive/src/parse.rs @@ -1,6 +1,5 @@ use std::iter::FromIterator; -use proc_macro_error::{abort, ResultExt}; use quote::ToTokens; use syn::{ self, parenthesized, @@ -53,7 +52,7 @@ impl Parse for StructOptAttr { if input.peek(Token![=]) { // `name = value` attributes. - let assign_token = input.parse::()?; // skip '=' + let _assign_token = input.parse::()?; // skip '=' if input.peek(LitStr) { let lit: LitStr = input.parse()?; @@ -61,12 +60,13 @@ impl Parse for StructOptAttr { let check_empty_lit = |s| { if lit_str.is_empty() { - abort!( - lit, + Err(input.error(format!( "`#[structopt({} = \"\")]` is deprecated in structopt 0.3, \ now it's default behavior", s - ); + ))) + } else { + Ok(()) } }; @@ -76,17 +76,17 @@ impl Parse for StructOptAttr { "default_value" => Ok(DefaultValue(name, Some(lit))), "version" => { - check_empty_lit("version"); + check_empty_lit("version")?; Ok(Version(name, lit)) } "author" => { - check_empty_lit("author"); + check_empty_lit("author")?; Ok(Author(name, Some(lit))) } "about" => { - check_empty_lit("about"); + check_empty_lit("about")?; Ok(About(name, Some(lit))) } @@ -102,19 +102,11 @@ impl Parse for StructOptAttr { _ => Ok(NameLitStr(name, lit)), } } else { - match input.parse::() { - Ok(expr) => { - if name_str == "skip" { - Ok(Skip(name, Some(expr))) - } else { - Ok(NameExpr(name, expr)) - } - } - - Err(_) => abort! { - assign_token, - "expected `string literal` or `expression` after `=`" - }, + let expr = input.parse::()?; + if name_str == "skip" { + Ok(Skip(name, Some(expr))) + } else { + Ok(NameExpr(name, expr)) } } } else if input.peek(syn::token::Paren) { @@ -125,39 +117,41 @@ impl Parse for StructOptAttr { match name_str.as_ref() { "parse" => { let parser_specs: Punctuated = - nested.parse_terminated(ParserSpec::parse)?; + Punctuated::parse_terminated_with(&nested, ParserSpec::parse)?; if parser_specs.len() == 1 { Ok(Parse(name, parser_specs[0].clone())) } else { - abort!(name, "`parse` must have exactly one argument") + return Err(syn::Error::new(name.span(), "`parse` must have exactly one argument")); } } - "raw" => match nested.parse::() { - Ok(bool_token) => { - let expr = ExprLit { - attrs: vec![], - lit: Lit::Bool(bool_token), - }; - let expr = Expr::Lit(expr); - Ok(MethodCall(name, vec![expr])) - } - - Err(_) => { - abort!(name, + "raw" => { + let bool_token = nested.parse::().map_err(|_err| { + // NOTE: this previously emitted: + // + // help = "if you meant to call `clap::Arg::raw()` method \ + // you should use bool literal, like `raw(true)` or `raw(false)`"; + // note = raw_method_suggestion(nested); + // + // using proc-macro-error, but this is not possible using proc-macro2-diagnostics + // because this function returns a syn::Result rather than a Result<_, Diagnostic>. + syn::Error::new(name.span(), "`#[structopt(raw(...))` attributes are removed in structopt 0.3, \ - they are replaced with raw methods"; - help = "if you meant to call `clap::Arg::raw()` method \ - you should use bool literal, like `raw(true)` or `raw(false)`"; - note = raw_method_suggestion(nested); - ); - } - }, + they are replaced with raw methods" + ) + })?; + let expr = ExprLit { + attrs: vec![], + lit: Lit::Bool(bool_token), + }; + let expr = Expr::Lit(expr); + Ok(MethodCall(name, vec![expr])) + } _ => { let method_args: Punctuated<_, Token![,]> = - nested.parse_terminated(Expr::parse)?; + Punctuated::parse_terminated_with(&nested, Expr::parse)?; Ok(MethodCall(name, Vec::from_iter(method_args))) } } @@ -174,19 +168,18 @@ impl Parse for StructOptAttr { "verbatim_doc_comment" => Ok(VerbatimDocComment(name)), "default_value" => Ok(DefaultValue(name, None)), - "about" => (Ok(About(name, None))), - "author" => (Ok(Author(name, None))), + "about" => Ok(About(name, None)), + "author" => Ok(Author(name, None)), "skip" => Ok(Skip(name, None)), - "version" => abort!( - name, + "version" => Err(input.error( "#[structopt(version)] is invalid attribute, \ structopt 0.3 inherits version from Cargo.toml by default, \ - no attribute needed" - ), + no attribute needed", + )), - _ => abort!(name, "unexpected attribute: {}", name_str), + name_str => Err(input.error(format!("unexpected attribute: {}", name_str))), } } } @@ -217,6 +210,7 @@ impl Parse for ParserSpec { } } +#[allow(dead_code)] fn raw_method_suggestion(ts: ParseBuffer) -> String { let do_parse = move || -> Result<(Ident, Punctuated), syn::Error> { let name = ts.parse()?; @@ -260,13 +254,14 @@ fn raw_method_suggestion(ts: ParseBuffer) -> String { } } -pub fn parse_structopt_attributes(all_attrs: &[Attribute]) -> Vec { +pub fn parse_structopt_attributes(all_attrs: &[Attribute]) -> syn::Result> { all_attrs .iter() - .filter(|attr| attr.path.is_ident("structopt")) - .flat_map(|attr| { - attr.parse_args_with(Punctuated::::parse_terminated) - .unwrap_or_abort() + .filter(|attr| attr.path().is_ident("structopt")) + .try_fold(Vec::new(), |mut acc, attr| { + let attrs = + attr.parse_args_with(Punctuated::::parse_terminated)?; + acc.extend(attrs); + Ok(acc) }) - .collect() } diff --git a/tests/ui/enum_flatten.stderr b/tests/ui/enum_flatten.stderr index d74fa85..5307cee 100644 --- a/tests/ui/enum_flatten.stderr +++ b/tests/ui/enum_flatten.stderr @@ -1,6 +1,5 @@ error: `flatten` is usable only with single-typed tuple variants --> $DIR/enum_flatten.rs:14:5 | -14 | / #[structopt(flatten)] -15 | | Variant1, - | |____________^ +14 | #[structopt(flatten)] + | ^ diff --git a/tests/ui/external_subcommand_wrong_type.stderr b/tests/ui/external_subcommand_wrong_type.stderr index 73f12d2..0d525bd 100644 --- a/tests/ui/external_subcommand_wrong_type.stderr +++ b/tests/ui/external_subcommand_wrong_type.stderr @@ -1,8 +1,18 @@ error[E0308]: mismatched types --> $DIR/external_subcommand_wrong_type.rs:13:15 | -13 | Other(Vec) - | ^^^^^^^ expected struct `CString`, found struct `OsString` +11 | enum Command { + | ______- +12 | | #[structopt(external_subcommand)] +13 | | Other(Vec) + | | - ^^^^^^^ expected `Vec`, found `Vec` + | |_________| + | arguments to this enum variant are incorrect | = note: expected struct `Vec` found struct `Vec` +note: tuple variant defined here + --> $DIR/external_subcommand_wrong_type.rs:13:5 + | +13 | Other(Vec) + | ^^^^^ diff --git a/tests/ui/opt_opt_nonpositional.stderr b/tests/ui/opt_opt_nonpositional.stderr index cb9f172..586bf7a 100644 --- a/tests/ui/opt_opt_nonpositional.stderr +++ b/tests/ui/opt_opt_nonpositional.stderr @@ -2,4 +2,4 @@ error: Option> type is meaningless for positional argument --> $DIR/opt_opt_nonpositional.rs:14:8 | 14 | n: Option>, - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^ diff --git a/tests/ui/opt_vec_nonpositional.stderr b/tests/ui/opt_vec_nonpositional.stderr index c6b343f..6f01de5 100644 --- a/tests/ui/opt_vec_nonpositional.stderr +++ b/tests/ui/opt_vec_nonpositional.stderr @@ -2,4 +2,4 @@ error: Option> type is meaningless for positional argument --> $DIR/opt_vec_nonpositional.rs:14:8 | 14 | n: Option>, - | ^^^^^^^^^^^^^^^^ + | ^^^^^^ diff --git a/tests/ui/positional_bool.stderr b/tests/ui/positional_bool.stderr index c3ed1ad..c602f29 100644 --- a/tests/ui/positional_bool.stderr +++ b/tests/ui/positional_bool.stderr @@ -1,9 +1,7 @@ error: `bool` cannot be used as positional parameter with default parser - - = help: if you want to create a flag add `long` or `short` - = help: If you really want a boolean parameter add an explicit parser, for example `parse(try_from_str)` - = note: see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs - + = help: if you want to create a flag add `long` or `short` + = help: If you really want a boolean parameter add an explicit parser, for example `parse(try_from_str)` + = note: see also https://github.com/TeXitoi/structopt/tree/master/examples/true_or_false.rs --> $DIR/positional_bool.rs:5:14 | 5 | verbose: bool, diff --git a/tests/ui/raw.stderr b/tests/ui/raw.stderr index 93b5e38..a428f94 100644 --- a/tests/ui/raw.stderr +++ b/tests/ui/raw.stderr @@ -1,18 +1,10 @@ error: `#[structopt(raw(...))` attributes are removed in structopt 0.3, they are replaced with raw methods - - = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` - = note: if you need to call `clap::Arg/App::case_insensitive` method you can do it like this: #[structopt(case_insensitive = true)] - --> $DIR/raw.rs:13:17 | 13 | #[structopt(raw(case_insensitive = "true"))] | ^^^ error: `#[structopt(raw(...))` attributes are removed in structopt 0.3, they are replaced with raw methods - - = help: if you meant to call `clap::Arg::raw()` method you should use bool literal, like `raw(true)` or `raw(false)` - = note: if you need to call `clap::Arg/App::requires_if` method you can do it like this: #[structopt(requires_if("one", "two"))] - --> $DIR/raw.rs:19:17 | 19 | #[structopt(raw(requires_if = r#""one", "two""#))] diff --git a/tests/ui/skip_without_default.stderr b/tests/ui/skip_without_default.stderr index d08be0c..6cbc182 100644 --- a/tests/ui/skip_without_default.stderr +++ b/tests/ui/skip_without_default.stderr @@ -1,7 +1,5 @@ error[E0277]: the trait bound `Kind: Default` is not satisfied - --> $DIR/skip_without_default.rs:22:17 - | -22 | #[structopt(skip)] - | ^^^^ the trait `Default` is not implemented for `Kind` - | -note: required by `std::default::Default::default` + --> $DIR/skip_without_default.rs:22:17 + | +22 | #[structopt(skip)] + | ^^^^ the trait `Default` is not implemented for `Kind` diff --git a/tests/ui/structopt_empty_attr.stderr b/tests/ui/structopt_empty_attr.stderr index bd3b3ed..7f4b00a 100644 --- a/tests/ui/structopt_empty_attr.stderr +++ b/tests/ui/structopt_empty_attr.stderr @@ -1,5 +1,5 @@ error: expected attribute arguments in parentheses: #[structopt(...)] - --> $DIR/structopt_empty_attr.rs:14:5 + --> $DIR/structopt_empty_attr.rs:14:7 | 14 | #[structopt] - | ^^^^^^^^^^^^ + | ^^^^^^^^^ diff --git a/tests/ui/subcommand_opt_opt.stderr b/tests/ui/subcommand_opt_opt.stderr index 25b37e5..daad02f 100644 --- a/tests/ui/subcommand_opt_opt.stderr +++ b/tests/ui/subcommand_opt_opt.stderr @@ -2,4 +2,4 @@ error: Option> type is not allowed for subcommand --> $DIR/subcommand_opt_opt.rs:18:10 | 18 | cmd: Option>, - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^ diff --git a/tests/ui/subcommand_opt_vec.stderr b/tests/ui/subcommand_opt_vec.stderr index a36071b..a9833a6 100644 --- a/tests/ui/subcommand_opt_vec.stderr +++ b/tests/ui/subcommand_opt_vec.stderr @@ -2,4 +2,4 @@ error: Option> type is not allowed for subcommand --> $DIR/subcommand_opt_vec.rs:18:10 | 18 | cmd: Option>, - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^