On 5/15/17 1:16 PM, Stanislav Blinov wrote:
On Monday, 15 May 2017 at 15:30:38 UTC, Steven Schveighoffer wrote:

Argument 2 is a string, which is not supported
file(line): Error: template foo cannot deduce function from argument
types !()(int, string), candidates are:
file(line): foo(Args...)(auto ref Args arg) if (noStringArgs!args)

I think the compiler should be able to figure this out, and report it.
The if constraint is a boolean expression, and so it can be divided
into the portions that pass or fail.

How? The constraint, any constraint, is de-facto user code.

Code evaluated at compile time. It actually has to evaluate each of the pieces, and knows why the whole if statement fails exactly.

The constraint:

void foo(T)(T t) if (cond1!T && cond2!T && cond3!T), the compiler knows both what each of those terms evaluate to, and therefore which ones are causing the thing not to be enabled.

Even in the
simple example I've provided, I would not expect the compiler to figure
out what are *my* expectations on the types.

I think you misunderstand, your example would still not compile, and instead of "here are all the ones I tried", it's "here are all the ones I tried, and in each case, I've highlighted why it didn't work".

Many times, you can figure out by looking at the constraints why it didn't work. However, I've encountered many cases where it's saying it doesn't pass isSomeDoodad!T when I thought T is a doodad. Then I need to figure out why it's not working.

Even your library code cannot be all-knowing about what the calling user is trying to do. It may be confusing to him as well. But just "here's a giant if statement, I as the compiler have figured out why it's not true, see if you can too!" is crap.

Even if the compiler was to divide the constraint into blocks and reason
about them separately,

It is.

it's still limited to error reporting we have
now: it will report "is(T == string) was expected to be false, but it's
true". Is that a good error message?

Yes. It's a perfect error message actually. What is confusing about it?


What I'd love to see is the constraint colorized to show green
segments that evaluate to true, and red segments that evaluate to
false. And then recursively show each piece when asked.

I think any time spent making a user-level solution will not scale.
The compiler knows the information, can ascertain why it fails, and
print a much nicer error message. Plus it makes compile-time much
longer to get information that is already available.

I don't see how that is possible. The constraints' complexity is
arbitrary, it's semantics are arbitrary. The compiler does a full
semantic pass, we end up with the error messages as if it was normal
program code. But the thing is, we need different error messages,
because it isn't "normal" program code.

It has to know. It has to evaluate the boolean to see if it should compile! The current situation would be like the compiler saying there's an error in your code, but won't tell you the line number. Surely it knows.

Imagine also a constraint like isInputRange!R. This basically attempts
to compile a dummy lambda. How would one handle this in user-code?

Umm... Exactly as it is implemented currently? With one important
distinction that I would be able to report *exactly why* the type in
question does not satisfy the constraint. Not an obscure

"Error: no property 'empty' for type (typename)"
"Error: expression 'foo.front()' is void and has no value"

but a descriptive

" Argument <number> does not satisfy the constraint isInputRange:"
"(typename) is expected to be an input range, but it doesn't implement
the required interface:"
"   property 'empty' is not defined."
"   property 'front' is defined, but returns void."

The first would be great. I'm having trouble really seeing a reason to prefer the second over the first, it's just a verbose description of the same thing.

Today we get an error that:

void foo(R)(R r) if(isInputRange!R)

doesn't compile for the obvious (to you) range type R. What it doesn't tell you is anything about why that doesn't work. We don't even get the "no property empty" message.

Let me give you a real example. The isForwardRange test used to look like this:

template isForwardRange(R)
{
     enum isForwardRange = isInputRange!R && is(typeof(
     (inout int = 0)
     {
         R r1 = R.init;
         static assert (is(typeof(r1.save) == R));
     }));
}

Here is the definition of isInputRange:

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
    }));
}

Simple, right? However, this was the situation before I applied a fix:

struct ForwardRange
{
   int front() { return 1; }
   void popFront() {}
   enum empty = false;
   ForwardRange save() { return this; }
}

static assert(isInputRange!R);
static assert(!isForwardRange!R);

You tell me, what is the issue? Having library-writer defined error messages are not going to help there because I didn't do it *exactly* right.

If you aren't sure what the answer is, here is the PR that fixed it: https://github.com/dlang/phobos/pull/3276

User code can collect all the information and present it in a readable
way. Compiler will never be able to. The best the compiler would do is
report "T does not satisfy isInputRange". And in my opinion, that is
what it should do. Adding complexity to the compiler to figure out all
imaginable variations doesn't seem like a very good idea. User code is
able to make all assessments it needs, it just doesn't have the ability
to elaborate.

No, the compiler just needs to detail its evaluation process that it's already doing.

If the constraint doesn't actually match the pragma message, you get MISLEADING messages, or maybe messages when it actually compiles. Much better to have the compiler tell you actually what it's doing.


Another example:

is(typeof(x) == string) && x.startsWith("_")

The best we can expect from the compiler is print that out and say it
evaluated to false.

It can say that is(typeof(x) == string) is false, or x.startsWith("_") is false.

User code, on the other hand, can generate a string "x must be a string
that starts with an underscore". Which one is better?

My version. Is x not a string, or does x not start with an underscore? Not enough information in your error message. And it doesn't give me more information than the actual constraint code, it's just written out verbosely.

-Steve

Reply via email to