Re: Simple and effective approaches to constraint error messages

2016-04-27 Thread Steven Schveighoffer via Digitalmars-d

On 4/25/16 6:14 PM, H. S. Teoh via Digitalmars-d wrote:

On Mon, Apr 25, 2016 at 05:20:08PM -0400, Steven Schveighoffer via 
Digitalmars-d wrote:

On 4/25/16 1:52 PM, Andrei Alexandrescu wrote:

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


I like the first option. However, I think it should be deeper than
that.

Sometimes you have code that you are sure matches one of the
constraints (e.g. isInputRange), but for some reason it doesn't. Sure,
it's good to know that your struct that looks exactly like an input
range isn't an input range, but to know why would be better.

I realize that more context for an error may be too verbose, but an
option to have the compiler tell you exactly why it is stopping
compilation is good when you can't figure out the obvious reason.


What about displaying the full context with -v? The compiler currently
already uses -v to show long error messages that are truncated by
default.


Sure, but the problem is that -v enables a TON of messages. I'd 
definitely be willing to deal with that if that's the only way to show it.



So for instance, having it say "constraint failed:
isInputRange!NotARange" is good, but if you pass some parameter it
says something like: "constraint failed: isInputRange!NotARange,
std.range.primitives:146 failed to compile: x.front

[...]

What about this: when a constraint fails, display the first (related
group of) error messages related to that constraint that the compiler
would have emitted if errors weren't gagged. So if isInputRange fails to
instantiate for some argument, the compiler would show the first error
message that resulted in template instantiation failure, e.g.:

std/range.d(123): Error: no property 'front' for type 'int'


Absolutely, that's what I'm looking for.

One thing that I remember being an issue with isXRange (can't remember 
which one) is that it required is(typeof(r.front) == ElementType!R), 
which looks innocuous enough. However, for ranges where front is a 
method, but is not labeled @property, this fails. This has since been fixed.


Someone who makes such a range, and then sees it failing because of some 
obscure compiler behavior is almost certainly never going to figure this 
out without an in-depth investigation. Having the compiler just tell 
them the answer is sooo much better.


-Steve


Re: Simple and effective approaches to constraint error messages

2016-04-26 Thread Timon Gehr via Digitalmars-d

On 26.04.2016 13:26, Marc Schütz wrote:


Currently, there is no boolean short-cut evaluation in template
constraints, see:

 bool foo()() {
 pragma(msg, "foo");
 return true;
 }

 bool bar()() {
 pragma(msg, "bar");
 return true;
 }

 void main() {
 static assert(__traits(compiles, foo() || bar()));
 }

Prints "foo" and "bar", even though bar() wouldn't need to be evaluated
anymore after foo() returned true.


There sometimes is short-cut evaluation. This only prints "Foo":

template Foo(){
pragma(msg, "Foo");
enum Foo=true;
}
auto bar()(){
pragma(msg, "bar");
return true;
}
void main(){
static assert(Foo!()||bar());
}

I don't see the point of the different behaviour for those cases.


Re: Simple and effective approaches to constraint error messages

2016-04-26 Thread Meta via Digitalmars-d
On Tuesday, 26 April 2016 at 15:56:55 UTC, Andrei Alexandrescu 
wrote:

On 04/26/2016 11:35 AM, Meta wrote:
The nice part about 1 is that every constraint gets it for 
free.


Only if in CNF, otherwise needs changed. -- Andrei


True, but I bet the majority of template constraints are in this 
form. The wiki page you linked also says that clauses that are 
not in CNF can be rewritten in CNF, but doing that automatically 
in the compiler would probably be difficult, bug-ridden, and 
annoying for end users.


Are you familiar with Nim's implementation of concepts? I think 
somebody actually implemented something close to this in D using 
inheritance and introspection.


http://nim-lang.org/docs/manual.html#generics-concepts


Re: Simple and effective approaches to constraint error messages

2016-04-26 Thread Andrei Alexandrescu via Digitalmars-d

On 04/26/2016 11:35 AM, Meta wrote:

The nice part about 1 is that every constraint gets it for free.


Only if in CNF, otherwise needs changed. -- Andrei


Re: Simple and effective approaches to constraint error messages

2016-04-26 Thread Meta via Digitalmars-d
On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu 
wrote:

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.


The problem here is that *every* constraint must be modified, 
which will a long and tedious process. The nice part about 1 is 
that every constraint gets it for free. Applying it recursively 
would provide a nice "stack trace" of constraint failure so the 
user can see exactly what went wrong and where. The problem then 
becomes the verbosity of errors, but we already have ways of 
dealing with that such as the -v switch.





Re: Simple and effective approaches to constraint error messages

2016-04-26 Thread Atila Neves via Digitalmars-d
On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu 
wrote:
It's been long asked in our community that failing template 
constraints issue better error messages. Consider:


[...]


I still prefer static inheritance. Having said that, I like the 
pragma(err) thing in the template constraint.


Atila


Re: Simple and effective approaches to constraint error messages

2016-04-26 Thread Kagamin via Digitalmars-d

https://issues.dlang.org/show_bug.cgi?id=9626 ?


Re: Simple and effective approaches to constraint error messages

2016-04-26 Thread Marc Schütz via Digitalmars-d
On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu 
wrote:

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




I prefer this one, because it should work without modifying 
library or user code.



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, "..."))



Currently, there is no boolean short-cut evaluation in template 
constraints, see:


bool foo()() {
pragma(msg, "foo");
return true;
}

bool bar()() {
pragma(msg, "bar");
return true;
}

void main() {
static assert(__traits(compiles, foo() || bar()));
}

Prints "foo" and "bar", even though bar() wouldn't need to be 
evaluated anymore after foo() returned true.


Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread H. S. Teoh via Digitalmars-d
On Mon, Apr 25, 2016 at 05:20:08PM -0400, Steven Schveighoffer via 
Digitalmars-d wrote:
> On 4/25/16 1:52 PM, Andrei Alexandrescu wrote:
> >It's been long asked in our community that failing template
> >constraints issue better error messages. Consider:
> 
> I like the first option. However, I think it should be deeper than
> that.
> 
> Sometimes you have code that you are sure matches one of the
> constraints (e.g. isInputRange), but for some reason it doesn't. Sure,
> it's good to know that your struct that looks exactly like an input
> range isn't an input range, but to know why would be better.
> 
> I realize that more context for an error may be too verbose, but an
> option to have the compiler tell you exactly why it is stopping
> compilation is good when you can't figure out the obvious reason.

What about displaying the full context with -v? The compiler currently
already uses -v to show long error messages that are truncated by
default.


> So for instance, having it say "constraint failed:
> isInputRange!NotARange" is good, but if you pass some parameter it
> says something like: "constraint failed: isInputRange!NotARange,
> std.range.primitives:146 failed to compile: x.front
[...]

What about this: when a constraint fails, display the first (related
group of) error messages related to that constraint that the compiler
would have emitted if errors weren't gagged. So if isInputRange fails to
instantiate for some argument, the compiler would show the first error
message that resulted in template instantiation failure, e.g.:

std/range.d(123): Error: no property 'front' for type 'int'

It's not completely nice, in that it exposes the implementation
somewhat, but it seems to be more useful when something goes wrong to
see concrete code that's failing than to get a message about
isInputRangeImpl!(X,Y,Z) failing to compile, and you have no idea what
that's supposed to mean because it's an internal Phobos implementation
detail.


T

-- 
Perhaps the most widespread illusion is that if we were in power we would 
behave very differently from those who now hold it---when, in truth, in order 
to get power we would have to become very much like them. -- Unknown


Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread QAston via Digitalmars-d
On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu 
wrote:

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.


Improvement in the generic case is a must. I personally either 
comment out the constraint in the lib source (if I can) or 
recreate the predicate in a context where I actually can see the 
error message. That's tedious and makes me hate template 
constraints because for templated libraries the source is there 
anyway, I prefer to be given real error (which shows me the exact 
issue) rather than a mystery puzzle.


Could your proposal be extended with showing the evaluation for 
isInputRange!NotARange to see why it returns false for given 
type, i.e. to see that compiler error:

Error: no property 'empty' for type 'NotARange'


So for example the error message could look like:
 /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
constraint stacktrace:
   std.range.primitives.isInputRange!NotARange
   boolean expression: is(typeof(
(inout int = 0)
{
NotARange r = NotARange.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
}));
  failed with error: no property 'empty' for type 'NotARange'


Btw. I see you've taken a focus on making D more usable. Probably 
teaching D to new people gave you some very needed perspective 
:P. Big thanks!




Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread Steven Schveighoffer via Digitalmars-d

On 4/25/16 1:52 PM, Andrei Alexandrescu wrote:

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


I like the first option. However, I think it should be deeper than that.

Sometimes you have code that you are sure matches one of the constraints 
(e.g. isInputRange), but for some reason it doesn't. Sure, it's good to 
know that your struct that looks exactly like an input range isn't an 
input range, but to know why would be better.


I realize that more context for an error may be too verbose, but an 
option to have the compiler tell you exactly why it is stopping 
compilation is good when you can't figure out the obvious reason.


So for instance, having it say "constraint failed: 
isInputRange!NotARange" is good, but if you pass some parameter it says 
something like: "constraint failed: isInputRange!NotARange, 
std.range.primitives:146 failed to compile: x.front


or something like that. This is a previous post related to this issue:

http://forum.dlang.org/post/m4nnrk$1ml5$1...@digitalmars.com

-Steve


Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread Andrei Alexandrescu via Digitalmars-d

On 04/25/2016 04:50 PM, Sebastiaan Koppe wrote:

What about overloaded functions with complex constraints? How would the
errors look when none of the overloaded constraints fully match?


Print reason for each. -- Andrei


Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread Sebastiaan Koppe via Digitalmars-d
On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu 
wrote:


Destroy!

Andrei


What about overloaded functions with complex constraints? How 
would the errors look when none of the overloaded constraints 
fully match?


auto fun(T)(T t) if (hasWheels!T && canFly!T) {}

auto fun(T)(T t) if (canFloat!T && isAirtight!T) {}

struct A
{
// suppose it has wheels and floats, but can't fly nor is it 
airtight

}

void main()
{
  A a;
  a.fun(); // `Error: either make A fly or airtight`
}


Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread Andrei Alexandrescu via Digitalmars-d

On 04/25/2016 02:17 PM, Daniel N wrote:

Currently when the compiler is in "__traits(compiles" or "is(typeof"
mode, it simply gags all errors, if it instead would save them to a side
buffer. Later the entire side-buffer could be dumped after a template
constraint totally failed. If a constraint succeeds the buffer is cleared.


Walter said that's liable to print a rather large and unstructured pile 
of messages. -- Andrei


Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread Daniel N via Digitalmars-d
On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu 
wrote:
It's been long asked in our community that failing template 
constraints issue better error messages. Consider:


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.




It should be possible to generate those errors even with 
constraints and no library update.


Currently when the compiler is in "__traits(compiles" or 
"is(typeof" mode, it simply gags all errors, if it instead would 
save them to a side buffer. Later the entire side-buffer could be 
dumped after a template constraint totally failed. If a 
constraint succeeds the buffer is cleared.





Re: Simple and effective approaches to constraint error messages

2016-04-25 Thread Adam D. Ruppe via Digitalmars-d
On Monday, 25 April 2016 at 17:52:58 UTC, Andrei Alexandrescu 
wrote:
/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 more-or-less what I've been wanting to do (though I was 
thinking of using color or something in the signature to show 
pass/fail/not tested on each clause, but your approach works too.)


It is very important that it shows what failed and what the 
arguments are. The rest is nice, but less impactful.


So this would make a big difference and should be a high priority 
to implement.


BTW I'd also like traditional overloaded functions to show the 
match/not match report of arguments. It lists them now but is 
hard to read a long list. If the computer just said "argument #2 
didn't match, int != string" or something it'd give at-a-glance 
info there too.


But indeed, constraints are the much higher return.


Idea #2: Allow custom error messages


Let me show you what I've been toying with the last few weeks:



struct Range {
bool empty() { return true; }
void popFront() {}
int front;
}

// you test it at declaration point to get static errors
// i recommend people do this now, even with our less-helpful
// isInputRagnge
mixin validateInputRange!Range;

/* *** */


import std.traits;

// the validate mixin prints the errors
mixin template validateInputRange(T) {
static assert(isInputRange!T, checkInputRange!T);
}

// the is template returns bool if it passed
template isInputRange(T) {
enum isInputRange = checkInputRange!T.length == 0;
}


// and the check function generates an array of error
// strings using introspection
pragma(inline, true)
template checkInputRange(T) {
string[] checkInputRangeHelper() {
string[] errors;

static if(!hasMember!(T, "empty"))
errors ~= "has no member `empty`";
else static if(!memberCanBeUsedInIf!(T, "empty"))
errors ~= "empty cannot be used in if";

static if(!hasMember!(T, "popFront"))
errors ~= "has no member `popFront`";
else static if(!isCallableWithZeroArguments!(T, "popFront"))
			errors ~= "`popFront()` is not callable. Found type: " ~ 
typeof(__traits(getMember, T, "popFront")).stringof ~ ". 
Expected: void()";


static if(!hasMember!(T, "front"))
errors ~= "has no member `front`";

return errors;
}

enum checkInputRange = checkInputRangeHelper();
}

/* *** */

// these can be added to std.traits

template memberCanBeUsedInIf(T, string member) {
static if(__traits(compiles, (inout int = 0){
T t = T.init;
if(__traits(getMember, t, member)) {}
}))
enum memberCanBeUsedInIf = true;
else
enum memberCanBeUsedInIf = false;
}

template isCallableWithZeroArguments(T, string member) {
static if(__traits(compiles, (inout int = 0){
T t = T.init;
(__traits(getMember, t, member))();
}))
enum isCallableWithZeroArguments = true;
else
enum isCallableWithZeroArguments = false;

}



===

With appropriate library support, those check functions could be 
pretty easily written and the rest generated automatically.


Now, the compiler doesn't know anything about the error strings, 
but generating them with simple CTFE gives us the full language 
to define everything. The compiler could just learn the pattern 
(or we add some pragma) that when isInputRange fails, it prints 
out the report the library generated.



But this is doable today and shouldn't break any code.