summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorJohannes Kirschbauer <hsjobeki@gmail.com>2026-01-08 16:49:59 +0100
committerJohannes Kirschbauer <hsjobeki@gmail.com>2026-01-19 13:24:05 +0100
commit38cab2baa28a756c296a40168c6438de7a8db4f4 (patch)
treeeebc750bfde6b4e30716b793a99fff24987cf125 /lib
parentansel: 0-unstable-2026-01-09 -> 0-unstable-2026-01-17 (#481512) (diff)
downloadnixpkgs-38cab2baa28a756c296a40168c6438de7a8db4f4.tar.gz
lib/types: unify public attribute set and ast
Diffstat (limited to 'lib')
-rw-r--r--lib/types.nix2885
1 files changed, 1443 insertions, 1442 deletions
diff --git a/lib/types.nix b/lib/types.nix
index bc6e28ed9363..bd9ac93df472 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -18,6 +18,7 @@ let
throwIf
toDerivation
toList
+ types
;
inherit (lib.lists)
concatLists
@@ -87,7 +88,7 @@ let
{
inherit name payload;
wrappedDeprecationMessage = makeWrappedDeprecationMessage payload;
- type = outer_types.types.${name};
+ type = types.${name};
binOp =
a: b:
let
@@ -134,1568 +135,1568 @@ let
baseType // { check = value: /* your check */; }
'';
- outer_types = rec {
- isType = type: x: (x._type or "") == type;
+in
+rec {
+ isType = type: x: (x._type or "") == type;
- setType =
- typeName: value:
- value
- // {
- _type = typeName;
- };
+ setType =
+ typeName: value:
+ value
+ // {
+ _type = typeName;
+ };
- # Default type merging function
- # takes two type functors and return the merged type
- defaultTypeMerge =
- f: f':
- let
- mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor;
- mergedPayload = f.binOp f.payload f'.payload;
+ # Default type merging function
+ # takes two type functors and return the merged type
+ defaultTypeMerge =
+ f: f':
+ let
+ mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor;
+ mergedPayload = f.binOp f.payload f'.payload;
- hasPayload =
- assert (f'.payload != null) == (f.payload != null);
- f.payload != null;
- hasWrapped =
- assert (f'.wrapped != null) == (f.wrapped != null);
- f.wrapped != null;
+ hasPayload =
+ assert (f'.payload != null) == (f.payload != null);
+ f.payload != null;
+ hasWrapped =
+ assert (f'.wrapped != null) == (f.wrapped != null);
+ f.wrapped != null;
- typeFromPayload = if mergedPayload == null then null else f.type mergedPayload;
- typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped;
- in
- # Abort early: cannot merge different types
- if f.name != f'.name then
- null
- else
+ typeFromPayload = if mergedPayload == null then null else f.type mergedPayload;
+ typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped;
+ in
+ # Abort early: cannot merge different types
+ if f.name != f'.name then
+ null
+ else
- if hasPayload then
- # Just return the payload if returning wrapped is deprecated
- if f ? wrappedDeprecationMessage then
- typeFromPayload
- else if hasWrapped then
- # Has both wrapped and payload
- throw ''
- Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.
+ if hasPayload then
+ # Just return the payload if returning wrapped is deprecated
+ if f ? wrappedDeprecationMessage then
+ typeFromPayload
+ else if hasWrapped then
+ # Has both wrapped and payload
+ throw ''
+ Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.
- Use either `functor.payload` or `functor.wrapped` but not both.
+ Use either `functor.payload` or `functor.wrapped` but not both.
- If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition.
- ''
- else
- typeFromPayload
- else if hasWrapped then
- typeFromWrapped
+ If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition.
+ ''
else
- f.type;
+ typeFromPayload
+ else if hasWrapped then
+ typeFromWrapped
+ else
+ f.type;
- # Default type functor
- defaultFunctor = name: {
- inherit name;
- type = types.${name} or null;
- wrapped = null;
- payload = null;
- binOp = a: b: null;
+ # Default type functor
+ defaultFunctor = name: {
+ inherit name;
+ type = lib.types.${name} or null;
+ wrapped = null;
+ payload = null;
+ binOp = a: b: null;
+ };
+
+ isOptionType = isType "option-type";
+ mkOptionType =
+ {
+ # Human-readable representation of the type, should be equivalent to
+ # the type function name.
+ name,
+ # Description of the type, defined recursively by embedding the wrapped type if any.
+ description ? null,
+ # A hint for whether or not this description needs parentheses. Possible values:
+ # - "noun": a noun phrase
+ # Example description: "positive integer",
+ # - "conjunction": a phrase with a potentially ambiguous "or" connective
+ # Example description: "int or string"
+ # - "composite": a phrase with an "of" connective
+ # Example description: "list of string"
+ # - "nonRestrictiveClause": a noun followed by a comma and a clause
+ # Example description: "positive integer, meaning >0"
+ # See the `optionDescriptionPhrase` function.
+ descriptionClass ? null,
+ # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING!
+ # Function applied to each definition that must return false when a definition
+ # does not match the type. It should not check more than the root of the value,
+ # because checking nested values reduces laziness, leading to unnecessary
+ # infinite recursions in the module system.
+ # Further checks of nested values should be performed by throwing in
+ # the merge function.
+ # Strict and deep type checking can be performed by calling lib.deepSeq on
+ # the merged value.
+ #
+ # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change,
+ # https://github.com/NixOS/nixpkgs/pull/173568 and
+ # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this,
+ # https://github.com/NixOS/nixpkgs/issues/191124 and
+ # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore
+ # this disclaimer.
+ check ? (x: true),
+ # Merge a list of definitions together into a single value.
+ # This function is called with two arguments: the location of
+ # the option in the configuration as a list of strings
+ # (e.g. ["boot" "loader "grub" "enable"]), and a list of
+ # definition values and locations (e.g. [ { file = "/foo.nix";
+ # value = 1; } { file = "/bar.nix"; value = 2 } ]).
+ merge ? mergeDefaultOption,
+ # Whether this type has a value representing nothingness. If it does,
+ # this should be a value of the form { value = <the nothing value>; }
+ # If it doesn't, this should be {}
+ # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`.
+ emptyValue ? { },
+ # Return a flat attrset of sub-options. Used to generate
+ # documentation.
+ getSubOptions ? prefix: { },
+ # List of modules if any, or null if none.
+ getSubModules ? null,
+ # Function for building the same option type with a different list of
+ # modules.
+ substSubModules ? m: null,
+ # Function that merge type declarations.
+ # internal, takes a functor as argument and returns the merged type.
+ # returning null means the type is not mergeable
+ typeMerge ? defaultTypeMerge functor,
+ # The type functor.
+ # internal, representation of the type as an attribute set.
+ # name: name of the type
+ # type: type function.
+ # wrapped: the type wrapped in case of compound types.
+ # payload: values of the type, two payloads of the same type must be
+ # combinable with the binOp binary operation.
+ # binOp: binary operation that merge two payloads of the same type.
+ functor ? defaultFunctor name,
+ # The deprecation message to display when this type is used by an option
+ # If null, the type isn't deprecated
+ deprecationMessage ? null,
+ # The types that occur in the definition of this type. This is used to
+ # issue deprecation warnings recursively. Can also be used to reuse
+ # nested types
+ nestedTypes ? { },
+ }:
+ {
+ _type = "option-type";
+ inherit
+ name
+ check
+ merge
+ emptyValue
+ getSubOptions
+ getSubModules
+ substSubModules
+ typeMerge
+ deprecationMessage
+ nestedTypes
+ descriptionClass
+ ;
+ functor =
+ if functor ? wrappedDeprecationMessage then
+ functor
+ // {
+ wrapped = functor.wrappedDeprecationMessage {
+ loc = null;
+ };
+ }
+ else
+ functor;
+ description = if description == null then name else description;
};
- isOptionType = isType "option-type";
- mkOptionType =
- {
- # Human-readable representation of the type, should be equivalent to
- # the type function name.
- name,
- # Description of the type, defined recursively by embedding the wrapped type if any.
- description ? null,
- # A hint for whether or not this description needs parentheses. Possible values:
- # - "noun": a noun phrase
- # Example description: "positive integer",
- # - "conjunction": a phrase with a potentially ambiguous "or" connective
- # Example description: "int or string"
- # - "composite": a phrase with an "of" connective
- # Example description: "list of string"
- # - "nonRestrictiveClause": a noun followed by a comma and a clause
- # Example description: "positive integer, meaning >0"
- # See the `optionDescriptionPhrase` function.
- descriptionClass ? null,
- # DO NOT USE WITHOUT KNOWING WHAT YOU ARE DOING!
- # Function applied to each definition that must return false when a definition
- # does not match the type. It should not check more than the root of the value,
- # because checking nested values reduces laziness, leading to unnecessary
- # infinite recursions in the module system.
- # Further checks of nested values should be performed by throwing in
- # the merge function.
- # Strict and deep type checking can be performed by calling lib.deepSeq on
- # the merged value.
- #
- # See https://github.com/NixOS/nixpkgs/pull/6794 that introduced this change,
- # https://github.com/NixOS/nixpkgs/pull/173568 and
- # https://github.com/NixOS/nixpkgs/pull/168295 that attempted to revert this,
- # https://github.com/NixOS/nixpkgs/issues/191124 and
- # https://github.com/NixOS/nixos-search/issues/391 for what happens if you ignore
- # this disclaimer.
- check ? (x: true),
- # Merge a list of definitions together into a single value.
- # This function is called with two arguments: the location of
- # the option in the configuration as a list of strings
- # (e.g. ["boot" "loader "grub" "enable"]), and a list of
- # definition values and locations (e.g. [ { file = "/foo.nix";
- # value = 1; } { file = "/bar.nix"; value = 2 } ]).
- merge ? mergeDefaultOption,
- # Whether this type has a value representing nothingness. If it does,
- # this should be a value of the form { value = <the nothing value>; }
- # If it doesn't, this should be {}
- # This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`.
- emptyValue ? { },
- # Return a flat attrset of sub-options. Used to generate
- # documentation.
- getSubOptions ? prefix: { },
- # List of modules if any, or null if none.
- getSubModules ? null,
- # Function for building the same option type with a different list of
- # modules.
- substSubModules ? m: null,
- # Function that merge type declarations.
- # internal, takes a functor as argument and returns the merged type.
- # returning null means the type is not mergeable
- typeMerge ? defaultTypeMerge functor,
- # The type functor.
- # internal, representation of the type as an attribute set.
- # name: name of the type
- # type: type function.
- # wrapped: the type wrapped in case of compound types.
- # payload: values of the type, two payloads of the same type must be
- # combinable with the binOp binary operation.
- # binOp: binary operation that merge two payloads of the same type.
- functor ? defaultFunctor name,
- # The deprecation message to display when this type is used by an option
- # If null, the type isn't deprecated
- deprecationMessage ? null,
- # The types that occur in the definition of this type. This is used to
- # issue deprecation warnings recursively. Can also be used to reuse
- # nested types
- nestedTypes ? { },
- }:
- {
- _type = "option-type";
- inherit
- name
- check
- merge
- emptyValue
- getSubOptions
- getSubModules
- substSubModules
- typeMerge
- deprecationMessage
- nestedTypes
- descriptionClass
- ;
- functor =
- if functor ? wrappedDeprecationMessage then
- functor
- // {
- wrapped = functor.wrappedDeprecationMessage {
- loc = null;
- };
- }
- else
- functor;
- description = if description == null then name else description;
- };
+ # optionDescriptionPhrase :: (str -> bool) -> optionType -> str
+ #
+ # Helper function for producing unambiguous but readable natural language
+ # descriptions of types.
+ #
+ # Parameters
+ #
+ # optionDescriptionPhase unparenthesize optionType
+ #
+ # `unparenthesize`: A function from descriptionClass string to boolean.
+ # It must return true when the class of phrase will fit unambiguously into
+ # the description of the caller.
+ #
+ # `optionType`: The option type to parenthesize or not.
+ # The option whose description we're returning.
+ #
+ # Returns value
+ #
+ # The description of the `optionType`, with parentheses if there may be an
+ # ambiguity.
+ optionDescriptionPhrase =
+ unparenthesize: t:
+ if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
- # optionDescriptionPhrase :: (str -> bool) -> optionType -> str
- #
- # Helper function for producing unambiguous but readable natural language
- # descriptions of types.
- #
- # Parameters
- #
- # optionDescriptionPhase unparenthesize optionType
- #
- # `unparenthesize`: A function from descriptionClass string to boolean.
- # It must return true when the class of phrase will fit unambiguously into
- # the description of the caller.
- #
- # `optionType`: The option type to parenthesize or not.
- # The option whose description we're returning.
- #
- # Returns value
- #
- # The description of the `optionType`, with parentheses if there may be an
- # ambiguity.
- optionDescriptionPhrase =
- unparenthesize: t:
- if unparenthesize (t.descriptionClass or null) then t.description else "(${t.description})";
+ noCheckForDocsModule = {
+ # When generating documentation, our goal isn't to check anything.
+ # Quite the opposite in fact. Generating docs is somewhat of a
+ # challenge, evaluating modules in a *lacking* context. Anything
+ # that makes the docs avoid an error is a win.
+ config._module.check = lib.mkForce false;
+ _file = "<built-in module that disables checks for the purpose of documentation generation>";
+ };
- noCheckForDocsModule = {
- # When generating documentation, our goal isn't to check anything.
- # Quite the opposite in fact. Generating docs is somewhat of a
- # challenge, evaluating modules in a *lacking* context. Anything
- # that makes the docs avoid an error is a win.
- config._module.check = lib.mkForce false;
- _file = "<built-in module that disables checks for the purpose of documentation generation>";
- };
+ # When adding new types don't forget to document them in
+ # nixos/doc/manual/development/option-types.section.md!
- # When adding new types don't forget to document them in
- # nixos/doc/manual/development/option-types.section.md!
- types = rec {
+ raw = mkOptionType {
+ name = "raw";
+ description = "raw value";
+ descriptionClass = "noun";
+ check = value: true;
+ merge = mergeOneOption;
+ };
- raw = mkOptionType {
- name = "raw";
- description = "raw value";
- descriptionClass = "noun";
- check = value: true;
- merge = mergeOneOption;
- };
+ anything = mkOptionType {
+ name = "anything";
+ description = "anything";
+ descriptionClass = "noun";
+ check = value: true;
+ merge =
+ loc: defs:
+ let
+ getType =
+ value: if isAttrs value && isStringLike value then "stringCoercibleSet" else builtins.typeOf value;
- anything = mkOptionType {
- name = "anything";
- description = "anything";
- descriptionClass = "noun";
- check = value: true;
- merge =
- loc: defs:
- let
- getType =
- value: if isAttrs value && isStringLike value then "stringCoercibleSet" else builtins.typeOf value;
+ # Returns the common type of all definitions, throws an error if they
+ # don't have the same type
+ commonType = foldl' (
+ type: def:
+ if getType def.value == type then
+ type
+ else
+ throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
+ ) (getType (head defs).value) defs;
- # Returns the common type of all definitions, throws an error if they
- # don't have the same type
- commonType = foldl' (
- type: def:
- if getType def.value == type then
- type
- else
- throw "The option `${showOption loc}' has conflicting option types in ${showFiles (getFiles defs)}"
- ) (getType (head defs).value) defs;
+ mergeFunction =
+ {
+ # Recursively merge attribute sets
+ set = (attrsOf anything).merge;
+ # This is the type of packages, only accept a single definition
+ stringCoercibleSet = mergeOneOption;
+ lambda =
+ loc: defs: arg:
+ anything.merge (loc ++ [ "<function body>" ]) (
+ map (def: {
+ file = def.file;
+ value = def.value arg;
+ }) defs
+ );
+ # Otherwise fall back to only allowing all equal definitions
+ }
+ .${commonType} or mergeEqualOption;
+ in
+ mergeFunction loc defs;
+ };
- mergeFunction =
- {
- # Recursively merge attribute sets
- set = (attrsOf anything).merge;
- # This is the type of packages, only accept a single definition
- stringCoercibleSet = mergeOneOption;
- lambda =
- loc: defs: arg:
- anything.merge (loc ++ [ "<function body>" ]) (
- map (def: {
- file = def.file;
- value = def.value arg;
- }) defs
- );
- # Otherwise fall back to only allowing all equal definitions
- }
- .${commonType} or mergeEqualOption;
- in
- mergeFunction loc defs;
- };
+ unspecified = mkOptionType {
+ name = "unspecified";
+ description = "unspecified value";
+ descriptionClass = "noun";
+ };
- unspecified = mkOptionType {
- name = "unspecified";
- description = "unspecified value";
- descriptionClass = "noun";
- };
+ bool = mkOptionType {
+ name = "bool";
+ description = "boolean";
+ descriptionClass = "noun";
+ check = isBool;
+ merge = mergeEqualOption;
+ };
- bool = mkOptionType {
- name = "bool";
- description = "boolean";
- descriptionClass = "noun";
- check = isBool;
- merge = mergeEqualOption;
- };
+ boolByOr = mkOptionType {
+ name = "boolByOr";
+ description = "boolean (merged using or)";
+ descriptionClass = "noun";
+ check = isBool;
+ merge =
+ loc: defs:
+ foldl' (
+ result: def:
+ # Under the assumption that .check always runs before merge, we can assume that all defs.*.value
+ # have been forced, and therefore we assume we don't introduce order-dependent strictness here
+ result || def.value
+ ) false defs;
+ };
- boolByOr = mkOptionType {
- name = "boolByOr";
- description = "boolean (merged using or)";
- descriptionClass = "noun";
- check = isBool;
- merge =
- loc: defs:
- foldl' (
- result: def:
- # Under the assumption that .check always runs before merge, we can assume that all defs.*.value
- # have been forced, and therefore we assume we don't introduce order-dependent strictness here
- result || def.value
- ) false defs;
- };
+ int = mkOptionType {
+ name = "int";
+ description = "signed integer";
+ descriptionClass = "noun";
+ check = isInt;
+ merge = mergeEqualOption;
+ };
- int = mkOptionType {
- name = "int";
- description = "signed integer";
- descriptionClass = "noun";
- check = isInt;
- merge = mergeEqualOption;
- };
+ # Specialized subdomains of int
+ ints =
+ let
+ betweenDesc = lowest: highest: "${toString lowest} and ${toString highest} (both inclusive)";
+ between =
+ lowest: highest:
+ assert lib.assertMsg (lowest <= highest) "ints.between: lowest must be smaller than highest";
+ addCheck int (x: x >= lowest && x <= highest)
+ // {
+ name = "intBetween";
+ description = "integer between ${betweenDesc lowest highest}";
+ };
+ ign =
+ lowest: highest: name: docStart:
+ between lowest highest
+ // {
+ inherit name;
+ description = docStart + "; between ${betweenDesc lowest highest}";
+ };
+ unsign =
+ bit: range: ign 0 (range - 1) "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
+ sign =
+ bit: range:
+ ign (0 - (range / 2)) (
+ range / 2 - 1
+ ) "signedInt${toString bit}" "${toString bit} bit signed integer";
- # Specialized subdomains of int
- ints =
- let
- betweenDesc = lowest: highest: "${toString lowest} and ${toString highest} (both inclusive)";
- between =
- lowest: highest:
- assert lib.assertMsg (lowest <= highest) "ints.between: lowest must be smaller than highest";
- addCheck int (x: x >= lowest && x <= highest)
- // {
- name = "intBetween";
- description = "integer between ${betweenDesc lowest highest}";
- };
- ign =
- lowest: highest: name: docStart:
- between lowest highest
- // {
- inherit name;
- description = docStart + "; between ${betweenDesc lowest highest}";
- };
- unsign =
- bit: range: ign 0 (range - 1) "unsignedInt${toString bit}" "${toString bit} bit unsigned integer";
- sign =
- bit: range:
- ign (0 - (range / 2)) (
- range / 2 - 1
- ) "signedInt${toString bit}" "${toString bit} bit signed integer";
+ in
+ {
+ # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md
+ /**
+ An int with a fixed range.
- in
- {
- # TODO: Deduplicate with docs in nixos/doc/manual/development/option-types.section.md
- /**
- An int with a fixed range.
+ # Example
+ :::{.example}
+ ## `lib.types.ints.between` usage example
- # Example
- :::{.example}
- ## `lib.types.ints.between` usage example
+ ```nix
+ (ints.between 0 100).check (-1)
+ => false
+ (ints.between 0 100).check (101)
+ => false
+ (ints.between 0 0).check 0
+ => true
+ ```
- ```nix
- (ints.between 0 100).check (-1)
- => false
- (ints.between 0 100).check (101)
- => false
- (ints.between 0 0).check 0
- => true
- ```
+ :::
+ */
+ inherit between;
- :::
- */
- inherit between;
+ unsigned = addCheck lib.types.int (x: x >= 0) // {
+ name = "unsignedInt";
+ description = "unsigned integer, meaning >=0";
+ descriptionClass = "nonRestrictiveClause";
+ };
+ positive = addCheck lib.types.int (x: x > 0) // {
+ name = "positiveInt";
+ description = "positive integer, meaning >0";
+ descriptionClass = "nonRestrictiveClause";
+ };
+ u8 = unsign 8 256;
+ u16 = unsign 16 65536;
+ # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808)
+ # the smallest int Nix accepts is -2^63 (-9223372036854775807)
+ u32 = unsign 32 4294967296;
+ # u64 = unsign 64 18446744073709551616;
- unsigned = addCheck types.int (x: x >= 0) // {
- name = "unsignedInt";
- description = "unsigned integer, meaning >=0";
- descriptionClass = "nonRestrictiveClause";
- };
- positive = addCheck types.int (x: x > 0) // {
- name = "positiveInt";
- description = "positive integer, meaning >0";
- descriptionClass = "nonRestrictiveClause";
- };
- u8 = unsign 8 256;
- u16 = unsign 16 65536;
- # the biggest int Nix accepts is 2^63 - 1 (9223372036854775808)
- # the smallest int Nix accepts is -2^63 (-9223372036854775807)
- u32 = unsign 32 4294967296;
- # u64 = unsign 64 18446744073709551616;
+ s8 = sign 8 256;
+ s16 = sign 16 65536;
+ s32 = sign 32 4294967296;
+ };
- s8 = sign 8 256;
- s16 = sign 16 65536;
- s32 = sign 32 4294967296;
- };
+ # Alias of u16 for a port number
+ port = ints.u16;
- # Alias of u16 for a port number
- port = ints.u16;
+ float = mkOptionType {
+ name = "float";
+ description = "floating point number";
+ descriptionClass = "noun";
+ check = isFloat;
+ merge = mergeEqualOption;
+ };
- float = mkOptionType {
- name = "float";
- description = "floating point number";
- descriptionClass = "noun";
- check = isFloat;
- merge = mergeEqualOption;
+ number = either int float;
+
+ numbers =
+ let
+ betweenDesc =
+ lowest: highest: "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)";
+ in
+ {
+ between =
+ lowest: highest:
+ assert lib.assertMsg (lowest <= highest) "numbers.between: lowest must be smaller than highest";
+ addCheck number (x: x >= lowest && x <= highest)
+ // {
+ name = "numberBetween";
+ description = "integer or floating point number between ${betweenDesc lowest highest}";
+ };
+
+ nonnegative = addCheck number (x: x >= 0) // {
+ name = "numberNonnegative";
+ description = "nonnegative integer or floating point number, meaning >=0";
+ descriptionClass = "nonRestrictiveClause";
+ };
+ positive = addCheck number (x: x > 0) // {
+ name = "numberPositive";
+ description = "positive integer or floating point number, meaning >0";
+ descriptionClass = "nonRestrictiveClause";
};
+ };
- number = either int float;
+ str = mkOptionType {
+ name = "str";
+ description = "string";
+ descriptionClass = "noun";
+ check = isString;
+ merge = mergeEqualOption;
+ };
- numbers =
- let
- betweenDesc =
- lowest: highest: "${builtins.toJSON lowest} and ${builtins.toJSON highest} (both inclusive)";
- in
- {
- between =
- lowest: highest:
- assert lib.assertMsg (lowest <= highest) "numbers.between: lowest must be smaller than highest";
- addCheck number (x: x >= lowest && x <= highest)
- // {
- name = "numberBetween";
- description = "integer or floating point number between ${betweenDesc lowest highest}";
- };
+ nonEmptyStr = mkOptionType {
+ name = "nonEmptyStr";
+ description = "non-empty string";
+ descriptionClass = "noun";
+ check = x: str.check x && builtins.match "[ \t\n]*" x == null;
+ inherit (str) merge;
+ };
- nonnegative = addCheck number (x: x >= 0) // {
- name = "numberNonnegative";
- description = "nonnegative integer or floating point number, meaning >=0";
- descriptionClass = "nonRestrictiveClause";
- };
- positive = addCheck number (x: x > 0) // {
- name = "numberPositive";
- description = "positive integer or floating point number, meaning >0";
- descriptionClass = "nonRestrictiveClause";
- };
- };
+ # Allow a newline character at the end and trim it in the merge function.
+ singleLineStr =
+ let
+ inherit (strMatching "[^\n\r]*\n?") check merge;
+ in
+ mkOptionType {
+ name = "singleLineStr";
+ description = "(optionally newline-terminated) single-line string";
+ descriptionClass = "noun";
+ inherit check;
+ merge = loc: defs: lib.removeSuffix "\n" (merge loc defs);
+ };
- str = mkOptionType {
- name = "str";
- description = "string";
- descriptionClass = "noun";
- check = isString;
- merge = mergeEqualOption;
+ strMatching =
+ pattern:
+ mkOptionType {
+ name = "strMatching ${escapeNixString pattern}";
+ description = "string matching the pattern ${pattern}";
+ descriptionClass = "noun";
+ check = x: str.check x && builtins.match pattern x != null;
+ inherit (str) merge;
+ functor = defaultFunctor "strMatching" // {
+ type = payload: strMatching payload.pattern;
+ payload = { inherit pattern; };
+ binOp = lhs: rhs: if lhs == rhs then lhs else null;
};
+ };
- nonEmptyStr = mkOptionType {
- name = "nonEmptyStr";
- description = "non-empty string";
- descriptionClass = "noun";
- check = x: str.check x && builtins.match "[ \t\n]*" x == null;
- inherit (str) merge;
+ # Merge multiple definitions by concatenating them (with the given
+ # separator between the values).
+ separatedString =
+ sep:
+ mkOptionType rec {
+ name = "separatedString";
+ description = "strings concatenated with ${builtins.toJSON sep}";
+ descriptionClass = "noun";
+ check = isString;
+ merge = loc: defs: concatStringsSep sep (getValues defs);
+ functor = (defaultFunctor name) // {
+ payload = { inherit sep; };
+ type = payload: lib.types.separatedString payload.sep;
+ binOp = lhs: rhs: if lhs.sep == rhs.sep then { inherit (lhs) sep; } else null;
};
+ };
- # Allow a newline character at the end and trim it in the merge function.
- singleLineStr =
- let
- inherit (strMatching "[^\n\r]*\n?") check merge;
- in
- mkOptionType {
- name = "singleLineStr";
- description = "(optionally newline-terminated) single-line string";
- descriptionClass = "noun";
- inherit check;
- merge = loc: defs: lib.removeSuffix "\n" (merge loc defs);
- };
+ lines = separatedString "\n";
+ commas = separatedString ",";
+ envVar = separatedString ":";
- strMatching =
- pattern:
- mkOptionType {
- name = "strMatching ${escapeNixString pattern}";
- description = "string matching the pattern ${pattern}";
- descriptionClass = "noun";
- check = x: str.check x && builtins.match pattern x != null;
- inherit (str) merge;
- functor = defaultFunctor "strMatching" // {
- type = payload: strMatching payload.pattern;
- payload = { inherit pattern; };
- binOp = lhs: rhs: if lhs == rhs then lhs else null;
- };
- };
+ passwdEntry =
+ entryType:
+ addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str))
+ // {
+ name = "passwdEntry ${entryType.name}";
+ description = "${
+ optionDescriptionPhrase (class: class == "noun") entryType
+ }, not containing newlines or colons";
+ descriptionClass = "nonRestrictiveClause";
+ };
- # Merge multiple definitions by concatenating them (with the given
- # separator between the values).
- separatedString =
- sep:
- mkOptionType rec {
- name = "separatedString";
- description = "strings concatenated with ${builtins.toJSON sep}";
- descriptionClass = "noun";
- check = isString;
- merge = loc: defs: concatStringsSep sep (getValues defs);
- functor = (defaultFunctor name) // {
- payload = { inherit sep; };
- type = payload: types.separatedString payload.sep;
- binOp = lhs: rhs: if lhs.sep == rhs.sep then { inherit (lhs) sep; } else null;
- };
- };
+ attrs = mkOptionType {
+ name = "attrs";
+ description = "attribute set";
+ check = isAttrs;
+ merge = loc: foldl' (res: def: res // def.value) { };
+ emptyValue = {
+ value = { };
+ };
+ };
- lines = separatedString "\n";
- commas = separatedString ",";
- envVar = separatedString ":";
+ fileset = mkOptionType {
+ name = "fileset";
+ description = "fileset";
+ descriptionClass = "noun";
+ check = isFileset;
+ merge = loc: defs: unions (map (x: x.value) defs);
+ emptyValue.value = empty;
+ };
- passwdEntry =
- entryType:
- addCheck entryType (str: !(hasInfix ":" str || hasInfix "\n" str))
- // {
- name = "passwdEntry ${entryType.name}";
- description = "${
- optionDescriptionPhrase (class: class == "noun") entryType
- }, not containing newlines or colons";
- descriptionClass = "nonRestrictiveClause";
- };
+ # A package is a top-level store path (/nix/store/hash-name). This includes:
+ # - derivations
+ # - more generally, attribute sets with an `outPath` or `__toString` attribute
+ # pointing to a store path, e.g. flake inputs
+ # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
+ # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
+ # ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
+ # If you don't need a *top-level* store path, consider using pathInStore instead.
+ package = mkOptionType {
+ name = "package";
+ descriptionClass = "noun";
+ check = x: isDerivation x || isStorePath x;
+ merge =
+ loc: defs:
+ let
+ res = mergeOneOption loc defs;
+ in
+ if builtins.isPath res || (builtins.isString res && !builtins.hasContext res) then
+ toDerivation res
+ else
+ res;
+ };
- attrs = mkOptionType {
- name = "attrs";
- description = "attribute set";
- check = isAttrs;
- merge = loc: foldl' (res: def: res // def.value) { };
- emptyValue = {
- value = { };
- };
- };
+ shellPackage = package // {
+ check = x: isDerivation x && hasAttr "shellPath" x;
+ };
- fileset = mkOptionType {
- name = "fileset";
- description = "fileset";
- descriptionClass = "noun";
- check = isFileset;
- merge = loc: defs: unions (map (x: x.value) defs);
- emptyValue.value = empty;
- };
+ pkgs = addCheck (
+ unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs
+ // {
+ name = "pkgs";
+ descriptionClass = "noun";
+ description = "Nixpkgs package set";
+ }
+ ) (x: (x._type or null) == "pkgs");
- # A package is a top-level store path (/nix/store/hash-name). This includes:
- # - derivations
- # - more generally, attribute sets with an `outPath` or `__toString` attribute
- # pointing to a store path, e.g. flake inputs
- # - strings with context, e.g. "${pkgs.foo}" or (toString pkgs.foo)
- # - hardcoded store path literals (/nix/store/hash-foo) or strings without context
- # ("/nix/store/hash-foo"). These get a context added to them using builtins.storePath.
- # If you don't need a *top-level* store path, consider using pathInStore instead.
- package = mkOptionType {
- name = "package";
+ path = pathWith {
+ absolute = true;
+ };
+
+ pathInStore = pathWith {
+ inStore = true;
+ };
+
+ externalPath = pathWith {
+ absolute = true;
+ inStore = false;
+ };
+
+ pathWith =
+ {
+ inStore ? null,
+ absolute ? null,
+ }:
+ throwIf (inStore != null && absolute != null && inStore && !absolute)
+ "In pathWith, inStore means the path must be absolute"
+ mkOptionType
+ {
+ name = "path";
+ description = (
+ (if absolute == null then "" else (if absolute then "absolute " else "relative "))
+ + "path"
+ + (
+ if inStore == null then "" else (if inStore then " in the Nix store" else " not in the Nix store")
+ )
+ );
descriptionClass = "noun";
- check = x: isDerivation x || isStorePath x;
- merge =
- loc: defs:
+
+ merge = mergeEqualOption;
+ functor = defaultFunctor "path" // {
+ type = pathWith;
+ payload = { inherit inStore absolute; };
+ binOp = lhs: rhs: if lhs == rhs then lhs else null;
+ };
+
+ check =
+ x:
let
- res = mergeOneOption loc defs;
+ isInStore = lib.path.hasStorePathPrefix (
+ if builtins.isPath x then
+ x
+ # Discarding string context is necessary to convert the value to
+ # a path and safe as the result is never used in any derivation.
+ else
+ /. + builtins.unsafeDiscardStringContext x
+ );
+ isAbsolute = builtins.substring 0 1 (toString x) == "/";
+ isExpectedType = (
+ if inStore == null || inStore then isStringLike x else isString x # Do not allow a true path, which could be copied to the store later on.
+ );
in
- if builtins.isPath res || (builtins.isString res && !builtins.hasContext res) then
- toDerivation res
- else
- res;
+ isExpectedType
+ && (inStore == null || inStore == isInStore)
+ && (absolute == null || absolute == isAbsolute);
};
- shellPackage = package // {
- check = x: isDerivation x && hasAttr "shellPath" x;
+ listOf =
+ elemType:
+ mkOptionType rec {
+ name = "listOf";
+ description = "list of ${
+ optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
+ }";
+ descriptionClass = "composite";
+ check = {
+ __functor = _self: isList;
+ isV2MergeCoherent = true;
};
-
- pkgs = addCheck (
- unique { message = "A Nixpkgs pkgs set can not be merged with another pkgs set."; } attrs
- // {
- name = "pkgs";
- descriptionClass = "noun";
- description = "Nixpkgs package set";
- }
- ) (x: (x._type or null) == "pkgs");
-
- path = pathWith {
- absolute = true;
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ evals = filter (x: x.optionalValue ? value) (
+ concatLists (
+ imap1 (
+ 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;
+ };
};
-
- pathInStore = pathWith {
- inStore = true;
+ emptyValue = {
+ value = [ ];
};
-
- externalPath = pathWith {
- absolute = true;
- inStore = false;
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules = m: listOf (elemType.substSubModules m);
+ functor = (elemTypeFunctor name { inherit elemType; }) // {
+ type = payload: lib.types.listOf payload.elemType;
};
+ nestedTypes.elemType = elemType;
+ };
- pathWith =
- {
- inStore ? null,
- absolute ? null,
- }:
- throwIf (inStore != null && absolute != null && inStore && !absolute)
- "In pathWith, inStore means the path must be absolute"
- mkOptionType
- {
- name = "path";
- description = (
- (if absolute == null then "" else (if absolute then "absolute " else "relative "))
- + "path"
- + (
- if inStore == null then "" else (if inStore then " in the Nix store" else " not in the Nix store")
- )
- );
- descriptionClass = "noun";
+ nonEmptyListOf =
+ elemType:
+ let
+ list = addCheck (lib.types.listOf elemType) (l: l != [ ]);
+ in
+ list
+ // {
+ description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}";
+ emptyValue = { }; # no .value attr, meaning unset
+ substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
+ };
- merge = mergeEqualOption;
- functor = defaultFunctor "path" // {
- type = pathWith;
- payload = { inherit inStore absolute; };
- binOp = lhs: rhs: if lhs == rhs then lhs else null;
- };
+ attrsOf = elemType: attrsWith { inherit elemType; };
- check =
- x:
- let
- isInStore = lib.path.hasStorePathPrefix (
- if builtins.isPath x then
- x
- # Discarding string context is necessary to convert the value to
- # a path and safe as the result is never used in any derivation.
- else
- /. + builtins.unsafeDiscardStringContext x
- );
- isAbsolute = builtins.substring 0 1 (toString x) == "/";
- isExpectedType = (
- if inStore == null || inStore then isStringLike x else isString x # Do not allow a true path, which could be copied to the store later on.
- );
- in
- isExpectedType
- && (inStore == null || inStore == isInStore)
- && (absolute == null || absolute == isAbsolute);
- };
+ # 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:
+ attrsWith {
+ inherit elemType;
+ lazy = true;
+ };
- listOf =
- elemType:
- mkOptionType rec {
- name = "listOf";
- description = "list of ${
- optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
- }";
- descriptionClass = "composite";
- check = {
- __functor = _self: isList;
- isV2MergeCoherent = true;
+ # base type for lazyAttrsOf and attrsOf
+ attrsWith =
+ let
+ # Push down position info.
+ pushPositions = map (
+ def:
+ mapAttrs (n: v: {
+ inherit (def) file;
+ value = v;
+ }) def.value
+ );
+ binOp =
+ lhs: rhs:
+ let
+ elemType = lhs.elemType.typeMerge rhs.elemType.functor;
+ lazy = if lhs.lazy == rhs.lazy then lhs.lazy else null;
+ 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;
+ in
+ if elemType == null || lazy == null || placeholder == null then
+ null
+ else
+ {
+ inherit elemType lazy placeholder;
};
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- evals = filter (x: x.optionalValue ? value) (
- concatLists (
- imap1 (
- n: def:
- imap1 (
- m: def':
- (mergeDefinitions (loc ++ [ "[definition ${toString n}-entry ${toString m}]" ]) elemType [
- {
- inherit (def) file;
- value = def';
- }
- ])
- ) def.value
- ) defs
- )
+ in
+ {
+ elemType,
+ lazy ? false,
+ placeholder ? "name",
+ }:
+ 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 = {
+ __functor = _self: isAttrs;
+ isV2MergeCoherent = true;
+ };
+ 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 = map (x: x.optionalValue.value or x.mergedValue) evals;
- valueMeta.list = map (v: v.checkedAndMerged.valueMeta) evals;
- };
- };
- emptyValue = {
- value = [ ];
- };
- getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" ]);
- getSubModules = elemType.getSubModules;
- substSubModules = m: listOf (elemType.substSubModules m);
- functor = (elemTypeFunctor name { inherit elemType; }) // {
- type = payload: types.listOf payload.elemType;
+ 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;
};
- nestedTypes.elemType = elemType;
- };
+ };
- nonEmptyListOf =
- elemType:
- let
- list = addCheck (types.listOf elemType) (l: l != [ ]);
- in
- list
+ emptyValue = {
+ value = { };
+ };
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<${placeholder}>" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules =
+ m:
+ attrsWith {
+ elemType = elemType.substSubModules m;
+ inherit lazy placeholder;
+ };
+ functor =
+ (elemTypeFunctor "attrsWith" {
+ inherit elemType lazy placeholder;
+ })
// {
- description = "non-empty ${optionDescriptionPhrase (class: class == "noun") list}";
- emptyValue = { }; # no .value attr, meaning unset
- substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
+ # Custom type merging required because of the "placeholder" attribute
+ inherit binOp;
};
+ 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:
- attrsWith {
- inherit elemType;
- lazy = true;
- };
+ # TODO: deprecate this in the future:
+ loaOf =
+ elemType:
+ lib.types.attrsOf elemType
+ // {
+ name = "loaOf";
+ deprecationMessage =
+ "Mixing lists with attribute values is no longer"
+ + " possible; please use `types.attrsOf` instead. See"
+ + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
+ nestedTypes.elemType = elemType;
+ };
- # base type for lazyAttrsOf and attrsOf
- attrsWith =
+ attrTag =
+ tags:
+ let
+ tags_ = tags;
+ in
+ let
+ tags = mapAttrs (
+ n: opt:
+ builtins.addErrorContext
+ "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}"
+ (
+ throwIf (opt._type or null != "option")
+ "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${
+ if opt ? _type then
+ if opt._type == "option-type" then
+ "was a bare type, not wrapped in mkOption."
+ else
+ "was of type ${lib.strings.escapeNixString opt._type}."
+ else
+ "was not."
+ }"
+ opt
+ // {
+ declarations =
+ opt.declarations or (
+ let
+ pos = builtins.unsafeGetAttrPos n tags_;
+ in
+ if pos == null then [ ] else [ pos.file ]
+ );
+ declarationPositions =
+ opt.declarationPositions or (
+ let
+ pos = builtins.unsafeGetAttrPos n tags_;
+ in
+ if pos == null then [ ] else [ pos ]
+ );
+ }
+ )
+ ) tags_;
+ choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags);
+ in
+ mkOptionType {
+ name = "attrTag";
+ description = "attribute-tagged union with choices: ${choicesStr}";
+ descriptionClass = "noun";
+ getSubOptions =
+ prefix: mapAttrs (tagName: tagOption: tagOption // { loc = prefix ++ [ tagName ]; }) tags;
+ check = v: isAttrs v && length (attrNames v) == 1 && tags ? ${head (attrNames v)};
+ merge =
+ loc: defs:
let
- # Push down position info.
- pushPositions = map (
+ choice = head (attrNames (head defs).value);
+ checkedValueDefs = map (
def:
- mapAttrs (n: v: {
- inherit (def) file;
- value = v;
- }) def.value
- );
- binOp =
- lhs: rhs:
- let
- elemType = lhs.elemType.typeMerge rhs.elemType.functor;
- lazy = if lhs.lazy == rhs.lazy then lhs.lazy else null;
- 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;
- in
- if elemType == null || lazy == null || placeholder == null then
- null
+ assert (length (attrNames def.value)) == 1;
+ if (head (attrNames def.value)) != choice then
+ throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}."
else
{
- inherit elemType lazy placeholder;
- };
+ inherit (def) file;
+ value = def.value.${choice};
+ }
+ ) defs;
in
- {
- elemType,
- lazy ? false,
- placeholder ? "name",
- }:
- 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 = {
- __functor = _self: isAttrs;
- isV2MergeCoherent = true;
- };
- 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 = { };
- };
- getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<${placeholder}>" ]);
- getSubModules = elemType.getSubModules;
- substSubModules =
- m:
- attrsWith {
- elemType = elemType.substSubModules m;
- inherit lazy placeholder;
- };
- functor =
- (elemTypeFunctor "attrsWith" {
- inherit elemType lazy placeholder;
- })
- // {
- # Custom type merging required because of the "placeholder" attribute
- inherit binOp;
+ if tags ? ${choice} then
+ {
+ ${choice} = (lib.modules.evalOptionValue (loc ++ [ choice ]) tags.${choice} checkedValueDefs).value;
+ }
+ else
+ throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}.";
+ nestedTypes = tags;
+ functor = defaultFunctor "attrTag" // {
+ type = { tags, ... }: lib.types.attrTag tags;
+ payload = { inherit tags; };
+ binOp =
+ let
+ # Add metadata in the format that submodules work with
+ wrapOptionDecl = option: {
+ options = option;
+ _file = "<attrTag {...}>";
+ pos = null;
};
- nestedTypes.elemType = elemType;
- };
-
- # TODO: deprecate this in the future:
- loaOf =
- elemType:
- types.attrsOf elemType
- // {
- name = "loaOf";
- deprecationMessage =
- "Mixing lists with attribute values is no longer"
- + " possible; please use `types.attrsOf` instead. See"
- + " https://github.com/NixOS/nixpkgs/issues/1800 for the motivation.";
- nestedTypes.elemType = elemType;
- };
-
- attrTag =
- tags:
- let
- tags_ = tags;
- in
- let
- tags = mapAttrs (
- n: opt:
- builtins.addErrorContext
- "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}"
- (
- throwIf (opt._type or null != "option")
- "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${
- if opt ? _type then
- if opt._type == "option-type" then
- "was a bare type, not wrapped in mkOption."
- else
- "was of type ${lib.strings.escapeNixString opt._type}."
- else
- "was not."
- }"
- opt
+ in
+ a: b: {
+ tags =
+ a.tags
+ // b.tags
+ // mapAttrs (
+ tagName: bOpt:
+ lib.mergeOptionDecls
+ # FIXME: loc is not accurate; should include prefix
+ # Fortunately, it's only used for error messages, where a "relative" location is kinda ok.
+ # It is also returned though, but use of the attribute seems rare?
+ [ tagName ]
+ [
+ (wrapOptionDecl a.tags.${tagName})
+ (wrapOptionDecl bOpt)
+ ]
// {
- declarations =
- opt.declarations or (
- let
- pos = builtins.unsafeGetAttrPos n tags_;
- in
- if pos == null then [ ] else [ pos.file ]
- );
- declarationPositions =
- opt.declarationPositions or (
- let
- pos = builtins.unsafeGetAttrPos n tags_;
- in
- if pos == null then [ ] else [ pos ]
- );
+ # mergeOptionDecls is not idempotent in these attrs:
+ declarations = a.tags.${tagName}.declarations ++ bOpt.declarations;
+ declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions;
}
- )
- ) tags_;
- choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags);
- in
- mkOptionType {
- name = "attrTag";
- description = "attribute-tagged union with choices: ${choicesStr}";
- descriptionClass = "noun";
- getSubOptions =
- prefix: mapAttrs (tagName: tagOption: tagOption // { loc = prefix ++ [ tagName ]; }) tags;
- check = v: isAttrs v && length (attrNames v) == 1 && tags ? ${head (attrNames v)};
- merge =
- loc: defs:
- let
- choice = head (attrNames (head defs).value);
- checkedValueDefs = map (
- def:
- assert (length (attrNames def.value)) == 1;
- if (head (attrNames def.value)) != choice then
- throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}."
- else
- {
- inherit (def) file;
- value = def.value.${choice};
- }
- ) defs;
- in
- if tags ? ${choice} then
- {
- ${choice} = (lib.modules.evalOptionValue (loc ++ [ choice ]) tags.${choice} checkedValueDefs).value;
- }
- else
- throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}.";
- nestedTypes = tags;
- functor = defaultFunctor "attrTag" // {
- type = { tags, ... }: types.attrTag tags;
- payload = { inherit tags; };
- binOp =
- let
- # Add metadata in the format that submodules work with
- wrapOptionDecl = option: {
- options = option;
- _file = "<attrTag {...}>";
- pos = null;
- };
- in
- a: b: {
- tags =
- a.tags
- // b.tags
- // mapAttrs (
- tagName: bOpt:
- lib.mergeOptionDecls
- # FIXME: loc is not accurate; should include prefix
- # Fortunately, it's only used for error messages, where a "relative" location is kinda ok.
- # It is also returned though, but use of the attribute seems rare?
- [ tagName ]
- [
- (wrapOptionDecl a.tags.${tagName})
- (wrapOptionDecl bOpt)
- ]
- // {
- # mergeOptionDecls is not idempotent in these attrs:
- declarations = a.tags.${tagName}.declarations ++ bOpt.declarations;
- declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions;
- }
- ) (builtins.intersectAttrs a.tags b.tags);
- };
+ ) (builtins.intersectAttrs a.tags b.tags);
};
- };
-
- # A value produced by `lib.mkLuaInline`
- luaInline = mkOptionType {
- name = "luaInline";
- description = "inline lua";
- descriptionClass = "noun";
- check = x: x._type or null == "lua-inline";
- merge = mergeEqualOption;
};
+ };
- uniq = unique { message = ""; };
+ # A value produced by `lib.mkLuaInline`
+ luaInline = mkOptionType {
+ name = "luaInline";
+ description = "inline lua";
+ descriptionClass = "noun";
+ check = x: x._type or null == "lua-inline";
+ merge = mergeEqualOption;
+ };
- unique =
- { message }:
- type:
- mkOptionType rec {
- name = "unique";
- inherit (type) description descriptionClass check;
- merge = mergeUniqueOption {
- inherit message;
- inherit (type) merge;
- };
- emptyValue = type.emptyValue;
- getSubOptions = type.getSubOptions;
- getSubModules = type.getSubModules;
- substSubModules = m: uniq (type.substSubModules m);
- functor = elemTypeFunctor name { elemType = type; } // {
- type = payload: types.unique { inherit message; } payload.elemType;
- };
- nestedTypes.elemType = type;
- };
+ uniq = unique { message = ""; };
- # Null or value of ...
- nullOr =
- elemType:
- mkOptionType rec {
- name = "nullOr";
- description = "null or ${
- optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
- }";
- descriptionClass = "conjunction";
- check = x: x == null || elemType.check x;
- merge =
- loc: defs:
- let
- nrNulls = count (def: def.value == null) defs;
- in
- if nrNulls == length defs then
- null
- else if nrNulls != 0 then
- throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
- else
- elemType.merge loc defs;
- emptyValue = {
- value = null;
- };
- getSubOptions = elemType.getSubOptions;
- getSubModules = elemType.getSubModules;
- substSubModules = m: nullOr (elemType.substSubModules m);
- functor = (elemTypeFunctor name { inherit elemType; }) // {
- type = payload: types.nullOr payload.elemType;
- };
- nestedTypes.elemType = elemType;
- };
+ unique =
+ { message }:
+ type:
+ mkOptionType rec {
+ name = "unique";
+ inherit (type) description descriptionClass check;
+ merge = mergeUniqueOption {
+ inherit message;
+ inherit (type) merge;
+ };
+ emptyValue = type.emptyValue;
+ getSubOptions = type.getSubOptions;
+ getSubModules = type.getSubModules;
+ substSubModules = m: uniq (type.substSubModules m);
+ functor = elemTypeFunctor name { elemType = type; } // {
+ type = payload: lib.types.unique { inherit message; } payload.elemType;
+ };
+ nestedTypes.elemType = type;
+ };
- functionTo =
- elemType:
- mkOptionType {
- name = "functionTo";
- description = "function that evaluates to a(n) ${
- optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
- }";
- descriptionClass = "composite";
- check = isFunction;
- merge = loc: defs: {
- # An argument attribute has a default when it has a default in all definitions
- __functionArgs = lib.zipAttrsWith (_: lib.all (x: x)) (
- lib.map (fn: lib.functionArgs fn.value) defs
- );
- __functor =
- _: callerArgs:
- (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (
- map (fn: {
- inherit (fn) file;
- value = fn.value callerArgs;
- }) defs
- )).mergedValue;
- };
- getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
- getSubModules = elemType.getSubModules;
- substSubModules = m: functionTo (elemType.substSubModules m);
- functor = (elemTypeFunctor "functionTo" { inherit elemType; }) // {
- type = payload: types.functionTo payload.elemType;
- };
- nestedTypes.elemType = elemType;
- };
+ # Null or value of ...
+ nullOr =
+ elemType:
+ mkOptionType rec {
+ name = "nullOr";
+ description = "null or ${
+ optionDescriptionPhrase (class: class == "noun" || class == "conjunction") elemType
+ }";
+ descriptionClass = "conjunction";
+ check = x: x == null || elemType.check x;
+ merge =
+ loc: defs:
+ let
+ nrNulls = count (def: def.value == null) defs;
+ in
+ if nrNulls == length defs then
+ null
+ else if nrNulls != 0 then
+ throw "The option `${showOption loc}` is defined both null and not null, in ${showFiles (getFiles defs)}."
+ else
+ elemType.merge loc defs;
+ emptyValue = {
+ value = null;
+ };
+ getSubOptions = elemType.getSubOptions;
+ getSubModules = elemType.getSubModules;
+ substSubModules = m: nullOr (elemType.substSubModules m);
+ functor = (elemTypeFunctor name { inherit elemType; }) // {
+ type = payload: lib.types.nullOr payload.elemType;
+ };
+ nestedTypes.elemType = elemType;
+ };
- # A submodule (like typed attribute set). See NixOS manual.
- submodule =
- modules:
- submoduleWith {
- shorthandOnlyDefinesConfig = true;
- modules = toList modules;
- };
+ functionTo =
+ elemType:
+ mkOptionType {
+ name = "functionTo";
+ description = "function that evaluates to a(n) ${
+ optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType
+ }";
+ descriptionClass = "composite";
+ check = isFunction;
+ merge = loc: defs: {
+ # An argument attribute has a default when it has a default in all definitions
+ __functionArgs = lib.zipAttrsWith (_: lib.all (x: x)) (
+ lib.map (fn: lib.functionArgs fn.value) defs
+ );
+ __functor =
+ _: callerArgs:
+ (mergeDefinitions (loc ++ [ "<function body>" ]) elemType (
+ map (fn: {
+ inherit (fn) file;
+ value = fn.value callerArgs;
+ }) defs
+ )).mergedValue;
+ };
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules = m: functionTo (elemType.substSubModules m);
+ functor = (elemTypeFunctor "functionTo" { inherit elemType; }) // {
+ type = payload: lib.types.functionTo payload.elemType;
+ };
+ nestedTypes.elemType = elemType;
+ };
- # A module to be imported in some other part of the configuration.
- deferredModule = deferredModuleWith { };
+ # A submodule (like typed attribute set). See NixOS manual.
+ submodule =
+ modules:
+ submoduleWith {
+ shorthandOnlyDefinesConfig = true;
+ modules = toList modules;
+ };
- # A module to be imported in some other part of the configuration.
- # `staticModules`' options will be added to the documentation, unlike
- # options declared via `config`.
- deferredModuleWith =
- attrs@{
- staticModules ? [ ],
- }:
- mkOptionType {
- name = "deferredModule";
- description = "module";
- descriptionClass = "noun";
- check = x: isAttrs x || isFunction x || path.check x;
- merge = loc: defs: {
- imports =
- staticModules
- ++ map (
- def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value
- ) defs;
- };
- inherit (submoduleWith { modules = staticModules; })
- getSubOptions
- getSubModules
- ;
- substSubModules =
- m:
- deferredModuleWith (
- attrs
- // {
- staticModules = m;
- }
- );
- functor = defaultFunctor "deferredModuleWith" // {
- type = types.deferredModuleWith;
- payload = {
- inherit staticModules;
- };
- binOp = lhs: rhs: {
- staticModules = lhs.staticModules ++ rhs.staticModules;
- };
- };
- };
+ # A module to be imported in some other part of the configuration.
+ deferredModule = deferredModuleWith { };
- # The type of a type!
- optionType = mkOptionType {
- name = "optionType";
- description = "optionType";
- descriptionClass = "noun";
- check = value: value._type or null == "option-type";
- merge =
- loc: defs:
- if length defs == 1 then
- (head defs).value
- else
- let
- # Prepares the type definitions for mergeOptionDecls, which
- # annotates submodules types with file locations
- optionModules = map (
- { value, file }:
- {
- _file = file;
- # There's no way to merge types directly from the module system,
- # but we can cheat a bit by just declaring an option with the type
- options = lib.mkOption {
- type = value;
- };
- }
- ) defs;
- # Merges all the types into a single one, including submodule merging.
- # This also propagates file information to all submodules
- mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
- in
- mergedOption.type;
+ # A module to be imported in some other part of the configuration.
+ # `staticModules`' options will be added to the documentation, unlike
+ # options declared via `config`.
+ deferredModuleWith =
+ attrs@{
+ staticModules ? [ ],
+ }:
+ mkOptionType {
+ name = "deferredModule";
+ description = "module";
+ descriptionClass = "noun";
+ check = x: isAttrs x || isFunction x || path.check x;
+ merge = loc: defs: {
+ imports =
+ staticModules
+ ++ map (
+ def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value
+ ) defs;
+ };
+ inherit (submoduleWith { modules = staticModules; })
+ getSubOptions
+ getSubModules
+ ;
+ substSubModules =
+ m:
+ deferredModuleWith (
+ attrs
+ // {
+ staticModules = m;
+ }
+ );
+ functor = defaultFunctor "deferredModuleWith" // {
+ type = lib.types.deferredModuleWith;
+ payload = {
+ inherit staticModules;
+ };
+ binOp = lhs: rhs: {
+ staticModules = lhs.staticModules ++ rhs.staticModules;
+ };
};
+ };
- submoduleWith =
- {
- modules,
- specialArgs ? { },
- shorthandOnlyDefinesConfig ? false,
- description ? null,
- class ? null,
- }@attrs:
+ # The type of a type!
+ optionType = mkOptionType {
+ name = "optionType";
+ description = "optionType";
+ descriptionClass = "noun";
+ check = value: value._type or null == "option-type";
+ merge =
+ loc: defs:
+ if length defs == 1 then
+ (head defs).value
+ else
let
- inherit (lib.modules) evalModules;
+ # Prepares the type definitions for mergeOptionDecls, which
+ # annotates submodules types with file locations
+ optionModules = map (
+ { value, file }:
+ {
+ _file = file;
+ # There's no way to merge types directly from the module system,
+ # but we can cheat a bit by just declaring an option with the type
+ options = lib.mkOption {
+ type = value;
+ };
+ }
+ ) defs;
+ # Merges all the types into a single one, including submodule merging.
+ # This also propagates file information to all submodules
+ mergedOption = fixupOptionType loc (mergeOptionDecls loc optionModules);
+ in
+ mergedOption.type;
+ };
- allModules =
- defs:
- map (
- { value, file }:
- if isAttrs value && shorthandOnlyDefinesConfig then
- {
- _file = file;
- config = value;
- }
- else
- {
- _file = file;
- imports = [ value ];
- }
- ) defs;
+ submoduleWith =
+ {
+ modules,
+ specialArgs ? { },
+ shorthandOnlyDefinesConfig ? false,
+ description ? null,
+ class ? null,
+ }@attrs:
+ let
+ inherit (lib.modules) evalModules;
- base = evalModules {
- inherit class specialArgs;
- modules = [
- {
- # This is a work-around for the fact that some sub-modules,
- # such as the one included in an attribute set, expects an "args"
- # attribute to be given to the sub-module. As the option
- # evaluation does not have any specific attribute name yet, we
- # provide a default for the documentation and the freeform type.
- #
- # This is necessary as some option declaration might use the
- # "name" attribute given as argument of the submodule and use it
- # as the default of option declarations.
- #
- # We use lookalike unicode single angle quotation marks because
- # of the docbook transformation the options receive. In all uses
- # &gt; and &lt; wouldn't be encoded correctly so the encoded values
- # would be used, and use of `<` and `>` would break the XML document.
- # It shouldn't cause an issue since this is cosmetic for the manual.
- _module.args.name = lib.mkOptionDefault "‹name›";
- }
- ]
- ++ modules;
- };
+ allModules =
+ defs:
+ map (
+ { value, file }:
+ if isAttrs value && shorthandOnlyDefinesConfig then
+ {
+ _file = file;
+ config = value;
+ }
+ else
+ {
+ _file = file;
+ imports = [ value ];
+ }
+ ) defs;
+
+ base = evalModules {
+ inherit class specialArgs;
+ modules = [
+ {
+ # This is a work-around for the fact that some sub-modules,
+ # such as the one included in an attribute set, expects an "args"
+ # attribute to be given to the sub-module. As the option
+ # evaluation does not have any specific attribute name yet, we
+ # provide a default for the documentation and the freeform type.
+ #
+ # This is necessary as some option declaration might use the
+ # "name" attribute given as argument of the submodule and use it
+ # as the default of option declarations.
+ #
+ # We use lookalike unicode single angle quotation marks because
+ # of the docbook transformation the options receive. In all uses
+ # &gt; and &lt; wouldn't be encoded correctly so the encoded values
+ # would be used, and use of `<` and `>` would break the XML document.
+ # It shouldn't cause an issue since this is cosmetic for the manual.
+ _module.args.name = lib.mkOptionDefault "‹name›";
+ }
+ ]
+ ++ modules;
+ };
- freeformType = base._module.freeformType;
+ freeformType = base._module.freeformType;
- name = "submodule";
+ name = "submodule";
- check = {
- __functor = _self: x: isAttrs x || isFunction x || path.check x;
- isV2MergeCoherent = true;
+ check = {
+ __functor = _self: x: isAttrs x || isFunction x || path.check x;
+ isV2MergeCoherent = true;
+ };
+ in
+ mkOptionType {
+ inherit name;
+ description =
+ if description != null then
+ description
+ else
+ let
+ docsEval = base.extendModules { modules = [ noCheckForDocsModule ]; };
+ in
+ if docsEval._module.freeformType ? description then
+ "open ${name} of ${
+ optionDescriptionPhrase (
+ class: class == "noun" || class == "composite"
+ ) docsEval._module.freeformType
+ }"
+ else
+ name;
+ 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 = { };
+ };
+ getSubOptions =
+ prefix:
+ let
+ docsEval = (
+ base.extendModules {
+ inherit prefix;
+ modules = [ noCheckForDocsModule ];
+ }
+ );
+ # Intentionally shadow the freeformType from the possibly *checked*
+ # configuration. See `noCheckForDocsModule` comment.
+ inherit (docsEval._module) freeformType;
in
- mkOptionType {
- inherit name;
- description =
- if description != null then
- description
+ docsEval.options
+ // optionalAttrs (freeformType != null) {
+ # Expose the sub options of the freeform type. Note that the option
+ # discovery doesn't care about the attribute name used here, so this
+ # is just to avoid conflicts with potential options from the submodule
+ _freeformOptions = freeformType.getSubOptions prefix;
+ };
+ getSubModules = modules;
+ substSubModules =
+ m:
+ submoduleWith (
+ attrs
+ // {
+ modules = m;
+ }
+ );
+ nestedTypes = lib.optionalAttrs (freeformType != null) {
+ freeformType = freeformType;
+ };
+ functor = defaultFunctor name // {
+ type = lib.types.submoduleWith;
+ payload = {
+ inherit
+ modules
+ class
+ specialArgs
+ shorthandOnlyDefinesConfig
+ description
+ ;
+ };
+ binOp = lhs: rhs: {
+ class =
+ # `or null` was added for backwards compatibility only. `class` is
+ # always set in the current version of the module system.
+ if lhs.class or null == null then
+ rhs.class or null
+ else if rhs.class or null == null then
+ lhs.class or null
+ else if lhs.class or null == rhs.class then
+ lhs.class or null
else
- let
- docsEval = base.extendModules { modules = [ noCheckForDocsModule ]; };
- in
- if docsEval._module.freeformType ? description then
- "open ${name} of ${
- optionDescriptionPhrase (
- class: class == "noun" || class == "composite"
- ) docsEval._module.freeformType
- }"
- else
- name;
- 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 = { };
- };
- getSubOptions =
- prefix:
+ throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\".";
+ modules = lhs.modules ++ rhs.modules;
+ specialArgs =
let
- docsEval = (
- base.extendModules {
- inherit prefix;
- modules = [ noCheckForDocsModule ];
- }
- );
- # Intentionally shadow the freeformType from the possibly *checked*
- # configuration. See `noCheckForDocsModule` comment.
- inherit (docsEval._module) freeformType;
+ intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
in
- docsEval.options
- // optionalAttrs (freeformType != null) {
- # Expose the sub options of the freeform type. Note that the option
- # discovery doesn't care about the attribute name used here, so this
- # is just to avoid conflicts with potential options from the submodule
- _freeformOptions = freeformType.getSubOptions prefix;
- };
- getSubModules = modules;
- substSubModules =
- m:
- submoduleWith (
- attrs
- // {
- modules = m;
- }
- );
- nestedTypes = lib.optionalAttrs (freeformType != null) {
- freeformType = freeformType;
- };
- functor = defaultFunctor name // {
- type = types.submoduleWith;
- payload = {
- inherit
- modules
- class
- specialArgs
- shorthandOnlyDefinesConfig
- description
- ;
- };
- binOp = lhs: rhs: {
- class =
- # `or null` was added for backwards compatibility only. `class` is
- # always set in the current version of the module system.
- if lhs.class or null == null then
- rhs.class or null
- else if rhs.class or null == null then
- lhs.class or null
- else if lhs.class or null == rhs.class then
- lhs.class or null
- else
- throw "A submoduleWith option is declared multiple times with conflicting class values \"${toString lhs.class}\" and \"${toString rhs.class}\".";
- modules = lhs.modules ++ rhs.modules;
- specialArgs =
- let
- intersecting = builtins.intersectAttrs lhs.specialArgs rhs.specialArgs;
- in
- if intersecting == { } then
- lhs.specialArgs // rhs.specialArgs
- else
- throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
- shorthandOnlyDefinesConfig =
- if lhs.shorthandOnlyDefinesConfig == null then
- rhs.shorthandOnlyDefinesConfig
- else if rhs.shorthandOnlyDefinesConfig == null then
- lhs.shorthandOnlyDefinesConfig
- else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig then
- lhs.shorthandOnlyDefinesConfig
- else
- throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
- description =
- if lhs.description == null then
- rhs.description
- else if rhs.description == null then
- lhs.description
- else if lhs.description == rhs.description then
- lhs.description
- else
- throw "A submoduleWith option is declared multiple times with conflicting descriptions";
- };
- };
- };
-
- # A value from a set of allowed ones.
- enum =
- values:
- let
- inherit (lib.lists) unique;
- show =
- v:
- if builtins.isString v then
- ''"${v}"''
- else if builtins.isInt v then
- toString v
- else if builtins.isBool v then
- boolToString v
+ if intersecting == { } then
+ lhs.specialArgs // rhs.specialArgs
else
- ''<${builtins.typeOf v}>'';
- in
- mkOptionType rec {
- name = "enum";
- description =
- # Length 0 or 1 enums may occur in a design pattern with type merging
- # where an "interface" module declares an empty enum and other modules
- # provide implementations, each extending the enum with their own
- # identifier.
- if values == [ ] then
- "impossible (empty enum)"
- else if builtins.length values == 1 then
- "value ${show (builtins.head values)} (singular enum)"
+ throw "A submoduleWith option is declared multiple times with the same specialArgs \"${toString (attrNames intersecting)}\"";
+ shorthandOnlyDefinesConfig =
+ if lhs.shorthandOnlyDefinesConfig == null then
+ rhs.shorthandOnlyDefinesConfig
+ else if rhs.shorthandOnlyDefinesConfig == null then
+ lhs.shorthandOnlyDefinesConfig
+ else if lhs.shorthandOnlyDefinesConfig == rhs.shorthandOnlyDefinesConfig then
+ lhs.shorthandOnlyDefinesConfig
else
- "one of ${concatMapStringsSep ", " show values}";
- descriptionClass = if builtins.length values < 2 then "noun" else "conjunction";
- check = flip elem values;
- merge = mergeEqualOption;
- functor = (defaultFunctor name) // {
- payload = { inherit values; };
- type = payload: types.enum payload.values;
- binOp = a: b: { values = unique (a.values ++ b.values); };
- };
- };
-
- # Either value of type `t1` or `t2`.
- either =
- t1: t2:
- mkOptionType rec {
- name = "either";
+ throw "A submoduleWith option is declared multiple times with conflicting shorthandOnlyDefinesConfig values";
description =
- if t1.descriptionClass or null == "nonRestrictiveClause" then
- # Plain, but add comma
- "${t1.description}, or ${
- optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2
- }"
+ if lhs.description == null then
+ rhs.description
+ else if rhs.description == null then
+ lhs.description
+ else if lhs.description == rhs.description then
+ lhs.description
else
- "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
- optionDescriptionPhrase (
- class: class == "noun" || class == "conjunction" || class == "composite"
- ) t2
- }";
- descriptionClass = "conjunction";
- check = {
- __functor = _self: x: t1.check x || t2.check x;
- isV2MergeCoherent = true;
- };
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- t1CheckedAndMerged =
- if t1.merge ? v2 then
- checkV2MergeCoherence loc t1 (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
- checkV2MergeCoherence loc t2 (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 = lib.warn ''
- One or more definitions did not pass the type-check of the 'either' type.
- ${headError.message}
- If `either`, `oneOf` or similar is used in freeformType, ensure that it is preceded by an 'attrsOf' such as: `freeformType = types.attrsOf (types.either t1 t2)`.
- Otherwise consider using the correct type for the option `${showOption loc}`. This will be an error in Nixpkgs 26.05.
- '' (mergeOneOption loc defs);
- };
- in
- checkedAndMerged;
- };
- typeMerge =
- f':
- let
- mt1 = t1.typeMerge (elemAt f'.payload.elemType 0).functor;
- mt2 = t2.typeMerge (elemAt f'.payload.elemType 1).functor;
- in
- if (name == f'.name) && (mt1 != null) && (mt2 != null) then functor.type mt1 mt2 else null;
- functor = elemTypeFunctor name {
- elemType = [
- t1
- t2
- ];
- };
- nestedTypes.left = t1;
- nestedTypes.right = t2;
+ throw "A submoduleWith option is declared multiple times with conflicting descriptions";
};
+ };
+ };
- # Any of the types in the given list
- oneOf =
- ts:
- let
- head' =
- if ts == [ ] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
- tail' = tail ts;
- in
- foldl' either head' tail';
+ # A value from a set of allowed ones.
+ enum =
+ values:
+ let
+ inherit (lib.lists) unique;
+ show =
+ v:
+ if builtins.isString v then
+ ''"${v}"''
+ else if builtins.isInt v then
+ toString v
+ else if builtins.isBool v then
+ boolToString v
+ else
+ ''<${builtins.typeOf v}>'';
+ in
+ mkOptionType rec {
+ name = "enum";
+ description =
+ # Length 0 or 1 enums may occur in a design pattern with type merging
+ # where an "interface" module declares an empty enum and other modules
+ # provide implementations, each extending the enum with their own
+ # identifier.
+ if values == [ ] then
+ "impossible (empty enum)"
+ else if builtins.length values == 1 then
+ "value ${show (builtins.head values)} (singular enum)"
+ else
+ "one of ${concatMapStringsSep ", " show values}";
+ descriptionClass = if builtins.length values < 2 then "noun" else "conjunction";
+ check = flip elem values;
+ merge = mergeEqualOption;
+ functor = (defaultFunctor name) // {
+ payload = { inherit values; };
+ type = payload: lib.types.enum payload.values;
+ binOp = a: b: { values = unique (a.values ++ b.values); };
+ };
+ };
- # Either value of type `coercedType` or `finalType`, the former is
- # converted to `finalType` using `coerceFunc`.
- coercedTo =
- coercedType: coerceFunc: finalType:
- assert lib.assertMsg (
- coercedType.getSubModules == null
- ) "coercedTo: coercedType must not have submodules (it’s a ${coercedType.description})";
- mkOptionType rec {
- name = "coercedTo";
- description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${
- optionDescriptionPhrase (class: class == "noun") coercedType
- } convertible to it";
- check = {
- __functor = _self: x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
- isV2MergeCoherent = true;
- };
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- finalDefs = (
- map (
- def:
- def
- // {
- value =
- let
- merged =
- if coercedType.merge ? v2 then
- checkV2MergeCoherence loc coercedType (
- coercedType.merge.v2 {
- inherit loc;
- defs = [ def ];
- }
- )
- else
- null;
- 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
- checkV2MergeCoherence loc finalType (
- finalType.merge.v2 {
- inherit loc;
- defs = finalDefs;
- }
- )
+ # Either value of type `t1` or `t2`.
+ either =
+ t1: t2:
+ mkOptionType rec {
+ name = "either";
+ description =
+ if t1.descriptionClass or null == "nonRestrictiveClause" then
+ # Plain, but add comma
+ "${t1.description}, or ${
+ optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t2
+ }"
+ else
+ "${optionDescriptionPhrase (class: class == "noun" || class == "conjunction") t1} or ${
+ optionDescriptionPhrase (
+ class: class == "noun" || class == "conjunction" || class == "composite"
+ ) t2
+ }";
+ descriptionClass = "conjunction";
+ check = {
+ __functor = _self: x: t1.check x || t2.check x;
+ isV2MergeCoherent = true;
+ };
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ t1CheckedAndMerged =
+ if t1.merge ? v2 then
+ checkV2MergeCoherence loc t1 (t1.merge.v2 { inherit loc defs; })
else
{
- value = finalType.merge loc finalDefs;
+ value = t1.merge loc defs;
+ headError = checkDefsForError t1.check loc defs;
+ valueMeta = { };
+ };
+ t2CheckedAndMerged =
+ if t2.merge ? v2 then
+ checkV2MergeCoherence loc t2 (t2.merge.v2 { inherit loc defs; })
+ else
+ {
+ value = t2.merge loc defs;
+ headError = checkDefsForError t2.check loc defs;
valueMeta = { };
- headError = checkDefsForError check loc defs;
};
- };
- emptyValue = finalType.emptyValue;
- getSubOptions = finalType.getSubOptions;
- getSubModules = finalType.getSubModules;
- substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
- typeMerge = t: null;
- functor = (defaultFunctor name) // {
- wrappedDeprecationMessage = makeWrappedDeprecationMessage { elemType = finalType; };
- };
- nestedTypes.coercedType = coercedType;
- nestedTypes.finalType = finalType;
- };
- /**
- Augment the given type with an additional type check function.
+ 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 = lib.warn ''
+ One or more definitions did not pass the type-check of the 'either' type.
+ ${headError.message}
+ If `either`, `oneOf` or similar is used in freeformType, ensure that it is preceded by an 'attrsOf' such as: `freeformType = types.attrsOf (types.either t1 t2)`.
+ Otherwise consider using the correct type for the option `${showOption loc}`. This will be an error in Nixpkgs 26.05.
+ '' (mergeOneOption loc defs);
+ };
+ in
+ checkedAndMerged;
+ };
+ typeMerge =
+ f':
+ let
+ mt1 = t1.typeMerge (elemAt f'.payload.elemType 0).functor;
+ mt2 = t2.typeMerge (elemAt f'.payload.elemType 1).functor;
+ in
+ if (name == f'.name) && (mt1 != null) && (mt2 != null) then functor.type mt1 mt2 else null;
+ functor = elemTypeFunctor name {
+ elemType = [
+ t1
+ t2
+ ];
+ };
+ nestedTypes.left = t1;
+ nestedTypes.right = t2;
+ };
- :::{.warning}
- This function has some broken behavior see: [#396021](https://github.com/NixOS/nixpkgs/issues/396021)
- Fixing is not trivial, we appreciate any help!
- :::
- */
- addCheck =
- elemType: check:
- if elemType.merge ? v2 then
- elemType
- // {
- check = {
- __functor = _self: x: elemType.check x && check x;
- isV2MergeCoherent = true;
- };
- merge = {
- __functor =
- self: loc: defs:
- (self.v2 { inherit loc defs; }).value;
- v2 =
- { loc, defs }:
- let
- orig = checkV2MergeCoherence loc elemType (elemType.merge.v2 { inherit loc defs; });
- headError' = if orig.headError != null then orig.headError else checkDefsForError check loc defs;
- in
- orig
+ # Any of the types in the given list
+ oneOf =
+ ts:
+ let
+ head' =
+ if ts == [ ] then throw "types.oneOf needs to get at least one type in its argument" else head ts;
+ tail' = tail ts;
+ in
+ foldl' either head' tail';
+
+ # Either value of type `coercedType` or `finalType`, the former is
+ # converted to `finalType` using `coerceFunc`.
+ coercedTo =
+ coercedType: coerceFunc: finalType:
+ assert lib.assertMsg (
+ coercedType.getSubModules == null
+ ) "coercedTo: coercedType must not have submodules (it’s a ${coercedType.description})";
+ mkOptionType rec {
+ name = "coercedTo";
+ description = "${optionDescriptionPhrase (class: class == "noun") finalType} or ${
+ optionDescriptionPhrase (class: class == "noun") coercedType
+ } convertible to it";
+ check = {
+ __functor = _self: x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
+ isV2MergeCoherent = true;
+ };
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ finalDefs = (
+ map (
+ def:
+ def
// {
- headError = headError';
- };
+ value =
+ let
+ merged =
+ if coercedType.merge ? v2 then
+ checkV2MergeCoherence loc coercedType (
+ coercedType.merge.v2 {
+ inherit loc;
+ defs = [ def ];
+ }
+ )
+ else
+ null;
+ 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
+ checkV2MergeCoherence loc finalType (
+ finalType.merge.v2 {
+ inherit loc;
+ defs = finalDefs;
+ }
+ )
+ else
+ {
+ value = finalType.merge loc finalDefs;
+ valueMeta = { };
+ headError = checkDefsForError check loc defs;
};
- }
- else
- elemType
- // {
- check = x: elemType.check x && check x;
- };
+ };
+ emptyValue = finalType.emptyValue;
+ getSubOptions = finalType.getSubOptions;
+ getSubModules = finalType.getSubModules;
+ substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
+ typeMerge = t: null;
+ functor = (defaultFunctor name) // {
+ wrappedDeprecationMessage = makeWrappedDeprecationMessage { elemType = finalType; };
+ };
+ nestedTypes.coercedType = coercedType;
+ nestedTypes.finalType = finalType;
};
- /**
- Merges two option types together.
+ /**
+ Augment the given type with an additional type check function.
+
+ :::{.warning}
+ This function has some broken behavior see: [#396021](https://github.com/NixOS/nixpkgs/issues/396021)
+ Fixing is not trivial, we appreciate any help!
+ :::
+ */
+ addCheck =
+ elemType: check:
+ if elemType.merge ? v2 then
+ elemType
+ // {
+ check = {
+ __functor = _self: x: elemType.check x && check x;
+ isV2MergeCoherent = true;
+ };
+ merge = {
+ __functor =
+ self: loc: defs:
+ (self.v2 { inherit loc defs; }).value;
+ v2 =
+ { loc, defs }:
+ let
+ orig = checkV2MergeCoherence loc elemType (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;
+ };
+
+ /**
+ Merges two option types together.
- :::{.note}
- Uses the type merge function of the first type, to merge it with the second type.
+ :::{.note}
+ Uses the type merge function of the first type, to merge it with the second type.
- Usually types can only be merged if they are of the same type
- :::
+ Usually types can only be merged if they are of the same type
+ :::
- # Inputs
+ # Inputs
- : `a` (option type): The first option type.
- : `b` (option type): The second option type.
+ : `a` (option type): The first option type.
+ : `b` (option type): The second option type.
- # Returns
+ # Returns
- - The merged option type.
- - `{ _type = "merge-error"; error = "Cannot merge types"; }` if the types can't be merged.
+ - The merged option type.
+ - `{ _type = "merge-error"; error = "Cannot merge types"; }` if the types can't be merged.
- # Examples
- :::{.example}
- ## `lib.types.mergeTypes` usage example
- ```nix
- let
- enumAB = lib.types.enum ["A" "B"];
- enumXY = lib.types.enum ["X" "Y"];
- # This operation could be notated as: [ A ] | [ B ] -> [ A B ]
- merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ]
- in
- assert merged.check "A"; # true
- assert merged.check "B"; # true
- assert merged.check "X"; # true
- assert merged.check "Y"; # true
- merged.check "C" # false
- ```
- :::
- */
- mergeTypes =
- a: b:
- assert isOptionType a && isOptionType b;
- let
- merged = a.typeMerge b.functor;
- in
- if merged == null then setType "merge-error" { error = "Cannot merge types"; } else merged;
- };
+ # Examples
+ :::{.example}
+ ## `lib.types.mergeTypes` usage example
+ ```nix
+ let
+ enumAB = lib.types.enum ["A" "B"];
+ enumXY = lib.types.enum ["X" "Y"];
+ # This operation could be notated as: [ A ] | [ B ] -> [ A B ]
+ merged = lib.types.mergeTypes enumAB enumXY; # -> enum [ "A" "B" "X" "Y" ]
+ in
+ assert merged.check "A"; # true
+ assert merged.check "B"; # true
+ assert merged.check "X"; # true
+ assert merged.check "Y"; # true
+ merged.check "C" # false
+ ```
+ :::
+ */
+ mergeTypes =
+ a: b:
+ assert isOptionType a && isOptionType b;
+ let
+ merged = a.typeMerge b.functor;
+ in
+ if merged == null then setType "merge-error" { error = "Cannot merge types"; } else merged;
-in
-outer_types // outer_types.types
+ # TODO: Migrate usage of lib.types.types in nixpkgs
+ # Then add a deprecation warning
+ types = lib.types;
+}