On the subject of error messages

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Tue May 16 05:27:30 PDT 2017


On 5/15/17 8:14 PM, Stanislav Blinov wrote:
> On Monday, 15 May 2017 at 20:55:35 UTC, Steven Schveighoffer wrote:
>> On 5/15/17 4:24 PM, Stanislav Blinov wrote:
>>> On Monday, 15 May 2017 at 19:44:11 UTC, Steven Schveighoffer wrote:
>>
>>>> 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.
>>
>> I'm going to snip away pretty much everything else and focus on this.
>>
>> The compiler absolutely 100% knows, and can demonstrate, exactly why a
>> template constraint failed. We don't have to go any further, or make
>> suggestions about how to fix it.
>
> In complex constraints, that is not enough. When we have loops (i.e.
> over arguments, or over struct members), would it report the
> iteration/name?

Yes.

> Would it know to report it if `false` came several
> levels deep in the loop body?

Yes.

> Would it know that we actually *care*
> about that information? (*cough* C++ *cough* pages and pages of error
> text because of a typo...)

The idea is to give a preliminary rough report of which part of a 
boolean expression caused it to evaluate to false. Then you recompile 
with a flag to tell the compiler to do a deeper dive.

> When we have nested static ifs, it's important to see, at a glance,
> which parts of the combination were false. Again, if they're several &&,
> || in a row, or nested, pointing to a single one wouldn't in any way be
> informative.

Why not?

> 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.

>> Just output what exactly is wrong, even if you have to recurse into
>> the depths of some obscure template isXXX, and all it's recursively
>> called templates, I can get the correct determination of where either
>> my type isn't right, or the constraint isn't right.
>
> Please look over my isMovable example (I'm not sure if you caught it, I
> posted it as a follow up to my other reply). Suppose the `false` is
> pointed at by the compiler:
>
>>    else static if (is(T == struct) &&
>>            (hasElaborateDestructor!T || hasElaborateCopyConstructor!T)) {
>>        foreach (m; T.init.tupleof) {
>>            static if (!isMovable!(typeof(m)) && (m == m.init)) {
>>                return false;
>>                       ^
>>                       |
>>            }
>>        }
>>        return true;
>>    } else
>
> That is very, *very* uninformative. I don't know which member it was, I
> don't know which part of the conditional was false. I don't know which
> part of the conditional further up was true. Would the compiler know to
> tell me all that? Would it know to test further, to collect *all*
> information, so that I don't have to incrementally recompile fixing one
> thing at a time?

The compiler, and by extension your hand-written error checking, cannot 
know the true intention of the user. 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.

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.

For checking to see that "something compiles", it could identify the 
code that fails to compile. Again, the compiler has all this information.

This isn't any harder than debugging, in fact it should be easier, as 
all the compiler metadata is available in memory, and most of the 
information can be conveyed without having to poke around.

And it should be just about as good as your hand-written message. But 
always accurate, and instantly available to all existing constraints.

> Most importantly, as a user who sees this for the first time, I'd have
> no idea *why* those checks are there. I'd have no context, no grounds to
> base my reasoning on, so I'd either have to jump back to docs to see if
> I missed a corner case, or start spelunking code that I didn't write,
> which is always so fun... Thing is, the compiler is exactly in that
> position. It doesn't read the docs, ever :) It's always spelunking code
> written by someone else. It can't tell what the constraint, as a unit,
> is *actually* testing for. It doesn't care that we shouldn't
> destructively move structs with const members. So it wouldn't be able to
> tell me either. All it will do is report me that that false was returned
> on that line, and (hopefully), some additional info, like member type
> and name.

We can't hand-hold everyone. At some point you have to learn programming 
and debugging :)

-Steve


More information about the Digitalmars-d mailing list