Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib.types.functor: use payload for all composed types #358749

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let
concatLists
concatMap
concatStringsSep
defaultTypeMerge
elem
filter
foldl'
Expand Down Expand Up @@ -749,7 +750,10 @@ let
foldl' (res: opt:
let t = res.type;
t' = opt.options.type;
mergedType = t.typeMerge t'.functor;
errContext = ''
`${showOption loc}' in `${opt._file}'
'';
mergedType = defaultTypeMerge errContext t.functor t'.functor;
typesMergeable = mergedType != null;
typeSet = if (bothHave "type") && typesMergeable
then { type = mergedType; }
Expand Down
139 changes: 95 additions & 44 deletions lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ let
let pos = builtins.unsafeGetAttrPos name v; in
if pos == null then "" else " at ${pos.file}:${toString pos.line}:${toString pos.column}";

deprecatedWrappedAt = context: throw ''
Type attribute functor.wrapped is deprecated.

Consume `type.nestedTypes` attribute instead.

For migrating custom option types use `functor.payload.elemType` instead of `functor.wrapped`.

To help with that the following function can be used within 'mkOptionType'

mkOptionType {
...
functor = defaultComposedFunctor name elemType;
};
'' + context;

outer_types =
rec {
isType = type: x: (x._type or "") == type;
Expand All @@ -82,48 +97,60 @@ rec {

# 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;
defaultTypeMerge = ctx: f: f':
let
mergedPayload = f.binOp f.payload f'.payload;
hasPayload = assert (f'.payload != null) == (f.payload != null); f.payload != null;
in
# Abort early: cannot merge different types
if f.name != f'.name
then null
then null
else

if hasPayload then
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.

If your code worked before remove `functor.payload` from the type definition.
''
else
# Has payload
if mergedPayload == null then null else f.type mergedPayload
if mergedPayload == null then null else
if !isAttrs mergedPayload then
throw ''
Invalid type: `${f.name}`

`type.functor.payload` must be an attribute set.

To fix this error update the type implementation.
'' + ctx
else
let
mergedType = f.type mergedPayload;
in
mergedType // {
functor = mergedType.functor // {
wrapped = deprecatedWrappedAt ctx;
};
}
else
if hasWrapped then
# Has wrapped
# TODO(@hsjobeki): This could also be a warning and removed in the future
if mergedWrapped == null then null else f.type mergedWrapped
else
f.type;
f.type;

# Default type functor
defaultFunctor = name: {
inherit name;
type = types.${name} or null;
wrapped = null;
payload = null;
wrapped = deprecatedWrappedAt "Unknown location";
binOp = a: b: null;
};

# Default functor for composed types
defaultComposedFunctor = name: elemType: {
inherit name;
type = { elemType }: types.${name} elemType;
payload = { inherit elemType; };
wrapped = deprecatedWrappedAt "Unknown location";
binOp = a: b:
let
elemType = a.elemType.typeMerge b.elemType.functor;
in
if elemType == null then null else { inherit elemType; };
};

isOptionType = isType "option-type";
mkOptionType =
{ # Human-readable representation of the type, should be equivalent to
Expand Down Expand Up @@ -182,12 +209,11 @@ rec {
, # 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
typeMerge ? defaultTypeMerge "unknown location" 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.
Expand Down Expand Up @@ -471,10 +497,13 @@ rec {
check = isString;
merge = loc: defs: concatStringsSep sep (getValues defs);
functor = (defaultFunctor name) // {
payload = sep;
binOp = sepLhs: sepRhs:
if sepLhs == sepRhs then sepLhs
else null;
payload = { inherit sep; };
type = payload: types.${name} payload.sep;
binOp = lhs: rhs:
if lhs.sep == rhs.sep then
{ sep = lhs.sep; }
else
null;
};
};

Expand Down Expand Up @@ -570,7 +599,7 @@ rec {
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
getSubModules = elemType.getSubModules;
substSubModules = m: listOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
functor = defaultComposedFunctor name elemType;
nestedTypes.elemType = elemType;
};

Expand All @@ -597,7 +626,7 @@ rec {
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: attrsOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
functor = defaultComposedFunctor name elemType;
nestedTypes.elemType = elemType;
};

Expand All @@ -623,7 +652,7 @@ rec {
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
functor = defaultComposedFunctor name elemType;
nestedTypes.elemType = elemType;
};

Expand Down Expand Up @@ -732,15 +761,15 @@ rec {

uniq = unique { message = ""; };

unique = { message }: type: mkOptionType rec {
unique = { message }: type: mkOptionType {
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 = (defaultFunctor name) // { wrapped = type; };
functor = defaultComposedFunctor "unique" type;
nestedTypes.elemType = type;
};

Expand All @@ -760,7 +789,7 @@ rec {
getSubOptions = elemType.getSubOptions;
getSubModules = elemType.getSubModules;
substSubModules = m: nullOr (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
functor = defaultComposedFunctor name elemType;
nestedTypes.elemType = elemType;
};

Expand All @@ -774,7 +803,7 @@ rec {
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<function body>" ]);
getSubModules = elemType.getSubModules;
substSubModules = m: functionTo (elemType.substSubModules m);
functor = (defaultFunctor "functionTo") // { wrapped = elemType; };
functor = defaultComposedFunctor "functionTo" elemType;
nestedTypes.elemType = elemType;
};

Expand Down Expand Up @@ -980,7 +1009,13 @@ rec {
else "conjunction";
check = flip elem values;
merge = mergeEqualOption;
functor = (defaultFunctor name) // { payload = values; binOp = a: b: unique (a ++ b); };
functor = (defaultFunctor name) // {
payload = { inherit values; };
type = payload: types.${name} payload.values;
binOp = a: b: {
values = unique (a.values ++ b.values);
};
};
};

# Either value of type `t1` or `t2`.
Expand All @@ -1005,13 +1040,29 @@ rec {
then t2.merge loc defs
else mergeOneOption loc defs;
typeMerge = f':
let mt1 = t1.typeMerge (elemAt f'.wrapped 0).functor;
mt2 = t2.typeMerge (elemAt f'.wrapped 1).functor;
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 = (defaultFunctor name) // { wrapped = [ t1 t2 ]; };
functor = (defaultFunctor name) // {
type = { elemType, ... }: types.${name} elemType;
payload = {
wrapped = deprecatedWrappedAt "Unknown location";
elemType = [ t1 t2 ];
};
binOp = lhs: rhs:
let
elemType = lhs.elemType.typeMerge rhs.elemType;
in
if elemType == null then
null
else
{
inherit elemType;
};
};
nestedTypes.left = t1;
nestedTypes.right = t2;
};
Expand Down Expand Up @@ -1044,7 +1095,7 @@ rec {
getSubModules = finalType.getSubModules;
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
typeMerge = t: null;
functor = (defaultFunctor name) // { wrapped = finalType; };
functor = defaultComposedFunctor name finalType;
nestedTypes.coercedType = coercedType;
nestedTypes.finalType = finalType;
};
Expand Down