Thread overview
User-defined template error messages
Jan 13, 2019
rikki cattermole
Jan 13, 2019
luckoverthere
Jan 13, 2019
Nicholas Wilson
January 13, 2019
We've had this problem for a long time - template doesn't match because of a complex constraint, the error message is unhelpful, and the user doesn't know what to do.

I think this is an important missed opportunity. One of the promises of Design by Introspection is it raises the level of error messages from mere built-in canned messages to higher-level messages expressed in terms of problem-space entities.

A simple way to address this would be with a two-pronged proposal:

1. Allow multiple "if" constraints in a template. All "if" constraints must match. This means a natural way to write complex constraints is a conjunction of simpler constraints.

2. Extend the syntax to:

if (condition) else string

The string (which crucially can be a CT expression involving template parameters) is the error message that would be displayed should the condition be false.

When an overload set is being looked up, if no match then the string corresponding to the first failed condition in each overload is printed as the error message.

Example from std. Current sig:

InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt)
if (isInputRange!InputRange1 && isInputRange!InputRange2
        && is(typeof(move(src.front, tgt.front))));

With the proposal:

InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt)
if (isInputRange!InputRange1)
else InputRange1.stringof ~ " must be an input range"
if (isInputRange!InputRange2)
else InputRange2.stringof ~ " must be an input range"
if (is(typeof(move(src.front, tgt.front))))
else "Cannot move from the front of "~InputRange1.stringof~
    " to the front of "~InputRange2.stringof;

Larger but offers the benefits of beautiful error messages.


Andrei
January 14, 2019
This is a great example of why I want signatures.

Having a concrete type that gives us a way to verify complex types clearly and without using complex CTFE or templates is a must if we want to help new users into D in the future.

Most importantly it needs to be baked into the type system enough that it allows conversion to a vtable as well as compile time verification of the input types implicitly. Based upon my experiments (so the type itself can adapt to an implementation).

The other problem is it needs some way to model internal attributes that are set externally (e.g. InputRange.Type), my named parameter DIP was based upon this requirement.

To go this direction won't be easy. So far my designs result in being able to do OOP in them and strangely they seem to be far more like best practice OOP than classes ever were. But alas it could just be the fact that I designed them talking ;)

January 13, 2019
On Sunday, 13 January 2019 at 18:54:52 UTC, Andrei Alexandrescu wrote:
> We've had this problem for a long time - template doesn't match because of a complex constraint, the error message is unhelpful, and the user doesn't know what to do.
>
> I think this is an important missed opportunity. One of the promises of Design by Introspection is it raises the level of error messages from mere built-in canned messages to higher-level messages expressed in terms of problem-space entities.
>
> A simple way to address this would be with a two-pronged proposal:
>
> 1. Allow multiple "if" constraints in a template. All "if" constraints must match. This means a natural way to write complex constraints is a conjunction of simpler constraints.
>
> 2. Extend the syntax to:
>
> if (condition) else string
>
> The string (which crucially can be a CT expression involving template parameters) is the error message that would be displayed should the condition be false.
>
> When an overload set is being looked up, if no match then the string corresponding to the first failed condition in each overload is printed as the error message.
>
> Example from std. Current sig:
>
> InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt)
> if (isInputRange!InputRange1 && isInputRange!InputRange2
>         && is(typeof(move(src.front, tgt.front))));
>
> With the proposal:
>
> InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt)
> if (isInputRange!InputRange1)
> else InputRange1.stringof ~ " must be an input range"
> if (isInputRange!InputRange2)
> else InputRange2.stringof ~ " must be an input range"
> if (is(typeof(move(src.front, tgt.front))))
> else "Cannot move from the front of "~InputRange1.stringof~
>     " to the front of "~InputRange2.stringof;
>
> Larger but offers the benefits of beautiful error messages.
>
>
> Andrei

I don't really like that syntax, it changes the meaning of what if/else mean and how they behave from everything else.

This makes more sense logically and is in line with how if/else functions. Though still not 100% accurate as the else should technically be with the last if statement.

InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt)
if (!isInputRange!InputRange1) InputRange1.stringof ~ " must be an input range"
if (!isInputRange!InputRange2) InputRange2.stringof ~ " must be an input range"
if (!is(typeof(move(src.front, tgt.front))))
    "Cannot move from the front of "~InputRange1.stringof~
    " to the front of "~InputRange2.stringof;
else
{
    // implementation ...
}

Still this sort of syntax only really works well if the statement is expressed using &&'s but you can't reduce it if you need || logic. All of the || logic would need to be in the same if statement. Then determining which condition was false would require addition if statements to provide a proper message.

We could just go with something like what is being done with asserts.

> InputRange2 moveAll(InputRange1, InputRange2)(InputRange1 src, InputRange2 tgt)
> if (isInputRange!InputRange1 && isInputRange!InputRange2
>         && is(typeof(move(src.front, tgt.front))));

For the above case detect what part of the if statement failed. The error message would be something like

<function declaration>
Arguments don't match as isInputRange!TheRangeTypeUsed is false.




January 13, 2019
On Sunday, 13 January 2019 at 18:54:52 UTC, Andrei Alexandrescu wrote:
> We've had this problem for a long time - template doesn't match because of a complex constraint, the error message is unhelpful, and the user doesn't know what to do.
>
> [...]

I've beat you to it: https://github.com/dlang/DIPs/pull/131 and I like mine more. Syntactically it is compile-time contract programming, with `in` replaced by `if`, easier to describe that way. See that PR thread for more discussion.

January 13, 2019
On 1/13/19 6:35 PM, Nicholas Wilson wrote:
> On Sunday, 13 January 2019 at 18:54:52 UTC, Andrei Alexandrescu wrote:
>> We've had this problem for a long time - template doesn't match because of a complex constraint, the error message is unhelpful, and the user doesn't know what to do.
>>
>> [...]
> 
> I've beat you to it: https://github.com/dlang/DIPs/pull/131 and I like mine more. Syntactically it is compile-time contract programming, with `in` replaced by `if`, easier to describe that way. See that PR thread for more discussion.

Ah cool, that moves the discussion there. Thanks!