Skip to content

Commit

Permalink
Redesign Display-like derive macros (#225, #217)
Browse files Browse the repository at this point in the history
- use `#[display("...", (<expr>),*)]` syntax instead of `#[display(fmt = "...", ("<expr>"),*)]`
- use `#[display(bound(<bound>))]` syntax instead of `#[display(bound = "<bound>")]`

Co-authored-by: Kai Ren <[email protected]>
  • Loading branch information
ilslv and tyranron authored Jan 24, 2023
1 parent 8277526 commit ae8eeb9
Show file tree
Hide file tree
Showing 33 changed files with 1,257 additions and 937 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
practice.
- The `TryFrom` derive now returns a dedicated error type instead of a
`&'static str` on error.
- The `Display` derive (and other `fmt`-like ones) now uses
`#[display("...", (<expr>),*)]` syntax instead of
`#[display(fmt = "...", ("<expr>"),*)]`, and `#[display(bound(<bound>))]`
instead of `#[display(bound = "<bound>")]`.

### New features

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ struct Point2D {

#[derive(PartialEq, From, Add, Display)]
enum MyEnum {
#[display(fmt = "int: {_0}")]
#[display("int: {_0}")]
Int(i32),
Uint(u32),
#[display(fmt = "nothing")]
#[display("nothing")]
Nothing,
}

Expand Down
1 change: 1 addition & 0 deletions impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ rustc_version = { version = "0.4", optional = true }

[dev-dependencies]
derive_more = { path = ".." }
itertools = "0.10.5"

[badges]
github = { repository = "JelteF/derive_more", workflow = "CI" }
Expand Down
57 changes: 18 additions & 39 deletions impl/doc/display.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# What `#[derive(Display)]` generates

**NB: These derives are fully backward-compatible with the ones from the display_derive crate.**

Deriving `Display` will generate a `Display` implementation, with a `fmt`
method that matches `self` and each of its variants. In the case of a struct or union,
only a single variant is available, and it is thus equivalent to a simple `let` statement.
Expand All @@ -10,7 +8,7 @@ In the case of an enum, each of its variants is matched.
For each matched variant, a `write!` expression will be generated with
the supplied format, or an automatically inferred one.

You specify the format on each variant by writing e.g. `#[display(fmt = "my val: {}", "some_val * 2")]`.
You specify the format on each variant by writing e.g. `#[display("my val: {}", some_val * 2)]`.
For enums, you can either specify it on each variant, or on the enum as a whole.

For variants that don't have a format specified, it will simply defer to the format of the
Expand All @@ -21,7 +19,7 @@ inner variable. If there is no such variable, or there is more than 1, an error

## The format of the format

You supply a format by attaching an attribute of the syntax: `#[display(fmt = "...", args...)]`.
You supply a format by attaching an attribute of the syntax: `#[display("...", args...)]`.
The format supplied is passed verbatim to `write!`. The arguments supplied handled specially,
due to constraints in the syntax of attributes. In the case of an argument being a simple
identifier, it is passed verbatim. If an argument is a string, it is **parsed as an expression**,
Expand All @@ -31,11 +29,6 @@ The variables available in the arguments is `self` and each member of the varian
with members of tuple structs being named with a leading underscore and their index,
i.e. `_0`, `_1`, `_2`, etc.

Although [captured identifiers in format strings are supported since 1.58
Rust](https://blog.rust-lang.org/2022/01/13/Rust-1.58.0.html#captured-identifiers-in-format-strings),
we support this feature on earlier versions of Rust too. This means that
`#[display(fmt = "Prefix: {field}")]` is completely valid on MSRV.


### Other formatting traits

Expand All @@ -59,7 +52,7 @@ E.g., for a structure `Foo` defined like this:
# trait Trait { type Type; }
#
#[derive(Display)]
#[display(fmt = "{} {} {:?} {:p}", a, b, c, d)]
#[display("{} {} {:?} {:p}", a, b, c, d)]
struct Foo<'a, T1, T2: Trait, T3> {
a: T1,
b: <T2 as Trait>::Type,
Expand All @@ -77,25 +70,22 @@ The following where clauses would be generated:
### Custom trait bounds

Sometimes you may want to specify additional trait bounds on your generic type parameters, so that they
could be used during formatting. This can be done with a `#[display(bound = "...")]` attribute.
could be used during formatting. This can be done with a `#[display(bound(...))]` attribute.

`#[display(bound = "...")]` accepts a single string argument in a format similar to the format
used in angle bracket list: `T: MyTrait, U: Trait1 + Trait2`.
`#[display(bound(...))]` accepts code tokens in a format similar to the format
used in angle bracket list (or `where` clause predicates): `T: MyTrait, U: Trait1 + Trait2`.

Only type parameters defined on a struct allowed to appear in bound-string and they can only be bound
by traits, i.e. no lifetime parameters or lifetime bounds allowed in bound-string.

As double-quote `fmt` arguments are parsed as an arbitrary Rust expression and passed to generated
`#[display("fmt", ...)]` arguments are parsed as an arbitrary Rust expression and passed to generated
`write!` as-is, it's impossible to meaningfully infer any kind of trait bounds for generic type parameters
used this way. That means that you'll **have to** explicitly specify all trait bound used. Either in the
struct/enum definition, or via `#[display(bound = "...")]` attribute.
struct/enum definition, or via `#[display(bound(...))]` attribute.

Note how we have to bound `U` and `V` by `Display` in the following example, as no bound is inferred.
Not even `Display`.

Also note, that `"c"` case is just a curious example. Bound inference works as expected if you simply
write `c` without double-quotes.

```rust
# use std::fmt::Display;
#
Expand All @@ -104,8 +94,8 @@ write `c` without double-quotes.
# trait MyTrait { fn my_function(&self) -> i32; }
#
#[derive(Display)]
#[display(bound = "T: MyTrait, U: Display, V: Display")]
#[display(fmt = "{} {} {}", "a.my_function()", "b.to_string().len()", "c")]
#[display(bound(T: MyTrait, U: Display))]
#[display("{} {} {}", a.my_function(), b.to_string().len(), c)]
struct MyStruct<T, U, V> {
a: T,
b: U,
Expand All @@ -127,11 +117,11 @@ struct MyStruct<T, U, V> {
struct MyInt(i32);

#[derive(DebugCustom)]
#[debug(fmt = "MyIntDbg(as hex: {_0:x}, as dec: {_0})")]
#[debug("MyIntDbg(as hex: {_0:x}, as dec: {_0})")]
struct MyIntDbg(i32);

#[derive(Display)]
#[display(fmt = "({x}, {y})")]
#[display("({x}, {y})")]
struct Point2D {
x: i32,
y: i32,
Expand All @@ -140,35 +130,26 @@ struct Point2D {
#[derive(Display)]
enum E {
Uint(u32),
#[display(fmt = "I am B {:b}", i)]
#[display("I am B {:b}", i)]
Binary {
i: i8,
},
#[display(fmt = "I am C {}", "_0.display()")]
#[display("I am C {}", _0.display())]
Path(PathBuf),
}

#[derive(Display)]
#[display(fmt = "Java EE: {}")]
enum EE {
#[display(fmt="A")]
A,
#[display(fmt="B")]
B,
}

#[derive(Display)]
#[display(fmt = "Hello there!")]
#[display("Hello there!")]
union U {
i: u32,
}

#[derive(Octal)]
#[octal(fmt = "7")]
#[octal("7")]
struct S;

#[derive(UpperHex)]
#[upper_hex(fmt = "UpperHex")]
#[upper_hex("UpperHex")]
struct UH;

#[derive(Display)]
Expand All @@ -178,7 +159,7 @@ struct Unit;
struct UnitStruct {}

#[derive(Display)]
#[display(fmt = "{}", "self.sign()")]
#[display("{}", self.sign())]
struct PositiveOrNegative {
x: i32,
}
Expand All @@ -198,8 +179,6 @@ assert_eq!(Point2D { x: 3, y: 4 }.to_string(), "(3, 4)");
assert_eq!(E::Uint(2).to_string(), "2");
assert_eq!(E::Binary { i: -2 }.to_string(), "I am B 11111110");
assert_eq!(E::Path("abc".into()).to_string(), "I am C abc");
assert_eq!(EE::A.to_string(), "Java EE: A");
assert_eq!(EE::B.to_string(), "Java EE: B");
assert_eq!(U { i: 2 }.to_string(), "Hello there!");
assert_eq!(format!("{:o}", S), "7");
assert_eq!(format!("{:X}", UH), "UpperHex");
Expand Down
4 changes: 2 additions & 2 deletions impl/doc/error.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct Tuple(Simple);
struct WithoutSource(#[error(not(source))] i32);

#[derive(Debug, Display, Error)]
#[display(fmt="An error with a backtrace")]
#[display("An error with a backtrace")]
struct WithSourceAndBacktrace {
source: Simple,
backtrace: Backtrace,
Expand All @@ -98,7 +98,7 @@ enum CompoundError {
#[error(backtrace)]
source: Simple,
},
#[display(fmt = "{source}")]
#[display("{source}")]
WithDifferentBacktrace {
source: Simple,
backtrace: Backtrace,
Expand Down
Loading

0 comments on commit ae8eeb9

Please sign in to comment.