Skip to content

Commit

Permalink
Reimplement attribute parsing without darling
Browse files Browse the repository at this point in the history
  • Loading branch information
dtolnay committed Feb 15, 2024
1 parent 07b8bcf commit 43fb6f6
Show file tree
Hide file tree
Showing 15 changed files with 730 additions and 566 deletions.
24 changes: 8 additions & 16 deletions derive_builder/tests/compile-fail/build_fn_error.stderr
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
error: Unknown field: `path`
error: unrecognized derive_builder attribute
--> tests/compile-fail/build_fn_error.rs:4:52
|
4 | #[builder(build_fn(error(validation_error = false, path = "hello")))]
| ^^^^

error: Cannot set `error(validation_error = false)` when using `validate`
--> tests/compile-fail/build_fn_error.rs:10:45
error: `error(validation_error = false)` cannot be set when using `validate`
--> tests/compile-fail/build_fn_error.rs:10:53
|
10 | #[builder(build_fn(error(validation_error = false), validate = "hello"))]
| ^^^^^
| ^^^^^^^^^^^^^^^^^^

error: Unknown field: `path`
error: unrecognized derive_builder attribute
--> tests/compile-fail/build_fn_error.rs:16:26
|
16 | #[builder(build_fn(error(path = "hello")))]
| ^^^^

error: Missing field `validation_error` at build_fn/error
--> tests/compile-fail/build_fn_error.rs:15:10
|
15 | #[derive(Builder)]
| ^^^^^^^
|
= note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)

error: Missing field `validation_error`
--> tests/compile-fail/build_fn_error.rs:22:20
error: unexpected end of input, expected nested attribute
--> tests/compile-fail/build_fn_error.rs:22:26
|
22 | #[builder(build_fn(error()))]
| ^^^^^
| ^
18 changes: 9 additions & 9 deletions derive_builder/tests/compile-fail/builder_field_custom.stderr
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
error: #[builder(default)] and #[builder(field(build="..."))] cannot be used together
--> tests/compile-fail/builder_field_custom.rs:8:19
--> tests/compile-fail/builder_field_custom.rs:9:9
|
8 | default = "1",
| ^^^
9 | field(build = "self.ipsum.map(|v| v + 42).unwrap_or(100)")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

error: #[builder(default)] and #[builder(field(ty="..."))] cannot be used together
--> tests/compile-fail/builder_field_custom.rs:18:25
--> tests/compile-fail/builder_field_custom.rs:18:30
|
18 | #[builder(default = "3", field(ty = "usize", build = "self.ipsum + 42"))]
| ^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10 changes: 6 additions & 4 deletions derive_builder/tests/compile-fail/deny_empty_default.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
error: Unknown literal value ``
--> $DIR/deny_empty_default.rs:8:25
error: unexpected end of input, expected expression
--> tests/compile-fail/deny_empty_default.rs:6:10
|
8 | #[builder(default = "")]
| ^^
6 | #[derive(Builder)]
| ^^^^^^^
|
= note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Unknown field: `name`
error: unrecognized derive_builder attribute
--> tests/compile-fail/rename_setter_struct_level.rs:7:18
|
7 | #[builder(setter(name = "foo"))]
Expand Down
36 changes: 18 additions & 18 deletions derive_builder/tests/compile-fail/vis_conflict.stderr
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
error: `public` and `private` cannot be used together
--> tests/compile-fail/vis_conflict.rs:7:15
error: this visibility conflicts with a `public` specified earlier
--> tests/compile-fail/vis_conflict.rs:5:19
|
7 | #[builder(public, private)]
| ^^^^^^
5 | #[builder(public, vis = "pub(crate)")]
| ^^^^^^^^^^^^^^^^^^

error: `vis="..."` cannot be used with `public` or `private`
--> tests/compile-fail/vis_conflict.rs:5:25
error: this visibility conflicts with a `public` specified earlier
--> tests/compile-fail/vis_conflict.rs:7:23
|
5 | #[builder(public, vis = "pub(crate)")]
| ^^^^^^^^^^^^
7 | #[builder(public, private)]
| ^^^^^^^

error: this visibility conflicts with a `public` specified earlier
--> tests/compile-fail/vis_conflict.rs:12:19
|
12 | #[builder(public, vis = "pub(crate)", build_fn(private, public))]
| ^^^^^^^^^^^^^^^^^^

error: `public` and `private` cannot be used together
error: this visibility conflicts with a `private` specified earlier
--> tests/compile-fail/vis_conflict.rs:12:57
|
12 | #[builder(public, vis = "pub(crate)", build_fn(private, public))]
| ^^^^^^

error: `vis="..."` cannot be used with `public` or `private`
--> tests/compile-fail/vis_conflict.rs:14:30
error: this visibility conflicts with a `private` specified earlier
--> tests/compile-fail/vis_conflict.rs:14:24
|
14 | #[builder(private, vis = "pub")]
| ^^^^^

error: `vis="..."` cannot be used with `public` or `private`
--> tests/compile-fail/vis_conflict.rs:12:25
|
12 | #[builder(public, vis = "pub(crate)", build_fn(private, public))]
| ^^^^^^^^^^^^
| ^^^^^^^^^^^

error[E0433]: failed to resolve: use of undeclared type `ExampleBuilder`
--> tests/compile-fail/vis_conflict.rs:19:5
Expand Down
1 change: 0 additions & 1 deletion derive_builder_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ clippy = []
lib_has_std = []

[dependencies]
darling = "0.20.6"
proc-macro2 = "1.0.37"
quote = "1.0.35"
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
Expand Down
94 changes: 39 additions & 55 deletions derive_builder_core/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::convert::TryFrom;

use proc_macro2::{Span, TokenStream};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{self, spanned::Spanned, Block, LitStr};
use syn::meta::ParseNestedMeta;
use syn::parse::ParseStream;
use syn::{self, spanned::Spanned, token, Block, Expr, ExprLit, Lit, Stmt};

/// A wrapper for expressions/blocks which automatically adds the start and end
/// braces.
Expand All @@ -13,73 +13,57 @@ use syn::{self, spanned::Spanned, Block, LitStr};
pub struct BlockContents(Block);

impl BlockContents {
pub fn is_empty(&self) -> bool {
self.0.stmts.is_empty()
}

pub fn span(&self) -> Span {
self.0.span()
#[cfg(test)]
pub(crate) fn new(block: Block) -> Self {
Self(block)
}
}

impl ToTokens for BlockContents {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
pub(crate) fn parse_nested_meta(meta: &ParseNestedMeta) -> syn::Result<Self> {
let expr: Expr = meta.value()?.parse()?;
if let Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = expr
{
Ok(Self(Block {
brace_token: token::Brace(lit.span()),
stmts: lit.parse_with(parse_nonempty_block)?,
}))
} else {
Ok(Self(Block {
brace_token: token::Brace(expr.span()),
stmts: vec![Stmt::Expr(expr, None)],
}))
}
}
}

impl TryFrom<&'_ LitStr> for BlockContents {
type Error = syn::Error;

fn try_from(s: &LitStr) -> Result<Self, Self::Error> {
let mut block_str = s.value();
block_str.insert(0, '{');
block_str.push('}');
LitStr::new(&block_str, s.span()).parse().map(Self)
fn parse_nonempty_block(input: ParseStream) -> syn::Result<Vec<Stmt>> {
if input.is_empty() {
Err(input.error("expected expression"))
} else {
Block::parse_within(input)
}
}

impl From<syn::Expr> for BlockContents {
fn from(v: syn::Expr) -> Self {
Self(Block {
brace_token: syn::token::Brace(v.span()),
stmts: vec![syn::Stmt::Expr(v, None)],
})
}
}

impl darling::FromMeta for BlockContents {
fn from_value(value: &syn::Lit) -> darling::Result<Self> {
if let syn::Lit::Str(s) = value {
let contents = BlockContents::try_from(s)?;
if contents.is_empty() {
Err(darling::Error::unknown_value("").with_span(s))
} else {
Ok(contents)
}
} else {
Err(darling::Error::unexpected_lit_type(value))
}
}

fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
if let syn::Expr::Lit(lit) = expr {
Self::from_value(&lit.lit)
} else {
Ok(Self::from(expr.clone()))
}
impl ToTokens for BlockContents {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
}
}

#[cfg(test)]
mod test {
use std::convert::TryInto;

use super::*;
use proc_macro2::Span;
use syn::MetaList;

fn parse(s: &str) -> Result<BlockContents, syn::Error> {
(&LitStr::new(s, Span::call_site())).try_into()
let mut block_contents = None;
let attr: MetaList = parse_quote!(field(build = #s));
attr.parse_nested_meta(|meta| {
block_contents = Some(BlockContents::parse_nested_meta(&meta)?);
Ok(())
})
.map(|()| block_contents.unwrap())
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions derive_builder_core/src/build_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ macro_rules! default_build_method {

#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
use crate::BlockContents;

#[test]
fn std() {
Expand All @@ -184,7 +184,7 @@ mod tests {
fn default_struct() {
let mut build_method = default_build_method!();
let alt_default =
DefaultExpression::explicit::<syn::Expr>(parse_quote!(Default::default()));
DefaultExpression::Explicit(BlockContents::new(parse_quote!({ Default::default() })));
build_method.default_struct = Some(&alt_default);

#[rustfmt::skip]
Expand Down
33 changes: 10 additions & 23 deletions derive_builder_core/src/default_expression.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::BlockContents;
use proc_macro2::Span;
use quote::ToTokens;
use syn::{meta::ParseNestedMeta, Token};

/// A `DefaultExpression` can be either explicit or refer to the canonical trait.
#[derive(Debug, Clone)]
Expand All @@ -10,6 +10,15 @@ pub enum DefaultExpression {
}

impl DefaultExpression {
pub(crate) fn parse_nested_meta(meta: &ParseNestedMeta) -> syn::Result<Self> {
if meta.input.peek(Token![=]) {
let block_contents = BlockContents::parse_nested_meta(meta)?;
Ok(DefaultExpression::Explicit(block_contents))
} else {
Ok(DefaultExpression::Trait)
}
}

/// Add the crate root path so the default expression can be emitted
/// to a `TokenStream`.
///
Expand All @@ -22,28 +31,6 @@ impl DefaultExpression {
expr: self,
}
}

pub fn span(&self) -> Span {
match self {
DefaultExpression::Explicit(block) => block.span(),
DefaultExpression::Trait => Span::call_site(),
}
}

#[cfg(test)]
pub fn explicit<I: Into<BlockContents>>(content: I) -> Self {
DefaultExpression::Explicit(content.into())
}
}

impl darling::FromMeta for DefaultExpression {
fn from_word() -> darling::Result<Self> {
Ok(DefaultExpression::Trait)
}

fn from_value(value: &syn::Lit) -> darling::Result<Self> {
Ok(Self::Explicit(BlockContents::from_value(value)?))
}
}

/// Wrapper for `DefaultExpression`
Expand Down
2 changes: 1 addition & 1 deletion derive_builder_core/src/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ mod tests {
#[test]
fn default_value() {
let mut initializer = default_initializer!();
let default_value = DefaultExpression::explicit::<syn::Expr>(parse_quote!(42));
let default_value = DefaultExpression::Explicit(BlockContents::new(parse_quote!({ 42 })));
initializer.default_value = Some(&default_value);

assert_eq!(
Expand Down
9 changes: 1 addition & 8 deletions derive_builder_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
//! [`derive_builder_core`]: https://!crates.io/crates/derive_builder_core
#![deny(warnings, missing_docs)]
#![cfg_attr(test, recursion_limit = "100")]

#[macro_use]
extern crate darling;

extern crate proc_macro;
extern crate proc_macro2;
Expand Down Expand Up @@ -49,7 +45,6 @@ pub(crate) use build_method::BuildMethod;
pub(crate) use builder::Builder;
pub(crate) use builder_field::{BuilderField, BuilderFieldType};
pub(crate) use change_span::change_span;
use darling::FromDeriveInput;
pub(crate) use default_expression::DefaultExpression;
pub(crate) use deprecation_notes::DeprecationNotes;
pub(crate) use doc_comment::doc_comment_from;
Expand All @@ -63,9 +58,7 @@ const DEFAULT_STRUCT_NAME: &str = "__default";
pub fn builder_for_struct(ast: syn::DeriveInput) -> proc_macro2::TokenStream {
let opts = match macro_options::Options::from_derive_input(&ast) {
Ok(val) => val,
Err(err) => {
return err.write_errors();
}
Err(err) => return err.into_compile_error(),
};

let mut builder = opts.as_builder();
Expand Down
Loading

0 comments on commit 43fb6f6

Please sign in to comment.