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

Reimplement attribute parsing without darling #310

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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)` and `validate` cannot be used together
--> 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"))]
| ^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 changes: 2 additions & 2 deletions derive_builder/tests/compile-fail/deny_empty_default.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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:8:25
|
8 | #[builder(default = "")]
| ^^
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
3 changes: 1 addition & 2 deletions derive_builder_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ 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"] }
syn = { version = "2.0.49", features = ["full", "extra-traits"] }

[dev-dependencies]
pretty_assertions = "1.2.1"
94 changes: 40 additions & 54 deletions derive_builder_core/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::convert::TryFrom;

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

/// A wrapper for expressions/blocks which automatically adds the start and end
/// braces.
Expand All @@ -13,72 +14,57 @@ use syn::{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 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