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