On the subject of error messages
Stanislav Blinov via Digitalmars-d
digitalmars-d at puremagic.com
Mon May 15 10:16:01 PDT 2017
On Monday, 15 May 2017 at 15:30:38 UTC, Steven Schveighoffer
wrote:
>>> Argument 2 is a string, which is not supported
>>> file(line): Error: template foo cannot deduce function from
>>> argument
>>> types !()(int, string), candidates are:
>>> file(line): foo(Args...)(auto ref Args arg) if
>>> (noStringArgs!args)
>
> I think the compiler should be able to figure this out, and
> report it. The if constraint is a boolean expression, and so it
> can be divided into the portions that pass or fail.
How? The constraint, any constraint, is de-facto user code. Even
in the simple example I've provided, I would not expect the
compiler to figure out what are *my* expectations on the types. I
provide code for doing that, the language gives me means to that
effect. What it doesn't give me though is a way to cleanly report
an error.
Even if the compiler was to divide the constraint into blocks and
reason about them separately, it's still limited to error
reporting we have now: it will report "is(T == string) was
expected to be false, but it's true". Is that a good error
message?
> What I'd love to see is the constraint colorized to show green
> segments that evaluate to true, and red segments that evaluate
> to false. And then recursively show each piece when asked.
>
> I think any time spent making a user-level solution will not
> scale. The compiler knows the information, can ascertain why it
> fails, and print a much nicer error message. Plus it makes
> compile-time much longer to get information that is already
> available.
I don't see how that is possible. The constraints' complexity is
arbitrary, it's semantics are arbitrary. The compiler does a full
semantic pass, we end up with the error messages as if it was
normal program code. But the thing is, we need different error
messages, because it isn't "normal" program code.
In fact, what truly doesn't scale is the binary "is/isn't"
solution we have now. Again, even if the compiler would display
at which line/column `false` was inferred, it's not good enough,
as it simply leaves the user to figure out what went wrong,
without any clear hint.
> Imagine also a constraint like isInputRange!R. This basically
> attempts to compile a dummy lambda. How would one handle this
> in user-code?
Umm... Exactly as it is implemented currently? With one important
distinction that I would be able to report *exactly why* the type
in question does not satisfy the constraint. Not an obscure
"Error: no property 'empty' for type (typename)"
"Error: expression 'foo.front()' is void and has no value"
but a descriptive
" Argument <number> does not satisfy the constraint isInputRange:"
"(typename) is expected to be an input range, but it doesn't
implement the required interface:"
" property 'empty' is not defined."
" property 'front' is defined, but returns void."
User code can collect all the information and present it in a
readable way. Compiler will never be able to. The best the
compiler would do is report "T does not satisfy isInputRange".
And in my opinion, that is what it should do. Adding complexity
to the compiler to figure out all imaginable variations doesn't
seem like a very good idea. User code is able to make all
assessments it needs, it just doesn't have the ability to
elaborate.
Another example:
is(typeof(x) == string) && x.startsWith("_")
The best we can expect from the compiler is print that out and
say it evaluated to false.
User code, on the other hand, can generate a string "x must be a
string that starts with an underscore". Which one is better?
More information about the Digitalmars-d
mailing list