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

[WIP] Fix bugzilla 6082 - Constructors of templated types should be callabl… #16910

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions changelog/dmd.template-constructor-deduction.dd
Original file line number Diff line number Diff line change
@@ -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);
---
45 changes: 45 additions & 0 deletions compiler/src/dmd/aggregate.d
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@

/// 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
Expand Down Expand Up @@ -474,6 +477,48 @@
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())

Check warning on line 512 in compiler/src/dmd/aggregate.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/aggregate.d#L510-L512

Added lines #L510 - L512 were not covered by tests
{
// This doesn't really work, we need to combine the template argument lists
visit(mem);

Check warning on line 515 in compiler/src/dmd/aggregate.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/aggregate.d#L515

Added line #L515 was not covered by tests
}
}
}
return this.uninstantiatedCtors;
}

override final Visibility visible() pure nothrow @nogc @safe
{
return visibility;
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dmd/aggregate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion compiler/src/dmd/expressionsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -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())
{
Expand Down Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dmd/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -6141,6 +6141,9 @@ class AggregateDeclaration : public ScopeDsymbol
Array<FuncDeclaration* > invs;
FuncDeclaration* inv;
Dsymbol* ctor;
private:
FuncDeclaration* uninstantiatedCtors;
public:
CtorDeclaration* defaultCtor;
AliasThis* aliasthis;
Array<DtorDeclaration* > userDtors;
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dmd/funcsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions compiler/src/dmd/templatesem.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions compiler/test/compilable/template_param_deduction_ctor.d
Original file line number Diff line number Diff line change
@@ -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))));

//////////////////////////////////////////
8 changes: 8 additions & 0 deletions compiler/test/runnable/ifti.d
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,12 @@ void main() {
}

test24731();
Array!string("test");
}

struct Array(T)
{
this(U)(U...) {}
this(T single) { __ctor!T(single); }
this()() {}
}
Loading