Ah, finally had some time to spend on this project
That was basically what I meant, except I wouldn't use a Visitor for visit_field
as I already know the fields of the struct and can directly call visit_type
on the field types with a Visitor that overrides visit_type_path
.
Yup, that code was already in place. I was actually only missing the implementation of the following stub:
fn is_required_generic_for_type(ty: &Type, generic: &Ident) -> bool
(where ty
is the type of the field in the struct and generic
is a identifier of a generic of that struct (e.g. P
for struct Foo<P>
)).
I'm aware of this, but m implementation does not yet handle this.
To be honest I'm also not sure if I want to support it, since then I might also have to support where bounds on the struct which make the life much more difficult. And all that for a feature that is mostly unused as far as I can tell (bounds on struct).
These are indeed interesting cases I will have to look at.
As a minimum however I would like to support basic lifetimes (maybe even without supporting lifetime bounds, since I consider them unusual on structs).
Thats exactly what I was refering to
From a functionality centered standpoint I would totally agree with your example code, as it (probably) makes the derive much simpler.
However from a usability standpoint I would say the following would be simpler to read/understand:
fn half_set() -> FooBuilder<Unset, Set<u16>> {
FooBuilder::new().field2(23)
}
fn main() {
let f: Foo<_> = half_set().field1("👋").build();
}
Then look no further
So I just finished implementing the function that was missing and all tests just turned green
For the simple structs that I tested (generics yes, no lifetimes/bounds/etc) the following did the trick:
fn is_required_generic_for_type(ty: &Type, generic: &Ident) -> bool {
struct PathVisitor<'g> {
generic: &'g Ident,
generic_is_required: bool,
}
impl<'g, 'ast> Visit<'ast> for PathVisitor<'g> {
fn visit_type_path(&mut self, node: &'ast TypePath) {
if node.qself.is_none() {
if let Some(first_segment) = node.path.segments.first() {
if first_segment.ident == *self.generic {
self.generic_is_required = true;
}
}
}
visit::visit_type_path(self, node);
}
}
let mut path_visitor = PathVisitor {
generic,
generic_is_required: false,
};
path_visitor.visit_type(ty);
return path_visitor.generic_is_required;
}
The check for node.qself.is_none()
is especially important I think, since the first path segment of a TypePath with a qualified self is not in the scope of the struct itself, but already in the scope of the qualifier. If this wasn't clear just ask and I will try to explain better ^^'
Otherwise the code is surprisingly simple. The performance could probably be improved by early returning from the visitor if generic_required
is true
, but I just hope people don't have ungodly long types in their structs -.-
In the end I can now successfully execute the following tests:
#[cfg(test)]
mod test {
use type_safe_builder::{Builder, GetBuilder, Set, Unset};
#[derive(Builder, Debug)]
struct StructSimple {
field1: u8,
field2: u16,
}
#[test]
fn builder_simple() {
let x = StructSimple::builder();
let x = x.field1(8);
let x = x.field2(16);
dbg!(x.build());
}
#[derive(Builder, Debug)]
struct StructWithGeneric<T> {
field1: u8,
field2: T,
}
#[test]
fn builder_with_generic() {
let x = StructWithGenericBuilder::new();
let x = x.field1(8);
let x = x.field2(32u32);
dbg!(x.build());
}
#[test]
fn builder_with_generic_type_interface() {
let x = get_builder_with_unspecified_generic();
let x = x.field2(true);
dbg!(x.build());
let x = get_builder_with_unspecified_generic();
let x = x.field2("some text");
dbg!(x.build());
}
fn get_builder_with_unspecified_generic() -> StructWithGenericBuilder<Set<u8>, Unset> {
StructWithGenericBuilder::new().field1(5)
}
#[derive(Builder, Debug)]
struct StructWithGenerics<T, U> {
field0: u8,
field1: T,
field2: U,
}
#[test]
fn builder_with_generics() {
let x = StructWithGenericBuilder::new();
let x = x.field1(8);
let x = x.field2(32u32);
dbg!(x.build());
}
#[test]
fn builder_with_generics_type_interface() {
let x = get_builder_with_unspecified_generics();
let x = x.field1(-10);
let x = x.field2(true);
dbg!(x.build());
let x = get_builder_with_unspecified_generics();
let x = x.field1("look");
let x = x.field2("some text");
dbg!(x.build());
}
fn get_builder_with_unspecified_generics() -> StructWithGenericsBuilder<Set<u8>, Unset, Unset> {
StructWithGenericsBuilder::new().field0(8)
}
}
Edit: multiple generics in a single field also work:
#[derive(Builder, Debug)]
struct StructWithGenerics<T, U> {
field0: u8,
field1: T,
field2: U,
field3: (T, U),
}
#[test]
fn builder_with_generics_type_interface() {
let x = get_builder_with_unspecified_generics();
let x = x.field1(-10);
let x = x.field2(true);
let x = x.field3((-5, false));
dbg!(x.build());
let x = get_builder_with_unspecified_generics();
let x = x.field1("look");
let x = x.field2("some text");
let x = x.field3(("another", "str"));
dbg!(x.build());
}
fn get_builder_with_unspecified_generics(
) -> StructWithGenericsBuilder<Set<u8>, Unset, Unset, Unset> {
StructWithGenericsBuilder::new().field0(8)
}
I just noticed something funny: since the Builder has no idea what the dependencies between the different fields generics are, the builder allows calling field1(u8).field2(u16).field3((false,true))
and only fails at the build step this is probably the biggest downside to not having to specify the generics upfront
If you want I can keep you updated, as I said I want to get at least basic reference support, so a few changes might still come