diff --git a/Cargo.lock b/Cargo.lock index 4ef3bead3cb..9f2a9a96a9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,7 +181,7 @@ checksum = "ebb4bd301db2e2ca1f5be131c24eb8ebf2d9559bc3744419e93baf8ddea7e670" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -228,7 +228,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.99", ] [[package]] @@ -531,7 +531,7 @@ dependencies = [ "proc-macro2", "pulldown-cmark", "quote", - "syn", + "syn 2.0.113", ] [[package]] @@ -584,7 +584,7 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -874,7 +874,7 @@ checksum = "1b4464d46ce68bfc7cb76389248c7c254def7baca8bece0693b02b83842c4c88" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -1429,7 +1429,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -1652,7 +1652,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2313,7 +2313,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2447,7 +2447,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -2934,7 +2934,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -3150,7 +3150,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.99", ] [[package]] @@ -3192,6 +3192,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.113" +source = "git+https://github.com/DJMcNab/syn.git?branch=with-default-fields#218d003ea6f37910d3d997f2183026c974063eec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sys-locale" version = "0.3.1" @@ -3313,7 +3323,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -3524,7 +3534,7 @@ checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -3888,7 +3898,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.99", "wasm-bindgen-shared", ] @@ -3910,7 +3920,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4226,7 +4236,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] diff --git a/clap_derive/Cargo.toml b/clap_derive/Cargo.toml index f3987867568..5b6032f4b3b 100644 --- a/clap_derive/Cargo.toml +++ b/clap_derive/Cargo.toml @@ -30,7 +30,7 @@ proc-macro = true bench = false [dependencies] -syn = { version = "2.0.8", features = ["full"] } +syn = { git = "https://github.com/DJMcNab/syn.git", branch = "with-default-fields", features = ["full"] } quote = "1.0.9" proc-macro2 = "1.0.69" heck = "0.5.0" diff --git a/clap_derive/src/derives/args.rs b/clap_derive/src/derives/args.rs index f8f95e41654..f67d8825d53 100644 --- a/clap_derive/src/derives/args.rs +++ b/clap_derive/src/derives/args.rs @@ -14,20 +14,21 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; +use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Generics}; use syn::{ - punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field, - Fields, FieldsNamed, Generics, + DataStructWithDefault, DataWithDefault, DeriveInputWithDefault, FieldWithDefault, + FieldsNamedWithDefault, FieldsWithDefault, }; use crate::item::{Item, Kind, Name}; use crate::utils::{inner_type, sub_type, Sp, Ty}; -pub(crate) fn derive_args(input: &DeriveInput) -> Result { +pub(crate) fn derive_args(input: &DeriveInputWithDefault) -> Result { let ident = &input.ident; match input.data { - Data::Struct(DataStruct { - fields: Fields::Named(ref fields), + DataWithDefault::Struct(DataStructWithDefault { + fields: FieldsWithDefault::Named(ref fields), .. }) => { let name = Name::Derived(ident.clone()); @@ -35,13 +36,13 @@ pub(crate) fn derive_args(input: &DeriveInput) -> Result { let name = Name::Derived(ident.clone()); let item = Item::from_args_struct(input, name)?; - let fields = Punctuated::::new(); + let fields = Punctuated::::new(); let fields = fields .iter() .map(|field| { @@ -59,7 +60,7 @@ pub(crate) fn gen_for_struct( item: &Item, item_name: &Ident, generics: &Generics, - fields: &[(&Field, Item)], + fields: &[(&FieldWithDefault, Item)], ) -> Result { if !matches!(&*item.kind(), Kind::Command(_)) { abort! { item.kind().span(), @@ -167,7 +168,7 @@ pub(crate) fn gen_for_struct( /// Generate a block of code to add arguments/subcommands corresponding to /// the `fields` to an cmd. pub(crate) fn gen_augment( - fields: &[(&Field, Item)], + fields: &[(&FieldWithDefault, Item)], app_var: &Ident, parent_item: &Item, override_required: bool, @@ -440,7 +441,9 @@ pub(crate) fn gen_augment( }}) } -pub(crate) fn gen_constructor(fields: &[(&Field, Item)]) -> Result { +pub(crate) fn gen_constructor( + fields: &[(&FieldWithDefault, Item)], +) -> Result { let fields = fields.iter().map(|(field, item)| { let field_name = field.ident.as_ref().unwrap(); let kind = item.kind(); @@ -552,7 +555,7 @@ pub(crate) fn gen_constructor(fields: &[(&Field, Item)]) -> Result Result { let mut genned_fields = Vec::new(); @@ -661,7 +664,7 @@ fn gen_parsers( item: &Item, ty: &Sp, field_name: &Ident, - field: &Field, + field: &FieldWithDefault, update: Option<&TokenStream>, ) -> Result { let span = ty.span(); @@ -776,8 +779,8 @@ pub(crate) fn raw_deprecated() -> TokenStream { pub(crate) fn collect_args_fields<'a>( item: &'a Item, - fields: &'a FieldsNamed, -) -> Result, syn::Error> { + fields: &'a FieldsNamedWithDefault, +) -> Result, syn::Error> { fields .named .iter() diff --git a/clap_derive/src/derives/parser.rs b/clap_derive/src/derives/parser.rs index bdbcfd39b8e..87db97461dd 100644 --- a/clap_derive/src/derives/parser.rs +++ b/clap_derive/src/derives/parser.rs @@ -14,25 +14,27 @@ use proc_macro2::TokenStream; use quote::quote; +use syn::DataStructWithDefault; +use syn::DataWithDefault; +use syn::DeriveInputWithDefault; +use syn::FieldWithDefault; +use syn::FieldsWithDefault; use syn::Ident; -use syn::Variant; -use syn::{ - self, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields, - Generics, -}; +use syn::VariantWithDefault; +use syn::{self, punctuated::Punctuated, token::Comma, Generics}; use crate::derives::args::collect_args_fields; use crate::derives::{args, into_app, subcommand}; use crate::item::Item; use crate::item::Name; -pub(crate) fn derive_parser(input: &DeriveInput) -> Result { +pub(crate) fn derive_parser(input: &DeriveInputWithDefault) -> Result { let ident = &input.ident; let pkg_name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default(); match input.data { - Data::Struct(DataStruct { - fields: Fields::Named(ref fields), + DataWithDefault::Struct(DataStructWithDefault { + fields: FieldsWithDefault::Named(ref fields), .. }) => { let name = Name::Assigned(quote!(#pkg_name)); @@ -40,13 +42,13 @@ pub(crate) fn derive_parser(input: &DeriveInput) -> Result { let name = Name::Assigned(quote!(#pkg_name)); let item = Item::from_args_struct(input, name)?; - let fields = Punctuated::::new(); + let fields = Punctuated::::new(); let fields = fields .iter() .map(|field| { @@ -56,7 +58,7 @@ pub(crate) fn derive_parser(input: &DeriveInput) -> Result, syn::Error>>()?; gen_for_struct(&item, ident, &input.generics, &fields) } - Data::Enum(ref e) => { + DataWithDefault::Enum(ref e) => { let name = Name::Assigned(quote!(#pkg_name)); let item = Item::from_subcommand_enum(input, name)?; let variants = e @@ -78,7 +80,7 @@ fn gen_for_struct( item: &Item, item_name: &Ident, generics: &Generics, - fields: &[(&Field, Item)], + fields: &[(&FieldWithDefault, Item)], ) -> Result { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); @@ -102,7 +104,7 @@ fn gen_for_enum( item: &Item, item_name: &Ident, generics: &Generics, - variants: &[(&Variant, Item)], + variants: &[(&VariantWithDefault, Item)], ) -> Result { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); diff --git a/clap_derive/src/derives/subcommand.rs b/clap_derive/src/derives/subcommand.rs index a9240c391f5..bf7a1b60e1a 100644 --- a/clap_derive/src/derives/subcommand.rs +++ b/clap_derive/src/derives/subcommand.rs @@ -14,18 +14,19 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; -use syn::{spanned::Spanned, Data, DeriveInput, FieldsUnnamed, Generics, Variant}; +use syn::{spanned::Spanned, Generics}; +use syn::{DataWithDefault, DeriveInputWithDefault, FieldsUnnamedWithDefault, VariantWithDefault}; use crate::derives::args; use crate::derives::args::collect_args_fields; use crate::item::{Item, Kind, Name}; use crate::utils::{is_simple_ty, subty_if_name}; -pub(crate) fn derive_subcommand(input: &DeriveInput) -> Result { +pub(crate) fn derive_subcommand(input: &DeriveInputWithDefault) -> Result { let ident = &input.ident; match input.data { - Data::Enum(ref e) => { + DataWithDefault::Enum(ref e) => { let name = Name::Derived(ident.clone()); let item = Item::from_subcommand_enum(input, name)?; let variants = e @@ -47,7 +48,7 @@ pub(crate) fn gen_for_enum( item: &Item, item_name: &Ident, generics: &Generics, - variants: &[(&Variant, Item)], + variants: &[(&VariantWithDefault, Item)], ) -> Result { if !matches!(&*item.kind(), Kind::Command(_)) { abort! { item.kind().span(), @@ -136,11 +137,11 @@ pub(crate) fn gen_for_enum( } fn gen_augment( - variants: &[(&Variant, Item)], + variants: &[(&VariantWithDefault, Item)], parent_item: &Item, override_required: bool, ) -> Result { - use syn::Fields::{Named, Unit, Unnamed}; + use syn::FieldsWithDefault::{Named, Unit, Unnamed}; let app_var = Ident::new("__clap_app", Span::call_site()); @@ -183,7 +184,7 @@ fn gen_augment( } Kind::Flatten(_) => match variant.fields { - Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + Unnamed(FieldsUnnamedWithDefault { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0].ty; let deprecations = if !override_required { item.deprecations() @@ -224,7 +225,7 @@ fn gen_augment( abort!(variant, "non single-typed tuple enums are not supported") } Unit => quote!( #subcommand_var ), - Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + Unnamed(FieldsUnnamedWithDefault { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0].ty; if override_required { quote_spanned! { ty.span()=> @@ -294,7 +295,7 @@ fn gen_augment( #subcommand_var #final_from_attrs } } - Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { + Unnamed(FieldsUnnamedWithDefault { ref unnamed, .. }) if unnamed.len() == 1 => { let ty = &unnamed[0].ty; let arg_block = if override_required { quote_spanned! { ty.span()=> @@ -356,8 +357,8 @@ fn gen_augment( }) } -fn gen_has_subcommand(variants: &[(&Variant, Item)]) -> Result { - use syn::Fields::Unnamed; +fn gen_has_subcommand(variants: &[(&VariantWithDefault, Item)]) -> Result { + use syn::FieldsWithDefault::Unnamed; let mut ext_subcmd = false; @@ -420,8 +421,10 @@ fn gen_has_subcommand(variants: &[(&Variant, Item)]) -> Result Result { - use syn::Fields::{Named, Unit, Unnamed}; +fn gen_from_arg_matches( + variants: &[(&VariantWithDefault, Item)], +) -> Result { + use syn::FieldsWithDefault::{Named, Unit, Unnamed}; let subcommand_name_var = format_ident!("__clap_name"); let sub_arg_matches_var = format_ident!("__clap_arg_matches"); @@ -571,8 +574,10 @@ fn gen_from_arg_matches(variants: &[(&Variant, Item)]) -> Result Result { - use syn::Fields::{Named, Unit, Unnamed}; +fn gen_update_from_arg_matches( + variants: &[(&VariantWithDefault, Item)], +) -> Result { + use syn::FieldsWithDefault::{Named, Unit, Unnamed}; let (flatten, variants): (Vec<_>, Vec<_>) = variants .iter() diff --git a/clap_derive/src/item.rs b/clap_derive/src/item.rs index 5e82cc0c690..002caa0efdd 100644 --- a/clap_derive/src/item.rs +++ b/clap_derive/src/item.rs @@ -17,8 +17,8 @@ use std::env; use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use proc_macro2::{self, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; -use syn::DeriveInput; -use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Field, Ident, LitStr, Type, Variant}; +use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Ident, LitStr, Type, Variant}; +use syn::{DeriveInput, DeriveInputWithDefault, Expr, FieldWithDefault, VariantWithDefault}; use crate::attr::{AttrKind, AttrValue, ClapAttr, MagicAttrName}; use crate::utils::{extract_doc_comment, format_doc_comment, inner_type, is_simple_ty, Sp, Ty}; @@ -53,7 +53,10 @@ pub(crate) struct Item { } impl Item { - pub(crate) fn from_args_struct(input: &DeriveInput, name: Name) -> Result { + pub(crate) fn from_args_struct( + input: &DeriveInputWithDefault, + name: Name, + ) -> Result { let ident = input.ident.clone(); let span = input.ident.span(); let attrs = &input.attrs; @@ -71,7 +74,7 @@ impl Item { } pub(crate) fn from_subcommand_enum( - input: &DeriveInput, + input: &DeriveInputWithDefault, name: Name, ) -> Result { let ident = input.ident.clone(); @@ -117,7 +120,7 @@ impl Item { } pub(crate) fn from_subcommand_variant( - variant: &Variant, + variant: &VariantWithDefault, struct_casing: Sp, env_casing: Sp, ) -> Result { @@ -125,12 +128,12 @@ impl Item { let ident = variant.ident.clone(); let span = variant.span(); let ty = match variant.fields { - syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => { - Ty::from_syn_ty(&unnamed[0].ty) - } - syn::Fields::Named(_) | syn::Fields::Unnamed(..) | syn::Fields::Unit => { - Sp::new(Ty::Other, span) - } + syn::FieldsWithDefault::Unnamed(syn::FieldsUnnamedWithDefault { + ref unnamed, .. + }) if unnamed.len() == 1 => Ty::from_syn_ty(&unnamed[0].ty), + syn::FieldsWithDefault::Named(_) + | syn::FieldsWithDefault::Unnamed(..) + | syn::FieldsWithDefault::Unit => Sp::new(Ty::Other, span), }; let kind = Sp::new(Kind::Command(ty), span); let mut res = Self::new( @@ -197,7 +200,7 @@ impl Item { } pub(crate) fn from_args_field( - field: &Field, + field: &FieldWithDefault, struct_casing: Sp, env_casing: Sp, ) -> Result { @@ -220,6 +223,23 @@ impl Item { if matches!(&*res.kind, Kind::Arg(_)) { res.push_doc_comment(&field.attrs, "help", Some("long_help")); } + if res.find_default_method().is_none() { + if let Some((_, ref default)) = field.default { + let val = quote!(#default); + let ty = &field.ty; + let val = quote_spanned!(default.span()=> { + static DEFAULT_VALUE: ::std::sync::OnceLock = ::std::sync::OnceLock::new(); + let s = DEFAULT_VALUE.get_or_init(|| { + let val: #ty = #val; + ::std::string::ToString::to_string(&val) + }); + let s: &'static str = &*s; + s + }); + let raw_ident = Ident::new("default_value", default.span()); + res.methods.push(Method::new(raw_ident, val)); + } + } match &*res.kind { Kind::Flatten(_) => { diff --git a/clap_derive/src/lib.rs b/clap_derive/src/lib.rs index 0001faa2c0c..1a4a1a56514 100644 --- a/clap_derive/src/lib.rs +++ b/clap_derive/src/lib.rs @@ -21,8 +21,10 @@ #![warn(clippy::print_stdout)] use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; -use syn::{Data, DataStruct, Fields}; +use syn::{ + parse_macro_input, DataStructWithDefault, DataWithDefault, DeriveInput, DeriveInputWithDefault, + FieldsWithDefault, +}; #[macro_use] mod macros; @@ -53,19 +55,19 @@ pub fn value_enum(input: TokenStream) -> TokenStream { /// context struct. #[proc_macro_derive(Parser, attributes(clap, structopt, command, arg, group))] pub fn parser(input: TokenStream) -> TokenStream { - let input: DeriveInput = parse_macro_input!(input); + let input: DeriveInputWithDefault = parse_macro_input!(input); derives::derive_parser(&input) .unwrap_or_else(|err| { let specific_dummy = match input.data { - Data::Struct(DataStruct { - fields: Fields::Named(ref _fields), + DataWithDefault::Struct(DataStructWithDefault { + fields: FieldsWithDefault::Named(ref _fields), .. }) => Some(dummies::args(&input.ident)), - Data::Struct(DataStruct { - fields: Fields::Unit, + DataWithDefault::Struct(DataStructWithDefault { + fields: FieldsWithDefault::Unit, .. }) => Some(dummies::args(&input.ident)), - Data::Enum(_) => Some(dummies::subcommand(&input.ident)), + DataWithDefault::Enum(_) => Some(dummies::subcommand(&input.ident)), _ => None, }; let dummy = specific_dummy @@ -85,7 +87,7 @@ pub fn parser(input: TokenStream) -> TokenStream { /// Generates the `Subcommand` impl. #[proc_macro_derive(Subcommand, attributes(clap, command, arg, group))] pub fn subcommand(input: TokenStream) -> TokenStream { - let input: DeriveInput = parse_macro_input!(input); + let input: DeriveInputWithDefault = parse_macro_input!(input); derives::derive_subcommand(&input) .unwrap_or_else(|err| { let dummy = dummies::subcommand(&input.ident); @@ -97,7 +99,7 @@ pub fn subcommand(input: TokenStream) -> TokenStream { /// Generates the `Args` impl. #[proc_macro_derive(Args, attributes(clap, command, arg, group))] pub fn args(input: TokenStream) -> TokenStream { - let input: DeriveInput = parse_macro_input!(input); + let input: DeriveInputWithDefault = parse_macro_input!(input); derives::derive_args(&input) .unwrap_or_else(|err| { let dummy = dummies::args(&input.ident); diff --git a/tests/derive/arguments.rs b/tests/derive/arguments.rs index c152698e07d..c0dcf43554a 100644 --- a/tests/derive/arguments.rs +++ b/tests/derive/arguments.rs @@ -48,6 +48,20 @@ fn argument_with_default() { assert!(Opt::try_parse_from(["test", "42", "24"]).is_err()); } +#[test] +fn argument_with_field_default() { + #[derive(Parser, PartialEq, Debug)] + struct Opt { + arg: i32 = 42, + } + assert_eq!( + Opt { arg: 24 }, + Opt::try_parse_from(["test", "24"]).unwrap() + ); + assert_eq!(Opt { arg: 42 }, Opt::try_parse_from(["test"]).unwrap()); + assert!(Opt::try_parse_from(["test", "42", "24"]).is_err()); +} + #[test] fn auto_value_name() { #[derive(Parser, PartialEq, Debug)] diff --git a/tests/derive/main.rs b/tests/derive/main.rs index 1d25b3ddec1..420226c37ce 100644 --- a/tests/derive/main.rs +++ b/tests/derive/main.rs @@ -1,3 +1,4 @@ +#![feature(default_field_values)] #![cfg(feature = "derive")] #![cfg(feature = "help")] #![cfg(feature = "usage")]