-
-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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
lib/types: add record
(simplified submodule)
#334680
base: master
Are you sure you want to change the base?
Conversation
42d9248
to
746b372
Compare
746b372
to
654bd51
Compare
can we nest records? something like this?
|
Yes. Currently nesting records (or submodules) within a record is the only way to achieve nested options. So you can't do (for example): settings = record {
fields {
foo.bar = mkOption {};
};
} But you can do: settings = record {
fields {
foo = mkOption {
type = record {
fields = {
bar = mkOption {};
};
};
};
};
} This limitation was a design decision by @roberth in the original PR #257511, for performance reasons. I suppose it could be re-evaluated in the future though.
Almost. All fields are options, not types. |
thanks for the answer i think something like this is an important but missing feature currently
of course, i was just simplifying |
@MattSturgeon any updates on this? i find myself less motivated to move forward in a few areas because the solution this PR offers to RFC42 provides a much better way than what currently exists @roberth and @infinisil - is it agreed that this PR is the proper way to address optional fields, or are either of you still not entirely convinced and reviewing other solutions? |
I'm in favor. Besides my earlier reasons, we've seen that taggedSubmodule is rather hard to implement in a complete way, whereas a solution based on Probably it can even reuse Anyway, I'd like to hear @infinisil's thoughts too. |
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.
Thoughts and possible suggestions (if they're good; this is all very new)
optionalFields ? { }, | ||
wildcard ? null, | ||
}@args: | ||
# TODO: assert that at least one arg is provided, i.e. args != { } |
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.
# TODO: assert that at least one arg is provided, i.e. args != { } |
Probably not worth checking. record { fields = { }; }
isn't better than record { }
.
Both are basically enum [ { } ]
, and that's fine.
value | ||
); | ||
fields' = checkFields fields; | ||
# TODO: should we also check optionalFields have no default? |
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.
Maybe I was wrong to use mkOption
.
It means that we have these extra checkFields
checks to cover known problems, but it also couples record
and module, holding back both types.
It seems that all of the reuse we do get is from option types, not options themselves, or we wouldn't be peeling out type
and default
ourselves, below.
And we don't even want default
s in the optional fields.
A mkField
would be simpler, more efficient, and independent of modules. It seems that we only need type
and default
, but internal
/visible
and meta
could be added.
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.
I agree a mkField
would make sense to further de-couple records from modules. This would also allow fields to declare whether they are optional, rather than having a separate optionalFields
argument.
builtins.addErrorContext "while evaluating the field `${fieldName}' of option `${showOption loc}'" ( | ||
(mergeDefinitions (loc ++ [ fieldName ]) fieldOption.type ( | ||
data.${fieldName} or [ ] | ||
# FIXME: isn't this handled by `mergeDefinitions` already? |
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.
# FIXME: isn't this handled by `mergeDefinitions` already? |
No, because we aren't passing the whole option.
What about the default's priority though? This looks like it gets added as a regular definition; not a mkOptionDefault
that gets replaced entirely by a regular definition.
) extraData; | ||
nestedTypes = fields' // { | ||
# potential collision with `_wildcard` field | ||
# TODO: should we prevent fields from having a wildcard? |
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.
# TODO: should we prevent fields from having a wildcard? | |
# TODO: should we prevent `fields` from having a `_wildcard`? |
Seems easy and cheap enough, especially if we assert it here in the nestedTypes
value, which I believe is only accessed when doing type merging, which is somewhat rare. (I'd have to check)
# TODO: surely defaults make no sense for optional fields? | ||
# And doesn't mergeDefinitions do this internally, anyway? |
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 a weird one. It could work like the type emptyValue
, and work like lazyAttrsOf
here - a trade-off between omitting the attribute entirely, as one may expect, or avoiding infinite recursions, as one may expect.
attrsOf
behavior: omit attribute when value is mkIf false
. Infinite recursion for defs like settings = mkIf cfg.settings.foo { bar = "bar"; }
lazyAttrsOf
behavior: keep the attribute even if mkIf false
, but insert an empty value, or throw.
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.
It sounds like we need a lazy and non-lazy mode for record types, since its users may wish for either behaviour.
In nixvim, for instance, we probably want the non-lazy behaviour where optional fields are not present in the resulting attrs.
This is because we usually want to be able to recursively map the attrs into a lua table using our version of lib.generators.toLua
.
I guess we could have our toLua
generator wrap recursion in tryEval
and not render anything that throws, but that seems like an overly large hammer for this issue.
Description of changes
Work in progress draft, based on #257511 by @roberth, but without
types.fix
and with additional support for optional fields.Pushing an early draft, I'll polish up the changes and this description shortly.
The main TODO for me is to add additional test-cases to cover more exotic useage. I've added a few inline TODO comments regarding things I'm unsure of or plan to investigate.
More TODO from https://github.com/NixOS/nixpkgs/pull/257511/files#r1621120811:
config
-driven submodules? #158598definedOptionsOnly
option toevalModules
#333799types.record
,types.fix
#257511config
despite option #158594cc @roberth @infinisil @GaetanLepage @traxys
Things done
nix.conf
? (See Nix manual)sandbox = relaxed
sandbox = true
nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD"
. Note: all changes have to be committed, also see nixpkgs-review usage./result/bin/
)Add a 👍 reaction to pull requests you find important.