lib.modules: init types checkAndMerge to allow adding 'valueMeta' (#391544)

+46 -3
lib/modules.nix
···
files = map (def: def.file) res.defsFinal;
definitionsWithLocations = res.defsFinal;
inherit (res) isDefined;
+
inherit (res.checkedAndMerged) valueMeta;
# This allows options to be correctly displayed using `${options.path.to.it}`
__toString = _: showOption loc;
};
···
# Type-check the remaining definitions, and merge them. Or throw if no definitions.
mergedValue =
if isDefined then
-
if all (def: type.check def.value) defsFinal then
+
if type.merge ? v2 then
+
# check and merge share the same closure
+
# .headError is either not-present, null, or a string describing the error
+
if checkedAndMerged.headError or null != null then
+
throw "A definition for option `${showOption loc}' is not of type `${type.description}'. TypeError: ${checkedAndMerged.headError.message}"
+
else
+
checkedAndMerged.value
+
else if all (def: type.check def.value) defsFinal then
type.merge loc defsFinal
else
let
···
# handling. If changed here, please change it there too.)
throw
"The option `${showOption loc}' was accessed but has no value defined. Try setting the option.";
+
+
checkedAndMerged =
+
(
+
# This function (which is immediately applied) checks that type.merge
+
# returns the proper attrset.
+
# Once use of the merge.v2 feature has propagated, consider removing this
+
# for an estimated one thousandth performance improvement (NixOS by nr.thunks).
+
{
+
headError,
+
value,
+
valueMeta,
+
}@args:
+
args
+
)
+
(
+
if type.merge ? v2 then
+
let
+
r = type.merge.v2 {
+
inherit loc;
+
defs = defsFinal;
+
};
+
in
+
r
+
// {
+
valueMeta = r.valueMeta // {
+
_internal = {
+
inherit type;
+
};
+
};
+
}
+
else
+
{
+
headError = null;
+
value = mergedValue;
+
valueMeta = { };
+
}
+
);
isDefined = defsFinal != [ ];
···
New option path as list of strings.
*/
to,
-
/**
Release number of the first release that contains the rename, ignoring backports.
Set it to the upcoming release, matching the nixpkgs/.version file.
*/
sinceRelease,
-
}:
doRename {
inherit from to;
+379
lib/tests/checkAndMergeCompat.nix
···
+
{
+
pkgs ? import ../.. { },
+
currLibPath ? ../.,
+
prevLibPath ? "${
+
pkgs.fetchFromGitHub {
+
owner = "nixos";
+
repo = "nixpkgs";
+
# Parent commit of [#391544](https://github.com/NixOS/nixpkgs/pull/391544)
+
# Which was before the type.merge.v2 introduction
+
rev = "bcf94dd3f07189b7475d823c8d67d08b58289905";
+
hash = "sha256-MuMiIY3MX5pFSOCvutmmRhV6RD0R3CG0Hmazkg8cMFI=";
+
}
+
}/lib",
+
}:
+
let
+
lib = import currLibPath;
+
+
lib_with_merge_v2 = lib;
+
lib_with_merge_v1 = import prevLibPath;
+
+
getMatrix =
+
{
+
getType ? null,
+
# If getType is set this is only used as test prefix
+
# And the type from getType is used
+
outerTypeName,
+
innerTypeName,
+
value,
+
testAttrs,
+
}:
+
let
+
evalModules.call_v1 = lib_with_merge_v1.evalModules;
+
evalModules.call_v2 = lib_with_merge_v2.evalModules;
+
outerTypes.outer_v1 = lib_with_merge_v1.types;
+
outerTypes.outer_v2 = lib_with_merge_v2.types;
+
innerTypes.inner_v1 = lib_with_merge_v1.types;
+
innerTypes.inner_v2 = lib_with_merge_v2.types;
+
in
+
lib.mapAttrs (
+
_: evalModules:
+
lib.mapAttrs (
+
_: outerTypes:
+
lib.mapAttrs (_: innerTypes: {
+
"test_${outerTypeName}_${innerTypeName}" = testAttrs // {
+
expr =
+
(evalModules {
+
modules = [
+
(m: {
+
options.foo = m.lib.mkOption {
+
type =
+
if getType != null then
+
getType outerTypes innerTypes
+
else
+
outerTypes.${outerTypeName} innerTypes.${innerTypeName};
+
default = value;
+
};
+
})
+
];
+
}).config.foo;
+
};
+
}) innerTypes
+
) outerTypes
+
) evalModules;
+
in
+
{
+
# AttrsOf string
+
attrsOf_str_ok = getMatrix {
+
outerTypeName = "attrsOf";
+
innerTypeName = "str";
+
value = {
+
bar = "test";
+
};
+
testAttrs = {
+
expected = {
+
bar = "test";
+
};
+
};
+
};
+
attrsOf_str_err_inner = getMatrix {
+
outerTypeName = "attrsOf";
+
innerTypeName = "str";
+
value = {
+
bar = 1; # not a string
+
};
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = "A definition for option `foo.bar' is not of type `string'.*";
+
};
+
};
+
};
+
attrsOf_str_err_outer = getMatrix {
+
outerTypeName = "attrsOf";
+
innerTypeName = "str";
+
value = [ "foo" ]; # not an attrset
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = "A definition for option `foo' is not of type `attribute set of string'.*";
+
};
+
};
+
};
+
+
# listOf string
+
listOf_str_ok = getMatrix {
+
outerTypeName = "listOf";
+
innerTypeName = "str";
+
value = [
+
"foo"
+
"bar"
+
];
+
testAttrs = {
+
expected = [
+
"foo"
+
"bar"
+
];
+
};
+
};
+
listOf_str_err_inner = getMatrix {
+
outerTypeName = "listOf";
+
innerTypeName = "str";
+
value = [
+
"foo"
+
1
+
]; # not a string
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = ''A definition for option `foo."\[definition 1-entry 2\]"' is not of type `string'.'';
+
};
+
};
+
};
+
listOf_str_err_outer = getMatrix {
+
outerTypeName = "listOf";
+
innerTypeName = "str";
+
value = {
+
foo = 42;
+
}; # not a list
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = "A definition for option `foo' is not of type `list of string'.*";
+
};
+
};
+
};
+
+
attrsOf_submodule_ok = getMatrix {
+
getType =
+
a: b:
+
a.attrsOf (
+
b.submodule (m: {
+
options.nested = m.lib.mkOption {
+
type = m.lib.types.str;
+
};
+
})
+
);
+
outerTypeName = "attrsOf";
+
innerTypeName = "submodule";
+
value = {
+
foo = {
+
nested = "test1";
+
};
+
bar = {
+
nested = "test2";
+
};
+
};
+
testAttrs = {
+
expected = {
+
foo = {
+
nested = "test1";
+
};
+
bar = {
+
nested = "test2";
+
};
+
};
+
};
+
};
+
attrsOf_submodule_err_inner = getMatrix {
+
outerTypeName = "attrsOf";
+
innerTypeName = "submodule";
+
getType =
+
a: b:
+
a.attrsOf (
+
b.submodule (m: {
+
options.nested = m.lib.mkOption {
+
type = m.lib.types.str;
+
};
+
})
+
);
+
value = {
+
foo = [ 1 ]; # not a submodule
+
bar = {
+
nested = "test2";
+
};
+
};
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = "A definition for option `foo.foo' is not of type `submodule'.*";
+
};
+
};
+
};
+
attrsOf_submodule_err_outer = getMatrix {
+
outerTypeName = "attrsOf";
+
innerTypeName = "submodule";
+
getType =
+
a: b:
+
a.attrsOf (
+
b.submodule (m: {
+
options.nested = m.lib.mkOption {
+
type = m.lib.types.str;
+
};
+
})
+
);
+
value = [ 123 ]; # not an attrsOf
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = ''A definition for option `foo' is not of type `attribute set of \(submodule\).*'';
+
};
+
};
+
};
+
+
# either
+
either_str_attrsOf_ok = getMatrix {
+
outerTypeName = "either";
+
innerTypeName = "str_or_attrsOf_str";
+
+
getType = a: b: a.either b.str (b.attrsOf a.str);
+
value = "string value";
+
testAttrs = {
+
expected = "string value";
+
};
+
};
+
either_str_attrsOf_err_1 = getMatrix {
+
outerTypeName = "either";
+
innerTypeName = "str_or_attrsOf_str";
+
+
getType = a: b: a.either b.str (b.attrsOf a.str);
+
value = 1;
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = "A definition for option `foo' is not of type `string or attribute set of string'.*";
+
};
+
};
+
};
+
either_str_attrsOf_err_2 = getMatrix {
+
outerTypeName = "either";
+
innerTypeName = "str_or_attrsOf_str";
+
+
getType = a: b: a.either b.str (b.attrsOf a.str);
+
value = {
+
bar = 1; # not a string
+
};
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = "A definition for option `foo.bar' is not of type `string'.*";
+
};
+
};
+
};
+
+
# Coereced to
+
coerce_attrsOf_str_to_listOf_str_run = getMatrix {
+
outerTypeName = "coercedTo";
+
innerTypeName = "attrsOf_str->listOf_str";
+
getType = a: b: a.coercedTo (b.attrsOf b.str) builtins.attrValues (b.listOf b.str);
+
value = {
+
bar = "test1"; # coerced to listOf string
+
foo = "test2"; # coerced to listOf string
+
};
+
testAttrs = {
+
expected = [
+
"test1"
+
"test2"
+
];
+
};
+
};
+
coerce_attrsOf_str_to_listOf_str_final = getMatrix {
+
outerTypeName = "coercedTo";
+
innerTypeName = "attrsOf_str->listOf_str";
+
getType = a: b: a.coercedTo (b.attrsOf b.str) (abort "This shouldnt run") (b.listOf b.str);
+
value = [
+
"test1"
+
"test2"
+
]; # already a listOf string
+
testAttrs = {
+
expected = [
+
"test1"
+
"test2"
+
]; # Order should be kept
+
};
+
};
+
coerce_attrsOf_str_to_listOf_err_coercer_input = getMatrix {
+
outerTypeName = "coercedTo";
+
innerTypeName = "attrsOf_str->listOf_str";
+
getType = a: b: a.coercedTo (b.attrsOf b.str) builtins.attrValues (b.listOf b.str);
+
value = [
+
{ }
+
{ }
+
]; # not coercible to listOf string, with the given coercer
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = ''A definition for option `foo."\[definition 1-entry 1\]"' is not of type `string'.*'';
+
};
+
};
+
};
+
coerce_attrsOf_str_to_listOf_err_coercer_ouput = getMatrix {
+
outerTypeName = "coercedTo";
+
innerTypeName = "attrsOf_str->listOf_str";
+
getType = a: b: a.coercedTo (b.attrsOf b.str) builtins.attrValues (b.listOf b.str);
+
value = {
+
foo = {
+
bar = 1;
+
}; # coercer produces wrong type -> [ { bar = 1; } ]
+
};
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = ''A definition for option `foo."\[definition 1-entry 1\]"' is not of type `string'.*'';
+
};
+
};
+
};
+
coerce_str_to_int_coercer_ouput = getMatrix {
+
outerTypeName = "coercedTo";
+
innerTypeName = "int->str";
+
getType = a: b: a.coercedTo b.int builtins.toString a.str;
+
value = [ ];
+
testAttrs = {
+
expectedError = {
+
type = "ThrownError";
+
msg = ''A definition for option `foo' is not of type `string or signed integer convertible to it.*'';
+
};
+
};
+
};
+
+
# Submodule
+
submodule_with_ok = getMatrix {
+
outerTypeName = "submoduleWith";
+
innerTypeName = "mixed_types";
+
getType =
+
a: b:
+
a.submodule (m: {
+
options.attrs = m.lib.mkOption {
+
type = b.attrsOf b.str;
+
};
+
options.list = m.lib.mkOption {
+
type = b.listOf b.str;
+
};
+
options.either = m.lib.mkOption {
+
type = b.either a.str a.int;
+
};
+
});
+
value = {
+
attrs = {
+
foo = "bar";
+
};
+
list = [
+
"foo"
+
"bar"
+
];
+
either = 123; # int
+
};
+
testAttrs = {
+
expected = {
+
attrs = {
+
foo = "bar";
+
};
+
list = [
+
"foo"
+
"bar"
+
];
+
either = 123;
+
};
+
};
+
};
+
}
+22 -2
lib/tests/modules.sh
···
set --
checkConfigOutput '^"42"$' config.value ./declare-coerced-value.nix
checkConfigOutput '^"24"$' config.value ./declare-coerced-value.nix ./define-value-string.nix
-
checkConfigError 'A definition for option .* is not.*string or signed integer convertible to it.*. Definition values:\n\s*- In .*: \[ \]' config.value ./declare-coerced-value.nix ./define-value-list.nix
+
checkConfigError 'A definition for option .*. is not of type .*.\n\s*- In .*: \[ \]' config.value ./declare-coerced-value.nix ./define-value-list.nix
# Check coerced option merging.
checkConfigError 'The option .value. in .*/declare-coerced-value.nix. is already declared in .*/declare-coerced-value-no-default.nix.' config.value ./declare-coerced-value.nix ./declare-coerced-value-no-default.nix
# Check coerced value with unsound coercion
checkConfigOutput '^12$' config.value ./declare-coerced-value-unsound.nix
-
checkConfigError 'A definition for option .* is not of type .*. Definition values:\n\s*- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
+
checkConfigError 'A definition for option .* is not of type .*.\n\s*- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
checkConfigError 'toInt: Could not convert .* to int' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
# Check `graph` attribute
···
checkConfigError 'attribute .*bar.* not found' config.sub.conditionalImportAsNixos.bar ./specialArgs-class.nix
checkConfigError 'attribute .*foo.* not found' config.sub.conditionalImportAsDarwin.foo ./specialArgs-class.nix
checkConfigOutput '"foo"' config.sub.conditionalImportAsDarwin.bar ./specialArgs-class.nix
+
# Check that some types expose the 'valueMeta'
+
checkConfigOutput '\{\}' options.str.valueMeta ./types-valueMeta.nix
+
checkConfigOutput '["foo", "bar"]' config.attrsOfResult ./types-valueMeta.nix
+
checkConfigOutput '2' config.listOfResult ./types-valueMeta.nix
+
+
# Check that composed types expose the 'valueMeta'
+
# attrsOf submodule (also on merged options,types)
+
checkConfigOutput '42' options.attrsOfModule.valueMeta.attrs.foo.configuration.options.bar.value ./composed-types-valueMeta.nix
+
checkConfigOutput '42' options.mergedAttrsOfModule.valueMeta.attrs.foo.configuration.options.bar.value ./composed-types-valueMeta.nix
+
+
# listOf submodule (also on merged options,types)
+
checkConfigOutput '42' config.listResult ./composed-types-valueMeta.nix
+
checkConfigOutput '42' config.mergedListResult ./composed-types-valueMeta.nix
+
+
# Add check
+
checkConfigOutput '^0$' config.v1CheckedPass ./add-check.nix
+
checkConfigError 'A definition for option .* is not of type .signed integer.*' config.v1CheckedFail ./add-check.nix
+
checkConfigOutput '^true$' config.v2checkedPass ./add-check.nix
+
checkConfigError 'A definition for option .* is not of type .attribute set of signed integer.*' config.v2checkedFail ./add-check.nix
+
cat <<EOF
====== module tests ======
+36
lib/tests/modules/add-check.nix
···
+
(
+
{ lib, ... }:
+
let
+
inherit (lib) types mkOption;
+
inherit (types) addCheck int attrsOf;
+
+
# type with a v1 merge
+
v1Type = addCheck int (v: v == 0);
+
+
# type with a v2 merge
+
v2Type = addCheck (attrsOf int) (v: v ? foo);
+
in
+
{
+
options.v1CheckedPass = mkOption {
+
type = v1Type;
+
default = 0;
+
};
+
options.v1CheckedFail = mkOption {
+
type = v1Type;
+
default = 1;
+
};
+
options.v2checkedPass = mkOption {
+
type = v2Type;
+
default = {
+
foo = 1;
+
};
+
# plug the value to make test script regex simple
+
apply = v: v.foo == 1;
+
};
+
options.v2checkedFail = mkOption {
+
type = v2Type;
+
default = { };
+
apply = v: lib.deepSeq v v;
+
};
+
}
+
)
+75
lib/tests/modules/composed-types-valueMeta.nix
···
+
{ lib, ... }:
+
let
+
inherit (lib) types mkOption;
+
+
attrsOfModule = mkOption {
+
type = types.attrsOf (
+
types.submodule {
+
options.bar = mkOption {
+
type = types.int;
+
};
+
}
+
);
+
};
+
+
listOfModule = mkOption {
+
type = types.listOf (
+
types.submodule {
+
options.bar = mkOption {
+
type = types.int;
+
};
+
}
+
);
+
};
+
+
in
+
{
+
imports = [
+
# Module A
+
({
+
options.attrsOfModule = attrsOfModule;
+
options.mergedAttrsOfModule = attrsOfModule;
+
options.listOfModule = listOfModule;
+
options.mergedListOfModule = listOfModule;
+
})
+
# Module B
+
({
+
options.mergedAttrsOfModule = attrsOfModule;
+
options.mergedListOfModule = listOfModule;
+
})
+
# Values
+
# It is important that the value is defined in a separate module
+
# Without valueMeta the actual value and sub-options wouldn't be accessible via:
+
# options.attrsOfModule.type.getSubOptions
+
({
+
attrsOfModule = {
+
foo.bar = 42;
+
};
+
mergedAttrsOfModule = {
+
foo.bar = 42;
+
};
+
})
+
(
+
{ options, ... }:
+
{
+
config.listOfModule = [
+
{
+
bar = 42;
+
}
+
];
+
config.mergedListOfModule = [
+
{
+
bar = 42;
+
}
+
];
+
# Result options to expose the list module to bash as plain attribute path
+
options.listResult = mkOption {
+
default = (builtins.head options.listOfModule.valueMeta.list).configuration.options.bar.value;
+
};
+
options.mergedListResult = mkOption {
+
default = (builtins.head options.mergedListOfModule.valueMeta.list).configuration.options.bar.value;
+
};
+
}
+
)
+
];
+
}
+60
lib/tests/modules/types-valueMeta.nix
···
+
{ lib, ... }:
+
let
+
inherit (lib) types mkOption;
+
+
inherit (types)
+
# attrsOf uses attrsWith internally
+
attrsOf
+
listOf
+
submoduleOf
+
str
+
;
+
in
+
{
+
imports = [
+
(
+
{ options, ... }:
+
{
+
# Should have an empty valueMeta
+
options.str = mkOption {
+
type = str;
+
};
+
+
# Should have some valueMeta which is an attribute set of the nested valueMeta
+
options.attrsOf = mkOption {
+
type = attrsOf str;
+
default = {
+
foo = "foo";
+
bar = "bar";
+
};
+
};
+
options.attrsOfResult = mkOption {
+
default = builtins.attrNames options.attrsOf.valueMeta.attrs;
+
};
+
+
# Should have some valueMeta which is the list of the nested valueMeta of types.str
+
# [ {} {} ]
+
options.listOf = mkOption {
+
type = listOf str;
+
default = [
+
"foo"
+
"bar"
+
];
+
};
+
options.listOfResult = mkOption {
+
default = builtins.length options.listOf.valueMeta.list;
+
};
+
+
# Should have some valueMeta which is the submodule evaluation
+
# { _module, options, config, ...}
+
options.submoduleOf = mkOption {
+
type = submoduleOf {
+
options.str = mkOption {
+
type = str;
+
};
+
};
+
};
+
}
+
)
+
];
+
}
+25
lib/tests/nix-unit.nix
···
+
{
+
pkgs ? import ../.. { },
+
}:
+
let
+
prevNixpkgs = pkgs.fetchFromGitHub {
+
owner = "nixos";
+
repo = "nixpkgs";
+
# Parent commit of [#391544](https://github.com/NixOS/nixpkgs/pull/391544)
+
# Which was before the type.merge.v2 introduction
+
rev = "bcf94dd3f07189b7475d823c8d67d08b58289905";
+
hash = "sha256-MuMiIY3MX5pFSOCvutmmRhV6RD0R3CG0Hmazkg8cMFI=";
+
};
+
in
+
(pkgs.runCommand "lib-cross-eval-merge-v2"
+
{
+
nativeBuildInputs = [ pkgs.nix-unit ];
+
}
+
''
+
export HOME=$TMPDIR
+
nix-unit --eval-store "$HOME" ${./checkAndMergeCompat.nix} \
+
--arg currLibPath "${../.}" \
+
--arg prevLibPath "${prevNixpkgs}/lib"
+
mkdir $out
+
''
+
)
+3
lib/tests/release.nix
···
pkgsBB.symlinkJoin {
name = "nixpkgs-lib-tests";
paths = map testWithNix nixVersions ++ [
+
(import ./nix-unit.nix {
+
inherit pkgs;
+
})
(import ./maintainers.nix {
inherit pkgs;
lib = import ../.;
+201 -76
lib/types.nix
···
toList
;
inherit (lib.lists)
-
all
concatLists
count
elemAt
···
mergeOneOption
mergeUniqueOption
showFiles
+
showDefs
showOption
;
inherit (lib.strings)
···
lib.optionalString (loc != null) "of the option `${showOption loc}` "
}is accessed, use `${lib.optionalString (loc != null) "type."}nestedTypes.elemType` instead.
'' payload.elemType;
+
+
checkDefsForError =
+
check: loc: defs:
+
let
+
invalidDefs = filter (def: !check def.value) defs;
+
in
+
if invalidDefs != [ ] then { message = "Definition values: ${showDefs invalidDefs}"; } else null;
outer_types = rec {
isType = type: x: (x._type or "") == type;
···
}";
descriptionClass = "composite";
check = isList;
-
merge =
-
loc: defs:
-
map (x: x.value) (
-
filter (x: x ? value) (
-
concatLists (
-
imap1 (
-
n: def:
+
merge = {
+
__functor =
+
self: loc: defs:
+
(self.v2 { inherit loc defs; }).value;
+
v2 =
+
{ loc, defs }:
+
let
+
evals = filter (x: x.optionalValue ? value) (
+
concatLists (
imap1 (
-
m: def':
-
(mergeDefinitions (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) elemType [
-
{
-
inherit (def) file;
-
value = def';
-
}
-
]).optionalValue
-
) def.value
-
) defs
-
)
-
)
-
);
+
n: def:
+
imap1 (
+
m: def':
+
(mergeDefinitions (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) elemType [
+
{
+
inherit (def) file;
+
value = def';
+
}
+
])
+
) def.value
+
) defs
+
)
+
);
+
in
+
{
+
headError = checkDefsForError check loc defs;
+
value = map (x: x.optionalValue.value or x.mergedValue) evals;
+
valueMeta.list = map (v: v.checkedAndMerged.valueMeta) evals;
+
};
+
};
emptyValue = {
value = [ ];
};
···
lazy ? false,
placeholder ? "name",
}:
-
mkOptionType {
+
mkOptionType rec {
name = if lazy then "lazyAttrsOf" else "attrsOf";
description =
(if lazy then "lazy attribute set" else "attribute set")
+ " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
descriptionClass = "composite";
check = isAttrs;
-
merge =
-
if lazy then
-
(
-
# Lazy merge Function
-
loc: defs:
-
zipAttrsWith
-
(
-
name: defs:
-
let
-
merged = mergeDefinitions (loc ++ [ name ]) elemType defs;
-
# mergedValue will trigger an appropriate error when accessed
-
in
-
merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
-
)
-
# Push down position info.
-
(pushPositions defs)
-
)
-
else
-
(
-
# Non-lazy merge Function
-
loc: defs:
-
mapAttrs (n: v: v.value) (
-
filterAttrs (n: v: v ? value) (
-
zipAttrsWith (name: defs: (mergeDefinitions (loc ++ [ name ]) elemType (defs)).optionalValue)
-
# Push down position info.
-
(pushPositions defs)
-
)
-
)
-
);
+
merge = {
+
__functor =
+
self: loc: defs:
+
(self.v2 { inherit loc defs; }).value;
+
v2 =
+
{ loc, defs }:
+
let
+
evals =
+
if lazy then
+
zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs)
+
else
+
# Filtering makes the merge function more strict
+
# Meaning it is less lazy
+
filterAttrs (n: v: v.optionalValue ? value) (
+
zipAttrsWith (name: defs: mergeDefinitions (loc ++ [ name ]) elemType defs) (pushPositions defs)
+
);
+
in
+
{
+
headError = checkDefsForError check loc defs;
+
value = mapAttrs (
+
n: v:
+
if lazy then
+
v.optionalValue.value or elemType.emptyValue.value or v.mergedValue
+
else
+
v.optionalValue.value
+
) evals;
+
valueMeta.attrs = mapAttrs (n: v: v.checkedAndMerged.valueMeta) evals;
+
};
+
};
+
emptyValue = {
value = { };
};
···
name = "submodule";
+
check = x: isAttrs x || isFunction x || path.check x;
in
mkOptionType {
inherit name;
···
docsEval = base.extendModules { modules = [ noCheckForDocsModule ]; };
in
docsEval._module.freeformType.description or name;
-
check = x: isAttrs x || isFunction x || path.check x;
-
merge =
-
loc: defs:
-
(base.extendModules {
-
modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
-
prefix = loc;
-
}).config;
+
inherit check;
+
merge = {
+
__functor =
+
self: loc: defs:
+
(self.v2 { inherit loc defs; }).value;
+
v2 =
+
{ loc, defs }:
+
let
+
configuration = base.extendModules {
+
modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
+
prefix = loc;
+
};
+
in
+
{
+
headError = checkDefsForError check loc defs;
+
value = configuration.config;
+
valueMeta = { inherit configuration; };
+
};
+
};
emptyValue = {
value = { };
};
···
}";
descriptionClass = "conjunction";
check = x: t1.check x || t2.check x;
-
merge =
-
loc: defs:
-
let
-
defList = map (d: d.value) defs;
-
in
-
if all (x: t1.check x) defList then
-
t1.merge loc defs
-
else if all (x: t2.check x) defList then
-
t2.merge loc defs
-
else
-
mergeOneOption loc defs;
+
merge = {
+
__functor =
+
self: loc: defs:
+
(self.v2 { inherit loc defs; }).value;
+
v2 =
+
{ loc, defs }:
+
let
+
t1CheckedAndMerged =
+
if t1.merge ? v2 then
+
t1.merge.v2 { inherit loc defs; }
+
else
+
{
+
value = t1.merge loc defs;
+
headError = checkDefsForError t1.check loc defs;
+
valueMeta = { };
+
};
+
t2CheckedAndMerged =
+
if t2.merge ? v2 then
+
t2.merge.v2 { inherit loc defs; }
+
else
+
{
+
value = t2.merge loc defs;
+
headError = checkDefsForError t2.check loc defs;
+
valueMeta = { };
+
};
+
+
checkedAndMerged =
+
if t1CheckedAndMerged.headError == null then
+
t1CheckedAndMerged
+
else if t2CheckedAndMerged.headError == null then
+
t2CheckedAndMerged
+
else
+
rec {
+
valueMeta = {
+
inherit headError;
+
};
+
headError = {
+
message = "The option `${showOption loc}` is neither a value of type `${t1.description}` nor `${t2.description}`, Definition values: ${showDefs defs}";
+
};
+
value = abort "(t.merge.v2 defs).value must only be accessed when `.headError == null`. This is a bug in code that consumes a module system type.";
+
};
+
in
+
checkedAndMerged;
+
};
typeMerge =
f':
let
···
optionDescriptionPhrase (class: class == "noun") coercedType
} convertible to it";
check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
-
merge =
-
loc: defs:
-
let
-
coerceVal = val: if coercedType.check val then coerceFunc val else val;
-
in
-
finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
+
merge = {
+
__functor =
+
self: loc: defs:
+
(self.v2 { inherit loc defs; }).value;
+
v2 =
+
{ loc, defs }:
+
let
+
finalDefs = (
+
map (
+
def:
+
def
+
// {
+
value =
+
let
+
merged = coercedType.merge.v2 {
+
inherit loc;
+
defs = [ def ];
+
};
+
in
+
if coercedType.merge ? v2 then
+
if merged.headError == null then coerceFunc def.value else def.value
+
else if coercedType.check def.value then
+
coerceFunc def.value
+
else
+
def.value;
+
}
+
) defs
+
);
+
in
+
if finalType.merge ? v2 then
+
finalType.merge.v2 {
+
inherit loc;
+
defs = finalDefs;
+
}
+
else
+
{
+
value = finalType.merge loc finalDefs;
+
valueMeta = { };
+
headError = checkDefsForError check loc defs;
+
};
+
};
emptyValue = finalType.emptyValue;
getSubOptions = finalType.getSubOptions;
getSubModules = finalType.getSubModules;
···
nestedTypes.coercedType = coercedType;
nestedTypes.finalType = finalType;
};
+
/**
Augment the given type with an additional type check function.
···
Fixing is not trivial, we appreciate any help!
:::
*/
-
addCheck = elemType: check: elemType // { check = x: elemType.check x && check x; };
-
+
addCheck =
+
elemType: check:
+
if elemType.merge ? v2 then
+
elemType
+
// {
+
check = x: elemType.check x && check x;
+
merge = {
+
__functor =
+
self: loc: defs:
+
(self.v2 { inherit loc defs; }).value;
+
v2 =
+
{ loc, defs }:
+
let
+
orig = elemType.merge.v2 { inherit loc defs; };
+
headError' = if orig.headError != null then orig.headError else checkDefsForError check loc defs;
+
in
+
orig
+
// {
+
headError = headError';
+
};
+
};
+
}
+
else
+
elemType
+
// {
+
check = x: elemType.check x && check x;
+
};
};
/**