Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Failed template match error now lists candidates. #1197

Merged
merged 1 commit into from over 1 year ago

5 participants

Alex Rønne Petersen Daniel Murphy David Nadlinger Walter Bright

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.

Alex Rønne Petersen

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.

@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++.

Alex Rønne Petersen

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

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 :-)

Daniel Murphy
Collaborator

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

Isn't that just for the lexer?

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.

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.

src/template.c
((7 lines not shown))
2123 2124 kind(), parent->toPrettyChars(), ident->toChars());
  2125 +
  2126 + // Display candidate template functions
  2127 + int numToDisplay = 100; // sensible number to display
  2128 + for (TemplateDeclaration *td = this; td; td = td->overnext)
  2129 + {
  2130 + ::errorSupplemental(td->loc, "%s", td->toPrettyChars());
  2131 + if (--numToDisplay == 0)
  2132 + {
  2133 + // Too many overloads to sensibly display.
  2134 + // Just show count of remaining overloads.
  2135 + int remaining = 0;
  2136 + while (td = td->overnext)
1
Daniel Murphy Collaborator
yebblies added a note

dmc produces a warning for this expression.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Daniel Murphy
Collaborator

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.

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

Daniel Murphy
Collaborator

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.

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?

Daniel Murphy
Collaborator

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

David Nadlinger
Collaborator

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.

Thanks @klickverbot, that appears to have worked.

Walter Bright WalterBright merged commit 9b4c8ee into from
Deleted user Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Deleted user Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Deleted user Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Deleted user Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Oct 27, 2012
Poita Template match errors now list candidates. 175594c
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 33 additions and 1 deletion. Show diff stats Hide diff stats

  1. +33 1 src/template.c
34 src/template.c
@@ -2119,8 +2119,28 @@ FuncDeclaration *TemplateDeclaration::deduceFunctionTemplate(Scope *sc, Loc loc,
2119 2119 if (!td_best)
2120 2120 {
2121 2121 if (!(flags & 1))
2122   - ::error(loc, "%s %s.%s does not match any function template declaration",
  2122 + {
  2123 + ::error(loc, "%s %s.%s does not match any function template declaration. Candidates are:",
2123 2124 kind(), parent->toPrettyChars(), ident->toChars());
  2125 +
  2126 + // Display candidate template functions
  2127 + int numToDisplay = 5; // sensible number to display
  2128 + for (TemplateDeclaration *td = this; td; td = td->overnext)
  2129 + {
  2130 + ::errorSupplemental(td->loc, "%s", td->toPrettyChars());
  2131 + if (!global.params.verbose && --numToDisplay == 0)
  2132 + {
  2133 + // Too many overloads to sensibly display.
  2134 + // Just show count of remaining overloads.
  2135 + int remaining = 0;
  2136 + for (; td; td = td->overnext)
  2137 + ++remaining;
  2138 + if (remaining > 0)
  2139 + ::errorSupplemental(loc, "... (%d more, -v to show) ...", remaining);
  2140 + break;
  2141 + }
  2142 + }
  2143 + }
2124 2144 goto Lerror;
2125 2145 }
2126 2146 if (td_ambig)
@@ -2337,6 +2357,18 @@ char *TemplateDeclaration::toChars()
2337 2357 tp->toCBuffer(&buf, &hgs);
2338 2358 }
2339 2359 buf.writeByte(')');
  2360 +
  2361 + if (onemember && onemember->toAlias())
  2362 + {
  2363 + FuncDeclaration *fd = onemember->toAlias()->isFuncDeclaration();
  2364 + if (fd && fd->type)
  2365 + {
  2366 + TypeFunction *tf = (TypeFunction *)fd->type;
  2367 + char const* args = Parameter::argsTypesToChars(tf->parameters, tf->varargs);
  2368 + buf.writestring(args);
  2369 + }
  2370 + }
  2371 +
2340 2372 #if DMDV2
2341 2373 if (constraint)
2342 2374 { buf.writestring(" if (");

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.