diff --git a/nix-meson-build-support/common/meson.build b/nix-meson-build-support/common/meson.build index 1405974d2..5fcf557e7 100644 --- a/nix-meson-build-support/common/meson.build +++ b/nix-meson-build-support/common/meson.build @@ -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 is included. +# boost::concurrent_flat_map includes , 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 + #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] diff --git a/src/libexpr-tests/error_traces.cc b/src/libexpr-tests/error_traces.cc index 7e7b5eb84..e722cc484 100644 --- a/src/libexpr-tests/error_traces.cc +++ b/src/libexpr-tests/error_traces.cc @@ -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")); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index d1aae64fa..0bd03b232 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -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("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow(); + state + .error( + "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( - "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,42 +768,79 @@ 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 doneKeys(cmp); + // Track which element each key came from + auto cmp = CompareValues(state, noPos, ""); + std::map 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) - continue; + 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; - state.callFunction(*op->value, {&e, 1}, newElements, noPos); - state.forceList( - newElements, - noPos, - "while evaluating the return value of the `operator` passed to builtins.genericClosure"); + try { + state.callFunction(*op->value, {&e, 1}, newElements, noPos); + state.forceList( + newElements, + noPos, + "while evaluating the return value of the `operator` passed to builtins.genericClosure"); - /* Add the values returned by the operator to the work set. */ - for (auto elem : newElements.listView()) { - state.forceValue(*elem, noPos); // "while evaluating one one of the elements returned by the `operator` - // passed to builtins.genericClosure"); - workSet.push_back(elem); + /* Add the values returned by the operator to the work set. */ + for (auto elem : newElements.listView()) { + state.forceValue(*elem, noPos); // "while evaluating one one of the elements returned by the `operator` + // 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; } } diff --git a/src/libstore/http-binary-cache-store.cc b/src/libstore/http-binary-cache-store.cc index fdb7c74fc..555da12ea 100644 --- a/src/libstore/http-binary-cache-store.cc +++ b/src/libstore/http-binary-cache-store.cc @@ -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 contentEncoding) + std::optional 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(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); } diff --git a/src/libstore/include/nix/store/http-binary-cache-store.hh b/src/libstore/include/nix/store/http-binary-cache-store.hh index b092b5b5e..ea3d77b79 100644 --- a/src/libstore/include/nix/store/http-binary-cache-store.hh +++ b/src/libstore/include/nix/store/http-binary-cache-store.hh @@ -103,18 +103,7 @@ protected: RestartableSource & source, uint64_t sizeHint, std::string_view mimeType, - std::optional 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); void getFile(const std::string & path, Sink & sink) override; diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc index 37264dfae..51b7a05fc 100644 --- a/src/libstore/s3-binary-cache-store.cc +++ b/src/libstore/s3-binary-cache-store.cc @@ -50,7 +50,7 @@ private: RestartableSource & source, uint64_t sizeHint, std::string_view mimeType, - std::optional contentEncoding); + std::optional 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 contentEncoding); + std::optional 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 contentEncoding); + std::optional 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 contentEncoding); + std::string createMultipartUpload(std::string_view key, std::string_view mimeType, std::optional 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 encoding) { + auto doUpload = [&](RestartableSource & src, uint64_t size, std::optional 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 contentEncoding) + std::optional 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 contentEncoding) + std::optional 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 contentEncoding) + std::optional 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 contentEncoding) + std::string_view key, std::string_view mimeType, std::optional 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(); diff --git a/tests/functional/lang/eval-fail-genericClosure-deeply-nested-element.err.exp b/tests/functional/lang/eval-fail-genericClosure-deeply-nested-element.err.exp new file mode 100644 index 000000000..a5567cbfc --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-deeply-nested-element.err.exp @@ -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 diff --git a/tests/functional/lang/eval-fail-genericClosure-deeply-nested-element.nix b/tests/functional/lang/eval-fail-genericClosure-deeply-nested-element.nix new file mode 100644 index 000000000..abc0591bb --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-deeply-nested-element.nix @@ -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) ]; + } +) diff --git a/tests/functional/lang/eval-fail-genericClosure-element-missing-key.err.exp b/tests/functional/lang/eval-fail-genericClosure-element-missing-key.err.exp new file mode 100644 index 000000000..3ba2a7ea8 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-element-missing-key.err.exp @@ -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 diff --git a/tests/functional/lang/eval-fail-genericClosure-element-missing-key.nix b/tests/functional/lang/eval-fail-genericClosure-element-missing-key.nix new file mode 100644 index 000000000..e39e4043b --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-element-missing-key.nix @@ -0,0 +1,4 @@ +builtins.genericClosure { + startSet = [ { nokey = 1; } ]; + operator = x: [ ]; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-element-not-attrset.err.exp b/tests/functional/lang/eval-fail-genericClosure-element-not-attrset.err.exp new file mode 100644 index 000000000..b469f6043 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-element-not-attrset.err.exp @@ -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" diff --git a/tests/functional/lang/eval-fail-genericClosure-element-not-attrset.nix b/tests/functional/lang/eval-fail-genericClosure-element-not-attrset.nix new file mode 100644 index 000000000..6850be1c2 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-element-not-attrset.nix @@ -0,0 +1,4 @@ +builtins.genericClosure { + startSet = [ "not an attrset" ]; + operator = x: [ ]; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-keys-incompatible-types.err.exp b/tests/functional/lang/eval-fail-genericClosure-keys-incompatible-types.err.exp new file mode 100644 index 000000000..04b458a48 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-keys-incompatible-types.err.exp @@ -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 diff --git a/tests/functional/lang/eval-fail-genericClosure-keys-incompatible-types.nix b/tests/functional/lang/eval-fail-genericClosure-keys-incompatible-types.nix new file mode 100644 index 000000000..3335416fd --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-keys-incompatible-types.nix @@ -0,0 +1,7 @@ +builtins.genericClosure { + startSet = [ + { key = 1; } + { key = "string"; } + ]; + operator = x: [ ]; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-keys-uncomparable.err.exp b/tests/functional/lang/eval-fail-genericClosure-keys-uncomparable.err.exp new file mode 100644 index 000000000..97e2bed02 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-keys-uncomparable.err.exp @@ -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 { }) diff --git a/tests/functional/lang/eval-fail-genericClosure-keys-uncomparable.nix b/tests/functional/lang/eval-fail-genericClosure-keys-uncomparable.nix new file mode 100644 index 000000000..6a1915b6a --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-keys-uncomparable.nix @@ -0,0 +1,7 @@ +builtins.genericClosure { + startSet = [ + { key = { }; } + { key = { }; } + ]; + operator = x: [ ]; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-missing-operator.err.exp b/tests/functional/lang/eval-fail-genericClosure-missing-operator.err.exp new file mode 100644 index 000000000..0dce0ffd9 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-missing-operator.err.exp @@ -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 diff --git a/tests/functional/lang/eval-fail-genericClosure-missing-operator.nix b/tests/functional/lang/eval-fail-genericClosure-missing-operator.nix new file mode 100644 index 000000000..0b7c63f6d --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-missing-operator.nix @@ -0,0 +1,3 @@ +builtins.genericClosure { + startSet = [ { key = 1; } ]; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-missing-startSet.err.exp b/tests/functional/lang/eval-fail-genericClosure-missing-startSet.err.exp new file mode 100644 index 000000000..b68c6542a --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-missing-startSet.err.exp @@ -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 diff --git a/tests/functional/lang/eval-fail-genericClosure-missing-startSet.nix b/tests/functional/lang/eval-fail-genericClosure-missing-startSet.nix new file mode 100644 index 000000000..b62802986 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-missing-startSet.nix @@ -0,0 +1,3 @@ +builtins.genericClosure { + operator = x: [ ]; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-not-attrset.err.exp b/tests/functional/lang/eval-fail-genericClosure-not-attrset.err.exp new file mode 100644 index 000000000..fd3360310 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-not-attrset.err.exp @@ -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" diff --git a/tests/functional/lang/eval-fail-genericClosure-not-attrset.nix b/tests/functional/lang/eval-fail-genericClosure-not-attrset.nix new file mode 100644 index 000000000..3998c3432 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-not-attrset.nix @@ -0,0 +1 @@ +builtins.genericClosure "not an attrset" diff --git a/tests/functional/lang/eval-fail-genericClosure-operator-not-function.err.exp b/tests/functional/lang/eval-fail-genericClosure-operator-not-function.err.exp new file mode 100644 index 000000000..d3c5a627a --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-operator-not-function.err.exp @@ -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" diff --git a/tests/functional/lang/eval-fail-genericClosure-operator-not-function.nix b/tests/functional/lang/eval-fail-genericClosure-operator-not-function.nix new file mode 100644 index 000000000..425cd427d --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-operator-not-function.nix @@ -0,0 +1,4 @@ +builtins.genericClosure { + startSet = [ { key = 1; } ]; + operator = "not a function"; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-operator-not-list.err.exp b/tests/functional/lang/eval-fail-genericClosure-operator-not-list.err.exp new file mode 100644 index 000000000..49d478033 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-operator-not-list.err.exp @@ -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" diff --git a/tests/functional/lang/eval-fail-genericClosure-operator-not-list.nix b/tests/functional/lang/eval-fail-genericClosure-operator-not-list.nix new file mode 100644 index 000000000..26f97c51c --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-operator-not-list.nix @@ -0,0 +1,4 @@ +builtins.genericClosure { + startSet = [ { key = 1; } ]; + operator = x: "not a list"; +} diff --git a/tests/functional/lang/eval-fail-genericClosure-startSet-not-list.err.exp b/tests/functional/lang/eval-fail-genericClosure-startSet-not-list.err.exp new file mode 100644 index 000000000..e711a23f5 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-startSet-not-list.err.exp @@ -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" diff --git a/tests/functional/lang/eval-fail-genericClosure-startSet-not-list.nix b/tests/functional/lang/eval-fail-genericClosure-startSet-not-list.nix new file mode 100644 index 000000000..834c82f65 --- /dev/null +++ b/tests/functional/lang/eval-fail-genericClosure-startSet-not-list.nix @@ -0,0 +1,4 @@ +builtins.genericClosure { + startSet = "not a list"; + operator = x: [ ]; +} diff --git a/tests/functional/lang/infinite-nesting.nix b/tests/functional/lang/infinite-nesting.nix new file mode 100644 index 000000000..1f937e63d --- /dev/null +++ b/tests/functional/lang/infinite-nesting.nix @@ -0,0 +1,4 @@ +let + mkInfinite = i: { "a${toString i}" = mkInfinite (i + 1); }; +in +mkInfinite 0