query
On this page

listToAttrs

lib.attrsets.listToAttrs

Primop
Docs pulled from | This Revision | about 23 hours ago


Nix manual

Takes 1 arguments

e

Construct a set from a list specifying the names and values of each attribute. Each element of the list should be a set consisting of a string-valued attribute name specifying the name of the attribute, and an attribute value specifying its value.

In case of duplicate occurrences of the same name, the first takes precedence.

Example:

builtins.listToAttrs
  [ { name = "foo"; value = 123; }
    { name = "bar"; value = 456; }
    { name = "bar"; value = 420; }
  ]

evaluates to

{ foo = 123; bar = 456; }

Time Complexity

O(n log n) where n = number of list elements

Noogle detected

Aliases

Detected Type
listToAttrs :: [{name :: String; value :: a}] -> AttrSet

Implementation

This function is implemented in c++ and is part of the native nix runtime.

src/libexpr/primops.cc:3311

static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
    state.forceList(*args[0], pos, "while evaluating the argument passed to builtins.listToAttrs");

    // Step 1. Sort the name-value attrsets in place using the memory we allocate for the result
    auto listView = args[0]->listView();
    size_t listSize = listView.size();
    auto & bindings = *state.mem.allocBindings(listSize);
    using ElemPtr = decltype(&bindings[0].value);

    for (const auto & [n, v2] : enumerate(listView)) {
        state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.listToAttrs");

        auto j = state.getAttr(state.s.name, v2->attrs(), "in a {name=...; value=...;} pair");

        auto name = state.forceStringNoCtx(
            *j->value,
            j->pos,
            "while evaluating the `name` attribute of an element of the list passed to builtins.listToAttrs");
        auto sym = state.symbols.create(name);

        // (ab)use Attr to store a Value * * instead of a Value *, so that we can stabilize the sort using the Value * *
        bindings[n] = Attr(sym, std::bit_cast<Value *>(&v2));
    }

    std::sort(&bindings[0], &bindings[listSize], [](const Attr & a, const Attr & b) {
        // Note that .value is actually a Value * * that corresponds to the position in the list
        return a < b || (!(a > b) && std::bit_cast<ElemPtr>(a.value) < std::bit_cast<ElemPtr>(b.value));
    });

    // Step 2. Unpack the bindings in place and skip name-value pairs with duplicate names
    Symbol prev;
    for (size_t n = 0; n < listSize; n++) {
        auto attr = bindings[n];
        if (prev == attr.name) {
            continue;
        }
        // Note that .value is actually a Value * *; see earlier comments
        Value * v2 = *std::bit_cast<ElemPtr>(attr.value);

        auto j = state.getAttr(state.s.value, v2->attrs(), "in a {name=...; value=...;} pair");
        prev = attr.name;
        bindings.push_back({prev, j->value, j->pos});
    }
    // help GC and clear end of allocated array
    for (size_t n = bindings.size(); n < listSize; n++) {
        bindings[n] = Attr{};
    }
    v.mkAttrs(&bindings);
}