From 0ade82d23a30cd0cf52d530e507e6f6ac41fa881 Mon Sep 17 00:00:00 2001 From: Emilia Bopp Date: Tue, 18 Nov 2025 12:23:53 +0100 Subject: [PATCH] libexpr: add builtins.warn Added builtins.warn` which takes two arguments: a message that is displayed as a warning during evaluation which must be a string and a value that is returned from the expression. The next commits add new settings to control the behavior of the new builtin: `debugger-on-warn` allows the user to start the debugger and `abort-on-warn` aborts evaluation with an error. Unlike upstream, I chose not to mark evaluation warnings from `builtins.warn` as distinct from other warnings because that breaks the commonly expected logging format `level: message`. Co-authored-by: Qyriad Upstream-PR: https://github.com/NixOS/nix/pull/10592 Fixes: https://git.lix.systems/lix-project/lix/issues/579 Change-Id: I8658c88e5c27952b65e8b9f5525a572e0680cc1f --- doc/manual/change-authors.yml | 5 +++++ doc/manual/rl-next/builtins-warn.md | 11 +++++++++++ lix/libexpr/builtins/warn.md | 6 ++++++ lix/libexpr/meson.build | 1 + lix/libexpr/primops.cc | 13 +++++++++++++ .../eval-fail-reject-non-string.err.exp | 10 ++++++++++ .../lang/builtins.warn/eval-okay.err.exp | 1 + .../lang/builtins.warn/eval-okay.out.exp | 1 + .../lang/builtins.warn/in-reject-non-string.nix | 1 + tests/functional2/lang/builtins.warn/in.nix | 1 + tests/functional2/lang/builtins.warn/test.toml | 6 ++++++ tests/unit/libexpr/primops.cc | 8 ++++++++ 12 files changed, 64 insertions(+) create mode 100644 doc/manual/rl-next/builtins-warn.md create mode 100644 lix/libexpr/builtins/warn.md create mode 100644 tests/functional2/lang/builtins.warn/eval-fail-reject-non-string.err.exp create mode 100644 tests/functional2/lang/builtins.warn/eval-okay.err.exp create mode 100644 tests/functional2/lang/builtins.warn/eval-okay.out.exp create mode 100644 tests/functional2/lang/builtins.warn/in-reject-non-string.nix create mode 100644 tests/functional2/lang/builtins.warn/in.nix create mode 100644 tests/functional2/lang/builtins.warn/test.toml diff --git a/doc/manual/change-authors.yml b/doc/manual/change-authors.yml index 313e0647c..a6e74266c 100644 --- a/doc/manual/change-authors.yml +++ b/doc/manual/change-authors.yml @@ -176,6 +176,11 @@ midnightveil: forgejo: midnightveil github: midnightveil +milibopp: + display_name: Emilia Bopp + forgejo: milibopp + github: milibopp + nan-git: display_name: NaN-git github: NaN-git diff --git a/doc/manual/rl-next/builtins-warn.md b/doc/manual/rl-next/builtins-warn.md new file mode 100644 index 000000000..ec9ffa6c0 --- /dev/null +++ b/doc/manual/rl-next/builtins-warn.md @@ -0,0 +1,11 @@ +--- +synopsis: "Add `builtins.warn` for emitting warnings from Nix code" +cls: [2248] +category: "Features" +credits: [milibopp, Qyriad] +--- + +Lix now has a builtin function for emitting warnings. +Like `builtins.trace`, it takes two arguments: the message to emit, and the expression to return. +_Unlike_ `builtins.trace`, `builtins.warn` requires the first argument — the message — to be a string. +In the future we may extend `builtins.warn` to accept a more structured API. diff --git a/lix/libexpr/builtins/warn.md b/lix/libexpr/builtins/warn.md new file mode 100644 index 000000000..a54187ffc --- /dev/null +++ b/lix/libexpr/builtins/warn.md @@ -0,0 +1,6 @@ +--- +name: warn +args: [msg, e2] +--- +Evaluate string *msg* and print it on standard error. Then return *e2*. +This function is useful for warning about unexpected conditions without aborting evaluation. diff --git a/lix/libexpr/meson.build b/lix/libexpr/meson.build index 0e2d02950..13a8e5950 100644 --- a/lix/libexpr/meson.build +++ b/lix/libexpr/meson.build @@ -148,6 +148,7 @@ builtin_definitions = files( 'builtins/typeOf.md', 'builtins/unsafeDiscardOutputDependency.md', 'builtins/unsafeGetAttrPos.md', + 'builtins/warn.md', 'builtins/zipAttrsWith.md', # keep-sorted end ) diff --git a/lix/libexpr/primops.cc b/lix/libexpr/primops.cc index cc4a7a9cb..3f73b370c 100644 --- a/lix/libexpr/primops.cc +++ b/lix/libexpr/primops.cc @@ -768,6 +768,19 @@ static void prim_trace(EvalState & state, Value * * args, Value & v) v = *args[1]; } +static void prim_warn(EvalState & state, Value ** args, Value & v) +{ + // We only accept a string argument for now. The use case for pretty printing a value is covered + // by `trace`. By rejecting non-strings we allow future versions to add more features without + // breaking existing code. + auto const msg = + state.forceString(*args[0], noPos, "while evaluating message for builtins.warn"); + + printTaggedWarning("%s", Uncolored(msg)); + + state.forceValue(*args[1], noPos); + v = *args[1]; +} /* Takes two arguments and evaluates to the second one. Used as the * builtins.traceVerbose implementation when --trace-verbose is not enabled diff --git a/tests/functional2/lang/builtins.warn/eval-fail-reject-non-string.err.exp b/tests/functional2/lang/builtins.warn/eval-fail-reject-non-string.err.exp new file mode 100644 index 000000000..be0cf2865 --- /dev/null +++ b/tests/functional2/lang/builtins.warn/eval-fail-reject-non-string.err.exp @@ -0,0 +1,10 @@ +error: + … while calling the 'warn' builtin + at /pwd/in.nix:1:1: + 1| builtins.warn { cat = "girl"; } 1337 + | ^ + 2| + + … while evaluating message for builtins.warn + + error: expected a string but found a set: { cat = "girl"; } diff --git a/tests/functional2/lang/builtins.warn/eval-okay.err.exp b/tests/functional2/lang/builtins.warn/eval-okay.err.exp new file mode 100644 index 000000000..e84bd6f55 --- /dev/null +++ b/tests/functional2/lang/builtins.warn/eval-okay.err.exp @@ -0,0 +1 @@ +warning: meow diff --git a/tests/functional2/lang/builtins.warn/eval-okay.out.exp b/tests/functional2/lang/builtins.warn/eval-okay.out.exp new file mode 100644 index 000000000..a369ea607 --- /dev/null +++ b/tests/functional2/lang/builtins.warn/eval-okay.out.exp @@ -0,0 +1 @@ +1337 diff --git a/tests/functional2/lang/builtins.warn/in-reject-non-string.nix b/tests/functional2/lang/builtins.warn/in-reject-non-string.nix new file mode 100644 index 000000000..99a2d9acb --- /dev/null +++ b/tests/functional2/lang/builtins.warn/in-reject-non-string.nix @@ -0,0 +1 @@ +builtins.warn { cat = "girl"; } 1337 diff --git a/tests/functional2/lang/builtins.warn/in.nix b/tests/functional2/lang/builtins.warn/in.nix new file mode 100644 index 000000000..271733070 --- /dev/null +++ b/tests/functional2/lang/builtins.warn/in.nix @@ -0,0 +1 @@ +builtins.warn "meow" 1337 diff --git a/tests/functional2/lang/builtins.warn/test.toml b/tests/functional2/lang/builtins.warn/test.toml new file mode 100644 index 000000000..db3224037 --- /dev/null +++ b/tests/functional2/lang/builtins.warn/test.toml @@ -0,0 +1,6 @@ +[[test]] +runner = "eval-okay" + +[[test]] +runner = "eval-fail" +in = "in-reject-non-string.nix" diff --git a/tests/unit/libexpr/primops.cc b/tests/unit/libexpr/primops.cc index 4e3c4072a..9eefe0aa4 100644 --- a/tests/unit/libexpr/primops.cc +++ b/tests/unit/libexpr/primops.cc @@ -121,6 +121,14 @@ namespace nix { ASSERT_NE(text.find("test string 123"), std::string::npos); } + TEST_F(PrimOpTest, warn) { + CaptureLogging l; + auto const v = eval("builtins.warn \"test string 123\" 123"); + ASSERT_THAT(v, IsIntEq(123)); + auto const text = l.get(); + ASSERT_NE(text.find("test string 123"), std::string::npos); + } + TEST_F(PrimOpTest, placeholder) { auto v = eval("builtins.placeholder \"out\""); ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"));