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

Feature/macros ono dot one #32

Merged
merged 14 commits into from
Jan 19, 2017
Merged

Feature/macros ono dot one #32

merged 14 commits into from
Jan 19, 2017

Conversation

colin-kiegel
Copy link
Owner

@colin-kiegel colin-kiegel commented Jan 14, 2017

@killercup: Here is the current state of the macros_one_dot_one branch. I continued the work of @badboy from #28.

I added backward compatibility to our existing feature:

  • allow attributes on fields

I also introduced new features:

  • select different setter patterns (instead of always mutable), e.g. #[setters(immutable)]
  • select visibility of setters (public/private), e.g. #[setters(private)]
  • logging via env_logger for better debugging, e.g. RUST_LOG=derive_builder=trace cargo test

Things I am not yet quite happy with

  • Parsing of attributes: The attribute representation by the syn crate is already very complex. In addition to that our derive_builder crate has quite a bunch of options which I expect to even grow in the future. This combination leads to quite nested attribute parsing in src/options.rs. I wonder if there is a cleaner approach to it.
  • I think it's time to add compilation tests or to test the generated tokens (e.g. private setters have a lacking testcase right now)
  • There are some early but unfinished stubs for getters. I left them in for now, but I suggest to split this into a separate PR (or just implement it). But before I do that split, I would like to get some overall feedback first. :-)

Any suggestions are welcome.

@colin-kiegel
Copy link
Owner Author

Note: Since all tests run inside of a sub-crate right now, we currently don't have doc-tests anymore. :-/

Copy link
Collaborator

@killercup killercup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! Looks like the original tests still work :)

I would like to change the attribute names to more explicitly show they are for derive-builder – but I also really want to land this! So I'm okay with not changing them right now.


And this is how it works:

```rust
#[macro_use] extern crate custom_derive;
#![feature(proc_macro)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this no more!

}

#[derive(Default, Debug)]
pub struct OptionsBuilder {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, this code looks like a great indicator for a new "indirect builder" setting!

(Indirect builders are the "original" builder pattern using a struct with the same fields but all are optional.)

}
}

impl OptionsBuilder {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seriously, the first bunch of methods can be derived, incl. the warn! output.

Also just as a warning, every tooling project I work on at some point ends up dogfooding itself :D

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it felt like irony to write so much builder boilerplate in this crate. LOL. ^^

But how could we bootstrap if derive_builder depends on derive_builder? Shall we depend on the latest previously published version? But if that depends on the version before .. well .. people will notice strange dependency chains in their projects. :-)

Copy link
Collaborator

@killercup killercup Jan 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just adopt the new rustbuild build system! It can easily download a "stage0" snapshot of derive-builder before building derive-builder stage1, and then build-derive again with the stage1 build to finally get a stage2 version we can work with. :trollface:

_ => {}
}
}
debug!("Ignoring attribute.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not use these attribute names, as they live in a global namespace and "getters" and "setters" is too generic (we just had that in diesel/rocket with "options"). How about something like #[builder_options(setters(pattern = mutable, private = true), getter(public = true)]?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, I see what you mean.

I am open to a discussion. However I think we should try to keep it short, since the purpose of this crate is to avoid boiler plate. One of the next logical steps IMO would be to add options per struct field. I.e. prefix, pattern, visibility, etc. could all be changed on a per-field level. At least it should be possible to ignore individual fields - and if we introduce that, we can also introduce overwriting all the other options.

#[derive(Builder)]
#[builder_options(setters(pattern = mutable, private = true), getters(public = true)]
struct Foo {
   foo: u32,
   #[builder_options(setter(prefix = with, pattern = immutable, private = false), getter(prefix = has, public = false)]
   bar: u32
}

vs.

#[derive(Builder)]
#[setters(mutable, private), getters(public)]
struct Foo {
   foo: u32,
   #[setter(prefix = with, immutable, public), getter(prefix = has, private)]
   bar: u32
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points, and I think you are right: Let's keep it concise, i.e. call the attributes (s|g)etter(s?).

Another point: Do you think it'd be nicer to have setter and getter options defined in the same , or in two separate attributes?

_ => {
panic!("Unknown option {:?}", ident)
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there should be an idiomatic solution to parsing syn attributes to structs. Maybe another derive crate? A small serde plugin for (de)serializing things to/from rust attributes? The possibilities are infinite! :D

#[derive(Deserialize)] struct Options { setters: SetterOptions, /* … */ }
#[derive(Deserialize)] struct GetterOptions { pattern: SetterPattern, public: bool, private: bool }
#[derive(Deserialize)] enum SetterPattern { Owned, Mutable, Immutable }

// …
let attr: syn::Attribute = attr;
let opt: Options = parse_attribute!(builder_options);

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this kind of separation is, what I was looking for. :-)
Sounds interesting, I'll have a look at serde. Never used it before, but since everyone is talking about that's probably an education gap I should close anyway. :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we definitely need a better approach. I am tracking this in dtolnay/syn#25.

}

fn parse_getters_options(&mut self, nested: &[syn::NestedMetaItem]) -> &mut Self {
panic!("TODO: parse getter options -> {:?}", nested);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably s/panic!/unimplemented!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually in published code I would like to get rid of these unimplemented functions. But generally you are right. :-)

@killercup killercup requested a review from badboy January 14, 2017 15:59
@killercup
Copy link
Collaborator

@colin-kiegel what about the doc tests in lib.rs? They can still do their thing, right?

@colin-kiegel
Copy link
Owner Author

@killercup It looks like having proc-macro = true in Cargo.toml disables all doc-tests. I think it's a bug: rust-lang/rust#39064

@colin-kiegel
Copy link
Owner Author

I updated the documentation - we can still change the syntax however.

was fixed by migrating to macros 1.1
this only adds the testcase
@killercup killercup mentioned this pull request Jan 16, 2017
@killercup
Copy link
Collaborator

@colin-kiegel I suggest we merge this and iterate on it in further PRs

@colin-kiegel colin-kiegel merged commit 0c1d5ae into master Jan 19, 2017
@colin-kiegel
Copy link
Owner Author

ok, done. :-)

Btw: I've already started some refactoring to allow for field-based options. I'll try to wrap it up in a new PR later this evening.

@colin-kiegel colin-kiegel deleted the feature/macros-ono-dot-one branch January 19, 2017 12:13
@killercup
Copy link
Collaborator

killercup commented Jan 19, 2017 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants