Better error messages - from reddit

Johannes Pfau nospam at example.com
Thu Mar 7 22:09:29 UTC 2019


Am Mon, 04 Mar 2019 17:18:05 -0800 schrieb H. S. Teoh:

>> ... the problem (from the perspective of issuing a nice error) is that
>> you can arbitrarily compose logic which makes sorting the what from the
>> chaff extremely difficult and that signal to noise is very important
>> (ever used -verrors=spec ?). Believe me I tried, saw a suggestion by
>> aliak in a Phobos PR thread and thought that would make things so much
>> easier, and thus arose that DIP.
> 
> Well, if you're looking for a perfect solution, then yes this will be
> very complicated and hairy to implement.
> 
> But to take care of the most common case, all we have to do is to assume
> that sig constraints are of the form (A && B && C && ...).  The compiler
> only needs to report which of these top level conjuncts failed.  If a
> sig constraint isn't of this form, then fallback to reporting the entire
> constraint as failed, i.e., treat it as the case (A) (single-argument
> conjunction).
> 
> It's not a perfect solution, but having this is already a lot better
> than the current pessimal state of things.  Your DIP represents an
> improvement over this most basic step, but IMO we should at least have
> this basic step first.  Don't let the perfect become the enemy of the
> good yet again.
> 
> 
> T

The DIP seems kind of intrusive to me. And Parsing a DNF form has one 
major drawback: For quite some time now we actually advised people to 
refactor their constraints into external helper functions, in order to 
have nicer ddoc. E.g. isDigest!X instead of isOutputRange!X && ... So 
now, we'd have to recommend the exact opposite. And both approaches are 
limited.

I think we have to take a step back here and see if we can't come up with 
a minimal composable solution, based on D's current features. So to 
analyze this problem:

We have a complex template constraint system, which is a superset of 
concepts. We can implement concepts in the library just fine (e.g. 
https://github.com/atilaneves/concepts). What we can't do is provide nice 
error messages.
But why is that? Well, all checking is done by CTFE D code, so with a 
CTFE library implementation of concepts, the compiler actually knows 
nothing about the concepts. So the only one being able to generate proper 
error messages is this CTFE library code. We don't we do that? Because we 
have no proper way to emit error messages! The pragma msg/static assert 
approaches all don't compose well with other D features 
(overloading, ...) and they won't produce nice error messages anyway. 
Probably better, but not nice.

So what if we simply introduce a bool __traits(constraintCheck, alias, 
condition, formatString) hook? Now we could do this:

struct InputRange
{
    T front;
    void popFront();
}

bool implements(T, Interface)
{
    bool result = true;
    foreach (member; Interface)
    {
        if (!hasMember(T, member))
            result &= __traits(constraintCheck, T, false, "Type %S does 
not implement interface member " ~ niceFormatting(...));
    }
    return result;
}

void testRange(R, T)(R range) if (implements!(R, InputRange!T))
{
    
}

void testRange(R, T)(R range) if (implements!(R, OutputRange!T))
{
    
}
====================
test.d:6:8: Error: template »testRange« cannot deduce function from 
argument types »!(Foo, Bar)« candidates are:

test.d:1:6: Note: »(R, T)(R range) if (implements!(R, InputRange!T))«:
     R: Type 'Foo' does not implement interface member 'Bar   
     InputRange.front'.
     R: Type 'Foo' does not implement interface member 'popFront()'.

     testRange!(Foo, Bar);
                 ^


test.d:4:6: Note: »(R, T)(R range) if (implements!(R, OutputRange!T))«:
     R: Type 'Foo' does not implement interface member 'put(Bar)'.

     testRange!(Foo, Bar);
                 ^


void testOdd(uint n)() if (__traits(constraintCheck, n, n % 2 == 1, "Need 
an odd integer."))
====================
test.d:6:8: Error: template »testOdd« cannot deduce function from 
argument types »!(2)« candidates are:

test.d:1:6: Note: »testOdd(uint n)«: n: Need an odd integer.
     testOdd(2);
             ^

I think this is really all we need to have to implement a replacement for 
concepts with perfectly nice error messages. We may have to put some 
thought into the exact design of __traits(constraintCheck), but I think 
it's perfectly possible. And if we the provide a implementsConcept!T in 
phobos, we should be fine. Even DDOC generators can largely benefit from 
this without any changes, but they could also recognize the 
`implementsConcept` name and provide specially formatted docs. An this 
approach will preserve flexibility of the current system while providing 
nice error messages for more cases than concepts could (see testOdd 
above).

Open questions:

* What to do if there are multiple overloads. Print diagnostics for all?
* If multiple __traits(constraintCheck) fail, do we print all?
* If multiple failed checks reference same alias we should merge the  
  diagnostic source code location info for all these?
* Do we allow the alias to refer to a member of some parameter type? If 
  so, where will diagnostics point at: the parameter or the definition of
  the member?: 

test.d:1:6: Note: »(R, T)(R range) if (implements!(R, OutputRange!T))«:
     R: interface member 'Foo.put(Bar)' is not @safe.

     test.d:2.4 void put(T t)
                            ^
* What do we want to allow in the format string? I think we need at least 
  the original type name in user context, though maybe we can also get   
  that in some other way. Maybe the original parameter name is useful?

* We should also consider what locations to print (Is the location of the 
original overload definition useful?)


-- 
Johannes


More information about the Digitalmars-d mailing list