Skip to content

Commit b234fd8

Browse files
authored
lib.types.defaultTypeMerge: refactor functor.{payload,wrapped} merging (#350906)
2 parents 8b6f73b + b978799 commit b234fd8

File tree

3 files changed

+60
-14
lines changed

3 files changed

+60
-14
lines changed

lib/tests/modules.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,10 @@ checkConfigError 'The option .theOption.nested. in .other.nix. is already declar
516516
# Test that types.optionType leaves types untouched as long as they don't need to be merged
517517
checkConfigOutput 'ok' config.freeformItems.foo.bar ./adhoc-freeformType-survives-type-merge.nix
518518

519+
# Test that specifying both functor.wrapped and functor.payload isn't allowed
520+
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
521+
522+
519523
# Anonymous submodules don't get nixed by import resolution/deduplication
520524
# because of an `extendModules` bug, issue 168767.
521525
checkConfigOutput '^1$' config.sub.specialisation.value ./extendModules-168767-imports.nix
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{ lib, options, ... }:
2+
let
3+
foo = lib.mkOptionType {
4+
name = "foo";
5+
functor = lib.types.defaultFunctor "foo" // {
6+
wrapped = lib.types.int;
7+
payload = 10;
8+
};
9+
};
10+
in
11+
{
12+
imports = [
13+
{
14+
options.foo = lib.mkOption {
15+
type = foo;
16+
};
17+
}
18+
{
19+
options.foo = lib.mkOption {
20+
type = foo;
21+
};
22+
}
23+
];
24+
25+
options.result = lib.mkOption {
26+
default = builtins.seq options.foo null;
27+
};
28+
}

lib/types.nix

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,23 +83,37 @@ rec {
8383
# Default type merging function
8484
# takes two type functors and return the merged type
8585
defaultTypeMerge = f: f':
86-
let wrapped = f.wrapped.typeMerge f'.wrapped.functor;
87-
payload = f.binOp f.payload f'.payload;
86+
let mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor;
87+
mergedPayload = f.binOp f.payload f'.payload;
88+
89+
hasPayload = assert (f'.payload != null) == (f.payload != null); f.payload != null;
90+
hasWrapped = assert (f'.wrapped != null) == (f.wrapped != null); f.wrapped != null;
8891
in
89-
# cannot merge different types
92+
# Abort early: cannot merge different types
9093
if f.name != f'.name
9194
then null
92-
# simple types
93-
else if (f.wrapped == null && f'.wrapped == null)
94-
&& (f.payload == null && f'.payload == null)
95-
then f.type
96-
# composed types
97-
else if (f.wrapped != null && f'.wrapped != null) && (wrapped != null)
98-
then f.type wrapped
99-
# value types
100-
else if (f.payload != null && f'.payload != null) && (payload != null)
101-
then f.type payload
102-
else null;
95+
else
96+
97+
if hasPayload then
98+
if hasWrapped then
99+
# Has both wrapped and payload
100+
throw ''
101+
Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.
102+
103+
Use either `functor.payload` or `functor.wrapped` but not both.
104+
105+
If your code worked before remove `functor.payload` from the type definition.
106+
''
107+
else
108+
# Has payload
109+
if mergedPayload == null then null else f.type mergedPayload
110+
else
111+
if hasWrapped then
112+
# Has wrapped
113+
# TODO(@hsjobeki): This could also be a warning and removed in the future
114+
if mergedWrapped == null then null else f.type mergedWrapped
115+
else
116+
f.type;
103117

104118
# Default type functor
105119
defaultFunctor = name: {

0 commit comments

Comments
 (0)