Better error messages - from reddit

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Mar 4 19:03:39 UTC 2019


On Mon, Mar 04, 2019 at 06:18:14PM +0000, jmh530 via Digitalmars-d wrote:
> On Monday, 4 March 2019 at 15:47:12 UTC, H. S. Teoh wrote:
> > [snip]
> > 
> > This is why years ago I said both here and in the Phobos PR queue
> > that sig constraints need to be written in such a way that anything
> > that *might* possibly be intended for that function ought to be
> > accepted, and static ifs should be used inside the function body to
> > dispatch to the code that handles the corresponding template
> > arguments, with an > assert(0) at the end that tells you why the
> > arguments weren't matched.  [snip]
> 
> What would be an example of a signature constraint that you think
> would be worth keeping? I.e., why not do everything with static ifs?

Haha, good question.  My current stance (which may change -- I'm not
fully convinced either way yet) is a matter of documentation.  Static
ifs inside the function body are invisible to the user, since they are
implementation details, so they don't tell the user what is expected by
the function.  A sig constraint is part of the function signature, and
therefore a good place to document intent.

Note that intent may not be the same thing as implementation.  For
example, if myFunc logically expects an input range, but the current
implementation can only handle bidirectional ranges, then I'd say put
isInputRange!R in the sig constraints, and static assert(0) inside the
function body if R is not also a bidirectional range.  IOW, the intent
is that myFunc should accept all input ranges, but at the moment our
algorithm can only handle a subset of that.  Instead of cluttering the
sig constraint with unreadable clauses like "if this is an input range,
and it also has .length, and it also has feature X and trait Y, and it
also howls at the moon at night", just keep it simple and to-the-point:
"myFunc expects an input range". The rest of the dirty implementation
details can be documented in the ddoc comment and checked with static
ifs / static asserts inside.

Similarly, if two different algorithms are used depending on whether R
is an input range or a forward range, that should be done via static if
-- the user doesn't care whether your algorithm treats forward ranges
differently from input ranges; your function should present a unified,
logically-whole API rather than two similar-looking overloads with
inscrutable sig constraints.  And if the function logically ought to
also accept bidirectional ranges but your current algorithm for whatever
reason breaks in that case, the sig constraint should still declare that
it accepts all input ranges, but you'd have a static assert in the
function body that explains to the user exactly why bidi ranges aren't
currently supported.

Of course, this doesn't quite solve the problem of bad error messages
when you passed something that you thought was an input range, but fails
the isInputRange constraint.  But with simplified sig constraints of
this sort, it's easier to tell what went wrong:

	couldn't match call to myFunc, candidates are:
	auto myFunc(R) if (isInputRange!R)

is a lot easier to read, and figure out what went wrong, than:

	couldn't match call to myFunc, candidates are:
	auto myFunc(R) if (isInputRange!R && !isForwardRange)
	auto myFunc(R) if (isForwardRange!R && hasLength!R && howlsAtMoon!R)
	auto myFunc(R) if (!howlsAtMoon!R && (isInputRange!R || is(ElementType!R : dchar)) && !isSomeString!R)

where you can't even tell at a glance which overload was the intended
one.

Once we've eliminated needless overloads from the equation, the problem
of emitting better error messages is simplified to emitting a sane error
message for one failed constraint of one overload, rather than N failed
constraints for M overloads.  For that, perhaps Andrei's idea of adding
string clauses to sig constraints might be one way of tackling this.

But in any case, there needs to be a way for a template like
isInputRange to emit a specific error like "R does not implement
.empty", rather than just silently failing (because errors are gagged --
D's version of SFINAE) and offloading to whatever other overloads there
may be.  Then when the compiler lists the candidate functions that
failed to match, it could also display this error message beside each
one to indicate what went wrong, where.


T

-- 
"I suspect the best way to deal with procrastination is to put off the procrastination itself until later. I've been meaning to try this, but haven't gotten around to it yet. " -- swr


More information about the Digitalmars-d mailing list