Unhelpful error messages

H. S. Teoh hsteoh at quickfur.ath.cx
Sat Jul 6 14:31:55 PDT 2013


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


More information about the Digitalmars-d mailing list