Failed template match error now lists candidates. #1197

Merged
merged 1 commit into from Nov 10, 2012

Projects

None yet

5 participants

@Poita
Contributor
Poita commented Oct 20, 2012

In an attempt to gradually improve template errors, when a template usage fails to match a template declaration it will now list the candidates, so that the user can see all the overloads in one place. This makes it easier to figure out why the match failed.

Example:

void foo(T)(T r) if (is(T : string)) {}
void foo(T)(T a, T b) {}
int x = foo(0);

gives

foo.d(3): Error: template foo.foo does not match any function template declaration. Candidates are:
foo.d(1):        foo.foo(T)(T r) if (is(T : string))
foo.d(2):        foo.foo(T)(T a, T b)
foo.d(1): Error: template foo.foo cannot deduce template function from argument types !()(int)

Notice that the location of each overload is shown, too.

If there are over 100 candidates, then it will only show 100 and then end with a line saying "... X more ...", just in case someone has generated some crazy number of overloads.

Next step from here is to offer better diagnostics as to why each instantiation failed.

@alexrp
Member
alexrp commented Oct 20, 2012

Very nice! I would suggest to make the maximum number configurable somehow, though. I think that might be useful for IDEs. Not sure if a command line option or environment variable is best.

@Poita
Contributor
Poita commented Oct 20, 2012

@alexrp I figured someone might say that. I was tempted to add an opt, but thought that it was too insignificant to clutter the command line with. I know Walter is against adding lots and lots of args (and I agree).

I would argue that we should just leave it as is for the moment, and if someone finds a genuine need to control it then we can easily add it later without disruption. I'm thinking this is a simply a case of YAGNI (You Ain't Gonna Need It).

FWIW, as far as I can tell, GCC and clang do not provide such an option for C++.

@alexrp
Member
alexrp commented Oct 20, 2012

GCC provides -fmax-errors (not an option to control template errors in particular).

@Poita
Contributor
Poita commented Oct 20, 2012

Yes, but dmd doesn't even provide that. It's hard coded:

    if (global.errors >= 20)        // moderate blizzard of cascading messages
        fatal();

I'm going to call that precedence :-)

@yebblies
Member

Yes, but dmd doesn't even provide that. It's hard coded:

Isn't that just for the lexer?

@Poita
Contributor
Poita commented Oct 20, 2012

Not sure, but I think this is getting off topic.

Here's the facts:

  1. GCC and clang do not provide an option for this particular number.
  2. If unneeded, adding the option would would clutter the command line args with no gain, and is difficult to remove.
  3. If it is needed, we can trivially add it later with no disruption.

Thus, assuming everyone is otherwise happy with this patch, I propose that it is merged, and we can discuss whether the option is needed in a separate forum.

@Poita
Contributor
Poita commented Oct 20, 2012

Added another commit which adds in the template function arguments to the candidate list (and all template function error messages).

Before:

foo.d(3): Error: template foo.foo does not match any function template declaration. Candidates are:
foo.d(1):        foo.foo(T) if (is(T : string))
foo.d(2):        foo.foo(T)
foo.d(1): Error: template foo.foo cannot deduce template function from argument types !()(int)

After:

foo.d(3): Error: template foo.foo does not match any function template declaration. Candidates are:
foo.d(1):        foo.foo(T)(T r) if (is(T : string))
foo.d(2):        foo.foo(T)(T a, T b)
foo.d(1): Error: template foo.foo cannot deduce template function from argument types !()(int)

Notice the addition of the function arguments. This allows you to discern between function template with the same template parameters and constraints, but different function parameters.

@yebblies yebblies commented on an outdated diff Oct 27, 2012
src/template.c
kind(), parent->toPrettyChars(), ident->toChars());
+
+ // Display candidate template functions
+ int numToDisplay = 100; // sensible number to display
+ for (TemplateDeclaration *td = this; td; td = td->overnext)
+ {
+ ::errorSupplemental(td->loc, "%s", td->toPrettyChars());
+ if (--numToDisplay == 0)
+ {
+ // Too many overloads to sensibly display.
+ // Just show count of remaining overloads.
+ int remaining = 0;
+ while (td = td->overnext)
@yebblies
yebblies Oct 27, 2012 Member

dmc produces a warning for this expression.

@yebblies
Member

A more complicated example does not end up looking so nice:


import std.conv;

void main()
{
    auto a = to!(void*)("hello");
}

Gives:

..\..\phobos\std\conv.d(269): Error: template std.conv.toImpl does not match any function template declaration. Candidates are:
..\..\phobos\std\conv.d(325):        std.conv.toImpl(T,S)(S value) if (isImplicitlyConvertible!(S,T) && !isEnumStrToStr!(S,T) && !isNullToStr!(S,T))
..\..\phobos\std\conv.d(431):        std.conv.toImpl(T,S)(ref S s) if (isRawStaticArray!(S))
..\..\phobos\std\conv.d(445):        std.conv.toImpl(T,S)(S value) if (is(S : Object) && !is(T : Object) && !isSomeString!(T) && hasMember!(S,"to") && is(typeof(S.init.to!(T)()) : T))
..\..\phobos\std\conv.d(466):        std.conv.toImpl(T,S)(S value) if (is(typeof(S.init.opCast!(T)()) : T) && !(isSomeString!(T) && !is(T == enum) && !isAggregateType!(T)))
..\..\phobos\std\conv.d(497):        std.conv.toImpl(T,S)(S value) if (!isImplicitlyConvertible!(S,T) && is(T == struct) && is(typeof(T(value))))
..\..\phobos\std\conv.d(547):        std.conv.toImpl(T,S)(S value) if (!isImplicitlyConvertible!(S,T) && is(T == class) && is(typeof(new T(value))))
..\..\phobos\std\conv.d(619):        std.conv.toImpl(T,S)(S value) if (!isImplicitlyConvertible!(S,T) && (is(S == class) || is(S == interface)) && !is(typeof(value.opCast!(T)()) : T) && (is(T == class) || is(T == interface)) && !is(typeof(new T(value))))
..\..\phobos\std\conv.d(780):        std.conv.toImpl(T,S)(S value) if (!(isImplicitlyConvertible!(S,T) && !isEnumStrToStr!(S,T) && !isNullToStr!(S,T)) && (isSomeString!(T) && !is(T == enum) && !isAggregateType!(T)))
..\..\phobos\std\conv.d(1065):        std.conv.toImpl(T,S)(S value, uint radix) if (isIntegral!(S) && isSomeString!(T) && !is(T == enum))
..\..\phobos\std\conv.d(1129):        std.conv.toImpl(T,S)(S s, in T leftBracket, in T separator = ", ", in T rightBracket = "]") if (!isSomeChar!(ElementType!(S)) && (isInputRange!(S) || isInputRange!(Unqual!(S))) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1166):        std.conv.toImpl(T,S)(ref S s, in T leftBracket, in T separator = " ", in T rightBracket = "]") if ((is(S == void[]) || is(S == const(void)[]) || is(S == immutable(void)[])) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1177):        std.conv.toImpl(T,S)(S s, in T leftBracket, in T keyval = ":", in T separator = ", ", in T rightBracket = "]") if (isAssociativeArray!(S) && !is(S == enum) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1203):        std.conv.toImpl(T,S)(S s, in T nullstr) if (is(S : Object) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1216):        std.conv.toImpl(T,S)(S s, in T left, in T separator = ", ", in T right = ")") if (is(S == struct) && !is(typeof(&S.init.toString)) && !isInputRange!(S) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1250):        std.conv.toImpl(T,S)(S s, in T left = to!(T)(S.stringof ~ "("), in T right = ")") if (is(S == typedef) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1266):        std.conv.toImpl(T,S)(S value) if (!isImplicitlyConvertible!(S,T) && (isNumeric!(S) || isSomeChar!(S)) && !is(S == enum) && (isNumeric!(T) || isSomeChar!(T)) && !is(T == enum))
..\..\phobos\std\conv.d(1324):        std.conv.toImpl(T,S)(S value) if (!isImplicitlyConvertible!(S,T) && !isSomeString!(S) && isDynamicArray!(S) && !(isSomeString!(T) && !is(T == enum)) && isArray!(T))
..\..\phobos\std\conv.d(1374):        std.conv.toImpl(T,S)(S value) if (isAssociativeArray!(S) && isAssociativeArray!(T) && !is(T == enum))
..\..\phobos\std\conv.d(1602):        std.conv.toImpl(T,S)(S value) if (isDynamicArray!(S) && isSomeString!(S) && !is(S == enum) && !(isSomeString!(T) && !is(T== enum)) && is(typeof(parse!(T)(value))))
..\..\phobos\std\conv.d(1617):        std.conv.toImpl(T,S)(S value, uint radix) if (isDynamicArray!(S) && isSomeString!(S) && !(isSomeString!(T) && !is(T == enum)) && is(typeof(parse!(T)(value,radix))))
..\..\phobos\std\conv.d(325): Error: template std.conv.toImpl cannot deduce template function from argument types !(void*)(string)
..\..\phobos\std\conv.d(269): Error: template instance toImpl!(void*) errors instantiating template
testx.d(6): Error: template instance std.conv.to!(void*).to!(string) error instantiating

Or, as my terminal likes to display it,

..\..\phobos\std\conv.d(269): Error: template std.conv.toImpl does not match any
 function template declaration. Candidates are:
..\..\phobos\std\conv.d(325):        std.conv.toImpl(T,S)(S value) if (isImplici
tlyConvertible!(S,T) && !isEnumStrToStr!(S,T) && !isNullToStr!(S,T))
..\..\phobos\std\conv.d(431):        std.conv.toImpl(T,S)(ref S s) if (isRawStat
icArray!(S))
..\..\phobos\std\conv.d(445):        std.conv.toImpl(T,S)(S value) if (is(S : Ob
ject) && !is(T : Object) && !isSomeString!(T) && hasMember!(S,"to") && is(typeof
(S.init.to!(T)()) : T))
..\..\phobos\std\conv.d(466):        std.conv.toImpl(T,S)(S value) if (is(typeof
(S.init.opCast!(T)()) : T) && !(isSomeString!(T) && !is(T == enum) && !isAggrega
teType!(T)))
..\..\phobos\std\conv.d(497):        std.conv.toImpl(T,S)(S value) if (!isImplic
itlyConvertible!(S,T) && is(T == struct) && is(typeof(T(value))))
..\..\phobos\std\conv.d(547):        std.conv.toImpl(T,S)(S value) if (!isImplic
itlyConvertible!(S,T) && is(T == class) && is(typeof(new T(value))))
..\..\phobos\std\conv.d(619):        std.conv.toImpl(T,S)(S value) if (!isImplic
itlyConvertible!(S,T) && (is(S == class) || is(S == interface)) && !is(typeof(va
lue.opCast!(T)()) : T) && (is(T == class) || is(T == interface)) && !is(typeof(n
ew T(value))))
..\..\phobos\std\conv.d(780):        std.conv.toImpl(T,S)(S value) if (!(isImpli
citlyConvertible!(S,T) && !isEnumStrToStr!(S,T) && !isNullToStr!(S,T)) && (isSom
eString!(T) && !is(T == enum) && !isAggregateType!(T)))
..\..\phobos\std\conv.d(1065):        std.conv.toImpl(T,S)(S value, uint radix)
if (isIntegral!(S) && isSomeString!(T) && !is(T == enum))
..\..\phobos\std\conv.d(1129):        std.conv.toImpl(T,S)(S s, in T leftBracket
, in T separator = ", ", in T rightBracket = "]") if (!isSomeChar!(ElementType!(
S)) && (isInputRange!(S) || isInputRange!(Unqual!(S))) && (isSomeString!(T) && !
is(T == enum)))
..\..\phobos\std\conv.d(1166):        std.conv.toImpl(T,S)(ref S s, in T leftBra
cket, in T separator = " ", in T rightBracket = "]") if ((is(S == void[]) || is(
S == const(void)[]) || is(S == immutable(void)[])) && (isSomeString!(T) && !is(T
 == enum)))
..\..\phobos\std\conv.d(1177):        std.conv.toImpl(T,S)(S s, in T leftBracket
, in T keyval = ":", in T separator = ", ", in T rightBracket = "]") if (isAssoc
iativeArray!(S) && !is(S == enum) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1203):        std.conv.toImpl(T,S)(S s, in T nullstr) if
 (is(S : Object) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1216):        std.conv.toImpl(T,S)(S s, in T left, in T
separator = ", ", in T right = ")") if (is(S == struct) && !is(typeof(&S.init.to
String)) && !isInputRange!(S) && (isSomeString!(T) && !is(T == enum)))
..\..\phobos\std\conv.d(1250):        std.conv.toImpl(T,S)(S s, in T left = to!(
T)(S.stringof ~ "("), in T right = ")") if (is(S == typedef) && (isSomeString!(T
) && !is(T == enum)))
..\..\phobos\std\conv.d(1266):        std.conv.toImpl(T,S)(S value) if (!isImpli
citlyConvertible!(S,T) && (isNumeric!(S) || isSomeChar!(S)) && !is(S == enum) &&
 (isNumeric!(T) || isSomeChar!(T)) && !is(T == enum))
..\..\phobos\std\conv.d(1324):        std.conv.toImpl(T,S)(S value) if (!isImpli
citlyConvertible!(S,T) && !isSomeString!(S) && isDynamicArray!(S) && !(isSomeStr
ing!(T) && !is(T == enum)) && isArray!(T))
..\..\phobos\std\conv.d(1374):        std.conv.toImpl(T,S)(S value) if (isAssoci
ativeArray!(S) && isAssociativeArray!(T) && !is(T == enum))
..\..\phobos\std\conv.d(1602):        std.conv.toImpl(T,S)(S value) if (isDynami
cArray!(S) && isSomeString!(S) && !is(S == enum) && !(isSomeString!(T) && !is(T
== enum)) && is(typeof(parse!(T)(value))))
..\..\phobos\std\conv.d(1617):        std.conv.toImpl(T,S)(S value, uint radix)
if (isDynamicArray!(S) && isSomeString!(S) && !(isSomeString!(T) && !is(T == enu
m)) && is(typeof(parse!(T)(value,radix))))
..\..\phobos\std\conv.d(325): Error: template std.conv.toImpl cannot deduce temp
late function from argument types !(void*)(string)
..\..\phobos\std\conv.d(269): Error: template instance toImpl!(void*) errors ins
tantiating template
testx.d(6): Error: template instance std.conv.to!(void*).to!(string) error insta
ntiating

Maybe this is still a net improvement, but this is going to produce a lot of garbage in a lot of cases.
This is 20 alternatives. 100 is waaay too many to output.

I would expect a much lower number to be reasonable, say 5. The compiler could print the full list when using the -v flag.

@Poita
Contributor
Poita commented Oct 27, 2012

@yebblies I have reduce the number to 5 and added the option of using -v for all. Last commits should have also fixed the dmc compile issue.

@yebblies
Member

That's much better, although it is still nasty for complicated cases like std.conv.to. I'm not sure there's much to be done about them though. @andralex What do you think?

Please rebase and squash it down to a single commit.

@Poita
Contributor
Poita commented Oct 27, 2012

I'm having trouble with the rebase. As you can see from the commits, I accidentally merged upstream/master before the last two commits. Any idea how I can squash this?

@yebblies
Member

Given how small the resulting changes are, I'd suggest copy+paste.

@klickverbot
Member

In case you don't know how to »fix« a particular change, you can always do:

# Reset the history but keep the working directory.
git reset master

# Commit the changes again.
git commit

This is what I like about Git: even if you don't know what the »proper« way of doing things is, you can always find a sequence of commands with the intended result once you understood the quite simple model behind it.

@Poita
Contributor
Poita commented Oct 27, 2012

Thanks @klickverbot, that appears to have worked.

@WalterBright WalterBright merged commit 9b4c8ee into dlang:master Nov 10, 2012

1 check was pending

default Pass: 3, Pending: 6
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment