Explain current error trace impl
This commit is contained in:
parent
c2b620f3ad
commit
963b8aa39b
|
@ -288,7 +288,100 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
|||
"?" << std::endl;
|
||||
}
|
||||
|
||||
// traces
|
||||
/*
|
||||
* Traces
|
||||
* ------
|
||||
*
|
||||
* The semantics of traces is a bit weird. We have only one option to
|
||||
* print them and to make them verbose (--show-trace). In the code they
|
||||
* are always collected, but they are not printed by default. The code
|
||||
* also collects more traces when the option is on. This means that there
|
||||
* is no way to print the simplified traces at all.
|
||||
*
|
||||
* I (layus) designed the code to attach positions to a restricted set of
|
||||
* messages. This means that we have a lot of traces with no position at
|
||||
* all, including most of the base error messages. For example "type
|
||||
* error: found a string while a set was expected" has no position, but
|
||||
* will come with several traces detailing it's precise relation to the
|
||||
* closest know position. This makes erroring without printing traces
|
||||
* quite useless.
|
||||
*
|
||||
* This is why I introduced the idea to always print a few traces on
|
||||
* error. The number 3 is quite arbitrary, and was selected so as not to
|
||||
* clutter the console on error. For the same reason, a trace with an
|
||||
* error position takes more space, and counts as two traces towards the
|
||||
* limit.
|
||||
*
|
||||
* The rest is truncated, unless --show-trace is passed. This preserves
|
||||
* the same bad semantics of --show-trace to both show the trace and
|
||||
* augment it with new data. Not too sure what is the best course of
|
||||
* action.
|
||||
*
|
||||
* The issue is that it is fundamentally hard to provide a trace for a
|
||||
* lazy language. The trace will only cover the current spine of the
|
||||
* evaluation, missing things that have been evaluated before. For
|
||||
* example, most type errors are hard to inspect because there is not
|
||||
* trace for the faulty value. These errors should really print the faulty
|
||||
* value itself.
|
||||
*
|
||||
* In function calls, the --show-trace flag triggers extra traces for each
|
||||
* function invocation. These work as scopes, allowing to follow the
|
||||
* current spine of the evaluation graph. Without that flag, the error
|
||||
* trace should restrict itself to a restricted prefix of that trace,
|
||||
* until the first scope. If we ever get to such a precise error
|
||||
* reporting, there would be no need to add an arbitrary limit here. We
|
||||
* could always print the full trace, and it would just be small without
|
||||
* the flag.
|
||||
*
|
||||
* One idea I had is for XxxError.addTrace() to perform nothing if one
|
||||
* scope has already been traced. Alternatively, we could stop here when
|
||||
* we encounter such a scope instead of after an arbitrary number of
|
||||
* traces. This however requires to augment traces with the notion of
|
||||
* "scope".
|
||||
*
|
||||
* This is particularly visible in code like evalAttrs(...) where we have
|
||||
* to make a decision between the two following options.
|
||||
*
|
||||
* ``` long traces
|
||||
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx)
|
||||
* {
|
||||
* try {
|
||||
* e->eval(*this, env, v);
|
||||
* if (v.type() != nAttrs)
|
||||
* throwTypeError("value is %1% while a set was expected", v);
|
||||
* } catch (Error & e) {
|
||||
* e.addTrace(pos, errorCtx);
|
||||
* throw;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ``` short traces
|
||||
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, const std::string_view & errorCtx)
|
||||
* {
|
||||
* e->eval(*this, env, v);
|
||||
* try {
|
||||
* if (v.type() != nAttrs)
|
||||
* throwTypeError("value is %1% while a set was expected", v);
|
||||
* } catch (Error & e) {
|
||||
* e.addTrace(pos, errorCtx);
|
||||
* throw;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The second example can be rewritten more concisely, but kept in this
|
||||
* form to highlight the symmetry. The first option adds more information,
|
||||
* because whatever caused an error down the line, in the generic eval
|
||||
* function, will get annotated with the code location that uses and
|
||||
* required it. The second option is less verbose, but does not provide
|
||||
* any context at all as to where and why a failing value was required.
|
||||
*
|
||||
* Scopes would fix that, by adding context only when --show-trace is
|
||||
* passed, and keeping the trace terse otherwise.
|
||||
*
|
||||
*/
|
||||
|
||||
if (!einfo.traces.empty()) {
|
||||
unsigned int count = 0;
|
||||
for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) {
|
||||
|
|
Loading…
Reference in a new issue