Drop lexically redundant traces - #16323
Draft
rhendric wants to merge 2 commits into
Draft
Conversation
xokdvium
reviewed
Aug 18, 2026
Comment on lines
+172
to
+175
| virtual std::partial_ordering comparePos(PosIdx otherPos) const | ||
| { | ||
| return getPos().partialCompare(otherPos); | ||
| } |
Contributor
There was a problem hiding this comment.
This better be noexcept right? Because it's mostly called in catch blocks and throwing in those is a terminate().
Contributor
|
I think I kinda like the idea. Having very detailed traces might be somewhat desirable if you really do want to trace through the forcing order and the eval stack exactly as it's written, but it's probably way too much for day-to-day use. Also, convincing oneself that the comparators do actually correctly specify a strict weak ordering is going to be non-trivial :) |
See comment on Expr::hasNewPos for explanation.
rhendric
force-pushed
the
rhendric/drop-enclosing-traces
branch
from
August 18, 2026 22:14
3d2b5fd to
259b92f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Now let's see if I can get away with a meatier change...
Motivation
The top two trace items here are redundant.
The
throwtrace already points the user to the expression being evaluated. Because thethrowcall expression is contained in both of the operator expressions, there's no need to also have traces that point out that, to get to thethrow, the evaluator had to go through the operators.Had the expression been
let x = throw "oops" + 3; in 10 - x, then the trace corresponding to the subtraction would no longer be redundant. This is because thethrowcall is not a subexpression of10 - x.The general principle here is that, if the last trace on the stack (or the error position, if no traces have been added yet) points to a location that is contained within the expression being evaluated, that expression should not add its own trace item when there is an error. We can use this principle to make traces a fair bit briefer without making things any less debuggable, which is pretty exciting, since the size of Nix traces is one of the perennial complaints about them.
The general principle seems to merit a few exceptions. Most notably, we want the repeated call site traces that are generated by a recursive function not to be dropped (as in tests/functional/lang/eval-fail-mutual-recursion) — at least, not without a ‘frames omitted’ message. So call sites get more of a pass than other expressions. I'm still working out the logic of the other edge cases.
Context
The first commit is a mechanical refactoring that allows the last
PosIdxused in an error or trace to be stored inErrorInfo. Many files were touched but it shouldn't take much brainpower to verify that the right things are happening. (Focus your brainpower on the libutil header files; I may have botched thestd::shared_ptr/r-value reference juggling.)The tricky question is whether a given position appears within a given expression. This question is answered by the new
comparePosvirtual method onExpr(‘compare’ because it actually returns astd::partial_ordering, indicating where the expression is relative to the position). In a world where we didn't care about increasing memory usage,comparePoscould be simply implemented by adding end positions to expressions during parsing. Instead,comparePoswalks the expression tree looking for matches; this is a bit gnarly and also a bit dishonest, as described in the comments. In particular, the dishonesty part means thatcomparePosis not suitable for, say, highlighting expressions in traces, if someone had their heart set on that. But it is honest enough to use for filtering lexically redundant traces.With
comparePosin place, most of the rest of the implementation consists of scatteringhasNewPoschecks beforeaddTracecalls, mostly in src/libexpr/eval.cc. (Expr::hasNewPosis a small helper that runscomparePosagainst the last position to be added to an error.) The form of the checks varies, because sometimes I have to do a bit of digging to find a suitable expression to compare against the last trace position. I introducedValue::maybeGetThunkExpr()to help with a common pattern of capturing an expression from a value before it is forced, to be used in the error handler. These checks are not yet comprehensive; I did the easy ones and just enough of the trickier ones to demonstrate the value of the approach.Things ready for review
comparePoslogic in nixexpr.ccThings still for me to do before you bother reviewing them
Add 👍 to pull requests you find important.
The Nix maintainer team uses a GitHub project board to schedule and track reviews.