On the subject of error messages

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Tue May 16 08:47:37 PDT 2017


On 5/16/17 9:54 AM, Stanislav Blinov wrote:
> On Tuesday, 16 May 2017 at 12:27:30 UTC, Steven Schveighoffer wrote:
>
>>> When we have tests using dummy lambdas, are we to expect users to
>>> immediately extract the lambda body, parse it, and figure out what's
>>> wrong?
>>
>> This is what you have to do today. The task has already been tried by
>> the compiler, and the result is known by the compiler. Just have the
>> compiler tell you.
>
> :) The compiler does not know what I'm checking for with that lambda. As
> far as the compiler is concerned, I'm interested in whether it compiles
> or not. It doesn't care what that means in the context of my constraint.
> Neither should the user.

You seem to be not understanding that a hand-written message of "needs 
to have member x" or something conveys the same information as the 
compiler saying "Error in this line: auto test = T.x"

Sure, it's not proper English. It gets the job done, and I don't have to 
instrument all my functions and template constraint helpers. I don't 
need to explode compile times, or debug why my messages don't match what 
actually happens.

>> The compiler, and by extension your hand-written error checking,
>> cannot know the true intention of the user.
>
> The constraint *is* hand-written error checking. What I'm talking about
> is hand-written human-readable error messages :) It's not about knowing
> user intentions, it's about informing them why they made a mistake.

hand-written == hand-compiled. That is, I translated what I think this 
boolean condition means into a human-readable format by hand. I could 
have got it wrong. Then a nice message is useless.

>> All it knows is you tried to do something that isn't supported. You
>> have to figure out what is wrong and fix it. If that takes several
>> iterations, that's what it takes. There is no solution that will give
>> you all the answers.
>
> Hold on a second, I think there's a mixup in terminology here. A user
> (caller) of move() is not supposed to be interested in what particular
> evaluation chain caused the constraint to be false. I, as an author,
> declare a contract (constraint). I am not interested in user's
> intentions. I am interested that I'm being called correctly. When the
> user violates the contract, I must inform them they did so, by reporting
> *why* their argument does not satisfy me. Not by telling *how* I figured
> that out (compiler output), but by telling *what* is wrong with the
> argument (human-readable error message).

The constraint tells the user why it doesn't work. There is no extra 
effort required. It's human readable (I know what isInputRange!R means). 
I don't need a specialized full-sentence message to understand that.

> If the user does want to know how the constraint caught their mistake,
> they're free to inspect what the compiler outputs.

And here is the problem. Your solution doesn't get us any closer to 
that. It's actually quite painful to do this today. When I have a type 
like this:

struct S
{
    int foo;
}

and the hand-written error message says: "Your type must be a struct 
that contains an integer property named 'foo'". How does that help me 
figure out what I did wrong? We can spend all day arguing over how nice 
this message is, but the truth is, what the constraint writer put in the 
constraints, and how the compiler is interpreting it, may be 2 different 
things. This helps nobody.

> That is why (static) asserts have an optional string argument: to
> display a clean an readable error message, instead of just dumping some
> code on the user. The same thing, in my opinion, is needed for constraints.

If assert(x == 5) would just print "Error: x == 4" automatically, we 
could eliminate most needs for a message.

You could solve this with a message, but again, this is a huge task to 
undertake on ALL code in existence, rather than fixing it all to a "good 
enough" degree that we don't need the messages. And it avoids the "human 
compiler" that is prone to error.

>> In your example, the compiler would point at isMovable!S as the issue.
>> Not super-informative, but is all it gives to prevent huge outputs.
>> Then you tell it to print more information, and it would say that
>> false was returned when the m member of type T is being checked, at
>> which point you could get a stack trace of what values were at each
>> level of recursion. Everywhere a boolean evaluated to true in order to
>> get to the point where false is returned would be colored green, every
>> time it was false, it would be colored red, and every time a short
>> circuit happened, it wouldn't be colored.
>
> Or the user could just read a string "This overload cannot be called,
> because argument 1 (struct S) has a destructor and non-statically
> initialized const members". No "then", no printing more information, no
> stack traces. User is informed their type is wrong and *why* it is
> wrong. If they disagree, if they think there's a bug in the constraint,
> or if they're interested in how the constraint works, they're free to go
> through all the hoops you describe.

That doesn't help me if the constraint author didn't put that message, 
or the constraint author put the *wrong* message, or wrote the 
constraint incorrectly.

-Steve


More information about the Digitalmars-d mailing list