Skip to content

Commit

Permalink
Accept field(ty = "...") to ease migration to syn 2.0
Browse files Browse the repository at this point in the history
On syn 2.0, field(type = "...") will break with a not-very-informative error message.

While we can't prevent that, we can make it possible for people to switch to the new property name without needing to change anything else, so that the eventual dependency version bump is fully source-compatible.
  • Loading branch information
TedDriggs committed Feb 1, 2024
1 parent 1b3017d commit 02589bf
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 16 deletions.
3 changes: 3 additions & 0 deletions derive_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased
- Accept `field(ty = "...")` as an alias for `field(type = "...")` in preparation for moving to syn 2.0, which doesn't allow the use of keywords as meta item paths.

## [0.13.0] - 2024-01-22
- Bump MSRV to 1.56.0
- Add `build_fn(error(validation_error = <bool>))` to disable generation of `ValidationError` within the builder's error so that `alloc::string` is avoided.
Expand Down
8 changes: 4 additions & 4 deletions derive_builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,13 +645,13 @@
//! #[derive(Debug, PartialEq, Default, Builder, Clone)]
//! #[builder(derive(Debug, PartialEq))]
//! struct Lorem {
//! #[builder(setter(into), field(type = "u32"))]
//! #[builder(setter(into), field(ty = "u32"))]
//! ipsum: u32,
//!
//! #[builder(field(type = "String", build = "()"))]
//! #[builder(field(ty = "String", build = "()"))]
//! dolor: (),
//!
//! #[builder(field(type = "&'static str", build = "self.amet.parse()?"))]
//! #[builder(field(ty = "&'static str", build = "self.amet.parse()?"))]
//! amet: u32,
//! }
//!
Expand All @@ -670,7 +670,7 @@
//! # }
//! ```
//!
//! The builder field type (`type =`) must implement `Default`.
//! The builder field type (`ty =`) must implement `Default`.
//!
//! The argument to `build` must be a literal string containing Rust code for the contents of a block, which must evaluate to the type of the target field.
//! It may refer to the builder struct as `self`, use `?`, etc.
Expand Down
4 changes: 2 additions & 2 deletions derive_builder/tests/builder_field_custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use std::num::ParseIntError;

#[derive(Debug, PartialEq, Default, Builder, Clone)]
pub struct Lorem {
#[builder(field(type = "Option<usize>", build = "self.ipsum.unwrap_or(42) + 1"))]
#[builder(field(ty = "Option<usize>", build = "self.ipsum.unwrap_or(42) + 1"))]
ipsum: usize,

#[builder(setter(into), field(type = "String", build = "self.dolor.parse()?"))]
#[builder(setter(into), field(ty = "String", build = "self.dolor.parse()?"))]
dolor: u32,
}

Expand Down
11 changes: 8 additions & 3 deletions derive_builder/tests/compile-fail/builder_field_custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ pub struct Lorem {
)]
ipsum: usize,

// `default` is incompatible with `field.type`, even without `field.build`
#[builder(default = "2", field(type = "usize"))]
// Both `ty` and `type` are temporarily allowed to ease the transition
// to syn 2.0, but they are mutually exclusive.
#[builder(field(ty = "usize", type = "usize"))]
dolor: usize,

// `default` is incompatible with `field.ty`, even without `field.build`
#[builder(default = "2", field(ty = "usize"))]
sit: usize,

// Both errors can occur on the same field
#[builder(default = "3", field(type = "usize", build = "self.ipsum + 42"))]
#[builder(default = "3", field(ty = "usize", build = "self.ipsum + 42"))]
amet: usize,
}

Expand Down
18 changes: 12 additions & 6 deletions derive_builder/tests/compile-fail/builder_field_custom.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@ error: #[builder(default)] and #[builder(field(build="..."))] cannot be used tog
8 | default = "1",
| ^^^

error: duplicate field - `type` is a deprecated alias for `ty`.
--> tests/compile-fail/builder_field_custom.rs:15:35
|
15 | #[builder(field(ty = "usize", type = "usize"))]
| ^^^^

error: #[builder(default)] and #[builder(field(type="..."))] cannot be used together
--> tests/compile-fail/builder_field_custom.rs:14:25
--> tests/compile-fail/builder_field_custom.rs:19:25
|
14 | #[builder(default = "2", field(type = "usize"))]
19 | #[builder(default = "2", field(ty = "usize"))]
| ^^^

error: #[builder(default)] and #[builder(field(build="..."))] cannot be used together
--> tests/compile-fail/builder_field_custom.rs:18:25
--> tests/compile-fail/builder_field_custom.rs:23:25
|
18 | #[builder(default = "3", field(type = "usize", build = "self.ipsum + 42"))]
23 | #[builder(default = "3", field(ty = "usize", build = "self.ipsum + 42"))]
| ^^^

error: #[builder(default)] and #[builder(field(type="..."))] cannot be used together
--> tests/compile-fail/builder_field_custom.rs:18:25
--> tests/compile-fail/builder_field_custom.rs:23:25
|
18 | #[builder(default = "3", field(type = "usize", build = "self.ipsum + 42"))]
23 | #[builder(default = "3", field(ty = "usize", build = "self.ipsum + 42"))]
| ^^^
30 changes: 29 additions & 1 deletion derive_builder_core/src/macro_options/darling_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,23 +215,51 @@ impl Visibility for StructLevelFieldMeta {
}
}

fn preserve_field_span(meta: &Meta) -> darling::Result<(Span, syn::Type)> {
match meta {
Meta::Path(_) => Err(Error::unsupported_format("word").with_span(meta)),
Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)),
Meta::NameValue(mnv) => Ok((mnv.path.span(), syn::Type::from_value(&mnv.lit)?)),
}
}

/// Contents of the `field` meta in `builder` attributes at the field level.
//
// This is a superset of the attributes permitted in `field` at the struct level.
// Perhaps in the future we will be able to use `#[darling(flatten)]`, but
// that does not exist right now: https://github.com/TedDriggs/darling/issues/146
#[derive(Debug, Clone, Default, FromMeta)]
#[darling(and_then = "Self::finalize")]
pub struct FieldLevelFieldMeta {
public: Flag,
private: Flag,
vis: Option<syn::Visibility>,
#[darling(rename = "type", with = "preserve_field_span", map = "Some", default)]
builder_type_old: Option<(Span, syn::Type)>,
/// Custom builder field type
#[darling(rename = "type")]
#[darling(rename = "ty")]
builder_type: Option<syn::Type>,
/// Custom builder field method, for making target struct field value
build: Option<BlockContents>,
}

impl FieldLevelFieldMeta {
fn finalize(mut self) -> darling::Result<Self> {
if let Some((type_field_span, ty)) = self.builder_type_old.take() {
if self.builder_type.is_some() {
return Err(Error::custom(
"duplicate field - `type` is a deprecated alias for `ty`.",
)
.with_span(&type_field_span));
}

self.builder_type = Some(ty);
}

Ok(self)
}
}

impl Visibility for FieldLevelFieldMeta {
fn public(&self) -> &Flag {
&self.public
Expand Down

0 comments on commit 02589bf

Please sign in to comment.