@@ -50,6 +50,32 @@ std::ostream & operator <<(std::ostream & str, const AbstractPos & pos)
5050 return str;
5151}
5252
53+ /* *
54+ * An arbitrarily defined value comparison for the purpose of using traces in the key of a sorted container.
55+ */
56+ inline bool operator <(const Trace& lhs, const Trace& rhs)
57+ {
58+ // `std::shared_ptr` does not have value semantics for its comparison
59+ // functions, so we need to check for nulls and compare the dereferenced
60+ // values here.
61+ if (lhs.pos != rhs.pos ) {
62+ if (!lhs.pos )
63+ return true ;
64+ if (!rhs.pos )
65+ return false ;
66+ if (*lhs.pos != *rhs.pos )
67+ return *lhs.pos < *rhs.pos ;
68+ }
69+ // This formats a freshly formatted hint string and then throws it away, which
70+ // shouldn't be much of a problem because it only runs when pos is equal, and this function is
71+ // used for trace printing, which is infrequent.
72+ return std::forward_as_tuple (lhs.hint .str (), lhs.frame )
73+ < std::forward_as_tuple (rhs.hint .str (), rhs.frame );
74+ }
75+ inline bool operator > (const Trace& lhs, const Trace& rhs) { return rhs < lhs; }
76+ inline bool operator <=(const Trace& lhs, const Trace& rhs) { return !(lhs > rhs); }
77+ inline bool operator >=(const Trace& lhs, const Trace& rhs) { return !(lhs < rhs); }
78+
5379std::optional<LinesOfCode> AbstractPos::getCodeLines () const
5480{
5581 if (line == 0 )
@@ -185,6 +211,69 @@ static bool printPosMaybe(std::ostream & oss, std::string_view indent, const std
185211 return hasPos;
186212}
187213
214+ void printTrace (
215+ std::ostream & output,
216+ const std::string_view & indent,
217+ size_t & count,
218+ const Trace & trace)
219+ {
220+ output << " \n " << " … " << trace.hint .str () << " \n " ;
221+
222+ if (printPosMaybe (output, indent, trace.pos ))
223+ count++;
224+ }
225+
226+ void printSkippedTracesMaybe (
227+ std::ostream & output,
228+ const std::string_view & indent,
229+ size_t & count,
230+ std::vector<Trace> & skippedTraces,
231+ std::set<Trace> tracesSeen)
232+ {
233+ if (skippedTraces.size () > 0 ) {
234+ // If we only skipped a few frames, print them out normally;
235+ // messages like "1 duplicate frames omitted" aren't helpful.
236+ if (skippedTraces.size () <= 5 ) {
237+ for (auto & trace : skippedTraces) {
238+ printTrace (output, indent, count, trace);
239+ }
240+ } else {
241+ output << " \n " << ANSI_WARNING " (" << skippedTraces.size () << " duplicate frames omitted)" ANSI_NORMAL << " \n " ;
242+ // Clear the set of "seen" traces after printing a chunk of
243+ // `duplicate frames omitted`.
244+ //
245+ // Consider a mutually recursive stack trace with:
246+ // - 10 entries of A
247+ // - 10 entries of B
248+ // - 10 entries of A
249+ //
250+ // If we don't clear `tracesSeen` here, we would print output like this:
251+ // - 1 entry of A
252+ // - (9 duplicate frames omitted)
253+ // - 1 entry of B
254+ // - (19 duplicate frames omitted)
255+ //
256+ // This would obscure the control flow, which went from A,
257+ // to B, and back to A again.
258+ //
259+ // In contrast, if we do clear `tracesSeen`, the output looks like this:
260+ // - 1 entry of A
261+ // - (9 duplicate frames omitted)
262+ // - 1 entry of B
263+ // - (9 duplicate frames omitted)
264+ // - 1 entry of A
265+ // - (9 duplicate frames omitted)
266+ //
267+ // See: `tests/functional/lang/eval-fail-mutual-recursion.nix`
268+ tracesSeen.clear ();
269+ }
270+ }
271+ // We've either printed each trace in `skippedTraces` normally, or
272+ // printed a chunk of `duplicate frames omitted`. Either way, we've
273+ // processed these traces and can clear them.
274+ skippedTraces.clear ();
275+ }
276+
188277std::ostream & showErrorInfo (std::ostream & out, const ErrorInfo & einfo, bool showTrace)
189278{
190279 std::string prefix;
@@ -333,7 +422,13 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
333422
334423 bool frameOnly = false ;
335424 if (!einfo.traces .empty ()) {
425+ // Stack traces seen since we last printed a chunk of `duplicate frames
426+ // omitted`.
427+ std::set<Trace> tracesSeen;
428+ // A consecutive sequence of stack traces that are all in `tracesSeen`.
429+ std::vector<Trace> skippedTraces;
336430 size_t count = 0 ;
431+
337432 for (const auto & trace : einfo.traces ) {
338433 if (trace.hint .str ().empty ()) continue ;
339434 if (frameOnly && !trace.frame ) continue ;
@@ -343,14 +438,21 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
343438 break ;
344439 }
345440
441+ if (tracesSeen.count (trace)) {
442+ skippedTraces.push_back (trace);
443+ continue ;
444+ }
445+ tracesSeen.insert (trace);
446+
447+ printSkippedTracesMaybe (oss, ellipsisIndent, count, skippedTraces, tracesSeen);
448+
346449 count++;
347450 frameOnly = trace.frame ;
348451
349- oss << " \n " << " … " << trace.hint .str () << " \n " ;
350-
351- if (printPosMaybe (oss, ellipsisIndent, trace.pos ))
352- count++;
452+ printTrace (oss, ellipsisIndent, count, trace);
353453 }
454+
455+ printSkippedTracesMaybe (oss, ellipsisIndent, count, skippedTraces, tracesSeen);
354456 oss << " \n " << prefix;
355457 }
356458
@@ -369,4 +471,5 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
369471
370472 return out;
371473}
474+
372475}
0 commit comments