• H. S. Teoh (77/77) Jul 06 2013 I'm posting this here 'cos I've no idea how to improve it, but it's
• Maxim Fomin (1/1) Jul 06 2013 http://d.puremagic.com/issues/
• Peter Alexander (19/20) Jul 07 2013 Ironically, this isn't helpful. H. S. Teoh is opening up a
• H. S. Teoh (30/48) Jul 08 2013 I don't see how an IDE could do better than the compiler. Combinatorial
• Peter Alexander (8/29) Jul 08 2013 I thinking of more of an interactive diagnostic: you choose which
• H. S. Teoh (28/51) Jul 08 2013 The problem is, this presumes knowledge of Phobos internals, which most
• Don (11/61) Jul 10 2013 The compiler could join all the constraints and then simplify it
• H. S. Teoh (9/27) Jul 10 2013 +1. Now that's cool, and is something actually implementable. Can we
• H. S. Teoh (12/30) Jul 12 2013 [...]
"H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
```I'm posting this here 'cos I've no idea how to improve it, but it's
definitely a problem:

First, let's define a struct that overloads opCast:

struct S {
T opCast(T)()
if (is(T == struct))
{
T t;
return t;
}
}

The code above is just a stub implementation; the idea is that S would
store some kind of key/value pairing, and opCast would automatically
assign key/value pairs to struct fields (hence the signature constraint
is(T==struct)). The reason opCast is used is to integrate nicely with
std.conv:

unittest {
S s;
... // add some key/value pairs to s

struct T {
int x, int y;
}

auto t = to!T(s);
}

On DMD, this works OK. But if we add a method to T:

unittest {
S s;
... // add some key/value pairs to s

struct T {
int x, int y;
bool f() { return true; }
}

auto t = to!T(s);
}

The existence of T.f() causes T to acquire an implicit context pointer,
which is a separate issue, but what I'm trying to get at here is, the
or so lines of errors saying that std.conv.to cannot be instantiated,
and that it doesn't match any of a large number of toImpl overloads.
But there is no indication whatsoever why that might be so -- after all,
opCast *is* defined! -- the signature constraint in toImpl that looks
for opCast is written like this:

if (is(typeof(S.init.opCast!T()) : T) && ...

The problem is, if S.init.opCast!T() fails to compile for *any* reason,
the signature constraint declines, but the user is left with no clue as
to why!

The cause of the problem only becomes clear if you replace to!T(s) with
s.opCast!T(), then the compiler will tell you exactly what's wrong with
opCast (in this case, that T cannot be instantiated outside of its
definition scope).

In fact, you can insert a random error into opCast and you wouldn't know
any better (as long as it's syntactically correct), since if you never
call it directly, only via std.conv.to, then you'll only ever see the
opaque wall of template instantiation failures, not the *real* reason
for the failure.

I don't know how to improve this situation. One thought is to rewrite
toImpl's signature constraint to only check for the *existence* of
opCast, so that any subsequent compile errors will become visible (even
if ugly). But that doesn't work for cases where something else may match
the signature constraints of another toImpl overload.

But the current state of things is very unhelpful -- you keep getting an
error that to!T() can't be instantiated, but all you see is that all the
toImpl overloads rejected T. Then if you comment out the "T t;" line in
opCast and replace the return with "return T.init", then things
mysteriously begin to work again. In fact, adding random changes into
opCast will either compile if you're lucky, or complain that to!T()
can't be instantiated. No message about any syntax error or other error
in opCast is ever given. You could be missing a case in a switch
statement, and all you'd learn is that to!T() can't be instantiated --
opCast isn't even mentioned anywhere, so you wouldn't even know to look
there in the first place!

Clearly, something needs to be improved, but it's unclear *what*, or
how.

T

--
Long, long ago, the ancient Chinese invented a device that lets them see
through walls. It was called the "window".
```
Jul 06 2013
"Maxim Fomin" <maxim maxim-fomin.ru> writes:
```http://d.puremagic.com/issues/
```
Jul 06 2013
"Peter Alexander" <peter.alexander.au gmail.com> writes:
```On Sunday, 7 July 2013 at 06:58:29 UTC, Maxim Fomin wrote:
http://d.puremagic.com/issues/

Ironically, this isn't helpful. H. S. Teoh is opening up a
discussion, not reporting a bug.

The problem here is more general than this specific case. Any
template constraint on any function could fail to pass, and the
best error you'll get is that it couldn't match any function.
Even if the error messages were improved to tell you what part of
the constraint failed, you still have no idea *why* it failed
(e.g. *why* is my range not a forward range?)

Overloads just make matters exponentially worse. Not only can the
compiler fail to provide a good error for a single function, but
needs to provide a good error for every possible candidate
function. If a constraint checks if another overloaded function
is callable then you end up with a combinatorial explosion of
reasons why your function didn't compile.

It's a tough situation and I think the only way this could even
reasonably be resolved is through some sophisticated IDE
integration. There is no way to display this kind of error report
in a blob of command line text.
```
Jul 07 2013
"H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
```On Sun, Jul 07, 2013 at 02:06:46PM +0200, Peter Alexander wrote:
[...]
The problem here is more general than this specific case. Any
template constraint on any function could fail to pass, and the best
error you'll get is that it couldn't match any function. Even if the
error messages were improved to tell you what part of the constraint
failed, you still have no idea *why* it failed (e.g. *why* is my
range not a forward range?)

Overloads just make matters exponentially worse. Not only can the
compiler fail to provide a good error for a single function, but
needs to provide a good error for every possible candidate function.
If a constraint checks if another overloaded function is callable
then you end up with a combinatorial explosion of reasons why your
function didn't compile.

It's a tough situation and I think the only way this could even
reasonably be resolved is through some sophisticated IDE
integration. There is no way to display this kind of error report in
a blob of command line text.

I don't see how an IDE could do better than the compiler. Combinatorial
explosion is a nasty problem, and if an IDE could solve it, so could the
compiler. Sure, the IDE could give you a nice scrollable GUI widget to
look through all the various reasons of the instantiation failure, but
fundamentally speaking, that's not much different from running grep
through 50 pages of compiler output. You still haven't solved the root
problem, which is to narrow down the exponential set of possible problem
causes to a manageable, human-comprehensible number.

I think the crux of the problem is that there's no reasonable way to
tell the difference between toImpl!(float)'s signature constraint
declining a particular instantiation because you're trying to convert a
struct, vs. toImpl!(struct)'s signature constraint declining it because
the struct causes compile errors.

If toImpl!(struct) accepted *all* struct instantiations, then when a
compile error occurs it would be much easier to track it down to a
problem with the struct. You'd know that toImpl actually supports
converting a struct, but due to the struct's non-conformance to some
requirements, this particular attempt failed. You'd be able to pinpoint
the problem to the struct immediately, rather than wondering whether the
problem was that toImpl doesn't support struct conversions.

But by rejecting all non-compiling types (including structs) outright,
it's basically pushing the problem up to the level of which of the 25
overloads of toImpl should be chosen for this particular instantiation
-- 24 overloads of which have nothing whatsoever to do with structs.
That's where the combinatorial explosion comes from.

T

--
Computers aren't intelligent; they only think they are.
```
Jul 08 2013
"Peter Alexander" <peter.alexander.au gmail.com> writes:
```On Monday, 8 July 2013 at 18:10:45 UTC, H. S. Teoh wrote:
On Sun, Jul 07, 2013 at 02:06:46PM +0200, Peter Alexander wrote:
It's a tough situation and I think the only way this could even
reasonably be resolved is through some sophisticated IDE
integration. There is no way to display this kind of error
report in
a blob of command line text.

I don't see how an IDE could do better than the compiler.
Combinatorial
explosion is a nasty problem, and if an IDE could solve it, so
could the
compiler. Sure, the IDE could give you a nice scrollable GUI
widget to
look through all the various reasons of the instantiation
failure, but
fundamentally speaking, that's not much different from running
grep
through 50 pages of compiler output. You still haven't solved
the root
problem, which is to narrow down the exponential set of
possible problem
causes to a manageable, human-comprehensible number.

I thinking of more of an interactive diagnostic: you choose which
overload you intended to instantiate and then get a list of
reasons why that failed to compile. Repeat recursively for any
sub-calls.

Maybe the compiler could just spew out every possible error for
every instantiation, and expect the user to grep, but that's not
going to be a pleasant experience.
```
Jul 08 2013
"H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
```On Mon, Jul 08, 2013 at 09:47:46PM +0200, Peter Alexander wrote:
On Monday, 8 July 2013 at 18:10:45 UTC, H. S. Teoh wrote:
On Sun, Jul 07, 2013 at 02:06:46PM +0200, Peter Alexander wrote:
It's a tough situation and I think the only way this could even
reasonably be resolved is through some sophisticated IDE
integration. There is no way to display this kind of error report in
a blob of command line text.

I don't see how an IDE could do better than the compiler.
Combinatorial explosion is a nasty problem, and if an IDE could solve
it, so could the compiler. Sure, the IDE could give you a nice
scrollable GUI widget to look through all the various reasons of the
instantiation failure, but fundamentally speaking, that's not much
different from running grep through 50 pages of compiler output. You
still haven't solved the root problem, which is to narrow down the
exponential set of possible problem causes to a manageable,
human-comprehensible number.

I thinking of more of an interactive diagnostic: you choose which
overload you intended to instantiate and then get a list of reasons
why that failed to compile. Repeat recursively for any sub-calls.

The problem is, this presumes knowledge of Phobos internals, which most
D users probably would have no clue about. How is one supposed to know
which of the 25 overloads should be used in this particular case anyway?
For all one knows, it may be a Phobos bug or something.

Whereas a message like "cannot instantiate S in std.blah.internal.func"
where S is the user-defined struct, would be a good indication as to
what might be wrong without needing to understand how Phobos works.

Maybe the compiler could just spew out every possible error for every
instantiation, and expect the user to grep, but that's not going to be
a pleasant experience.

The original problem, though, was that the error happened inside a
signature constraint, so it was gagged. The actual error that caused the
failure was *suppressed*. The stuff that happened afterwards -- the
reams of failed overload candidates, etc., are just collateral damage.
The real issue here is that the primary cause of the problem has been
gagged but the collateral damage fills the screen.

That's why I said, had toImpl been written in a way that voluntarily
took up all structs, instead of silently declining one that failed to
compile (and gagging the offending error), then the actual cause of the
problem would've been at least there to be found, even if it's buried in
15 pages of errors. Right now, it's not even *visible*; you have to
first sort through the collateral damage and *guess* where the problem
might be, before you could even pick up any trail of the evidence. And
then you still can't get an actual error message out of it; you have to
basically copy-n-paste the toImpl overload, sans signature constraint,
into your code before the compiler will even give you an error message
that gives any indication as to what the problem is.

T

--
Why can't you just be a nonconformist like everyone else? -- YHL
```
Jul 08 2013
"Don" <turnyourkidsintocash nospam.com> writes:
```On Monday, 8 July 2013 at 20:46:35 UTC, H. S. Teoh wrote:
On Mon, Jul 08, 2013 at 09:47:46PM +0200, Peter Alexander wrote:
On Monday, 8 July 2013 at 18:10:45 UTC, H. S. Teoh wrote:
On Sun, Jul 07, 2013 at 02:06:46PM +0200, Peter Alexander
wrote:
It's a tough situation and I think the only way this could
even
reasonably be resolved is through some sophisticated IDE
integration. There is no way to display this kind of error
report in
a blob of command line text.

I don't see how an IDE could do better than the compiler.
Combinatorial explosion is a nasty problem, and if an IDE
could solve
it, so could the compiler. Sure, the IDE could give you a nice
scrollable GUI widget to look through all the various reasons
of the
instantiation failure, but fundamentally speaking, that's not
much
different from running grep through 50 pages of compiler
output. You
still haven't solved the root problem, which is to narrow
down the
exponential set of possible problem causes to a manageable,
human-comprehensible number.

I thinking of more of an interactive diagnostic: you choose
which
overload you intended to instantiate and then get a list of
reasons
why that failed to compile. Repeat recursively for any
sub-calls.

The problem is, this presumes knowledge of Phobos internals,
which most
D users probably would have no clue about. How is one supposed
to know
which of the 25 overloads should be used in this particular
case anyway?
For all one knows, it may be a Phobos bug or something.

Whereas a message like "cannot instantiate S in
std.blah.internal.func"
where S is the user-defined struct, would be a good indication
as to
what might be wrong without needing to understand how Phobos
works.

Maybe the compiler could just spew out every possible error
for every
instantiation, and expect the user to grep, but that's not
going to be
a pleasant experience.

The compiler could join all the constraints and then simplify it
to create the error message. (eg by creating a Binary Decision
Diagram (BDD))

eg given constraints:

if ( A  && B && C )
if ( (A  && D) || ( A && E && F) )
if ( E && G )

Suppose A is true but the conditions fail. The compiler could
then write that no templates match because ( B || D || E ) is
false.
```
Jul 10 2013
"H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
```On Wed, Jul 10, 2013 at 03:05:25PM +0200, Don wrote:
On Monday, 8 July 2013 at 20:46:35 UTC, H. S. Teoh wrote:
On Mon, Jul 08, 2013 at 09:47:46PM +0200, Peter Alexander wrote:

[...]
Maybe the compiler could just spew out every possible error for
every instantiation, and expect the user to grep, but that's not
going to be a pleasant experience.

The compiler could join all the constraints and then simplify it to
create the error message. (eg by creating a Binary Decision Diagram
(BDD))

eg given constraints:

if ( A  && B && C )
if ( (A  && D) || ( A && E && F) )
if ( E && G )

Suppose A is true but the conditions fail. The compiler could then
write that no templates match because ( B || D || E ) is false.

+1. Now that's cool, and is something actually implementable.  Can we

T

--
The right half of the brain controls the left half of the body. This
means that only left-handed people are in their right mind. -- Manoj
Srivastava
```
Jul 10 2013
"H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
```On Wed, Jul 10, 2013 at 03:05:25PM +0200, Don wrote:
On Monday, 8 July 2013 at 20:46:35 UTC, H. S. Teoh wrote:
On Mon, Jul 08, 2013 at 09:47:46PM +0200, Peter Alexander wrote:

[...]
Maybe the compiler could just spew out every possible error for
every instantiation, and expect the user to grep, but that's not
going to be a pleasant experience.

The compiler could join all the constraints and then simplify it to
create the error message. (eg by creating a Binary Decision Diagram
(BDD))

eg given constraints:

if ( A  && B && C )
if ( (A  && D) || ( A && E && F) )
if ( E && G )

Suppose A is true but the conditions fail. The compiler could then
write that no templates match because ( B || D || E ) is false.

[...]

Hmm, looking at BDD's again, it appears that the simplification process
may not be quite so simple. Depending on the ordering of constraints,
you may end up with a BDD of exponential length. The bad thing is that
finding the best ordering is NP-hard. There are heuristic algorithms
that work well for "normal" cases, though, so hopefully this won't be
*too* nasty to implement.

T

--
Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry
```
Jul 12 2013