-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Unexpected behavior of customized @Builder when @Builder.Default is used #2115
Comments
Just FYI, you can make that |
It's not a matter of documentation. It is a matter of making a distinction between 'feel free to mess with these' and 'hands off / don't mess with these unless you really know what you are doing', and then documenting how you can recognize the 'dont touch those' fields. Which kinda takes care of itself if we chuck a $ in there, because the (hopefully?) universally agreed upon java-ese for 'no touchy'. Any field marked with If we apply this change it's gonna mess with all existing code that touches these fields directly, which is backwards incompatible, but the odds are increased that such code is doing the wrong thing already. Which makes the breaking change warranted. I'm positively inclined to dollarizing ALL fields involved in special treatment, be it singular, or default. The upcoming release doesn't have any breaking changes yet. Let's push this forward to 1.18.10. |
Good point @rzwitserloot. I imagine the perfect solution to make such mistakes impossible altogether would be to wrap the fields of the builder in a bean and use setters. That would make the delomboked code unreadable or at least overcomplicated. That's why I suggested just to put some documentation on this. I can agree that dollarizing the value field is a better idea and should solve the problem. |
Just bumped into this breaking change. I thought you are supposed to increase major version for those, not patch version, or are you not following semver? |
Would it be possible to set the default value of the field in the Builder to the same value as what's defined for the field annotated with I find that it's super easy to fall into the trap when you add custom variants in the generated builder. Another issue that makes it hard to have builder customizations+default values is because Lombok doesn't generate the different setters in the builder if there is a custom variant with the same name. If this was taking in consideration the full signature then people could delegate to the generated version in most situations. |
@Sam-Kruglov project lombok does not believe semver as an absolutist concept makes sense. Almost any change is 'breaking', provided a sufficiently academic case. We bump minor version if we feel the cases where things do break are plausible if exotic and it's not in an experimental feature (we don't as a rule break common stuff at all, of course). We did not find messing with the singular-generated field as plausible, but evidently some are doing this. We did consider that, but then realized that, as this bug report shows, most of those uses are probably broken already; making your compiler now error out on this is in fact doing you a favour. Perhaps you disagree with our assessments, but no going back now. Not seeing those fields is the point. You're not supposed to interact with these fields, what we do with em is too tricky to try to encode in a set-in-stone spec. If we did that, people would start relying on that behaviour. It's unfortunate we were unclear about how that's a bad idea, but we've fixed that now. @EugenCepoi no, because the field(s) used in the builder may not even be of the same type. You can use |
Even if they are not of the same type, this doesn't necessarily prevent you from doing the defaulting in a different way that plays more nicely with custom code. One problem is that you seem to do the defaulting in the generated build method, perhaps you could instead do it at initialization time. Thanks for the pointer to |
This has caused an incompatibility issue with Jackson. Jackson's BeanDeserializer looks for the $set version of the property at this line, doesn't find it and so doesn't override the default. |
My apologies ^^^ - turns out the test had a bug and the upgrade merely highlighted that bug. Nothing to see here, move along. |
@EugenCepoi We can fix your case but then we break another case. Imagine this scenario:
If lombok initializes the builder field with the default value, then we'd be incrementing that counter every time you invoke We can do one of four things here:
Perhaps if we did it all over again we'd have shot down the 'the default expression has side effects' case and thus opened the door to ditch the But here's one idea: It's a bit of work and therefore may not make it (there's so much to do, and not enough time to do it all, but we would accept PRs if this idea ends up being worthwhile), but what if lombok 'scans' the explicitly written builder class for any attempt to set that variable that has an associated
That lombok will generate a warning on the It's a bit complicated (we'd have to scan for What do you think? |
Thinking on it, there is a magic unicorn, but I'm not sure we want it: Instead of detecting if you fail to invoke
into: and we'd have to flag as error something like:
as well as:
but we could theoretically do all that. |
I would still be worried about the different ways to access the field (false negatives) and shadowing (false positives). foo.x = 7; Should we fix that? Well, we might. ...
FooBuilder copyXInto(FooBuilder foo) {
foo.x = this.x;
foo.x$set = true;
}
...
void resetFoo() {
x = 0;
foo.x$set = false;
}
... The only thing I can image is to disallow direct field modification, and generate private setters. |
Hey thanks for giving this a second thought :) Regarding the side effect use case, this feels somewhat unnatural to me. I'd expect it to have similar semantics as default values for attributes in Java, which does run that code anyway even if you provide a different value through the constructor or some other mean. I wonder in practice how common are either use cases. I can definitely say that the issue I mention occurred in the project I'm working on several times, to my self and colleagues of mine. And is not related to some technical aspect in our code base, but mostly just about how we imagine this API would work. From an outside perspective the different options sound quite complex, I really would just imagine something that sets the default value "statically" which stays close to std Java and keep generating the setter if there isn't another one with the same signature. That said, I've been using the Tolerate workaround so far and delegated to the generated setter which does the job, so I'm fine with that :) Thank you! |
When customizing @builder like this:
The following test fails:
One might be surprised by an unexpected behavior of the code above. The reason is that between() is not setting
scheduledFor$set = true
so scheduledFor is overwritten by the default value on build(). The correct usage would be to useMessagePublishRequestBuilder::scheduledFor()
instead of setting the field directly like this:I wonder if such problem could be avoided or at least better documented.
The text was updated successfully, but these errors were encountered: