I'm unsure if this is a minimal case:
interface I(T)
{
T foo();
}
interface I2(T, U)
{
T foo();
U bar();
}
void fn(T)(I!T t1) { }
void fn(T, U)(I2!(T, U) t1) { }
class C : I!int
{
int foo() { return 5;}
}
void main()
{
auto c = new C;
I!int i = c;
fn!(int)(i); // compiles
fn!(int)(c); // fails
}
output:
testtemplate.d(26): Error: template testtemplate.fn(T) does not match any function template declaration
testtemplate.d(26): Error: template testtemplate.fn(T) cannot deduce template function from argument types !(int)(C)
testtemplate.d(26): Error: template instance errors instantiating template
This compiled with 2.036.
Comment #1 by clugdbug — 2010-07-26T11:33:55Z
Reduced test case:
class I(X) { } // also for interface
void fn(T)(I!T t1) { }
void fn(A, B)(int t1) { }
class C : I!int { }
void bug4173()
{
C c;
fn!(int)(c);
}
Comment #2 by clugdbug — 2010-08-30T13:52:14Z
This bug was triggered by svn commit 273, which was related to opDispatch.
The immediate change was in CallExp::semantic(). Previously, it attempted full instantiation, and if that failed, it tried partial explicit instantiation.
After this change, it calls needsTypeInference(). It does partial instantiation if true, otherwise does full instantiation.
In this test case, there is one template which requires partial explicit instantantion, and one which does not. So, in 2.036 and earlier, the full instantiation succeeded, before it even considered the two-argument template.
Seems as though the code for explicit instantiation is slightly more capable at the moment, than the partial instantiation code.
Bug 4430 is probably another instance of the same bug.
Comment #3 by clugdbug — 2010-09-03T01:45:03Z
It seems to be just luck that this ever worked at all. Template parameter deduction has never worked for base classes or interfaces of a class. So although this particular case used to work, it was very fragile -- very closely related cases would fail.
I've only been able to fix this by implementing template parameter deduction for base classes. It allows them as a "match with implicit conversion".
Thus, it also fixes bug 1715 "Template specialization checks for equality rather than convertibility" and bug 1970 "Templated interfaces not matched".
But I've found a very annoying case:
interface I(X) {}
class C: I!(char), I!(double) {}
void foo(T)(I!(T) x) {}
void main()
{
C c = new C;
foo(c); // could match with I!char or I!double
}
This should be treated as a match-with-implicit-conversion for c, but the type T has not been determined, so template instantiation should fail.
If the signature is instead: foo(T)(I!(T) x, T y){} and called with foo(c, 1.2), then instantiation should succeed.
Comment #4 by clugdbug — 2010-09-03T11:19:35Z
This patch is a drop-in replacement for TypeClass::deduceType() in template.c, line 2180 in D1, 2480 in D2. The patch is unchanged between D1 & D2.
It fixes these bugs:
bug 1715 Template specialization checks for equality rather than convertibility
bug 1970 Templated interfaces not matched
bug 4173 Regression(2.037) Explicitly instantiated templates still try to do IFTI in some cases
It is far more general than any of those bugs. It checks all base interfaces, and detects ambiguities. It copes with some pretty difficult cases:
interface TwoWay(A,B) {}
class C1: TwoWay!(char, float), TwoWay!(int, float) {}
class C2: TwoWay!(int, char), TwoWay!(float, char) {}
C1 twoway;
C2 twoway2;
B foo(A, B)(TwoWay!(A, B) x, TwoWay!(B, A) y) { return B.init;}
static assert(is(typeof(foo(twoway, twoway2)) == float));
static assert(is(typeof(foo(twoway2, twoway)) == char));
though it can only do this if it can determine at least one template parameter
from the first function parameter.
PATCH: Template.c line 2480, replacing TypeClass::deduceType()
----------------------
/* Helper for TypeClass::deduceType().
* Classes can match with implicit conversion to a base class or interface.
* This is complicated, because there may be more than one base class which
* matches. In such cases, one or more parameters remain ambiguous.
* For example,
*
* interface I(X, Y) {}
* class C : I(uint, double), I(char, double) {}
* C x;
* foo(T, U)( I!(T, U) x)
*
* deduces that U is double, but T remains ambiguous (could be char or uint).
*
* Given a baseclass b, and initial deduced types 'dedtypes', this function
* tries to match tparam with b, and also tries all base interfaces of b.
* If a match occurs, numBaseClassMatches is incremented, and the new deduced
* types are ANDed with the current 'best' estimate for dedtypes.
*/
void deduceBaseClassParameters(BaseClass *b,
Scope *sc, Type *tparam, TemplateParameters *parameters, Objects *dedtypes,
Objects *best, int &numBaseClassMatches)
{
TemplateInstance *parti = b->base->parent->isTemplateInstance();
if (parti)
{
// Make a temporary copy of dedtypes so we don't destroy it
Objects *tmpdedtypes = new Objects();
tmpdedtypes->setDim(dedtypes->dim);
memcpy(tmpdedtypes->data, dedtypes->data, dedtypes->dim * sizeof(void *));
TypeInstance *t = new TypeInstance(0, parti);
MATCH m = t->deduceType(sc, tparam, parameters, tmpdedtypes);
if (m != MATCHnomatch)
{
// If this is the first ever match, it becomes our best estimate
if (numBaseClassMatches==0)
memcpy(best->data, tmpdedtypes->data, tmpdedtypes->dim * sizeof(void *));
else for (size_t k = 0; k < tmpdedtypes->dim; ++k)
{
// If we've found more than one possible type for a parameter,
// mark it as unknown.
if (tmpdedtypes->data[k] != best->data[k])
best->data[k] = dedtypes->data[k];
}
++numBaseClassMatches;
}
}
// Now recursively test the inherited interfaces
for (size_t j = 0; j < b->baseInterfaces_dim; ++j)
{
deduceBaseClassParameters( &(b->baseInterfaces)[j],
sc, tparam, parameters, dedtypes,
best, numBaseClassMatches);
}
}
MATCH TypeClass::deduceType(Scope *sc, Type *tparam, TemplateParameters *parameters, Objects *dedtypes)
{
//printf("TypeClass::deduceType(this = %s)\n", toChars());
/* If this class is a template class, and we're matching
* it against a template instance, convert the class type
* to a template instance, too, and try again.
*/
TemplateInstance *ti = sym->parent->isTemplateInstance();
if (tparam && tparam->ty == Tinstance)
{
if (ti && ti->toAlias() == sym)
{
TypeInstance *t = new TypeInstance(0, ti);
MATCH m = t->deduceType(sc, tparam, parameters, dedtypes);
// Even if the match fails, there is still a chance it could match
// a base class.
if (m != MATCHnomatch)
return m;
}
/* Match things like:
* S!(T).foo
*/
TypeInstance *tpi = (TypeInstance *)tparam;
if (tpi->idents.dim)
{ Identifier *id = (Identifier *)tpi->idents.data[tpi->idents.dim - 1];
if (id->dyncast() == DYNCAST_IDENTIFIER && sym->ident->equals(id))
{
Type *tparent = sym->parent->getType();
if (tparent)
{
/* Slice off the .foo in S!(T).foo
*/
tpi->idents.dim--;
MATCH m = tparent->deduceType(sc, tpi, parameters, dedtypes);
tpi->idents.dim++;
return m;
}
}
}
// If it matches exactly or via implicit conversion, we're done
MATCH m = Type::deduceType(sc, tparam, parameters, dedtypes);
if (m != MATCHnomatch)
return m;
/* There is still a chance to match via implicit conversion to
* a base class or interface. Because there could be more than one such
* match, we need to check them all.
*/
int numBaseClassMatches = 0; // Have we found an interface match?
// Our best guess at dedtypes
Objects *best = new Objects();
best->setDim(dedtypes->dim);
ClassDeclaration *s = sym;
while(s && s->baseclasses->dim > 0)
{
// Test the base class
deduceBaseClassParameters((BaseClass *)(s->baseclasses->data[0]),
sc, tparam, parameters, dedtypes,
best, numBaseClassMatches);
// Test the interfaces inherited by the base class
for (size_t i = 0; i < s->interfaces_dim; ++i)
{
BaseClass *b = s->interfaces[i];
deduceBaseClassParameters(b, sc, tparam, parameters, dedtypes,
best, numBaseClassMatches);
}
s = ((BaseClass *)(s->baseclasses->data[0]))->base;
}
if (numBaseClassMatches == 0)
return MATCHnomatch;
// If we got at least one match, copy the known types into dedtypes
memcpy(dedtypes->data, best->data, best->dim * sizeof(void *));
return MATCHconvert;
}
// Extra check
if (tparam && tparam->ty == Tclass)
{
TypeClass *tp = (TypeClass *)tparam;
//printf("\t%d\n", (MATCH) implicitConvTo(tp));
return implicitConvTo(tp);
}
return Type::deduceType(sc, tparam, parameters, dedtypes);
}
Comment #5 by clugdbug — 2010-09-03T11:24:34Z
Created attachment 745
Template Interface tests for test suite
These 16 tests are for both D1 and D2. Compile with dmd -c templiface.d