www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Unhelpful error messages

reply "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
resulting error message is completely unhelpful: it just shows about 20
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
next sibling parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
http://d.puremagic.com/issues/
Jul 06 2013
next sibling parent "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
prev sibling next sibling parent "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
prev sibling next sibling parent "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
prev sibling next sibling parent "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
prev sibling next sibling parent "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 have this pretty please? :-) 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
prev sibling parent "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
prev sibling parent "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