query
On this page

attrTag

lib.types.attrTag

Docs pulled from | This Revision | 29 minutes ago


Contribute
Enhance the ecosystem with your expertise! Contribute to fill the gaps in documentation. Your input can make a difference.

Noogle detected

Implementation

The following is the current implementation of this function.

tags:
    let
      tags_ = tags;
    in
    let
      tags = mapAttrs (
        n: opt:
        builtins.addErrorContext
          "while checking that attrTag tag ${lib.strings.escapeNixIdentifier n} is an option with a type${inAttrPosSuffix tags_ n}"
          (
            throwIf (opt._type or null != "option")
              "In attrTag, each tag value must be an option, but tag ${lib.strings.escapeNixIdentifier n} ${
                if opt ? _type then
                  if opt._type == "option-type" then
                    "was a bare type, not wrapped in mkOption."
                  else
                    "was of type ${lib.strings.escapeNixString opt._type}."
                else
                  "was not."
              }"
              opt
            // {
              declarations =
                opt.declarations or (
                  let
                    pos = builtins.unsafeGetAttrPos n tags_;
                  in
                  if pos == null then [ ] else [ pos.file ]
                );
              declarationPositions =
                opt.declarationPositions or (
                  let
                    pos = builtins.unsafeGetAttrPos n tags_;
                  in
                  if pos == null then [ ] else [ pos ]
                );
            }
          )
      ) tags_;
      choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags);
    in
    mkOptionType {
      name = "attrTag";
      description = "attribute-tagged union with choices: ${choicesStr}";
      descriptionClass = "noun";
      getSubOptions =
        prefix: mapAttrs (tagName: tagOption: tagOption // { loc = prefix ++ [ tagName ]; }) tags;
      check = v: isAttrs v && length (attrNames v) == 1 && tags ? ${head (attrNames v)};
      merge =
        loc: defs:
        let
          choice = head (attrNames (head defs).value);
          checkedValueDefs = map (
            def:
            assert (length (attrNames def.value)) == 1;
            if (head (attrNames def.value)) != choice then
              throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}."
            else
              {
                inherit (def) file;
                value = def.value.${choice};
              }
          ) defs;
        in
        if tags ? ${choice} then
          {
            ${choice} = (lib.modules.evalOptionValue (loc ++ [ choice ]) tags.${choice} checkedValueDefs).value;
          }
        else
          throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}.";
      nestedTypes = tags;
      functor = defaultFunctor "attrTag" // {
        type = { tags, ... }: lib.types.attrTag tags;
        payload = { inherit tags; };
        binOp =
          let
            # Add metadata in the format that submodules work with
            wrapOptionDecl = option: {
              options = option;
              _file = "<attrTag {...}>";
              pos = null;
            };
          in
          a: b: {
            tags =
              a.tags
              // b.tags
              // mapAttrs (
                tagName: bOpt:
                lib.mergeOptionDecls
                  # FIXME: loc is not accurate; should include prefix
                  #        Fortunately, it's only used for error messages, where a "relative" location is kinda ok.
                  #        It is also returned though, but use of the attribute seems rare?
                  [ tagName ]
                  [
                    (wrapOptionDecl a.tags.${tagName})
                    (wrapOptionDecl bOpt)
                  ]
                // {
                  # mergeOptionDecls is not idempotent in these attrs:
                  declarations = a.tags.${tagName}.declarations ++ bOpt.declarations;
                  declarationPositions = a.tags.${tagName}.declarationPositions ++ bOpt.declarationPositions;
                }
              ) (builtins.intersectAttrs a.tags b.tags);
          };
      };
    }