Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign Display derive macro #225

Merged
merged 44 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
984c81b
WIP
ilslv Nov 16, 2022
10effda
WIP
ilslv Nov 17, 2022
d080672
WIP
ilslv Nov 17, 2022
42e55a4
WIP
ilslv Nov 17, 2022
8adf9d5
WIP
ilslv Nov 18, 2022
10d629f
Support unions
ilslv Nov 18, 2022
b280b8a
Support unit structs with attribute
ilslv Nov 18, 2022
6a290e1
Support unit structs
ilslv Nov 18, 2022
79fd08e
Support Infallible-like enums with 0 variants
ilslv Nov 18, 2022
306cd66
Support Infallible-like enums with 0 variants
ilslv Nov 18, 2022
a02c0c2
Remove old commented out code
ilslv Nov 18, 2022
96e6430
Merge branch 'master' into 217-display-expr
ilslv Nov 18, 2022
c5492c8
Revert docs, as they need more in-depth rewrite
ilslv Nov 18, 2022
2305e04
Add test for `struct UnitTupleStruct();`
ilslv Nov 18, 2022
1ba69ce
Add better structured tests for unit structs
ilslv Nov 18, 2022
88bd94b
Single-field structs tests
ilslv Nov 18, 2022
caf56ca
Multi-field structs tests
ilslv Nov 18, 2022
86a95e0
No-variants enum tests
ilslv Nov 18, 2022
8f323d0
Unit-variants enum tests
ilslv Nov 18, 2022
7e8f429
Single-field variants enum tests
ilslv Nov 18, 2022
68c0390
Single-field variants enum tests
ilslv Nov 18, 2022
4677c40
Multi-field variants enum tests
ilslv Nov 18, 2022
ccedff6
Single-field struct Octal and Binary tests
ilslv Nov 18, 2022
194023a
Docs and minor refactorings
ilslv Nov 18, 2022
27b6354
Docs and minor refactorings vol.2
ilslv Nov 21, 2022
752d0d5
Re-implement argument parsing
ilslv Nov 21, 2022
911b8fc
Fix doctests
ilslv Nov 21, 2022
48c43e7
Fix doctests
ilslv Nov 21, 2022
3b6cdba
Clippy
ilslv Nov 21, 2022
aeaeec3
Fix doctests
ilslv Nov 21, 2022
86aabdc
Add support for parsing qself (all cases should be covered now)
ilslv Nov 22, 2022
2a26958
Merge branch 'master' into 217-display-expr
ilslv Nov 22, 2022
6ab6e8d
Clippy
ilslv Nov 22, 2022
c853259
Docs and minor refactorings
ilslv Nov 22, 2022
111e854
Add tests for generic structs
ilslv Nov 22, 2022
f430cd5
Add tests for generic structs
ilslv Nov 22, 2022
7338351
Add todo!() for implicit display on unit enum variant
ilslv Nov 22, 2022
eee1131
Add todo!() for implicit display on unit enum variant
ilslv Nov 22, 2022
5a70ab7
Add error with suggestion for legacy fmt syntax
ilslv Nov 22, 2022
1fa9236
Return support for implicit formatting of unit enum variant on `Displ…
ilslv Nov 22, 2022
82893fe
CHANGELOG
ilslv Nov 22, 2022
f4113c5
Minor corrections
tyranron Jan 23, 2023
d864deb
Merge branch 'master' into 217-display-expr
tyranron Jan 24, 2023
7d312fd
Fix doc
tyranron Jan 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
55 changes: 17 additions & 38 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
`#[display(bound(...))]` accepts a single string argument in a format similar to the format
tyranron marked this conversation as resolved.
Show resolved Hide resolved
used in angle bracket list: `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