From dc45ca2e42862e9b77b543de8095699534c555fd Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 24 Sep 2024 15:25:03 +0200 Subject: [PATCH 01/11] lib.types: init attrsWith --- lib/options.nix | 29 +++++++++++++++--------- lib/types.nix | 60 ++++++++++++++++++++++++++----------------------- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/lib/options.nix b/lib/options.nix index f4d0d9d36cfc9..e08955464f756 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -427,21 +427,28 @@ rec { Placeholders will not be quoted as they are not actual values: (showOption ["foo" "*" "bar"]) == "foo.*.bar" (showOption ["foo" "" "bar"]) == "foo..bar" + (showOption ["foo" "" "bar"]) == "foo..bar" */ showOption = parts: let + # If the part is a named placeholder of the form "<...>" don't escape it. + # Required for compatibility with: namedAttrsOf + # Can lead to misleading escaping if somebody uses literally "<...>" in their option names. + # This is the trade-off to allow for named placeholders in option names. + isNamedPlaceholder = builtins.match "\<(.*)\>"; + # "" # functionTo + # "" # attrsOf submoule + # "" # attrsWith { name = "customName"; elemType = submoule; } + # We assume that these are "special values" and not real configuration data. + # If it is real configuration data, it is rendered incorrectly. + specialIdentifiers = [ + "*" # listOf (submodule {}) + ]; escapeOptionPart = part: - let - # We assume that these are "special values" and not real configuration data. - # If it is real configuration data, it is rendered incorrectly. - specialIdentifiers = [ - "" # attrsOf (submodule {}) - "*" # listOf (submodule {}) - "" # functionTo - ]; - in if builtins.elem part specialIdentifiers - then part - else lib.strings.escapeNixIdentifier part; + if builtins.elem part specialIdentifiers || isNamedPlaceholder part != null + then part + else lib.strings.escapeNixIdentifier part; in (concatStringsSep ".") (map escapeOptionPart parts); + showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files); showDefs = defs: concatMapStrings (def: diff --git a/lib/types.nix b/lib/types.nix index 6c4a66c4e3c0b..6e5fc28b02e6c 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -568,48 +568,52 @@ rec { substSubModules = m: nonEmptyListOf (elemType.substSubModules m); }; - attrsOf = elemType: mkOptionType rec { - name = "attrsOf"; - description = "attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; - descriptionClass = "composite"; - check = isAttrs; - merge = 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. - (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs))); - emptyValue = { value = {}; }; - getSubOptions = prefix: elemType.getSubOptions (prefix ++ [""]); - getSubModules = elemType.getSubModules; - substSubModules = m: attrsOf (elemType.substSubModules m); - functor = (defaultFunctor name) // { wrapped = elemType; }; - nestedTypes.elemType = elemType; - }; + attrsOf = elemType: attrsWith { inherit elemType; }; # A version of attrsOf that's lazy in its values at the expense of # conditional definitions not working properly. E.g. defining a value with # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an # error that it's not defined. Use only if conditional definitions don't make sense. - lazyAttrsOf = elemType: mkOptionType rec { - name = "lazyAttrsOf"; - description = "lazy attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}"; - descriptionClass = "composite"; - check = isAttrs; - merge = loc: defs: + lazyAttrsOf = elemType: attrsWith { inherit elemType; lazy = true; }; + + # base type for lazyAttrsOf and attrsOf + attrsWith = { + elemType, + name ? "name", + lazy ? false, + }: + let + typeName = "attrsOf"; + lazyMergeFn = 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. - (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs); + (pushPositions defs); + + mergeFn = 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))); + # Push down position info. + pushPositions = map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value); + in + mkOptionType { + name = typeName; + 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 lazyMergeFn else mergeFn; emptyValue = { value = {}; }; - getSubOptions = prefix: elemType.getSubOptions (prefix ++ [""]); + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${name}>"]); getSubModules = elemType.getSubModules; - substSubModules = m: lazyAttrsOf (elemType.substSubModules m); - functor = (defaultFunctor name) // { wrapped = elemType; }; + substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; }; + functor = (defaultFunctor typeName) // { wrapped = elemType; }; nestedTypes.elemType = elemType; }; From 0a2f174429f04a2132a7513f66a67de90911a264 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 27 Sep 2024 10:19:11 +0200 Subject: [PATCH 02/11] lib.attrsWith: init eval tests --- lib/tests/misc.nix | 38 ++++++++++++++++++++++++++++++++++++++ lib/types.nix | 6 +++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index 116d86cdfb3fb..ca8de7c28c626 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -1851,6 +1851,44 @@ runTests { expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "" "bar" ] [ "foo" "bar" ] ]; }; + testAttrsWithName = { + expr = let + eval = evalModules { + modules = [ + { + options = { + foo = lib.mkOption { + type = lib.types.attrsWith { + name = "MyCustomPlaceholder"; + elemType = lib.types.submodule { + options.bar = lib.mkOption { + type = lib.types.int; + default = 42; + }; + }; + }; + }; + }; + } + ]; + }; + opt = eval.options.foo; + in + (opt.type.getSubOptions opt.loc).bar.loc; + expected = [ + "foo" + "" + "bar" + ]; + }; + + testShowOptionWithPlaceholder = { + # , *, should now be escaped. It is used as a placeholder by convention. + # Other symbols should be escaped. `{}` + expr = lib.showOption ["" "" "*" "{foo}"]; + expected = "..*.\"{foo}\""; + }; + testCartesianProductOfEmptySet = { expr = cartesianProduct {}; expected = [ {} ]; diff --git a/lib/types.nix b/lib/types.nix index 6e5fc28b02e6c..812d54efba748 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -584,7 +584,7 @@ rec { lazy ? false, }: let - typeName = "attrsOf"; + typeName = if lazy then "lazyAttrsOf" else "attrsOf"; lazyMergeFn = loc: defs: zipAttrsWith (name: defs: let merged = mergeDefinitions (loc ++ [name]) elemType defs; @@ -596,7 +596,7 @@ rec { mergeFn = loc: defs: mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs: - (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue + (mergeDefinitions (loc ++ [name]) elemType (defs)).optionalValue ) # Push down position info. (pushPositions defs))); @@ -613,7 +613,7 @@ rec { getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${name}>"]); getSubModules = elemType.getSubModules; substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; }; - functor = (defaultFunctor typeName) // { wrapped = elemType; }; + functor = (defaultFunctor typeName) // { wrapped = elemType; type = t: attrsWith { elemType = t; inherit name lazy; }; }; nestedTypes.elemType = elemType; }; From 3bd01bca8fec2d208a168edb50c457fc36a6c96f Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 2 Oct 2024 09:48:54 +0200 Subject: [PATCH 03/11] lib/attrsWith: inline merge functions --- lib/types.nix | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/types.nix b/lib/types.nix index 812d54efba748..f08402533c956 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -585,21 +585,6 @@ rec { }: let typeName = if lazy then "lazyAttrsOf" else "attrsOf"; - lazyMergeFn = 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); - - mergeFn = 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))); # Push down position info. pushPositions = map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value); in @@ -608,7 +593,25 @@ rec { 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 lazyMergeFn else mergeFn; + 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))) + ); emptyValue = { value = {}; }; getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${name}>"]); getSubModules = elemType.getSubModules; From 11a6fd813a9f83b56d87546162e4e80f13ca7776 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Sat, 5 Oct 2024 03:04:56 +0200 Subject: [PATCH 04/11] lib.types.attrsWith: Improved type merging --- lib/types.nix | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/types.nix b/lib/types.nix index f08402533c956..66292739fd429 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -616,7 +616,35 @@ rec { getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${name}>"]); getSubModules = elemType.getSubModules; substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; }; - functor = (defaultFunctor typeName) // { wrapped = elemType; type = t: attrsWith { elemType = t; inherit name lazy; }; }; + functor = defaultFunctor "attrsWith" // { + payload = { + inherit elemType name lazy; + }; + binOp = lhs: rhs: + let + elemType = lhs.elemType.typeMerge rhs.elemType.functor; + name = + if lhs.name == rhs.name then + lhs.name + else if lhs.name == "name" then + rhs.name + else if rhs.name == "name" then + lhs.name + else + null; + lazy = + if lhs.lazy == rhs.lazy then + lhs.lazy + else + null; + in + if elemType == null || name == null || lazy == null then + null + else + { + inherit elemType name lazy; + }; + }; nestedTypes.elemType = elemType; }; From a21dc4a543ba6950ca26d7d2e16afc8b5dcd7d84 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 8 Oct 2024 21:26:10 +0200 Subject: [PATCH 05/11] lib.types.attrsWith: format whitespaces --- lib/types.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/types.nix b/lib/types.nix index 66292739fd429..f6cb6bd34614c 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -623,7 +623,7 @@ rec { binOp = lhs: rhs: let elemType = lhs.elemType.typeMerge rhs.elemType.functor; - name = + name = if lhs.name == rhs.name then lhs.name else if lhs.name == "name" then @@ -632,7 +632,7 @@ rec { lhs.name else null; - lazy = + lazy = if lhs.lazy == rhs.lazy then lhs.lazy else From f9108287ac17b2463bda35aad8469961df8c933b Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 8 Oct 2024 21:38:44 +0200 Subject: [PATCH 06/11] lib.types.attrsWith: init documentation --- .../manual/development/option-types.section.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index b44a84553b37b..3c6afe5ff6b7c 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -399,6 +399,22 @@ Composed types are types that take a type as parameter. `listOf returned instead for the same `mkIf false` definition. ::: +`types.attrsWith` *`attrs`* + +: An attribute set of where all the values are of *`attrs.elemType`* type. + + `attrs.lazy` (`Bool`, default: `false` ) + : If set to `true` attributes will be evaluated lazily. See also: `types.lazyAttrsOf` + + `attrs.name` (`String`, default: `name` ) + : Placeholder string in documentation for the attribute names. + The default value `name` results in the placeholder `` + + ::: {.note} + This is the underlying implementation of `types.attrsOf` and `types.lazyAttrsOf` + ::: + + `types.uniq` *`t`* : Ensures that type *`t`* cannot be merged. It is used to ensure option From 249372691df7a92a60485c0d8b97bfd490912ac2 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 8 Oct 2024 22:03:54 +0200 Subject: [PATCH 07/11] lib.types.attrsWith: add required functor attributes --- lib/types.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/types.nix b/lib/types.nix index f6cb6bd34614c..a63608df6abca 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -617,6 +617,8 @@ rec { getSubModules = elemType.getSubModules; substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; }; functor = defaultFunctor "attrsWith" // { + wrapped = elemType; + type = t: attrsWith { elemType = t; inherit name lazy; }; payload = { inherit elemType name lazy; }; From 95c2276e5978f404645a4013d8aa286efef23313 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 9 Oct 2024 09:16:59 +0200 Subject: [PATCH 08/11] lib.types.attrsWith: doc add missing listing --- nixos/doc/manual/development/option-types.section.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index 3c6afe5ff6b7c..82eaae47504d0 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -403,6 +403,9 @@ Composed types are types that take a type as parameter. `listOf : An attribute set of where all the values are of *`attrs.elemType`* type. + `attrs.elemType` (`type`, required ) + : The expected type of all attribute values. + `attrs.lazy` (`Bool`, default: `false` ) : If set to `true` attributes will be evaluated lazily. See also: `types.lazyAttrsOf` From 66f9d13f00ac65ff730a5bdc647bc139ab592a9d Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 17 Oct 2024 16:06:42 +0200 Subject: [PATCH 09/11] lib.types.attrsWith: add some more unit tests --- lib/tests/modules.sh | 5 ++ lib/tests/modules/lazy-attrsWith.nix | 45 +++++++++++++++++ lib/tests/modules/name-merge-attrsWith-1.nix | 53 ++++++++++++++++++++ lib/tests/modules/name-merge-attrsWith-2.nix | 39 ++++++++++++++ lib/types.nix | 4 +- 5 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 lib/tests/modules/lazy-attrsWith.nix create mode 100644 lib/tests/modules/name-merge-attrsWith-1.nix create mode 100644 lib/tests/modules/name-merge-attrsWith-2.nix diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 685856f16f69d..ca02e6be55b62 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -570,6 +570,11 @@ checkConfigOutput '^38|27$' options.submoduleLine38.declarationPositions.1.line # nested options work checkConfigOutput '^34$' options.nested.nestedLine34.declarationPositions.0.line ./declaration-positions.nix +# AttrsWith tests +checkConfigOutput '^11$' config.result ./lazy-attrsWith.nix +checkConfigOutput '^"mergedName..nested"$' config.result ./name-merge-attrsWith-1.nix +checkConfigError 'The option .mergedName. in .*\.nix. is already declared in .*\.nix' config.mergedName ./name-merge-attrsWith-2.nix + cat < = "id" + name = "id"; + elemType = types.submodule { + options.nested = mkOption { + type = types.int; + default = 1; + }; + }; + }; + }; + } + ) + # Module B + ( + { ... }: + { + options.mergedName = mkOption { + # default: "" + type = types.attrsOf (types.submodule { }); + # default = {}; + }; + } + ) + + # Output + ( + { + options, + ... + }: + { + options.result = mkOption { + default = lib.concatStringsSep "." (options.mergedName.type.getSubOptions options.mergedName.loc) + .nested.loc; + }; + } + ) + ]; +} diff --git a/lib/tests/modules/name-merge-attrsWith-2.nix b/lib/tests/modules/name-merge-attrsWith-2.nix new file mode 100644 index 0000000000000..ab7e8e3193e6d --- /dev/null +++ b/lib/tests/modules/name-merge-attrsWith-2.nix @@ -0,0 +1,39 @@ +# Non mergable attrsWith +{ lib, ... }: +let + inherit (lib) types mkOption; +in +{ + imports = [ + # Module A + ( + { ... }: + { + options.mergedName = mkOption { + default = { }; + type = types.attrsWith { + name = "id"; + elemType = types.submodule { + options.nested = mkOption { + type = types.int; + default = 1; + }; + }; + }; + }; + } + ) + # Module B + ( + { ... }: + { + options.mergedName = mkOption { + type = types.attrsWith { + name = "other"; + elemType = types.submodule { }; + }; + }; + } + ) + ]; +} diff --git a/lib/types.nix b/lib/types.nix index a63608df6abca..29a4dcef7928e 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -617,9 +617,9 @@ rec { getSubModules = elemType.getSubModules; substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; }; functor = defaultFunctor "attrsWith" // { - wrapped = elemType; - type = t: attrsWith { elemType = t; inherit name lazy; }; + type = payload: attrsWith payload; payload = { + # Important!: Add new function attributes here in case of future changes inherit elemType name lazy; }; binOp = lhs: rhs: From ebe48f1eafc63f6d06e0f9476c08640fbf99129d Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 17 Oct 2024 17:06:47 +0200 Subject: [PATCH 10/11] lib.types.defaultTypeMerge: change precedence of payload vs wrapped --- lib/types.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/types.nix b/lib/types.nix index 29a4dcef7928e..b91f51c177f2d 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -93,12 +93,14 @@ rec { else if (f.wrapped == null && f'.wrapped == null) && (f.payload == null && f'.payload == null) then f.type + # value types + else if (f.payload != null && f'.payload != null) then + if payload != null + then f.type payload + else null # composed types else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null) then f.type wrapped - # value types - else if (f.payload != null && f'.payload != null) && (payload != null) - then f.type payload else null; # Default type functor @@ -617,7 +619,7 @@ rec { getSubModules = elemType.getSubModules; substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; }; functor = defaultFunctor "attrsWith" // { - type = payload: attrsWith payload; + wrapped = elemType; payload = { # Important!: Add new function attributes here in case of future changes inherit elemType name lazy; @@ -640,7 +642,7 @@ rec { else null; in - if elemType == null || name == null || lazy == null then + if elemType == null || lazy == null || name == null then null else { From 5a3d22c1a4b55b8b8a6b23d237e5416dd5b6a60a Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 22 Oct 2024 09:55:13 +0200 Subject: [PATCH 11/11] types/attrsWith: rename parameter 'name' to 'placeholder' --- lib/types.nix | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/types.nix b/lib/types.nix index b91f51c177f2d..232a09805e824 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -582,7 +582,7 @@ rec { # base type for lazyAttrsOf and attrsOf attrsWith = { elemType, - name ? "name", + placeholder ? "name", lazy ? false, }: let @@ -615,25 +615,25 @@ rec { (pushPositions defs))) ); emptyValue = { value = {}; }; - getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${name}>"]); + getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<${placeholder}>"]); getSubModules = elemType.getSubModules; - substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit name lazy; }; + substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit lazy placeholder; }; functor = defaultFunctor "attrsWith" // { wrapped = elemType; payload = { # Important!: Add new function attributes here in case of future changes - inherit elemType name lazy; + inherit elemType lazy placeholder; }; binOp = lhs: rhs: let elemType = lhs.elemType.typeMerge rhs.elemType.functor; - name = - if lhs.name == rhs.name then - lhs.name - else if lhs.name == "name" then - rhs.name - else if rhs.name == "name" then - lhs.name + placeholder = + if lhs.placeholder == rhs.placeholder then + lhs.placeholder + else if lhs.placeholder == "name" then + rhs.placeholder + else if rhs.placeholder == "name" then + lhs.placeholder else null; lazy = @@ -642,11 +642,11 @@ rec { else null; in - if elemType == null || lazy == null || name == null then + if elemType == null || lazy == null || placeholder == null then null else { - inherit elemType name lazy; + inherit elemType placeholder lazy; }; }; nestedTypes.elemType = elemType;