diff --git a/doc/manual/rl-next/tostring-frames.md b/doc/manual/rl-next/tostring-frames.md new file mode 100644 index 000000000..3efda9385 --- /dev/null +++ b/doc/manual/rl-next/tostring-frames.md @@ -0,0 +1,60 @@ +--- +synopsis: 'Implicit `__toString` now have stack trace entries' +issues: [] +cls: [3055] +category: Improvements +credits: [horrors] +--- + +Coercion of attribute sets to strings via their `__toString` attribute now produce stack +frames pointing to the coercion site and the attribute definition. This makes locating a +coercion function error easier as the fault location is now more likely to be presented. + +Previously: +``` +nix-repl> builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" +error: + … while calling the 'substring' builtin + at «string»:1:1: + 1| builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" + | ^ + + … caused by explicit throw + at «string»:1:48: + 1| builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" + | ^ + + error: bar +``` + +Now: +``` +nix-repl> builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" +error: + … while calling the 'substring' builtin + at «string»:1:1: + 1| builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" + | ^ + + … while converting a set to string + at «string»:1:25: + 1| builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" + | ^ + + … from call site + at «string»:1:29: + 1| builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" + | ^ + + … while calling '__toString' + at «string»:1:42: + 1| builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" + | ^ + + … caused by explicit throw + at «string»:1:48: + 1| builtins.substring 1 1 "${{ __toString = self: throw ''bar''; }}" + | ^ + + error: bar +``` diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index 0769eef20..8cee40a25 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -2309,10 +2309,15 @@ std::optional EvalState::tryAttrsToString(const PosIdx pos, Value & auto i = v.attrs->find(ctx.s.toString); if (i != v.attrs->end()) { Value v1; - callFunction(*i->value, v, v1, pos); - return coerceToString(pos, v1, context, - "while evaluating the result of the `__toString` attribute", - coerceMore, copyToStore).toOwned(); + try { + callFunction(*i->value, v, v1, i->pos); + return coerceToString(pos, v1, context, + "while evaluating the result of the `__toString` attribute", + coerceMore, copyToStore).toOwned(); + } catch (EvalError & e) { + e.addTrace(ctx.positions[pos], "while converting a set to string"); + throw; + } } return {}; diff --git a/tests/functional/lang/eval-fail-toString.err.exp b/tests/functional/lang/eval-fail-toString.err.exp new file mode 100644 index 000000000..1b9fef4b1 --- /dev/null +++ b/tests/functional/lang/eval-fail-toString.err.exp @@ -0,0 +1,14 @@ +error: + … while calling the 'toString' builtin + at /pwd/lang/eval-fail-toString.nix:1:1: + 1| toString { __toString = 1; } + | ^ + 2| + + … while converting a set to string + + error: attempt to call something which is not a function but an integer: 1 + at /pwd/lang/eval-fail-toString.nix:1:12: + 1| toString { __toString = 1; } + | ^ + 2| diff --git a/tests/functional/lang/eval-fail-toString.nix b/tests/functional/lang/eval-fail-toString.nix new file mode 100644 index 000000000..485d5eab5 --- /dev/null +++ b/tests/functional/lang/eval-fail-toString.nix @@ -0,0 +1 @@ +toString { __toString = 1; }