query
On this page

mergeDefinitions

lib.mergeDefinitions

Docs pulled from | This Revision | 19 minutes ago


Nixpkgs manual

Merge definitions of a value of a given type.

Noogle detected

Aliases

Implementation

The following is the current implementation of this function.

mergeDefinitions = loc: type: defs: rec {
    defsFinal' =
      # Fast path: the overwhelming majority of options have exactly one
      # definition whose value carries no property wrapper
      # (mkIf/mkMerge/mkOverride/mkOrder/definition). In that case the
      # discharge/filter/sort pipeline below is a no-op but still allocates
      # several intermediate lists and closures. Detect it up front and hand the
      # original singleton straight to the type merge.
      if
        length defs == 1
        && addErrorContext "while evaluating definitions from `${(head defs).file}':" (
          !(head defs).value ? _type
        )
      then
        {
          values = defs;
          highestPrio = defaultOverridePriority;
        }
      else
        let
          # Process mkMerge and mkIf properties.
          defsNormalized = concatMap (
            m:
            map (
              value:
              if value._type or null == "definition" then
                value
              else
                {
                  inherit (m) file;
                  inherit value;
                }
            ) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
          ) defs;

          # Process mkOverride properties.
          defsFiltered = filterOverrides' defsNormalized;

          # Sort mkOrder properties.
          defsSorted =
            # Avoid sorting if we don't have to.
            if any (def: def.value._type or "" == "order") defsFiltered.values then
              sortProperties defsFiltered.values
            else
              defsFiltered.values;
        in
        {
          values = defsSorted;
          inherit (defsFiltered) highestPrio;
        };
    defsFinal = defsFinal'.values;

    # Type-check the remaining definitions, and merge them. Or throw if no definitions.
    mergedValue =
      if isDefined then
        if type.merge ? v2 then
          # check and merge share the same closure
          # .headError is either not-present, null, or a string describing the error
          if checkedAndMerged.headError or null != null then
            throw "A definition for option `${showOption loc}' is not of type `${type.description}'. TypeError: ${checkedAndMerged.headError.message}"
          else
            checkedAndMerged.value
        else if all (def: type.check def.value) defsFinal then
          type.merge loc defsFinal
        else
          let
            allInvalid = filter (def: !type.check def.value) defsFinal;
          in
          throw "A definition for option `${showOption loc}' is not of type `${type.description}'. Definition values:${showDefs allInvalid}"
      else if type.emptyValue ? value then
        type.emptyValue.value
      else
        # (nixos-option detects this specific error message and gives it special
        # handling.  If changed here, please change it there too.)
        throw
          "The option `${showOption loc}' was accessed but has no value defined. Try setting the option.";

    checkedAndMerged =
      (
        # This function (which is immediately applied) checks that type.merge
        # returns the proper attrset.
        # Once use of the merge.v2 feature has propagated, consider removing this
        # for an estimated one thousandth performance improvement (NixOS by nr.thunks).
        {
          headError,
          value,
          valueMeta,
        }@args:
        args
      )
        (
          if type.merge ? v2 then
            let
              # Check for v2 merge coherence
              r = checkV2MergeCoherence loc type (
                type.merge.v2 {
                  inherit loc;
                  defs = defsFinal;
                }
              );
            in
            r
            // {
              valueMeta = r.valueMeta // {
                _internal = {
                  inherit type;
                };
              };
            }
          else
            {
              headError = null;
              value = mergedValue;
              valueMeta = { };
            }
        );

    isDefined = defsFinal != [ ];

    optionalValue = if isDefined then { value = mergedValue; } else { };
  };