From f32c37b88cb1cfbed408eca10d78b5db77516806 Mon Sep 17 00:00:00 2001 From: Timothy Gallion Date: Thu, 1 Jan 2026 15:17:53 -0500 Subject: [PATCH] lib.types.defaultTypeMerge: Fix for functors with no `wrapped` attr Downstream types that want to use `lib.types.defaultTypeMerge` must include `wrapped` even though it is deprecated or the internal `wrappedDeprecationMessage`. This is caused by accessing the `wrapped` attr in `lib.types.defaultTypeMerge`. The logic should first check if the attr exists and then if it does check if it is null. --- lib/tests/modules.sh | 2 ++ .../modules/default-type-merge-payload.nix | 32 +++++++++++++++++++ lib/types.nix | 7 ++-- 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 lib/tests/modules/default-type-merge-payload.nix diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index bc89d4c20851..0ef680213946 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -728,6 +728,8 @@ checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survive # Test that specifying both functor.wrapped and functor.payload isn't allowed checkConfigError 'Type foo defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.' config.result ./default-type-merge-both.nix +# Test that not including functor.wrapped is allowed +checkConfigOutput 'ok' config.result ./default-type-merge-payload.nix # Anonymous submodules don't get nixed by import resolution/deduplication # because of an `extendModules` bug, issue 168767. diff --git a/lib/tests/modules/default-type-merge-payload.nix b/lib/tests/modules/default-type-merge-payload.nix new file mode 100644 index 000000000000..d6cdc5906074 --- /dev/null +++ b/lib/tests/modules/default-type-merge-payload.nix @@ -0,0 +1,32 @@ +{ lib, options, ... }: +let + fooOf = + elemType: + lib.mkOptionType { + name = "foo"; + functor = { + name = "foo"; + type = payload: fooOf payload.elemType; + binOp = a: _b: a; + payload.elemType = elemType; + }; + }; +in +{ + imports = [ + { + options.foo = lib.mkOption { + type = fooOf lib.types.int; + }; + } + { + options.foo = lib.mkOption { + type = fooOf lib.types.int; + }; + } + ]; + + options.result = lib.mkOption { + default = builtins.seq options.foo "ok"; + }; +} diff --git a/lib/types.nix b/lib/types.nix index 25286303f11e..cd0a73dab9e2 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -158,8 +158,11 @@ rec { assert (f'.payload != null) == (f.payload != null); f.payload != null; hasWrapped = - assert (f'.wrapped != null) == (f.wrapped != null); - f.wrapped != null; + let + hasWrappedNonNull = set: set ? "wrapped" && set.wrapped != null; + in + assert (hasWrappedNonNull f') == (hasWrappedNonNull f); + hasWrappedNonNull f; typeFromPayload = if mergedPayload == null then null else f.type mergedPayload; typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped;