Repository: pacak/bpaf (rust)

Issue:
Fallback is not documented in usage
Following the example here: https://github.com/pacak/bpaf/blob/4f64f34b2370b30b3ec98872f5ba1a936a6a9e81/examples/multiple_fallback.rs

If I have something like:

```rust
use bpaf::Bpaf;
use once_cell::sync::Lazy;

pub static CONFIG: Lazy<Config> = Lazy::new(|| Config {
    http_port: 3000,
});

#[derive(Clone, Debug, Bpaf)]
#[bpaf(options, version)]
pub struct Config {
    /// Listening port of HTTP server
    #[bpaf(long, env("APP_HTTP_PORT"), fallback(CONFIG.http_port))]
    pub http_port: u16,
}
```

Expected:

```
        --http-port <ARG>                [env:APP_HTTP_PORT: N/A]
                                         Listening port of HTTP server
                                         [default: 3000]
```

Actual:

```
        --http-port <ARG>                [env:APP_HTTP_PORT: N/A]
                                         Listening port of HTTP server
```

Pull request description:
Add a way to display fallback values with Debug and Display traits
Add two methods to `ParseFallback`: `display_fallback` and `debug_fallback` which will format fallback value and append it to `--help` output.

Fixes #167



Patch:
diff --git a/Changelog.md b/Changelog.md
index 2da338b..6f26a6c 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -14,6 +14,7 @@
 - support deriving `req_flag` consumers
 - support deriving `catch` annotation
 - errors generated by `some` can now be handled with `fallback`/`fallback_with`
+- fallback values can be made visible in `--help` with `display_fallback`/`debug_fallback`
 
 ## bpaf [0.7.10], bpaf_derive [0.3.5] - 2013-03-19
 - improve error messages for typos like `-llvm` instead of `--llvm`
diff --git a/bpaf_derive/src/field.rs b/bpaf_derive/src/field.rs
index 491d227..1738553 100644
--- a/bpaf_derive/src/field.rs
+++ b/bpaf_derive/src/field.rs
@@ -53,6 +53,8 @@ enum PostprAttr {
     HideUsage(Span),
     GroupHelp(Span, Box<Expr>),
     Catch(Span),
+    DisplayFallback(Span),
+    DebugFallback(Span),
 }
 
 impl PostprAttr {
@@ -70,6 +72,8 @@ impl PostprAttr {
             | PostprAttr::Hide(..)
             | PostprAttr::HideUsage(..)
             | PostprAttr::Catch(..)
+            | PostprAttr::DisplayFallback(..)
+            | PostprAttr::DebugFallback(..)
             | PostprAttr::GroupHelp(..) => true,
         }
     }
@@ -88,6 +92,8 @@ impl PostprAttr {
             | PostprAttr::Hide(span)
             | PostprAttr::HideUsage(span)
             | PostprAttr::Catch(span)
+            | PostprAttr::DisplayFallback(span)
+            | PostprAttr::DebugFallback(span)
             | PostprAttr::GroupHelp(span, _) => *span,
         }
     }
@@ -201,6 +207,8 @@ impl ToTokens for PostprAttr {
             PostprAttr::Complete(_span, f) => quote!(complete(#f)),
             PostprAttr::CompleteShell(_span, f) => quote!(complete_shell(#f)),
             PostprAttr::GroupHelp(_span, m) => quote!(group_help(#m)),
+            PostprAttr::DisplayFallback(_span) => quote!(display_fallback()),
+            PostprAttr::DebugFallback(_span) => quote!(debug_fallback()),
         }
         .to_tokens(tokens);
     }
diff --git a/bpaf_derive/src/field/named_field.rs b/bpaf_derive/src/field/named_field.rs
index 39f2d27..989bab1 100644
--- a/bpaf_derive/src/field/named_field.rs
+++ b/bpaf_derive/src/field/named_field.rs
@@ -119,6 +119,8 @@ impl Field {
         };
         let mut help = Vec::new();
 
+        let mut fallback = 0u8;
+
         let mut stage = 0;
         for attr in attrs {
             if attr.path().is_ident("doc") {
@@ -129,6 +131,7 @@ impl Field {
             } else if attr.path().is_ident("bpaf") {
                 #[allow(clippy::cognitive_complexity)]
                 attr.parse_args_with(|input: ParseStream| loop {
+                    fallback = fallback.saturating_sub(1);
                     if input.is_empty() {
                         break Ok(());
                     }
@@ -256,8 +259,25 @@ impl Field {
                             .push(PostprAttr::Guard(span, guard_fn, Box::new(msg)));
                     } else if keyword == "fallback" {
                         check_stage(&mut stage, 4, &keyword)?;
+                        fallback = 2;
                         res.postpr
                             .push(PostprAttr::Fallback(span, parse_expr(input)?));
+                    } else if keyword == "display_fallback" {
+                        check_stage(&mut stage, 4, &keyword)?;
+                        if fallback != 1 {
+                            break Err(input_copy.error(
+                                "You can only use display_fallback immediately after fallback",
+                            ));
+                        }
+                        res.postpr.push(PostprAttr::DisplayFallback(span));
+                    } else if keyword == "debug_fallback" {
+                        check_stage(&mut stage, 4, &keyword)?;
+                        if fallback != 1 {
+                            break Err(input_copy.error(
+                                "You can only use display_fallback immediately after fallback",
+                            ));
+                        }
+                        res.postpr.push(PostprAttr::DebugFallback(span));
                     } else if keyword == "fallback_with" {
                         check_stage(&mut stage, 4, &keyword)?;
                         res.postpr
diff --git a/docs/src/fallback/cases.txt b/docs/src/fallback/cases.txt
index d61ca71..c6f14e8 100644
--- a/docs/src/fallback/cases.txt
+++ b/docs/src/fallback/cases.txt
@@ -1,15 +1,15 @@
 ? Allows you to specify a default value used when argument is not specified
 >
 OK
-Options { version: 42 }
+Options { jobs: 42 }
 
 ? If value is present - fallback value is ignored
-> --version 10
+> --jobs 10
 OK
-Options { version: 10 }
+Options { jobs: 10 }
 
 ? Parsing errors are preserved and preserved to user
-> --version ten
+> --jobs ten
 Stderr
 Couldn't parse "ten": invalid digit found in string
 
@@ -18,8 +18,9 @@ Couldn't parse "ten": invalid digit found in string
 ? `bpaf` encases parsers with fallback value in usage with `[]`
 > --help
 Stdout
-Usage: [--version VERS]
+Usage: [--jobs JOBS]
 
 Available options:
-        --version <VERS>
-    -h, --help            Prints help information
+        --jobs <JOBS>  Number of jobs
+                       [default: 42]
+    -h, --help         Prints help information
diff --git a/docs/src/fallback/combine.rs b/docs/src/fallback/combine.rs
index 6d8dc45..b937d4f 100644
--- a/docs/src/fallback/combine.rs
+++ b/docs/src/fallback/combine.rs
@@ -4,9 +4,13 @@ use bpaf::*;
 //
 #[allow(dead_code)]
 pub struct Options {
-    version: usize,
+    jobs: usize,
 }
 pub fn options() -> OptionParser<Options> {
-    let version = long("version").argument("VERS").fallback(42);
-    construct!(Options { version }).to_options()
+    let jobs = long("jobs")
+        .help("Number of jobs")
+        .argument("JOBS")
+        .fallback(42)
+        .display_fallback();
+    construct!(Options { jobs }).to_options()
 }
diff --git a/docs/src/fallback/derive.rs b/docs/src/fallback/derive.rs
index 74ca038..fc47c77 100644
--- a/docs/src/fallback/derive.rs
+++ b/docs/src/fallback/derive.rs
@@ -5,6 +5,7 @@ use bpaf::*;
 //
 #[allow(dead_code)]
 pub struct Options {
-    #[bpaf(argument("VERS"), fallback(42))]
-    version: usize,
+    /// Number of jobs
+    #[bpaf(argument("JOBS"), fallback(42), display_fallback)]
+    jobs: usize,
 }
diff --git a/src/docs/fallback.md b/src/docs/fallback.md
index d16ae38..48ea0bd 100644
--- a/src/docs/fallback.md
+++ b/src/docs/fallback.md
@@ -6,11 +6,15 @@
 #[derive(Debug, Clone)]
 # #[allow(dead_code)]
 pub struct Options {
-    version: usize,
+    jobs: usize,
 }
 pub fn options() -> OptionParser<Options> {
-    let version = long("version").argument("VERS").fallback(42);
-    construct!(Options { version }).to_options()
+    let jobs = long("jobs")
+        .help("Number of jobs")
+        .argument("JOBS")
+        .fallback(42)
+        .display_fallback();
+    construct!(Options { jobs }).to_options()
 }
 ```
 
@@ -24,8 +28,9 @@ pub fn options() -> OptionParser<Options> {
 #[bpaf(options)]
 # #[allow(dead_code)]
 pub struct Options {
-    #[bpaf(argument("VERS"), fallback(42))]
-    version: usize,
+    /// Number of jobs
+    #[bpaf(argument("JOBS"), fallback(42), display_fallback)]
+    jobs: usize,
 }
 ```
 
@@ -37,29 +42,30 @@ pub struct Options {
 Allows you to specify a default value used when argument is not specified
 ```console
 % app 
-Options { version: 42 }
+Options { jobs: 42 }
 ```
 
 If value is present - fallback value is ignored
 ```console
-% app --version 10
-Options { version: 10 }
+% app --jobs 10
+Options { jobs: 10 }
 ```
 
 Parsing errors are preserved and preserved to user
 ```console
-% app --version ten
+% app --jobs ten
 Couldn't parse "ten": invalid digit found in string
 ```
 
 `bpaf` encases parsers with fallback value in usage with `[]`
 ```console
 % app --help
-Usage: [--version VERS]
+Usage: [--jobs JOBS]
 
 Available options:
-        --version <VERS>
-    -h, --help            Prints help information
+        --jobs <JOBS>  Number of jobs
+                       [default: 42]
+    -h, --help         Prints help information
 ```
 
 </details>
diff --git a/src/info.rs b/src/info.rs
index 92a1a0f..3b364df 100644
--- a/src/info.rs
+++ b/src/info.rs
@@ -613,7 +613,7 @@ fn perform_invariant_check(meta: &Meta, fresh: bool) {
                 perform_invariant_check(i, false);
             }
         }
-        Meta::HideUsage(x) | Meta::Optional(x) | Meta::Many(x) | Meta::Decorated(x, _) => {
+        Meta::HideUsage(x) | Meta::Optional(x) | Meta::Many(x) | Meta::Decorated(x, _, _) => {
             perform_invariant_check(x, false);
         }
         Meta::Item(i) => match i {
diff --git a/src/lib.rs b/src/lib.rs
index fffefb5..ad5d6c3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -432,7 +432,7 @@ pub mod parsers {
     #[cfg(feature = "autocomplete")]
     pub use crate::complete_shell::ParseCompShell;
     pub use crate::params::{NamedArg, ParseArgument, ParseCommand, ParsePositional};
-    pub use crate::structs::{ParseBox, ParseMany, ParseOptional, ParseSome};
+    pub use crate::structs::{ParseBox, ParseFallback, ParseMany, ParseOptional, ParseSome};
 }
 
 use structs::{
@@ -1010,13 +1010,20 @@ pub trait Parser<T> {
     ///
     /// # See also
     /// [`fallback_with`](Parser::fallback_with) would allow to try to fallback to a value that
-    /// comes from a failing computation such as reading a file.
+    /// comes from a failing computation such as reading a file. By default fallback value will
+    /// not be shown in the `--help` output, you can change that by using
+    /// [`display_fallback`](ParseFallback::display_fallback) and
+    /// [`debug_fallback`](ParseFallback::debug_fallback).
     #[must_use]
     fn fallback(self, value: T) -> ParseFallback<Self, T>
     where
         Self: Sized + Parser<T>,
     {
-        ParseFallback { inner: self, value }
+        ParseFallback {
+            inner: self,
+            value,
+            value_str: String::new(),
+        }
     }
     // }}}
 
@@ -1174,13 +1181,13 @@ pub trait Parser<T> {
     ///
     #[doc = include_str!("docs/group_help.md")]
     ///
-    fn group_help(self, message: &'static str) -> ParseGroupHelp<Self>
+    fn group_help<M: Into<String>>(self, message: M) -> ParseGroupHelp<Self>
     where
         Self: Sized + Parser<T>,
     {
         ParseGroupHelp {
             inner: self,
-            message,
+            message: message.into(),
         }
     }
     // }}}
diff --git a/src/manpage.rs b/src/manpage.rs
index e44adf0..4a2fc16 100644
--- a/src/manpage.rs
+++ b/src/manpage.rs
@@ -301,7 +301,7 @@ fn command_help(manpage: &mut Manpage, item: &HelpItem, path: &str) {
                 }
             }
         }
-        HelpItem::Decor { help } => {
+        HelpItem::Decor { help, margin: _ } => {
             manpage.subsection(*help);
         }
         _ => {}
@@ -310,7 +310,7 @@ fn command_help(manpage: &mut Manpage, item: &HelpItem, path: &str) {
 
 fn help_item(manpage: &mut Manpage, item: HelpItem, command_path: Option<&str>) {
     match item {
-        HelpItem::Decor { help } => {
+        HelpItem::Decor { help, margin: _ } => {
             manpage.subsection(help);
         }
         HelpItem::BlankDecor => {
diff --git a/src/meta.rs b/src/meta.rs
index 0af7c39..d9244e3 100644
--- a/src/meta.rs
+++ b/src/meta.rs
@@ -1,5 +1,11 @@
 use crate::item::Item;
 
+#[doc(hidden)]
+#[derive(Copy, Clone, Debug)]
+pub enum DecorPlace {
+    Header,
+    Suffix,
+}
 #[doc(hidden)]
 #[derive(Clone, Debug)]
 pub enum Meta {
@@ -8,7 +14,7 @@ pub enum Meta {
     Optional(Box<Meta>),
     Item(Item),
     Many(Box<Meta>),
-    Decorated(Box<Meta>, &'static str),
+    Decorated(Box<Meta>, String, DecorPlace),
     Skip,
     HideUsage(Box<Meta>),
 }
@@ -57,7 +63,7 @@ impl Meta {
                     args.extend(shorts);
                 }
             },
-            Meta::HideUsage(m) | Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _) => {
+            Meta::HideUsage(m) | Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _, _) => {
                 m.collect_shorts(flags, args);
             }
             Meta::Skip => {}
diff --git a/src/meta_help.rs b/src/meta_help.rs
index f1235ab..5961abe 100644
--- a/src/meta_help.rs
+++ b/src/meta_help.rs
@@ -6,6 +6,7 @@ use crate::{
     buffer::{Buffer, Checkpoint, Style},
     info::Info,
     item::{Item, ShortLong},
+    meta::DecorPlace,
     Meta,
 };
 
@@ -26,6 +27,7 @@ impl std::fmt::Display for Metavar {
 pub(crate) enum HelpItem<'a> {
     Decor {
         help: &'a str,
+        margin: DecorPlace,
     },
     BlankDecor,
     Positional {
@@ -59,10 +61,13 @@ pub(crate) enum HelpItem<'a> {
 }
 
 #[derive(Default, Debug)]
+/// A collection of all the help items separated into flags, positionals and commands
+///
+/// Items are stored as references and can be trivially copied
 pub(crate) struct HelpItems<'a> {
-    pub(crate) cmds: Vec<HelpItem<'a>>,
-    pub(crate) psns: Vec<HelpItem<'a>>,
     pub(crate) flgs: Vec<HelpItem<'a>>,
+    pub(crate) psns: Vec<HelpItem<'a>>,
+    pub(crate) cmds: Vec<HelpItem<'a>>,
 }
 
 impl HelpItem<'_> {
@@ -83,6 +88,7 @@ fn dedup(items: &mut BTreeSet<String>, buf: &mut Buffer, cp: Checkpoint) {
 
 impl<'a> HelpItems<'a> {
     #[inline(never)]
+    /// Store a reference to this item into corresponding class - flag, positional or command
     pub(crate) fn classify_item(&mut self, item: &'a Item) {
         match item {
             Item::Positional {
@@ -154,6 +160,7 @@ impl<'a> HelpItems<'a> {
         }
     }
 
+    /// Recursively classify contents of the Meta
     pub(crate) fn classify(&mut self, meta: &'a Meta) {
         match meta {
             Meta::And(xs) | Meta::Or(xs) => {
@@ -164,10 +171,19 @@ impl<'a> HelpItems<'a> {
             Meta::HideUsage(x) | Meta::Optional(x) | Meta::Many(x) => self.classify(x),
             Meta::Item(item) => self.classify_item(item),
 
-            Meta::Decorated(m, help) => {
-                self.flgs.push(HelpItem::Decor { help });
-                self.cmds.push(HelpItem::Decor { help });
-                self.psns.push(HelpItem::Decor { help });
+            Meta::Decorated(m, help, DecorPlace::Header) => {
+                self.flgs.push(HelpItem::Decor {
+                    help,
+                    margin: DecorPlace::Header,
+                });
+                self.cmds.push(HelpItem::Decor {
+                    help,
+                    margin: DecorPlace::Header,
+                });
+                self.psns.push(HelpItem::Decor {
+                    help,
+                    margin: DecorPlace::Header,
+                });
                 self.classify(m);
 
                 if self.flgs.last().map_or(false, HelpItem::is_decor) {
@@ -188,6 +204,25 @@ impl<'a> HelpItems<'a> {
                     self.psns.push(HelpItem::BlankDecor);
                 }
             }
+            Meta::Decorated(m, help, DecorPlace::Suffix) => {
+                let flgs = self.flgs.len();
+                let cmds = self.cmds.len();
+                let psns = self.psns.len();
+                self.classify(m);
+                let xs = if flgs != self.flgs.len() {
+                    &mut self.flgs
+                } else if psns != self.psns.len() {
+                    &mut self.psns
+                } else if cmds != self.cmds.len() {
+                    &mut self.cmds
+                } else {
+                    return;
+                };
+                xs.push(HelpItem::Decor {
+                    help,
+                    margin: DecorPlace::Suffix,
+                })
+            }
             Meta::Skip => (),
         }
     }
@@ -284,8 +319,15 @@ fn write_metavar(buf: &mut Buffer, metavar: Metavar) {
 
 fn write_help_item(buf: &mut Buffer, item: &HelpItem) {
     match item {
-        HelpItem::Decor { help } => {
-            buf.margin(2);
+        HelpItem::Decor { help, margin } => {
+            match margin {
+                DecorPlace::Header => {
+                    buf.margin(2);
+                }
+                DecorPlace::Suffix => {
+                    buf.tabstop();
+                }
+            }
             buf.write_str(help, Style::Text);
         }
         HelpItem::BlankDecor => {}
diff --git a/src/meta_usage.rs b/src/meta_usage.rs
index 1c5d685..b462916 100644
--- a/src/meta_usage.rs
+++ b/src/meta_usage.rs
@@ -123,7 +123,7 @@ fn collect_usage_meta(meta: &Meta, is_pos: &mut bool) -> Option<UsageMeta> {
                 UsageMeta::Many(Box::new(inner))
             }
         }
-        Meta::Decorated(meta, _) => collect_usage_meta(meta, is_pos)?,
+        Meta::Decorated(meta, _, _) => collect_usage_meta(meta, is_pos)?,
         Meta::HideUsage(_) | Meta::Skip => return None,
         Meta::Item(i) => match i {
             Item::Positional {
diff --git a/src/meta_youmean.rs b/src/meta_youmean.rs
index d676d94..2146b6f 100644
--- a/src/meta_youmean.rs
+++ b/src/meta_youmean.rs
@@ -170,7 +170,7 @@ fn collect_suggestions<'a>(
         Meta::HideUsage(meta)
         | Meta::Optional(meta)
         | Meta::Many(meta)
-        | Meta::Decorated(meta, _) => {
+        | Meta::Decorated(meta, _, _) => {
             collect_suggestions(arg, meta, variants, at_top_level);
         }
         Meta::Skip => {}
diff --git a/src/structs.rs b/src/structs.rs
index 415322d..03efc0f 100644
--- a/src/structs.rs
+++ b/src/structs.rs
@@ -1,5 +1,5 @@
 //! Structures that implement different methods on [`Parser`] trait
-use crate::{args::Conflict, info::Error, item::Item, Args, Meta, Parser};
+use crate::{args::Conflict, info::Error, item::Item, meta::DecorPlace, Args, Meta, Parser};
 use std::marker::PhantomData;
 
 #[cfg(feature = "autocomplete")]
@@ -39,7 +39,7 @@ where
 /// Parser with attached message to several fields, created with [`group_help`](Parser::group_help).
 pub struct ParseGroupHelp<P> {
     pub(crate) inner: P,
-    pub(crate) message: &'static str,
+    pub(crate) message: String,
 }
 
 impl<T, P> Parser<T> for ParseGroupHelp<P>
@@ -51,7 +51,8 @@ where
     }
 
     fn meta(&self) -> Meta {
-        Meta::Decorated(Box::new(self.inner.meta()), self.message)
+        let meta = Box::new(self.inner.meta());
+        Meta::Decorated(meta, self.message.clone(), DecorPlace::Header)
     }
 }
 
@@ -363,6 +364,7 @@ where
 pub struct ParseFallback<P, T> {
     pub(crate) inner: P,
     pub(crate) value: T,
+    pub(crate) value_str: String,
 }
 
 impl<P, T> Parser<T> for ParseFallback<P, T>
@@ -371,23 +373,54 @@ where
     T: Clone,
 {
     fn eval(&self, args: &mut Args) -> Result<T, Error> {
-        let mut clone = args.clone();
-        match self.inner.eval(&mut clone) {
-            Ok(ok) => {
-                std::mem::swap(args, &mut clone);
-                Ok(ok)
-            }
-            e @ Err(Error::Message(_, false) | Error::ParseFailure(_)) => e,
-            Err(Error::Missing(_) | Error::Message(_, true)) => {
-                #[cfg(feature = "autocomplete")]
-                args.swap_comps(&mut clone);
-                Ok(self.value.clone())
+        {
+            let parser = &self.inner;
+            let value = &self.value;
+            let mut clone = args.clone();
+            match parser.eval(&mut clone) {
+                Ok(ok) => {
+                    std::mem::swap(args, &mut clone);
+                    Ok(ok)
+                }
+                e @ Err(Error::Message(_, false) | Error::ParseFailure(_)) => e,
+                Err(Error::Missing(_) | Error::Message(_, true)) => {
+                    #[cfg(feature = "autocomplete")]
+                    args.swap_comps(&mut clone);
+                    Ok(value.clone())
+                }
             }
         }
     }
 
     fn meta(&self) -> Meta {
-        Meta::Optional(Box::new(self.inner.meta()))
+        let m = Meta::Optional(Box::new(self.inner.meta()));
+        if self.value_str.is_empty() {
+            m
+        } else {
+            Meta::Decorated(
+                Box::from(m),
+                self.value_str.clone(),
+                crate::meta::DecorPlace::Suffix,
+            )
+        }
+    }
+}
+
+impl<P, T: std::fmt::Display> ParseFallback<P, T> {
+    /// Show [`fallback`](Parser::fallback) value in `--help` using [`Display`](std::fmt::Display)
+    /// representation
+    pub fn display_fallback(mut self) -> Self {
+        self.value_str = format!("[default: {}]", self.value);
+        self
+    }
+}
+
+impl<P, T: std::fmt::Debug> ParseFallback<P, T> {
+    /// Show [`fallback`](Parser::fallback) value in `--help` using [`Debug`](std::fmt::Debug)
+    /// representation
+    pub fn debug_fallback(mut self) -> Self {
+        self.value_str = format!("[default: {:?}]", self.value);
+        self
     }
 }
 
@@ -822,7 +855,7 @@ where
                     res
                 }
                 Meta::Item(i) => vec![i.clone()],
-                Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _) => meta_items(&*m),
+                Meta::Optional(m) | Meta::Many(m) | Meta::Decorated(m, _, _) => meta_items(&*m),
                 Meta::Skip | Meta::HideUsage(_) => Vec::new(),
             }
         }


Test patch:
diff --git a/bpaf_derive/src/field_tests.rs b/bpaf_derive/src/field_tests.rs
index de60e9c..d21c79c 100644
--- a/bpaf_derive/src/field_tests.rs
+++ b/bpaf_derive/src/field_tests.rs
@@ -83,6 +83,18 @@ fn derive_fallback() {
     assert_eq!(input.to_token_stream().to_string(), output.to_string());
 }
 
+#[test]
+fn derive_fallback_display() {
+    let input: NamedField = parse_quote! {
+        #[bpaf(fallback(3.1415), display_fallback)]
+        number: f64
+    };
+    let output = quote! {
+        ::bpaf::long("number").argument::<f64>("ARG").fallback(3.1415).display_fallback()
+    };
+    assert_eq!(input.to_token_stream().to_string(), output.to_string());
+}
+
 #[test]
 fn derive_fallback_with() {
     let input: NamedField = parse_quote! {
diff --git a/tests/help_format.rs b/tests/help_format.rs
index 2146a7a..d40ee5e 100644
--- a/tests/help_format.rs
+++ b/tests/help_format.rs
@@ -259,3 +259,115 @@ Available options:
 ";
     assert_eq!(r, expected);
 }
+
+#[test]
+fn fallback_display_simple_arg() {
+    let parser = long("a")
+        .help("help for a")
+        .argument("NUM")
+        .fallback(42)
+        .display_fallback()
+        .to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["--help"]))
+        .unwrap_err()
+        .unwrap_stdout();
+    let expected = "\
+Usage: [--a NUM]
+
+Available options:
+        --a <NUM>  help for a
+                   [default: 42]
+    -h, --help     Prints help information
+";
+
+    assert_eq!(r, expected);
+}
+
+#[test]
+fn fallback_display_simple_pos() {
+    let parser = positional("NUM")
+        .help("help for pos")
+        .fallback(42)
+        .display_fallback()
+        .to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["--help"]))
+        .unwrap_err()
+        .unwrap_stdout();
+
+    let expected = "\
+Usage: [<NUM>]
+
+Available positional items:
+    <NUM>  help for pos
+           [default: 42]
+
+Available options:
+    -h, --help  Prints help information
+";
+
+    assert_eq!(r, expected);
+}
+
+#[test]
+fn fallback_display_tuple() {
+    #[derive(Copy, Clone, Debug)]
+    struct Pair(u32, u32);
+    impl std::fmt::Display for Pair {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            write!(f, "Pair {}, {}", self.0, self.1)
+        }
+    }
+
+    let a = long("a").help("help for a").argument("NUM");
+    let b = long("b").help("help for b").argument("NUM");
+    let parser = construct!(a, b)
+        .map(|(a, b)| Pair(a, b))
+        .fallback(Pair(42, 333))
+        .display_fallback()
+        .to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["--help"]))
+        .unwrap_err()
+        .unwrap_stdout();
+
+    let expected = "\
+Usage: [--a NUM --b NUM]
+
+Available options:
+        --a <NUM>  help for a
+        --b <NUM>  help for b
+                   [default: Pair 42, 333]
+    -h, --help     Prints help information
+";
+
+    assert_eq!(r, expected);
+}
+
+#[test]
+fn fallback_display_no_help() {
+    let parser = long("a")
+        .argument("NUM")
+        .fallback(42)
+        .display_fallback()
+        .to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["--help"]))
+        .unwrap_err()
+        .unwrap_stdout();
+    let expected = "\
+Usage: [--a NUM]
+
+Available options:
+        --a <NUM>
+                   [default: 42]
+    -h, --help     Prints help information
+";
+
+    assert_eq!(r, expected);
+}