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

Don't require FromMeta for field types, if the field is transformed #204

Conversation

DzenanJupic
Copy link

Maybe there's already a way of doing that, that I missed.

If a field is transformed using map or and_then it would be nice if there's no FromMeta bound on the field type, but only on the argument type of the transform function.

This is not totally straightforward, since it's not yet possible to directly name the argument/return type of a function. But it's possible using a dedicated function and type inference.

I'm not sure if this fits the coding style of the rest of the repo. I'm happy to change it.
The code is formatted using rustfmt 1.5.1-nightly (215e3cd 2022-11-03).

If a field is tranformed using `map` or `and_then`,
there should not be a `FromMeta` bound on the actual
field type, but only on the argument to the `map` or
`and_then` function.
@DzenanJupic
Copy link
Author

This changes the behaviour slightly since the fallback value is also transformed using either map or and_then.
That should only matter for Option<_> though, since by default all from_none implementations return None.

Hope this is not a problem.

Copy link
Owner

@TedDriggs TedDriggs left a comment

Choose a reason for hiding this comment

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

This needs some better commenting and explanation, as well as some tests that explore the behavior change around from_none to make it easier to decide if those are a real problem or not.

I'm not yet sure that this is better enough than using #[darling(bound = "...")] on the deriving struct/enum to justify the added complexity.

Does serde attempt to do this?

@@ -172,9 +172,48 @@ impl<'a> ToTokens for CheckMissing<'a> {
let ty = self.0.ty;
let name_in_attr = &self.0.name_in_attr;

let custom_from_none = self.0.post_transform.map(|post_transform| {
let (transformer_ret, handle_error) = match &*post_transform.transformer.to_string()
Copy link
Owner

Choose a reason for hiding this comment

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

This match feels very suspicious; it feels like some of the logic should be moved to methods of the PostfixTransform struct, and if we now need the transform to be exactly one of these two functions we should change the type of PostfixTransform::transformer to an enum rather than allowing any Ident.

Comment on lines +188 to +202
quote! {
fn __from_none<__Fn, __Arg, __Ret>(
__function: __Fn,
__errors: &mut ::darling::error::Accumulator,
) -> ::darling::export::Option<__Ret>
where
__Fn: ::darling::export::FnOnce(__Arg) -> #transformer_ret,
__Arg: ::darling::FromMeta,
{
let __ret = <__Arg as ::darling::FromMeta>::from_none().map(__function);
#handle_error
__ret
}
}
});
Copy link
Owner

Choose a reason for hiding this comment

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

This seems like a behavior change with a bigger impact than it first suggests; right now, the map and and_then calls are only done if a value was present; doing them in absent conditions could produce very subtle behavior changes.

@DzenanJupic
Copy link
Author

There's currently no bound for these fields. The reason FromMeta is required, is that <FiledTy as FromMeta>::from_none is called. So when FieldTy does not implement FromMeta it does not compile.

As far as I know, serde does not have a concept of map or and_then. Only of with. Fields that are annotated with with are not required to implement (De)Serialize in serde: playground.

I had some time to think about the change, and the way I implemented it is definitely not backwards compatible. Not only, because the transform functions are called in case of a missing value, but mainly, because this also changes the FromMeta implementation that is used when FieldTy does implement FromMeta.

It should be possible to implement this change without changing the current behaviour using autoref specialization. As a result, the FromMeta implementation of FieldTy would be used, if it has one (same as current behaviour), and the FromMeta implementation of the map/and_then return type, if FieldTy has none (new behaviour for code, that did not compile previously). If FieldTy does not implement FromMeta, the transform function would still be applied, but since such code did not compile previously, this could just be a documented anomaly.

Alternatively (or additionally), it should also be possible to instead drop the FromMeta requirement for fields with a with attribute. This would be closer to what serde does. This would still require autoref specialization to be backwards compatible though (for the same reason as above).

Would love to hear your thoughts about this.

@TedDriggs
Copy link
Owner

I'd be interested in seeing a version of the with approach; maybe first a writeup of the goal experience and a couple test-cases that show the behavior change?

Also, have you considered adding a FromMeta impl to whatever the FieldTy is? (or is it a remote type?)

@TedDriggs
Copy link
Owner

I’ve decided that I really like the composability of with, map, and and_then - see #235 for a concrete use-case - so I’m not inclined to give that up in exchange for allowing use of with to cover non-FromMeta fields. I would be curious to know if providing an explicit default value addressed this issue by avoiding the from_none call; it feels like it should, and if it doesn’t then maybe a targeted fix for that would address this issue.

@TedDriggs TedDriggs closed this May 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants