Error message formatter for range primitives

Elronnd elronnd at elronnd.net
Wed Jan 5 12:32:10 UTC 2022


Cool project!

The mechanism you use is very special-purpose, in that you have 
to write a lot of specific code to get such nice output.  There's 
a trick I came up with, that I've been meaning to post about, 
which gives slightly less nice output, but requires no manual 
effort and is completely general.

It looks like this: first, write a validator function

bool testInputRange(T)() {
	static assert(is(typeof(T.init.empty)));
	static assert(is(typeof(T.init.front)));
	static assert(is(typeof(T.init.popFront)));
	return true;
}

(The return value is a dummy.  It's not strictly necessary, but 
I'm not going to bother getting rid of it here for the purposes 
of concision.)

We can then say:

void f(T)(T x) if (isInputRange!T) { ... }
enum isInputRange(T) = is(typeof(testInputRange!T));

as usual.  No surprises.  But now replace the definition with:

enum isInputRange(T) = is(typeof(testInputRange!T)) || 
testInputRange!T && false;

Obviously, testInputRange!T && false is just false, and 
is(typeof(testInputRange!T)) || false is just 
is(typeof(testInputRange!T)).  So this seems logically equivalent 
to the previous definition.  But now we get nice error messages:

struct S {}
f(S());

gives an error like this:

range_check.d(12): Error: static assert:  `is(typeof(S().empty))` 
is false
range_check.d(10):        instantiated from here: 
`testInputRange!(S)`
range_check.d(4):        instantiated from here: 
`isInputRange!(S)`

Telling us exactly what the problem is (no 'empty' function), 
with no manual effort.

---

There is one issue with this: it doesn't tell you about more than 
one problem at once.  In this case, S was also missing front and 
popFront, but the error message only mentioned empty.  One 
solution is as follows:

void StaticAssert(alias x)() if (x) {}
bool testInputRange(T)() {
	StaticAssert!(is(typeof(T.init.empty)));
	StaticAssert!(is(typeof(T.init.front)));
	StaticAssert!(is(typeof(T.init.popFront)));
	return true;
}

Now we get to hear about all the problems, but we don't get to 
know what they actually were:

range_check.d(12): Error: template instance 
`range_check.StaticAssert!false` does not match template 
declaration `StaticAssert(alias x)()`
   with `x = false`
   must satisfy the following constraint:
`       x`
range_check.d(13): Error: template instance 
`range_check.StaticAssert!false` does not match template 
declaration `StaticAssert(alias x)()`
   with `x = false`
   must satisfy the following constraint:
`       x`
range_check.d(14): Error: template instance 
`range_check.StaticAssert!false` does not match template 
declaration `StaticAssert(alias x)()`
   with `x = false`
   must satisfy the following constraint:
`       x`
range_check.d(4): Error: template instance 
`range_check.isInputRange!(S)` error instantiating
range_check.d(8): Error: template `range_check.f` cannot deduce 
function from argument types `!()(S)`
range_check.d(4):        Candidates are: `f(T)(T x)`
range_check.d(5):                        `f(T)(T x)`

Well, we get the line numbers (12, 13, 14), so we can check the 
source code, but it would be much nicer if the error message 
itself would tell us the problem.


More information about the Digitalmars-d-announce mailing list