Merge pull request #284551 from hercules-ci/types-attrTag

Add `types.attrTag`
This commit is contained in:
Silvan Mosberger 2024-04-09 20:48:44 +02:00 committed by GitHub
commit 4f1d724b82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 387 additions and 19 deletions

View file

@ -15,6 +15,7 @@ let
isList
isString
isStorePath
throwIf
toDerivation
toList
;
@ -65,6 +66,11 @@ let
fixupOptionType
mergeOptionDecls
;
inAttrPosSuffix = v: name:
let pos = builtins.unsafeGetAttrPos name v; in
if pos == null then "" else " at ${pos.file}:${toString pos.line}:${toString pos.column}";
outer_types =
rec {
__attrsFailEvaluation = true;
@ -152,7 +158,7 @@ rec {
# If it doesn't, this should be {}
# This may be used when a value is required for `mkIf false`. This allows the extra laziness in e.g. `lazyAttrsOf`.
emptyValue ? {}
, # Return a flat list of sub-options. Used to generate
, # Return a flat attrset of sub-options. Used to generate
# documentation.
getSubOptions ? prefix: {}
, # List of modules if any, or null if none.
@ -623,6 +629,100 @@ rec {
nestedTypes.elemType = elemType;
};
attrTag = 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";
descriptionClass = "noun";
getSubOptions = prefix:
mapAttrs
(tagName: tagOption: {
"${lib.showOption prefix}" =
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, ... }: 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);
};
};
};
uniq = unique { message = ""; };
unique = { message }: type: mkOptionType rec {