-
-
Notifications
You must be signed in to change notification settings - Fork 514
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
fix(project): config merging #1460
Conversation
✅ Deploy Preview for biomejs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
@@ -52,7 +52,7 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━ | |||
```block | |||
test.js:1:1 lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ | |||
|
|||
× This is an unexpected use of the debugger statement. | |||
! This is an unexpected use of the debugger statement. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only test case where I think the original snapshot was actually incorrect. The config already specified it should be a warning, but it looks like it was previously still reported as an error.
CodSpeed Performance ReportMerging #1460 will degrade performances by 12.97%Falling back to comparing Summary
Benchmarks breakdown
|
I am not sure that
Couldn't this be simpler to remove the current implementation of Instead of merging the deserialized and overridden state with a default state, we could expose queries. #[derive(Default, Mergeable)]
struct Config {
enabled: Option<bool>,
threshold: Option<u32>,
}
impl Config {
fn enabled(&self) -> bool {
self.enabled.unwrap_or(true)
}
fn threshold(&self) -> u32 {
self.threshold.unwrap_or(255)
}
}
fn main() {
let first_config = Config::default();
let override_config = Config { enabled: Some(false) };
let final = first_config.merge_with(override_config);
assert_eq!(final.enabled(), false);
assert_eq!(final.threshold(), 255);
} |
Yeah, I should have provided some rationale for that. The
I can see the appeal for using queries indeed, although I would have some concerns with that approach as well. Semantically I would even consider it more risky, since the A similar concern would arise with the queries themselves, because by convention direct field accesses should then be avoided in favor of using the query methods, and it's easy to make a mistake there. Migration-wise, hunting down all configuration field accesses and updating them to use queries also feels like a bigger task than I'd be willing to undertake. |
I haven't seen any issue in terms of performance: PR
|
Personally, I think this solution works. Sure, |
I still think it is not the good place for these traits. We could move them (also By the way, I am not fond of the |
What's your opinion on this? It would be great to collect some feedback on the PR and then address it later. |
Merge::merge_in_defaults looks like a code sniff to me. However, I don't know exactly what's wrong with it - it's more of an intuition. I will comment here if anything comes to mind. |
To be honest I was a bit bumped I needed For instance, Short of using queries, like you suggested, I don’t see how we can avoid that. I do like the idea of generating all deserializers, btw. Maybe once we do that, we could just let the macro for it generate a |
We should just generate the necessary code directly in the deserializer, no need of introducing a
Yes, I understand your point. |
@Conaclos I think I may also have another proposal that hopefully addresses most of your concerns. What if we just got rid of all the #[derive(Partial)]
struct Configuration {
formatter: FormatterConfiguration
} Would generate: #[derive(Default, Deserializable, Merge)]
struct PartialConfiguration {
formatter: Option<PartialFormatterConfiguration>,
}
impl From<PartialConfiguration> for Configuration {
fn from(other: PartialConfiguration) -> Self {
Self {
formatter: other
.formatter
.map(FormatterConfiguration::from)
.unwrap_or_default()
}
}
} The What I personally really like about this is that it makes it very easy to extend mergeability to the lint rule options, which currently all lack the But there’s two difficulties with this approach still:
What do you think? |
That's an interesting approach. I also thought about something like this (although less achieved than yours). However, I am not comfortable of generating a create_partial! {
#[derive(Bpaf, Deserializable, Deserialize, ...)}
struct MyConfiguration {
prop: bool
}
} This also implies many code change? I have to think about this... NOTE: Feel free to open another PR to propose a derive macro for |
I think the derives of the generated struct can also be specified like this: #[derive(Partial)]
#[partial(derive(Bpaf, Deserialize, JsonSchema))]
struct Configuration {
#[partial] // Signals that `FormatterConfiguration` also has a Partial derive.
formatter: FormatterConfiguration
} Speaking of I do agree it will take quite a few code changes, but at least they’ll be pointed out by the compiler. I also expect they’ll be mostly simplifications, since code won’t need to deal with |
I personally like your idea. Maybe you can make a try in another PR? And keep this one around in case you find some shortcomings / technical issues. |
@Conaclos please let us know if want us to merge the pr |
Looks good to me! We can try another approach in a follow-up PR. Let's merge the PR since this fixes some bugs. |
35f4f3e
to
71f76c7
Compare
Thanks for merging! I will follow up with the |
Summary
This is my first shot at fixing #1440. There are two main changes:
Option
) now have aNoneState
in addition to theDefault
state. As you might guess, theNoneState
represents the state where all theOption
s areNone
. This state is used as the starting point for deserialization instead ofDefault
, which allows merging to detect which fields may be overridden, and which ones shouldn't.MergeWith
trait has been replaced with aMerge
trait, which has also been moved frombiome_service
tobiome_deserialize
. All the implementations ofMergeWith
have been removed in favor of aMergeable
derive macro that generates all theMerge
implementations.It is worth mentioning that the
Merge
trait has a dedicatedmerge_in_defaults()
method, which we use to merge in the defaults after merging ofextends
configs has been done. The reason it requires a separate method (as opposed to simplyConfiguration::default().merge_with(config)
) is becausemerge_with()
overridesNone
fields with the value from the struct it is merging with, while in the case of merging defaults we need to also recursively check if that value has otherNone
fields for which aDefault
might be available.I have added test cases for the issue reported in #1440 and another to highlight that it is possible for a config that extends another to revert values to their default value (which the original
merge_if_not_default()
method would have had trouble with).One complication I ran into is that the configurations coming out of
bpaf
arguments are initialized withDefault
instead ofNoneState
, and yet we want to merge them into the configuration from the config file. This was previously resolved by customMergeWith
implementations, although I have to say this was easy to overlook. I have now resolved this by explicitly patching the configs as needed in thecheck
/lint
/format
commands. It's not super pretty, but at least it's explicit and localized.Test Plan
I only had to update 1 test case (see comment) and added two more test cases to demonstrate more merging behavior. Still, I might very well have missed something (especially around the CLI invocations), so it would be great if someone could help identify pain points.
Future Work
I think it is possible to simplify the
Override
structs as well based on this, but I've left it out of scope for now since the PR is big enough as it is.There is also still an issue where rule options cannot be deeply merged. I've left it for now, since I don't think it's a regression. I expect it's also not very high priority, but it would be nice if it would work as expected. It will require wrapping all the rule options in
Option
as well though.