diff --git a/changelog/dmd.template-constructor-deduction.dd b/changelog/dmd.template-constructor-deduction.dd new file mode 100644 index 000000000000..9839bb0a40ed --- /dev/null +++ b/changelog/dmd.template-constructor-deduction.dd @@ -0,0 +1,20 @@ +Constructor calls can implicitly instantiate struct templates + +Implicit Function Template Instantiation now also works with struct constructors. +Example: + +--- +struct Tuple(T...) +{ + T fields; + this(T t) { this.fields = t; } +} + +static assert(Tuple(1, 2, 3).fields[0] == 1); +--- + +This removes the need for helper functions such as: + +--- +auto tuple(T...)(T args) => Tuple!T(args); +--- diff --git a/compiler/src/dmd/aggregate.d b/compiler/src/dmd/aggregate.d index becc864b4de4..99828c0cc629 100644 --- a/compiler/src/dmd/aggregate.d +++ b/compiler/src/dmd/aggregate.d @@ -132,6 +132,9 @@ extern (C++) abstract class AggregateDeclaration : ScopeDsymbol /// CtorDeclaration or TemplateDeclaration Dsymbol ctor; + /// If this aggregate is inside a TemplateDeclaration, this stores the cached overload + // set of ctors without substituted template arguments, for implicit template instantiation + private FuncDeclaration uninstantiatedCtors; /// default constructor - should have no arguments, because /// it would be stored in TypeInfo_Class.defaultConstructor @@ -474,6 +477,48 @@ extern (C++) abstract class AggregateDeclaration : ScopeDsymbol return s; } + /** + * Returns: for an aggregate inside a template declaration, + * get the template function declarations of constructors, without + * having their template arguments substituted. This is used to + * implicitly instantiate struct templates based on constructor arguments, + * e.g. Tuple(1, 2) => Tuple!(int, int) + */ + extern (D) final FuncDeclaration getUninstantiatedCtors() + { + if (this.uninstantiatedCtors) + return this.uninstantiatedCtors; + + void visit(Dsymbol mem) + { + if (auto fd = mem.isFuncDeclaration()) + { + if (fd.isCtorDeclaration()) + { + assert(fd.overnext0 is null); + fd.overnext0 = this.uninstantiatedCtors; + this.uninstantiatedCtors = fd; + } + } + } + + foreach (mem; *this.members) + { + visit(mem); + if (auto td = mem.isTemplateDeclaration()) + { + if (!td.onemember) + continue; + if (auto fd = td.onemember.isFuncDeclaration()) + { + // This doesn't really work, we need to combine the template argument lists + visit(mem); + } + } + } + return this.uninstantiatedCtors; + } + override final Visibility visible() pure nothrow @nogc @safe { return visibility; diff --git a/compiler/src/dmd/aggregate.h b/compiler/src/dmd/aggregate.h index 8fd12e1d168a..6e9535268f5c 100644 --- a/compiler/src/dmd/aggregate.h +++ b/compiler/src/dmd/aggregate.h @@ -96,6 +96,9 @@ class AggregateDeclaration : public ScopeDsymbol FuncDeclaration *inv; // invariant Dsymbol *ctor; // CtorDeclaration or TemplateDeclaration +private: + FuncDeclaration* uninstantiatedCtors; +public: // default constructor - should have no arguments, because // it would be stored in TypeInfo_Class.defaultConstructor diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 8f748ebf9a6e..eaebe71bd93b 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -6642,6 +6642,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor TypeFunction tf; const(char)* p; Dsymbol s; + bool isAggregateCall = false; exp.f = null; if (auto fe = exp.e1.isFuncExp()) { @@ -6688,12 +6689,27 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } else if (exp.e1.op == EXP.template_) { - s = (cast(TemplateExp)exp.e1).td; + { + TemplateExp te = exp.e1.isTemplateExp(); + s = te.td; + isAggregateCall = te.td.onemember && te.td.onemember.isAggregateDeclaration(); + } L2: exp.f = resolveFuncCall(exp.loc, sc, s, tiargs, null, exp.argumentList, exp.isUfcsRewrite ? FuncResolveFlag.ufcs : FuncResolveFlag.standard); if (!exp.f || exp.f.errors) return setError(); + + if (isAggregateCall) + if (auto cd = exp.f.isCtorDeclaration()) + { + // We resolved to a constructor, but calling a raw constructor gives a "need `this`" + // error, so we extract that instantiated struct type from the constructor and retry + // a CallExp with that. + exp = new CallExp(exp.loc, new TypeExp(exp.loc, cd.type.nextOf()), exp.argumentList.arguments, exp.argumentList.names); + goto Lagain; + } + if (exp.f.needThis()) { if (hasThis(sc)) diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index 47f2343a6570..606147e9e89e 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -6141,6 +6141,9 @@ class AggregateDeclaration : public ScopeDsymbol Array invs; FuncDeclaration* inv; Dsymbol* ctor; +private: + FuncDeclaration* uninstantiatedCtors; +public: CtorDeclaration* defaultCtor; AliasThis* aliasthis; Array userDtors; diff --git a/compiler/src/dmd/funcsem.d b/compiler/src/dmd/funcsem.d index 9caaca41c996..0836dc53d744 100644 --- a/compiler/src/dmd/funcsem.d +++ b/compiler/src/dmd/funcsem.d @@ -1491,8 +1491,9 @@ enum FuncResolveFlag : ubyte } /******************************************* - * Given a symbol that could be either a FuncDeclaration or - * a function template, resolve it to a function symbol. + * Given a symbol that could be either a FuncDeclaration, + * a function template, or a struct template with constructors, + * resolve it to a function symbol. * Params: * loc = instantiation location * sc = instantiation scope diff --git a/compiler/src/dmd/templatesem.d b/compiler/src/dmd/templatesem.d index 1358896585c1..4639d1bcd43a 100644 --- a/compiler/src/dmd/templatesem.d +++ b/compiler/src/dmd/templatesem.d @@ -2116,6 +2116,10 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, //printf("td = %s\n", td.toChars()); auto f = td.onemember ? td.onemember.isFuncDeclaration() : null; + if (!f && td.onemember) + if (auto ad = td.onemember.isAggregateDeclaration()) + f = ad.getUninstantiatedCtors(); + if (!f) { if (!tiargs) @@ -2361,7 +2365,8 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, if (td_best && ti_best && m.count == 1) { // Matches to template function - assert(td_best.onemember && td_best.onemember.isFuncDeclaration()); + assert(td_best.onemember); + assert(td_best.onemember.isFuncDeclaration() || td_best.onemember.isAggregateDeclaration()); /* The best match is td_best with arguments tdargs. * Now instantiate the template. */ @@ -2372,7 +2377,12 @@ void functionResolve(ref MatchAccumulator m, Dsymbol dstart, Loc loc, Scope* sc, auto ti = new TemplateInstance(loc, td_best, ti_best.tiargs); ti.templateInstanceSemantic(sc, argumentList); - m.lastf = ti.toAlias().isFuncDeclaration(); + auto ta = ti.toAlias(); + if (auto ad = ta.isAggregateDeclaration()) + m.lastf = ad.ctor.isFuncDeclaration(); + else + m.lastf = ta.isFuncDeclaration(); + if (!m.lastf) goto Lnomatch; if (ti.errors) diff --git a/compiler/test/compilable/template_param_deduction_ctor.d b/compiler/test/compilable/template_param_deduction_ctor.d new file mode 100644 index 000000000000..9243e059241c --- /dev/null +++ b/compiler/test/compilable/template_param_deduction_ctor.d @@ -0,0 +1,54 @@ +// Template parameter deduction for constructors +// dlang.org/dips/40 + +////////////////////////////////////////// +// Basic succes case +struct Dip40(T) +{ + T x; + this(T x) { this.x = x; } +} + +static assert (Dip40(30).x == 30); +static assert ("a".Dip40.x == "a"); + +////////////////////////////////////////// +// Variadic template arguments +struct Tuple(T...) +{ + T fields; + this(T t) { this.fields = t; } +} + +static assert (Tuple(1, 2, 3).fields[0] == 1); +static assert(is(typeof(Tuple('a', "b")) == Tuple!(char, string))); + +////////////////////////////////////////// +// Constructor is required for now +struct CtorLess(T) +{ + T x; +} +static assert(!is(typeof(CtorLess('a')))); + +////////////////////////////////////////// +// Partial instantiation not supported +struct Pair(T, U) +{ + T x; + U y; + this(T x, U y) { this.x = x; this.y = y; } +} +static assert(!is(typeof(Pair!char('a', "b")))); + +////////////////////////////////////////// +// Ambiguity errors are checked +struct Ambig(T) +{ + T x; + this(int x, int y) { this.x = x; } + this(T x) { this.x = x; } +} +static assert(!is(typeof(Ambig(0)))); + +////////////////////////////////////////// diff --git a/compiler/test/runnable/ifti.d b/compiler/test/runnable/ifti.d index 293b8198d7fb..e1d8eb453a09 100644 --- a/compiler/test/runnable/ifti.d +++ b/compiler/test/runnable/ifti.d @@ -128,4 +128,12 @@ void main() { } test24731(); + Array!string("test"); +} + +struct Array(T) +{ + this(U)(U...) {} + this(T single) { __ctor!T(single); } + this()() {} }