1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-08 11:36:03 +01:00

Compare commits

...

8 commits

Author SHA1 Message Date
John Ericson
479b6b73a9
Merge pull request #14509 from Mic92/no-tbb
build: Disable libstdc++ TBB backend to avoid unnecessary dependency
2025-11-07 20:42:34 +00:00
John Ericson
3c2dcf42e9
Merge pull request #14477 from lovesegfault/http-upload-headers
refactor(libstore): pass headers into upload methods
2025-11-07 20:41:14 +00:00
John Ericson
5a97c00f29
Merge pull request #14499 from roberth/genericClosure-errors
`builtins.genericClosure`: improve errors
2025-11-07 20:10:34 +00:00
Jörg Thalheim
2f3ec16793 build: Disable libstdc++ TBB backend to avoid unnecessary dependency
boost::concurrent_flat_map (used in libutil and libstore) includes the
C++17 <execution> header. GCC's libstdc++ implements parallel algorithms
using Intel TBB as the backend, which creates a link-time dependency on
libtbb even though we don't actually use any parallel algorithms.

Disable the TBB backend for libstdc++ by setting
_GLIBCXX_USE_TBB_PAR_BACKEND=0. This makes parallel algorithms fall back
to serial execution, which is acceptable since we don't use them anyway.

This only affects libstdc++ (GCC's standard library); other standard
libraries like libc++ (LLVM) are unaffected.
2025-11-07 20:58:46 +01:00
Robert Hensing
3ee8e45f8e tests: Replace fragile genericClosure unit tests
We now have functional tests for these. The unit tests added negligible
value while imposing a much higher maintenance cost.

The maintenance cost is high:
  - No automatic accept option
  - They broke 5+ times during this session due to implementation changes (trace count, ordering)
  - They require understanding ANSI escape codes, Uncolored() wrappers, trace reversal
  - They test empty traces HintFmt("") from withTrace(pos, "") - pure implementation detail
  - They're fragile: adding any trace anywhere breaks the exact count assertions

The additional value over functional tests is minimal:
  - Functional tests already verify the error message
  - Functional tests already show trace order and content (as users see it, helps review)
  - Unit tests verify "exactly 3 traces, not 2 or 4" - but users don't count traces
  - Unit tests verify empty traces exist - but users never see them

The white-box testing catches the wrong things:
  - It catches "you added helpful context" as a failure
  - It doesn't catch "the context is confusing" (which functional tests would show)
  - It enforces implementation details that should be allowed to evolve
2025-11-07 00:27:39 +01:00
Robert Hensing
d262efc240 libexpr: improve error messages for builtins.genericClosure
Show which element(s) are involved at each error point:

- When an element is missing the "key" attribute, show the element
- When an element is not an attribute set, show the element
- When comparing keys fails, show both elements being compared
- When calling operator fails, show which element was being processed

This provides concrete context using ValuePrinter with errorPrintOptions.

Note: errorPrintOptions uses maxDepth=10 by default, which may print
quite deeply nested structures in error messages. This could potentially
be overwhelming, but follows the existing default for error contexts.
2025-11-06 22:28:49 +01:00
Robert Hensing
ca787bc3e0 tests: add error tests for builtins.genericClosure
Covers error conditions for:
- Invalid argument types (not an attrset)
- Missing required attributes (startSet, operator)
- Type mismatches (startSet/operator not correct type)
- Element validation (elements not attrsets, missing key attribute)
- Key comparison errors (incompatible types, uncomparable types)
- Operator return value validation (not a list)
2025-11-06 21:33:41 +01:00
Bernardo Meurer Costa
a0d4714073
refactor(libstore): pass headers into upload methods
This will make it easier to attach additional headers (e.g. storage
class) on the s3 side of things and makes `Content-Encoding` less
special.
2025-11-04 22:55:32 +00:00
29 changed files with 345 additions and 126 deletions

View file

@ -42,6 +42,26 @@ if cxx.get_id() == 'clang'
add_project_arguments('-fpch-instantiate-templates', language : 'cpp')
endif
# Detect if we're using libstdc++ (GCC's standard library)
# libstdc++ uses Intel TBB as backend for C++17 parallel algorithms when <execution> is included.
# boost::concurrent_flat_map includes <execution>, which would require linking against TBB.
# Since we don't actually use parallel algorithms, disable the TBB backend to avoid the dependency.
# TBB is a dependency of blake3 and leaking into our build environment.
is_using_libstdcxx = cxx.compiles(
'''
#include <ciso646>
#ifndef __GLIBCXX__
#error "not libstdc++"
#endif
int main() { return 0; }
''',
name : 'using libstdc++',
)
if is_using_libstdcxx
add_project_arguments('-D_GLIBCXX_USE_TBB_PAR_BACKEND=0', language : 'cpp')
endif
# Darwin ld doesn't like "X.Y.ZpreABCD+W"
nix_soversion = meson.project_version().split('+')[0].split('pre')[0]

View file

@ -139,63 +139,6 @@ TEST_F(ErrorTraceTest, NestedThrows)
#define ASSERT_DERIVATION_TRACE3(args, type, message, context1, context2) \
ASSERT_TRACE4(args, type, message, context1, context2, DERIVATION_TRACE_HINTFMT("foo"))
TEST_F(ErrorTraceTest, genericClosure)
{
ASSERT_TRACE2(
"genericClosure 1",
TypeError,
HintFmt("expected a set but found %s: %s", "an integer", Uncolored(ANSI_CYAN "1" ANSI_NORMAL)),
HintFmt("while evaluating the first argument passed to builtins.genericClosure"));
ASSERT_TRACE2(
"genericClosure {}",
TypeError,
HintFmt("attribute '%s' missing", "startSet"),
HintFmt("in the attrset passed as argument to builtins.genericClosure"));
ASSERT_TRACE2(
"genericClosure { startSet = 1; }",
TypeError,
HintFmt("expected a list but found %s: %s", "an integer", Uncolored(ANSI_CYAN "1" ANSI_NORMAL)),
HintFmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"));
ASSERT_TRACE2(
"genericClosure { startSet = [{ key = 1;}]; operator = true; }",
TypeError,
HintFmt("expected a function but found %s: %s", "a Boolean", Uncolored(ANSI_CYAN "true" ANSI_NORMAL)),
HintFmt("while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"));
ASSERT_TRACE2(
"genericClosure { startSet = [{ key = 1;}]; operator = item: true; }",
TypeError,
HintFmt("expected a list but found %s: %s", "a Boolean", Uncolored(ANSI_CYAN "true" ANSI_NORMAL)),
HintFmt("while evaluating the return value of the `operator` passed to builtins.genericClosure"));
ASSERT_TRACE2(
"genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }",
TypeError,
HintFmt("expected a set but found %s: %s", "a Boolean", Uncolored(ANSI_CYAN "true" ANSI_NORMAL)),
HintFmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
ASSERT_TRACE2(
"genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
TypeError,
HintFmt("attribute '%s' missing", "key"),
HintFmt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure"));
ASSERT_TRACE2(
"genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }",
EvalError,
HintFmt("cannot compare %s with %s", "a string", "an integer"),
HintFmt("while comparing the `key` attributes of two genericClosure elements"));
ASSERT_TRACE2(
"genericClosure { startSet = [ true ]; operator = item: [{ key = ''a''; }]; }",
TypeError,
HintFmt("expected a set but found %s: %s", "a Boolean", Uncolored(ANSI_CYAN "true" ANSI_NORMAL)),
HintFmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
}
TEST_F(ErrorTraceTest, replaceStrings)
{
ASSERT_TRACE2(
@ -1050,17 +993,35 @@ TEST_F(ErrorTraceTest, bitXor)
TEST_F(ErrorTraceTest, lessThan)
{
ASSERT_TRACE1("lessThan 1 \"foo\"", EvalError, HintFmt("cannot compare %s with %s", "an integer", "a string"));
ASSERT_TRACE1(
"lessThan 1 \"foo\"",
EvalError,
HintFmt(
"cannot compare %s with %s; values are %s and %s",
"an integer",
"a string",
Uncolored(ANSI_CYAN "1" ANSI_NORMAL),
Uncolored(ANSI_MAGENTA "\"foo\"" ANSI_NORMAL)));
ASSERT_TRACE1(
"lessThan {} {}",
EvalError,
HintFmt("cannot compare %s with %s; values of that type are incomparable", "a set", "a set"));
HintFmt(
"cannot compare %s with %s; values of that type are incomparable (values are %s and %s)",
"a set",
"a set",
Uncolored("{ }"),
Uncolored("{ }")));
ASSERT_TRACE2(
"lessThan [ 1 2 ] [ \"foo\" ]",
EvalError,
HintFmt("cannot compare %s with %s", "an integer", "a string"),
HintFmt(
"cannot compare %s with %s; values are %s and %s",
"an integer",
"a string",
Uncolored(ANSI_CYAN "1" ANSI_NORMAL),
Uncolored(ANSI_MAGENTA "\"foo\"" ANSI_NORMAL)),
HintFmt("while comparing two list elements"));
}

View file

@ -681,7 +681,14 @@ struct CompareValues
if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer().value < v2->fpoint();
if (v1->type() != v2->type())
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
state
.error<EvalError>(
"cannot compare %s with %s; values are %s and %s",
showType(*v1),
showType(*v2),
ValuePrinter(state, *v1, errorPrintOptions),
ValuePrinter(state, *v2, errorPrintOptions))
.debugThrow();
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
@ -711,7 +718,11 @@ struct CompareValues
default:
state
.error<EvalError>(
"cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2))
"cannot compare %s with %s; values of that type are incomparable (values are %s and %s)",
showType(*v1),
showType(*v2),
ValuePrinter(state, *v1, errorPrintOptions),
ValuePrinter(state, *v2, errorPrintOptions))
.debugThrow();
#pragma GCC diagnostic pop
}
@ -757,31 +768,60 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value ** ar
`workSet', adding the result to `workSet', continuing until
no new elements are found. */
ValueList res;
// `doneKeys' doesn't need to be a GC root, because its values are
// reachable from res.
auto cmp = CompareValues(state, noPos, "while comparing the `key` attributes of two genericClosure elements");
std::set<Value *, decltype(cmp)> doneKeys(cmp);
// Track which element each key came from
auto cmp = CompareValues(state, noPos, "");
std::map<Value *, Value *, decltype(cmp)> keyToElem(cmp);
while (!workSet.empty()) {
Value * e = *(workSet.begin());
workSet.pop_front();
state.forceAttrs(
*e,
noPos,
"while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure");
try {
state.forceAttrs(*e, noPos, "");
} catch (Error & err) {
err.addTrace(nullptr, "in genericClosure element %s", ValuePrinter(state, *e, errorPrintOptions));
throw;
}
auto key = state.getAttr(
state.s.key,
e->attrs(),
"in one of the attrsets generated by (or initially passed to) builtins.genericClosure");
const Attr * key;
try {
key = state.getAttr(state.s.key, e->attrs(), "");
} catch (Error & err) {
err.addTrace(nullptr, "in genericClosure element %s", ValuePrinter(state, *e, errorPrintOptions));
throw;
}
state.forceValue(*key->value, noPos);
if (!doneKeys.insert(key->value).second)
try {
auto [it, inserted] = keyToElem.insert({key->value, e});
if (!inserted)
continue;
} catch (Error & err) {
// Try to find which element we're comparing against
Value * otherElem = nullptr;
for (auto & [otherKey, elem] : keyToElem) {
try {
cmp(key->value, otherKey);
} catch (Error &) {
// Found the element we're comparing against
otherElem = elem;
break;
}
}
if (otherElem) {
// Traces are printed in reverse order; pre-swap them.
err.addTrace(nullptr, "with element %s", ValuePrinter(state, *otherElem, errorPrintOptions));
err.addTrace(nullptr, "while comparing element %s", ValuePrinter(state, *e, errorPrintOptions));
} else {
// Couldn't find the specific element, just show current
err.addTrace(nullptr, "while checking key of element %s", ValuePrinter(state, *e, errorPrintOptions));
}
throw;
}
res.push_back(e);
/* Call the `operator' function with `e' as argument. */
Value newElements;
try {
state.callFunction(*op->value, {&e, 1}, newElements, noPos);
state.forceList(
newElements,
@ -794,6 +834,14 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value ** ar
// passed to builtins.genericClosure");
workSet.push_back(elem);
}
} catch (Error & err) {
err.addTrace(
nullptr,
"while calling %s on genericClosure element %s",
state.symbols[state.s.operator_],
ValuePrinter(state, *e, errorPrintOptions));
throw;
}
}
/* Create the result list. */

View file

@ -4,7 +4,6 @@
#include "nix/store/nar-info-disk-cache.hh"
#include "nix/util/callback.hh"
#include "nix/store/store-registration.hh"
#include "nix/util/compression.hh"
namespace nix {
@ -139,13 +138,14 @@ void HttpBinaryCacheStore::upload(
RestartableSource & source,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding)
std::optional<Headers> headers)
{
auto req = makeRequest(path);
req.method = HttpMethod::PUT;
if (contentEncoding) {
req.headers.emplace_back("Content-Encoding", *contentEncoding);
if (headers) {
req.headers.reserve(req.headers.size() + headers->size());
std::ranges::move(std::move(*headers), std::back_inserter(req.headers));
}
req.data = {sizeHint, source};
@ -154,18 +154,14 @@ void HttpBinaryCacheStore::upload(
getFileTransfer()->upload(req);
}
void HttpBinaryCacheStore::upload(std::string_view path, CompressedSource & source, std::string_view mimeType)
{
upload(path, static_cast<RestartableSource &>(source), source.size(), mimeType, source.getCompressionMethod());
}
void HttpBinaryCacheStore::upsertFile(
const std::string & path, RestartableSource & source, const std::string & mimeType, uint64_t sizeHint)
{
try {
if (auto compressionMethod = getCompressionMethod(path)) {
CompressedSource compressed(source, *compressionMethod);
upload(path, compressed, mimeType);
Headers headers = {{"Content-Encoding", *compressionMethod}};
upload(path, compressed, compressed.size(), mimeType, std::move(headers));
} else {
upload(path, source, sizeHint, mimeType, std::nullopt);
}

View file

@ -103,18 +103,7 @@ protected:
RestartableSource & source,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding);
/**
* Uploads data to the binary cache (CompressedSource overload).
*
* This overload infers both the size and compression method from the CompressedSource.
*
* @param path The path in the binary cache to upload to
* @param source The compressed source (knows size and compression method)
* @param mimeType The MIME type of the content
*/
void upload(std::string_view path, CompressedSource & source, std::string_view mimeType);
std::optional<Headers> headers);
void getFile(const std::string & path, Sink & sink) override;

View file

@ -50,7 +50,7 @@ private:
RestartableSource & source,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding);
std::optional<Headers> headers);
/**
* Uploads a file to S3 using multipart upload.
@ -67,7 +67,7 @@ private:
RestartableSource & source,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding);
std::optional<Headers> headers);
/**
* A Sink that manages a complete S3 multipart upload lifecycle.
@ -89,7 +89,7 @@ private:
std::string_view path,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding);
std::optional<Headers> headers);
void operator()(std::string_view data) override;
void finish();
@ -102,8 +102,7 @@ private:
* @see
* https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html#API_CreateMultipartUpload_RequestSyntax
*/
std::string createMultipartUpload(
std::string_view key, std::string_view mimeType, std::optional<std::string_view> contentEncoding);
std::string createMultipartUpload(std::string_view key, std::string_view mimeType, std::optional<Headers> headers);
/**
* Uploads a single part of a multipart upload
@ -134,18 +133,19 @@ private:
void S3BinaryCacheStore::upsertFile(
const std::string & path, RestartableSource & source, const std::string & mimeType, uint64_t sizeHint)
{
auto doUpload = [&](RestartableSource & src, uint64_t size, std::optional<std::string_view> encoding) {
auto doUpload = [&](RestartableSource & src, uint64_t size, std::optional<Headers> headers) {
if (s3Config->multipartUpload && size > s3Config->multipartThreshold) {
uploadMultipart(path, src, size, mimeType, encoding);
uploadMultipart(path, src, size, mimeType, std::move(headers));
} else {
upload(path, src, size, mimeType, encoding);
upload(path, src, size, mimeType, std::move(headers));
}
};
try {
if (auto compressionMethod = getCompressionMethod(path)) {
CompressedSource compressed(source, *compressionMethod);
doUpload(compressed, compressed.size(), compressed.getCompressionMethod());
Headers headers = {{"Content-Encoding", *compressionMethod}};
doUpload(compressed, compressed.size(), std::move(headers));
} else {
doUpload(source, sizeHint, std::nullopt);
}
@ -161,7 +161,7 @@ void S3BinaryCacheStore::upload(
RestartableSource & source,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding)
std::optional<Headers> headers)
{
debug("using S3 regular upload for '%s' (%d bytes)", path, sizeHint);
if (sizeHint > AWS_MAX_PART_SIZE)
@ -170,7 +170,7 @@ void S3BinaryCacheStore::upload(
renderSize(sizeHint),
renderSize(AWS_MAX_PART_SIZE));
HttpBinaryCacheStore::upload(path, source, sizeHint, mimeType, contentEncoding);
HttpBinaryCacheStore::upload(path, source, sizeHint, mimeType, std::move(headers));
}
void S3BinaryCacheStore::uploadMultipart(
@ -178,10 +178,10 @@ void S3BinaryCacheStore::uploadMultipart(
RestartableSource & source,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding)
std::optional<Headers> headers)
{
debug("using S3 multipart upload for '%s' (%d bytes)", path, sizeHint);
MultipartSink sink(*this, path, sizeHint, mimeType, contentEncoding);
MultipartSink sink(*this, path, sizeHint, mimeType, std::move(headers));
source.drainInto(sink);
sink.finish();
}
@ -191,7 +191,7 @@ S3BinaryCacheStore::MultipartSink::MultipartSink(
std::string_view path,
uint64_t sizeHint,
std::string_view mimeType,
std::optional<std::string_view> contentEncoding)
std::optional<Headers> headers)
: store(store)
, path(path)
{
@ -227,7 +227,7 @@ S3BinaryCacheStore::MultipartSink::MultipartSink(
buffer.reserve(chunkSize);
partEtags.reserve(estimatedParts);
uploadId = store.createMultipartUpload(path, mimeType, contentEncoding);
uploadId = store.createMultipartUpload(path, mimeType, std::move(headers));
}
void S3BinaryCacheStore::MultipartSink::operator()(std::string_view data)
@ -279,7 +279,7 @@ void S3BinaryCacheStore::MultipartSink::uploadChunk(std::string chunk)
}
std::string S3BinaryCacheStore::createMultipartUpload(
std::string_view key, std::string_view mimeType, std::optional<std::string_view> contentEncoding)
std::string_view key, std::string_view mimeType, std::optional<Headers> headers)
{
auto req = makeRequest(key);
@ -296,8 +296,9 @@ std::string S3BinaryCacheStore::createMultipartUpload(
req.data = {payload};
req.mimeType = mimeType;
if (contentEncoding) {
req.headers.emplace_back("Content-Encoding", *contentEncoding);
if (headers) {
req.headers.reserve(req.headers.size() + headers->size());
std::move(headers->begin(), headers->end(), std::back_inserter(req.headers));
}
auto result = getFileTransfer()->enqueueFileTransfer(req).get();

View file

@ -0,0 +1,18 @@
error:
… while calling the 'seq' builtin
at /pwd/lang/eval-fail-genericClosure-deeply-nested-element.nix:25:1:
24| in
25| builtins.seq finiteVal (
| ^
26| builtins.genericClosure {
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-deeply-nested-element.nix:26:3:
25| builtins.seq finiteVal (
26| builtins.genericClosure {
| ^
27| startSet = [
… in genericClosure element { finite = { a0 = { a1 = { a2 = { a3 = { a4 = { a5 = { a6 = { a7 = { a8 = { ... }; }; }; }; }; }; }; }; }; }; «1 attribute elided» }
error: attribute 'key' missing

View file

@ -0,0 +1,35 @@
let
finite = {
a0 = {
a1 = {
a2 = {
a3 = {
a4 = {
a5 = {
a6 = {
a7 = {
a8 = {
a9 = "deep";
};
};
};
};
};
};
};
};
};
};
finiteVal = builtins.deepSeq finite finite;
in
builtins.seq finiteVal (
builtins.genericClosure {
startSet = [
{
infinite = import ./infinite-nesting.nix;
finite = finiteVal;
}
];
operator = x: [ (import ./infinite-nesting.nix) ];
}
)

View file

@ -0,0 +1,10 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-element-missing-key.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = [ { nokey = 1; } ];
… in genericClosure element { nokey = 1; }
error: attribute 'key' missing

View file

@ -0,0 +1,4 @@
builtins.genericClosure {
startSet = [ { nokey = 1; } ];
operator = x: [ ];
}

View file

@ -0,0 +1,10 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-element-not-attrset.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = [ "not an attrset" ];
… in genericClosure element "not an attrset"
error: expected a set but found a string: "not an attrset"

View file

@ -0,0 +1,4 @@
builtins.genericClosure {
startSet = [ "not an attrset" ];
operator = x: [ ];
}

View file

@ -0,0 +1,12 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-keys-incompatible-types.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = [
… while comparing element { key = "string"; }
… with element { key = 1; }
error: cannot compare a string with an integer; values are "string" and 1

View file

@ -0,0 +1,7 @@
builtins.genericClosure {
startSet = [
{ key = 1; }
{ key = "string"; }
];
operator = x: [ ];
}

View file

@ -0,0 +1,12 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-keys-uncomparable.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = [
… while comparing element { key = { }; }
… with element { key = { }; }
error: cannot compare a set with a set; values of that type are incomparable (values are { } and { })

View file

@ -0,0 +1,7 @@
builtins.genericClosure {
startSet = [
{ key = { }; }
{ key = { }; }
];
operator = x: [ ];
}

View file

@ -0,0 +1,10 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-missing-operator.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = [ { key = 1; } ];
… in the attrset passed as argument to builtins.genericClosure
error: attribute 'operator' missing

View file

@ -0,0 +1,3 @@
builtins.genericClosure {
startSet = [ { key = 1; } ];
}

View file

@ -0,0 +1,10 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-missing-startSet.nix:1:1:
1| builtins.genericClosure {
| ^
2| operator = x: [ ];
… in the attrset passed as argument to builtins.genericClosure
error: attribute 'startSet' missing

View file

@ -0,0 +1,3 @@
builtins.genericClosure {
operator = x: [ ];
}

View file

@ -0,0 +1,10 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-not-attrset.nix:1:1:
1| builtins.genericClosure "not an attrset"
| ^
2|
… while evaluating the first argument passed to builtins.genericClosure
error: expected a set but found a string: "not an attrset"

View file

@ -0,0 +1 @@
builtins.genericClosure "not an attrset"

View file

@ -0,0 +1,10 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-operator-not-function.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = [ { key = 1; } ];
… while evaluating the 'operator' attribute passed as argument to builtins.genericClosure
error: expected a function but found a string: "not a function"

View file

@ -0,0 +1,4 @@
builtins.genericClosure {
startSet = [ { key = 1; } ];
operator = "not a function";
}

View file

@ -0,0 +1,12 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-operator-not-list.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = [ { key = 1; } ];
… while calling operator on genericClosure element { key = 1; }
… while evaluating the return value of the `operator` passed to builtins.genericClosure
error: expected a list but found a string: "not a list"

View file

@ -0,0 +1,4 @@
builtins.genericClosure {
startSet = [ { key = 1; } ];
operator = x: "not a list";
}

View file

@ -0,0 +1,10 @@
error:
… while calling the 'genericClosure' builtin
at /pwd/lang/eval-fail-genericClosure-startSet-not-list.nix:1:1:
1| builtins.genericClosure {
| ^
2| startSet = "not a list";
… while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure
error: expected a list but found a string: "not a list"

View file

@ -0,0 +1,4 @@
builtins.genericClosure {
startSet = "not a list";
operator = x: [ ];
}

View file

@ -0,0 +1,4 @@
let
mkInfinite = i: { "a${toString i}" = mkInfinite (i + 1); };
in
mkInfinite 0