Skip to content
Merged
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
40 changes: 5 additions & 35 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -751,47 +751,17 @@ let
t' = opt.options.type;
mergedType = t.typeMerge t'.functor;
typesMergeable = mergedType != null;

# TODO: Remove this when all downstream reliances of internals: 'functor.wrapped' are sufficiently migrated.
# A function that adds the deprecated wrapped message to a type.
addDeprecatedWrapped = t:
t // {
functor = t.functor // {
wrapped = t.functor.wrappedDeprecationMessage {
inherit loc;
};
};
};

typeSet =
if opt.options ? type then
if res ? type then
if typesMergeable then
{
type =
if mergedType ? functor.wrappedDeprecationMessage then
addDeprecatedWrapped mergedType
else
mergedType;
}
else
# Keep in sync with the same error below!
throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
else if opt.options.type ? functor.wrappedDeprecationMessage then
{ type = addDeprecatedWrapped opt.options.type; }
else
{}
else
{};

typeSet = if (bothHave "type") && typesMergeable
then { type = mergedType; }
else {};
bothHave = k: opt.options ? ${k} && res ? ${k};
in
if bothHave "default" ||
bothHave "example" ||
bothHave "description" ||
bothHave "apply"
bothHave "apply" ||
(bothHave "type" && (! typesMergeable))
then
# Keep in sync with the same error above!
throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
else
let
Expand Down
9 changes: 0 additions & 9 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,6 @@ checkConfigOutput '^true$' config.conditionalWorks ./declare-attrsOf.nix ./attrs
checkConfigOutput '^false$' config.conditionalWorks ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
checkConfigOutput '^"empty"$' config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix

# Check attrsWith type merging
checkConfigError 'The option `mergedLazyNonLazy'\'' in `.*'\'' is already declared in `.*'\''\.' options.mergedLazyNonLazy ./lazy-attrsWith.nix
checkConfigOutput '^11$' config.lazyResult ./lazy-attrsWith.nix
checkConfigError 'infinite recursion encountered' config.nonLazyResult ./lazy-attrsWith.nix

# Test the attrsOf functor.wrapped warning
# shellcheck disable=2016
NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `type.functor.wrapped` attribute of the option `mergedLazyLazy` is accessed, use `nestedTypes.elemType` instead.' options.mergedLazyLazy.type.functor.wrapped ./lazy-attrsWith.nix

# Even with multiple assignments, a type error should be thrown if any of them aren't valid
checkConfigError 'A definition for option .* is not of type .*' \
Expand Down Expand Up @@ -583,7 +575,6 @@ checkConfigOutput '^38|27$' options.submoduleLine38.declarationPositions.1.line
# nested options work
checkConfigOutput '^34$' options.nested.nestedLine34.declarationPositions.0.line ./declaration-positions.nix


cat <<EOF
====== module tests ======
$pass Pass
Expand Down
57 changes: 0 additions & 57 deletions lib/tests/modules/lazy-attrsWith.nix

This file was deleted.

118 changes: 42 additions & 76 deletions lib/types.nix
Original file line number Diff line number Diff line change
Expand Up @@ -83,39 +83,35 @@ 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;
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;

typeFromPayload = if mergedPayload == null then null else f.type mergedPayload;
typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped;
hasPayload = assert (f'.payload != null) == (f.payload != null); f.payload != null;
hasWrapped = assert (f'.wrapped != null) == (f.wrapped != null); f.wrapped != null;
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
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 either `functor.wrapped` or `functor.payload` from the type definition.
If your code worked before remove `functor.payload` from the type definition.
''
else
typeFromPayload
# Has payload
if mergedPayload == null then null else f.type mergedPayload
else
if hasWrapped then
typeFromWrapped
# 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;

Expand Down Expand Up @@ -586,78 +582,48 @@ rec {
substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
};

attrsOf = elemType: attrsWith { inherit elemType; };
attrsOf = elemType: mkOptionType rec {
name = "attrsOf";
description = "attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
descriptionClass = "composite";
check = isAttrs;
merge = loc: defs:
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
(mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
)
# Push down position info.
(map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
emptyValue = { value = {}; };
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: attrsOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
nestedTypes.elemType = 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; };

# 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;
in
if elemType == null || lazy == null then
null
else
{
inherit elemType lazy;
};
in
{
elemType,
lazy ? false,
}:
mkOptionType {
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}";
lazyAttrsOf = elemType: mkOptionType rec {
name = "lazyAttrsOf";
description = "lazy attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
descriptionClass = "composite";
check = isAttrs;
merge = if lazy then (
# Lazy merge Function
loc: defs:
zipAttrsWith (name: defs:
let merged = mergeDefinitions (loc ++ [name]) elemType defs;
# mergedValue will trigger an appropriate error when accessed
in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
)
# Push down position info.
(pushPositions defs)
) else (
# Non-lazy merge Function
loc: defs:
mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
(mergeDefinitions (loc ++ [name]) elemType (defs)).optionalValue
)
# Push down position info.
(pushPositions defs)))
);
merge = loc: defs:
zipAttrsWith (name: defs:
let merged = mergeDefinitions (loc ++ [name]) elemType defs;
# mergedValue will trigger an appropriate error when accessed
in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
)
# Push down position info.
(map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
emptyValue = { value = {}; };
getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
getSubModules = elemType.getSubModules;
substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit lazy; };
functor = defaultFunctor "attrsWith" // {
wrappedDeprecationMessage = { loc }: lib.warn ''
The deprecated `type.functor.wrapped` attribute of the option `${showOption loc}` is accessed, use `type.nestedTypes.elemType` instead.
'' elemType;
payload = {
# Important!: Add new function attributes here in case of future changes
inherit elemType lazy;
};
inherit binOp;
};
substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = elemType; };
nestedTypes.elemType = elemType;
};

Expand Down