On the subject of error messages

Stanislav Blinov via Digitalmars-d digitalmars-d at puremagic.com
Mon May 15 13:24:15 PDT 2017


On Monday, 15 May 2017 at 19:44:11 UTC, Steven Schveighoffer 
wrote:
> On 5/15/17 1:16 PM, Stanislav Blinov wrote:
>> 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.
>
> Code evaluated at compile time. It actually has to evaluate 
> each of the pieces, and knows why the whole if statement fails 
> exactly.
>
> The constraint:
>
> void foo(T)(T t) if (cond1!T && cond2!T && cond3!T), the 
> compiler knows both what each of those terms evaluate to, and 
> therefore which ones are causing the thing not to be enabled.

Yes, that is what it is: code. Code that follows language rules, 
not some ADL. No mater how expressive the language is, it is 
still code. And if, on compile error, the compiler shows me the 
code from the foreign library that I thought I was using 
correctly, I'm supposed to now do what the compiler just did and 
figure out where I made a mistake.

>> 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 think you misunderstand, your example would still not 
> compile, and instead of "here are all the ones I tried", it's 
> "here are all the ones I tried, and in each case, I've 
> highlighted why it didn't work". [...]
> [...] Then I need to figure out why it's not working.

It is exactly for the cases where the logic is more complex than 
a simple test that my proposal is for. I'm not suggesting to 
abuse it throughout.

>> now: it will report "is(T == string) was expected to be false, 
>> but it's
>> true". Is that a good error message?
>
> Yes. It's a perfect error message actually. What is confusing 
> about it?

There is no context. To get at context, I have to look at the 
code.

>> 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.
>
> It has to know. It has to evaluate the boolean to see if it 
> should compile! The current situation would be like the 
> compiler saying there's an error in your code, but won't tell 
> you the line number. Surely it knows.

It "knows" it evaluated false. It doesn't know how to give user a 
digestible hint to make that false go away.

> Today we get an error that:
>
> void foo(R)(R r) if(isInputRange!R)
>
> doesn't compile for the obvious (to you) range type R. What it 
> doesn't tell you is anything about why that doesn't work. We 
> don't even get the "no property empty" message.

Exactly my point.

> Let me give you a real example. The isForwardRange test used to 
> look like this:
>
> template isForwardRange(R)

I'm going to need to digest that.


> No, the compiler just needs to detail its evaluation process 
> that it's already doing.

That is not enough. Failure may be X levels deep in some obscure 
chunk surrounded by static ifs and and a bunch of wonky tests 
involving attempted lambda compilations (which I'd have to parse 
and "compile" in my head in order to try to understand what 
failed).

> If the constraint doesn't actually match the pragma message, 
> you get MISLEADING messages, or maybe messages when it actually 
> compiles. Much better to have the compiler tell you actually 
> what it's doing.

The library author is free to take as many passes over their 
messages as they deem necessary. They can be as vague or as 
precise as needed. It is their responsibility.

>> 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.
>
> It can say that is(typeof(x) == string) is false, or 
> x.startsWith("_") is 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?
>
> My version. Is x not a string, or does x not start with an 
> underscore? Not enough information in your error message. And 
> it doesn't give me more information than the actual constraint 
> code, it's just written out verbosely.

I've already demonstrated that the message text can be made as 
precise as is required for a concrete use case, there's no need 
to nitpick ;) I could as easily report either:

"x is not a string"

or

"string x should start with an underscore".

or

"Argument 10 (int) is not a string. This method is only callable 
with strings that start with an underscore".

"Value of argument 10 (string) does not start with an underscore. 
This method is only callable with strings that start with an 
underscore".

I, as a responsible author, can decide what amount of information 
must be presented. It could be accompanied by code, I'm not 
against that. The main point is to explain the error without 
*requiring* the user to use their head as a compiler.


More information about the Digitalmars-d mailing list