From 553d8e9ee95aa3a774778362b2c15f872dfd1987 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 28 Oct 2025 18:47:45 +0100 Subject: [PATCH] doc/option-types: Improve union type docs --- nixos/doc/manual/development/option-types.section.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index b7b9f04acd95..d2e0c7299415 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -316,22 +316,34 @@ A union of types is a type such that a value is valid when it is valid for at le If some values are instances of more than one of the types, it is not possible to distinguish which type they are meant to be instances of. If that's needed, consider using a [sum type](#sec-option-types-sums). + `types.either` *`t1 t2`* : Type *`t1`* or type *`t2`*, e.g. `with types; either int str`. Multiple definitions cannot be merged. + ::: {.warning} + `either` and `oneOf` eagerly decide the active type based on the passed types' shallow check method. For composite types like `attrsOf` and `submodule`, which both match all attribute set definitions, the first type argument will be chosen for the returned option value, and this therefore also decides how nested values are checked and merged. For example, `either (attrsOf int) (submodule {...})` will always use `attrsOf int` for any attribute set value, even if it was intended as a submodule. This behavior is a trade-off that keeps the implementation simple and the evaluation order predictable, avoiding unexpected strictness problems such as infinite recursions. When proper type discrimination is needed, consider using a [sum type](#sec-option-types-sums) like `attrTag` instead. + ::: + + `types.oneOf` \[ *`t1 t2`* ... \] : Type *`t1`* or type *`t2`* and so forth, e.g. `with types; oneOf [ int str bool ]`. Multiple definitions cannot be merged. + ::: {.warning} + `either` and `oneOf` eagerly decide the active type based on the passed types' shallow check method. For composite types like `attrsOf` and `submodule`, which both match all attribute set definitions, the first matching type in the list will be chosen for the returned option value, and this therefore also decides how nested values are checked and merged. For example, `oneOf [ (attrsOf int) (submodule {...}) ]` will always use `attrsOf int` for any attribute set value, even if it was intended as a submodule. This behavior is a trade-off that keeps the implementation simple and the evaluation order predictable, avoiding unexpected strictness problems such as infinite recursions. When proper type discrimination is needed, consider using a [sum type](#sec-option-types-sums) like `attrTag` instead. + ::: + `types.nullOr` *`t`* : `null` or type *`t`*. Multiple definitions are merged according to type *`t`*. + This is mostly equivalent to `either (enum [ null ]) t`, but `nullOr` provides a `null` fallback for attribute values with `mkIf false` definitions in `lazyAttrsOf (nullOr t)`, whereas `either` would throw an error when the attribute is accessed. + ## Sum types {#sec-option-types-sums}