Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed template match error now lists candidates. #1197

Merged
merged 1 commit into from
Nov 10, 2012

Conversation

Poita
Copy link
Contributor

@Poita 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
Copy link
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
Copy link
Contributor Author

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
Copy link
Member

alexrp commented Oct 20, 2012

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

@Poita
Copy link
Contributor Author

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
Copy link
Member

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

Isn't that just for the lexer?

@Poita
Copy link
Contributor Author

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
Copy link
Contributor Author

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.

// Too many overloads to sensibly display.
// Just show count of remaining overloads.
int remaining = 0;
while (td = td->overnext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dmc produces a warning for this expression.

@yebblies
Copy link
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
Copy link
Contributor Author

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
Copy link
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
Copy link
Contributor Author

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
Copy link
Member

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

@dnadlinger
Copy link
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
Copy link
Contributor Author

Poita commented Oct 27, 2012

Thanks @klickverbot, that appears to have worked.

WalterBright added a commit that referenced this pull request Nov 10, 2012
Failed template match error now lists candidates.
@WalterBright WalterBright merged commit 9b4c8ee into dlang:master Nov 10, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants