Unverified Commit 70ab11c2 authored by Johannes Kirschbauer's avatar Johannes Kirschbauer
Browse files

lib/modules: add new merge.v2 for 'types.{either,coercedTo}'

parent 648dbed1
Loading
Loading
Loading
Loading
+29 −9
Original line number Diff line number Diff line
@@ -1165,7 +1165,12 @@ let
    # Type-check the remaining definitions, and merge them. Or throw if no definitions.
    mergedValue =
      if isDefined then
        if type.checkAndMerge or null != null then
        if type.merge ? v2 then
          # check and merge share the same closure
          # .headError is either non-present null or an error describing string
          if checkedAndMerged.headError or null != null then
            throw "A definition for option `${showOption loc}' is not of type `${type.description}'. TypeError: ${checkedAndMerged.headError}"
          else
            checkedAndMerged.value
        else if all (def: type.check def.value) defsFinal then
          type.merge loc defsFinal
@@ -1180,15 +1185,30 @@ let
        throw
          "The option `${showOption loc}' was accessed but has no value defined. Try setting the option.";

    checkedAndMerged =
      if type.checkAndMerge or null != null then
        type.checkAndMerge loc defsFinal
    ensureMergedValueEnvelope =
      v:
      if attrNames v == attrNames defaultCheckedAndMerged then
        v
      else
        {
        throw "Invalid 'type.merge.v2' of type: '${type.name}' must return exactly the following attributes: ${builtins.toJSON (attrNames defaultCheckedAndMerged)} but got ${builtins.toJSON (attrNames v)}";

    defaultCheckedAndMerged = {
      headError = null;
      value = mergedValue;
      valueMeta = { };
    };

    checkedAndMerged = ensureMergedValueEnvelope (
      if type.merge ? v2 then
        type.merge.v2 {
          inherit loc;
          defs = defsFinal;
        }
      else
        defaultCheckedAndMerged

    );

    isDefined = defsFinal != [ ];

    optionalValue = if isDefined then { value = mergedValue; } else { };
+164 −106
Original line number Diff line number Diff line
@@ -212,10 +212,6 @@ let
        # definition values and locations (e.g. [ { file = "/foo.nix";
        # value = 1; } { file = "/bar.nix"; value = 2 } ]).
        merge ? mergeDefaultOption,
        #
        # This field does not have a default implementation, so that users' changes
        # to `check` and `merge` are propagated.
        checkAndMerge ? null,
        # Whether this type has a value representing nothingness. If it does,
        # this should be a value of the form { value = <the nothing value>; }
        # If it doesn't, this should be {}
@@ -264,7 +260,6 @@ let
          deprecationMessage
          nestedTypes
          descriptionClass
          checkAndMerge
          ;
        functor =
          if functor ? wrappedDeprecationMessage then
@@ -718,9 +713,12 @@ let
          }";
          descriptionClass = "composite";
          check = isList;
          merge = loc: defs: (checkAndMerge loc defs).value;
          checkAndMerge =
            loc: defs:
          merge = {
            __functor =
              self: loc: defs:
              (self.v2 { inherit loc defs; }).value;
            v2 =
              { loc, defs }:
              let
                evals = filter (x: x.optionalValue ? value) (
                  concatLists (
@@ -740,9 +738,11 @@ let
                );
              in
              {
                headError = checkDefsForError check loc defs;
                value = map (x: x.optionalValue.value or x.mergedValue) evals;
                valueMeta.list = map (v: v.checkedAndMerged.valueMeta) evals;
              };
          };
          emptyValue = {
            value = [ ];
          };
@@ -828,9 +828,12 @@ let
            + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
          descriptionClass = "composite";
          check = isAttrs;
          merge = loc: defs: (checkAndMerge loc defs).value;
          checkAndMerge =
            loc: defs:
          merge = {
            __functor =
              self: loc: defs:
              (self.v2 { inherit loc defs; }).value;
            v2 =
              { loc, defs }:
              let
                evals =
                  if lazy then
@@ -843,6 +846,7 @@ let
                    );
              in
              {
                headError = checkDefsForError check loc defs;
                value = mapAttrs (
                  n: v:
                  if lazy then
@@ -852,6 +856,7 @@ let
                ) evals;
                valueMeta.attrs = mapAttrs (n: v: v.checkedAndMerged.valueMeta) evals;
              };
          };

          emptyValue = {
            value = { };
@@ -1234,6 +1239,7 @@ let

          name = "submodule";

          check = x: isAttrs x || isFunction x || path.check x;
        in
        mkOptionType {
          inherit name;
@@ -1245,15 +1251,13 @@ let
                docsEval = base.extendModules { modules = [ noCheckForDocsModule ]; };
              in
              docsEval._module.freeformType.description or name;
          check = x: isAttrs x || isFunction x || path.check x;
          merge =
            loc: defs:
            (base.extendModules {
              modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
              prefix = loc;
            }).config;
          checkAndMerge =
            loc: defs:
          inherit check;
          merge = {
            __functor =
              self: loc: defs:
              (self.v2 { inherit loc defs; }).value;
            v2 =
              { loc, defs }:
              let
                configuration = base.extendModules {
                  modules = [ { _module.args.name = last loc; } ] ++ allModules defs;
@@ -1261,9 +1265,11 @@ let
                };
              in
              {
                headError = checkDefsForError check loc defs;
                value = configuration.config;
                valueMeta = configuration;
              };
          };
          emptyValue = {
            value = { };
          };
@@ -1411,17 +1417,48 @@ let
              }";
          descriptionClass = "conjunction";
          check = x: t1.check x || t2.check x;
          merge =
            loc: defs:
          merge = {
            __functor =
              self: loc: defs:
              (self.v2 { inherit loc defs; }).value;
            v2 =
              { loc, defs }:
              let
              defList = map (d: d.value) defs;
            in
            if all (x: t1.check x) defList then
              t1.merge loc defs
            else if all (x: t2.check x) defList then
              t2.merge loc defs
                t1CheckedAndMerged =
                  if t1.merge ? v2 then
                    t1.merge.v2 { inherit loc defs; }
                  else
                    {
                      value = t1.merge loc defs;
                      headError = checkDefsForError t1.check loc defs;
                      valueMeta = { };
                    };
                t2CheckedAndMerged =
                  if t2.merge ? v2 then
                    t2.merge.v2 { inherit loc defs; }
                  else
              mergeOneOption loc defs;
                    {
                      value = t2.merge loc defs;
                      headError = checkDefsForError t2.check loc defs;
                      valueMeta = { };
                    };

                checkedAndMerged =
                  if t1CheckedAndMerged.headError == null then
                    t1CheckedAndMerged
                  else if t2CheckedAndMerged.headError == null then
                    t2CheckedAndMerged
                  else
                    rec {
                      valueMeta = {
                        inherit headError;
                      };
                      headError = "The option `${showOption loc}` is neither a value of type `${t1.description}` nor `${t2.description}`, Definition values: ${showDefs defs}";
                      value = null;
                    };
              in
              checkedAndMerged;
          };
          typeMerge =
            f':
            let
@@ -1462,12 +1499,43 @@ let
            optionDescriptionPhrase (class: class == "noun") coercedType
          } convertible to it";
          check = x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
          merge =
            loc: defs:
          merge = {
            __funtor =
              self: loc: defs:
              (self.v2 { inherit loc defs; }).value;
            v2 =
              { loc, defs }:
              let
                isMergeV2 = coercedType.merge ? v2;
                coerceDef =
                  def:
                  let
              coerceVal = val: if coercedType.check val then coerceFunc val else val;
                    merged = coercedType.merge.v2 {
                      inherit loc;
                      defs = [ def ];
                    };
                  in
            finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
                  if isMergeV2 then
                    if merged.headError == null then coerceFunc def.value else def.value
                  else if coercedType.check def.value then
                    coerceFunc def.value
                  else
                    def.value;

                finalDefs = (map (def: def // { value = coerceDef def; }) defs);
              in
              if finalType.merge ? v2 then
                finalType.merge.v2 {
                  inherit loc;
                  defs = finalDefs;
                }
              else
                {
                  value = finalType.merge loc finalDefs;
                  valueMeta = { };
                  headError = checkDefsForError check loc finalDefs;
                };
          };
          emptyValue = finalType.emptyValue;
          getSubOptions = finalType.getSubOptions;
          getSubModules = finalType.getSubModules;
@@ -1490,25 +1558,15 @@ let
      */
      addCheck =
        elemType: check:
        elemType
        // {
          check = x: elemType.check x && check x;
        }
        // (lib.optionalAttrs (elemType.checkAndMerge != null) {
          checkAndMerge =
            loc: defs:
        let
              v = (elemType.checkAndMerge loc defs);
            in
            if all (def: elemType.check def.value && check def.value) defs then
              v
            else
              let
                allInvalid = filter (def: !elemType.check def.value || !check def.value) defs;
          final = elemType // {
            check = x: elemType.check x && check x;
            # addCheck discards the merge.v2 function
            #
            merge = if elemType.merge ? v2 then elemType.merge.__functor { inherit final; } else elemType.merge;
          };
        in
              throw "A definition for option `${showOption loc}' is not of type `${elemType.description}'. Definition values:${showDefs allInvalid}";
        });

        final;
    };

    /**