It's been long asked in our community that failing template constraints issue better error messages. Consider:

R find(R, E)(R range, E elem)
{
    for (; !range.empty; range.popFront)
        if (range.front == elem) break;
    return range;
}

struct NotARange {}

void main()
{
    NotARange nar;
    nar = nar.find(42);
}

This program uses no constraints. Attempting to compile yields:

/d240/f632.d(3): Error: no property 'empty' for type 'NotARange'
/d240/f632.d(3): Error: no property 'popFront' for type 'NotARange'
/d240/f632.d(4): Error: no property 'front' for type 'NotARange'
/d240/f632.d(13): Error: template instance f632.find!(NotARange, int) error instantiating

which is actually quite informative if you're okay with error messages pointing inside the template body (which is presumably a preexisting library) instead of the call site.

Let's add constraints:

import std.range;

R find(R, E)(R range, E elem)
if (isInputRange!R && is(typeof(range == elem) == bool))
{ ... }
...

Now we get:

/d935/f781.d(16): Error: template f781.find cannot deduce function from argument types !()(NotARange, int), candidates are: /d935/f781.d(3): f781.find(R, E)(R range, E elem) if (isInputRange!R && is(typeof(range == elem) == bool))

That does not point inside the template implementation anymore (just the declaration, which is good) but is arguably more opaque: at this point it's less, not more, clear to the user what steps to take to make the code work. Even if they know what an input range is, the failing constraint is a complex expression so it's unclear which clause of the conjunction failed.

Idea #1: Detect and use CNF, print which clause failed
====

CNF (https://en.wikipedia.org/wiki/Conjunctive_normal_form) is a formula shape in Boolean logic that groups clauses into a top-level conjunction.

The compiler could detect and use when CNF is used (as in the example above), and when printing the error message it only shows the first failing conjunction, e.g.:

/d935/f781.d(16): Error: template f781.find cannot deduce function from argument types !()(NotARange, int), candidates are: /d935/f781.d(3): f781.find(R, E)(R range, E elem) constraint failed: isInputRange!NotARange

This is quite a bit better - it turns out many constraints in Phobos are rather complex, so this would improve many of them. One other nice thing is no language change is necessary.

Idea #2: Allow custom error messages
====

The basic idea here is to define pragma(err, "message") as an expression that formats "message" as an error and returns false. Then we can write:

R find(R, E)(R range, E elem)
if ((isInputRange!R || pragma(err, R.stringof~" must be an input range")
   &&
   (is(typeof(range == elem) == bool) || pragma(err, "..."))

Now, when printing the failed candidate, the compiler adds the error message(s) produced by the failing constraint.

The problem with this is verbosity - e.g. we almost always want to write the same message when isInputRange fails, so naturally we'd like to encapsulate the message within isInputRange. This could go as follows. Currently:

template isInputRange(R)
{
    enum bool isInputRange = is(typeof(
    (inout int = 0)
    {
        R r = R.init;     // can define a range object
        if (r.empty) {}   // can test for empty
        r.popFront();     // can invoke popFront()
        auto h = r.front; // can get the front of the range
    }));
}

Envisioned (simplified):

template lval(T)
{
  static @property ref T lval() { static T r = T.init; return r; }
}

template isInputRange(R)
{
  enum bool isInputRange =
    (is(typeof({if(lval!R.empty) {}})
      || pragma(err, "cannot test for empty")) &&
    (is(typeof(lval!R.popFront())
      || pragma(err, "cannot invoke popFront")
    (is(typeof({ return lval!R.front; }))
      || pragma(err, can get the front of the range));
}

Then the way it goes, the compiler collects the concatenation of pragma(msg, "xxx") during the invocation of isInputRange!R and prints it if it fails as part of a constraint.

Further simplifications should be possible, e.g. make is() support an error string etc.


Destroy!

Andrei

Reply via email to