-
-
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?
Changes from all commits
9772f6a
068d0a8
88c2f6d
63d0a5e
654bd51
7c16803
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ lib, ... }: | ||
|
||
let | ||
inherit (lib) mkOption types; | ||
|
||
person = types.record { | ||
fields = { | ||
nixerSince = mkOption { type = types.int; }; | ||
name = mkOption { type = types.str; }; | ||
isCool = mkOption { | ||
type = types.bool; | ||
default = "yeah"; | ||
}; | ||
}; | ||
}; | ||
|
||
in | ||
{ | ||
options.people = mkOption { type = types.attrsOf person; }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ lib, ... }: | ||
|
||
let | ||
inherit (lib) mkOption types; | ||
|
||
person = types.record { | ||
fields = { | ||
nixerSince = mkOption { type = types.int; }; | ||
name = mkOption { type = types.str; }; | ||
}; | ||
optionalFields = { | ||
age = mkOption { type = types.ints.unsigned; }; | ||
}; | ||
wildcard = mkOption { type = types.bool; }; | ||
}; | ||
|
||
in | ||
{ | ||
options.people = mkOption { type = types.attrsOf person; }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ lib, ... }: | ||
|
||
let | ||
inherit (lib) mkOption types; | ||
|
||
person = types.record { | ||
fields = { | ||
nixerSince = mkOption { type = types.int; }; | ||
name = mkOption { type = types.str; }; | ||
}; | ||
wildcard = mkOption { type = types.bool; }; | ||
}; | ||
|
||
in | ||
{ | ||
options.people = mkOption { type = types.attrsOf person; }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ lib, ... }: | ||
|
||
let | ||
inherit (lib) mkOption types; | ||
|
||
person = types.record { | ||
fields = { | ||
nixerSince = mkOption { type = types.int; }; | ||
name = mkOption { type = types.str; }; | ||
isCool = mkOption { | ||
type = types.bool; | ||
default = true; | ||
}; | ||
}; | ||
}; | ||
|
||
in | ||
{ | ||
options.people = mkOption { type = types.attrsOf person; }; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ lib, ... }: | ||
{ | ||
people.alice = { | ||
nixerSince = 2016; | ||
name = "Alice"; | ||
hiRes = true; | ||
mechKeyboard = true; | ||
spiders = false; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
people.alice = { | ||
nixerSince = 2016; | ||
name = "Alice"; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
people.bob = { | ||
nixerSince = 2019; | ||
name = "Bob"; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
people.mallory = { | ||
nixerSince = "beginning of time"; | ||
name = "Bobby"; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
people.mike = { | ||
nixerSince = 2020; | ||
name = "Mike"; | ||
age = 27; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,124 @@ | ||||||
{ lib }: | ||||||
|
||||||
let | ||||||
inherit (lib) | ||||||
mapAttrs | ||||||
zipAttrsWith | ||||||
removeAttrs | ||||||
attrNames | ||||||
showOption | ||||||
optional | ||||||
mergeDefinitions | ||||||
mkOptionType | ||||||
isAttrs | ||||||
; | ||||||
|
||||||
record = | ||||||
{ | ||||||
fields ? { }, | ||||||
optionalFields ? { }, | ||||||
wildcard ? null, | ||||||
}@args: | ||||||
# TODO: assert that at least one arg is provided, i.e. args != { } | ||||||
let | ||||||
checkFields = mapAttrs ( | ||||||
name: value: | ||||||
if value._type or null != "option" then | ||||||
throw "Record field `${lib.escapeNixIdentifier name}` must be declared with `mkOption`." | ||||||
else if value ? apply then | ||||||
throw "In field ${lib.escapeNixIdentifier name} records do not support options with `apply`" | ||||||
else if value ? readOnly then | ||||||
throw "In field ${lib.escapeNixIdentifier name} records do not support options with `readOnly`" | ||||||
else | ||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I was wrong to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree a |
||||||
optionalFields' = checkFields optionalFields; | ||||||
wildcard = | ||||||
if args.wildcard or null == null then | ||||||
null | ||||||
else if args.wildcard._type or null != "option" then | ||||||
throw "Record wildcard must be declared with `mkOption`." | ||||||
else if args.wildcard ? apply then | ||||||
throw "Records do not support options with `apply`, but wildcard has `apply`." | ||||||
else | ||||||
args.wildcard; | ||||||
in | ||||||
mkOptionType { | ||||||
name = "record"; | ||||||
description = if wildcard == null then "record" else "open record of ${wildcard.description}"; | ||||||
descriptionClass = if wildcard == null then "noun" else "composite"; | ||||||
check = isAttrs; | ||||||
merge = | ||||||
loc: defs: | ||||||
let | ||||||
data = zipAttrsWith (name: values: values) ( | ||||||
map ( | ||||||
def: | ||||||
mapAttrs (_fieldName: fieldValue: { | ||||||
inherit (def) file; | ||||||
value = fieldValue; | ||||||
}) def.value | ||||||
) defs | ||||||
); | ||||||
requiredFieldValues = mapAttrs ( | ||||||
fieldName: fieldOption: | ||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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 |
||||||
++ optional (fieldOption ? default) { | ||||||
value = fieldOption.default; | ||||||
file = "the default value of option ${showOption loc}"; | ||||||
} | ||||||
)).mergedValue | ||||||
) | ||||||
) fields'; | ||||||
optionalFieldValues = lib.concatMapAttrs ( | ||||||
fieldName: fieldOption: | ||||||
builtins.addErrorContext | ||||||
"while evaluating the optional field `${fieldName}' of option `${showOption loc}'" | ||||||
( | ||||||
let | ||||||
opt = mergeDefinitions (loc ++ [ fieldName ]) fieldOption.type ( | ||||||
data.${fieldName} or [ ] | ||||||
# TODO: surely defaults make no sense for optional fields? | ||||||
# And doesn't mergeDefinitions do this internally, anyway? | ||||||
Comment on lines
+86
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a weird one. It could work like the type
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I guess we could have our |
||||||
++ optional (fieldOption ? default) { | ||||||
value = fieldOption.default; | ||||||
file = "the default value of option ${showOption loc}"; | ||||||
} | ||||||
); | ||||||
in | ||||||
lib.optionalAttrs opt.isDefined { ${fieldName} = opt.mergedValue; } | ||||||
) | ||||||
) optionalFields'; | ||||||
extraData = removeAttrs data (attrNames fields' ++ attrNames optionalFields'); | ||||||
in | ||||||
if wildcard == null then | ||||||
if extraData == { } then | ||||||
requiredFieldValues | ||||||
else | ||||||
throw "A definition for option `${showOption loc}' has an unknown field." | ||||||
else | ||||||
requiredFieldValues | ||||||
// optionalFieldValues | ||||||
// mapAttrs ( | ||||||
fieldName: fieldDefs: | ||||||
builtins.addErrorContext | ||||||
"while evaluating the wildcard field `${fieldName}' of option `${showOption loc}'" | ||||||
((mergeDefinitions (loc ++ [ fieldName ]) wildcard.type fieldDefs).mergedValue) | ||||||
) 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 commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Seems easy and cheap enough, especially if we assert it here in the |
||||||
_wildcard = wildcard; | ||||||
}; | ||||||
}; | ||||||
|
||||||
in | ||||||
# public | ||||||
{ | ||||||
inherit record; | ||||||
} |
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.
Probably not worth checking.
record { fields = { }; }
isn't better thanrecord { }
.Both are basically
enum [ { } ]
, and that's fine.