Skip to content

Commit

Permalink
Fix bugzilla 6082 - Constructors of templated types should be callabl…
Browse files Browse the repository at this point in the history
…e via IFTI
  • Loading branch information
dkorpel committed Oct 2, 2024
1 parent 4ccb01f commit c88e5ee
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 4 deletions.
19 changes: 19 additions & 0 deletions changelog/dmd.template-constructor-deduction.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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 @@ 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
Expand Down Expand Up @@ -687,6 +690,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;
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 @@ -95,6 +95,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
17 changes: 17 additions & 0 deletions compiler/src/dmd/expressionsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -6630,6 +6630,23 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor
exp.isUfcsRewrite ? FuncResolveFlag.ufcs : FuncResolveFlag.standard);
if (!exp.f || exp.f.errors)
return setError();

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.
if (auto sd = cd.type.nextOf().isAggregate().isStructDeclaration())
{
exp = new CallExp(exp.loc, new TypeExp(exp.loc, cd.type.nextOf()), exp.argumentList.arguments, exp.argumentList.names);
version(none)
exp.e1 = new DotVarExp(
exp.e1.loc, new StructLiteralExp(exp.loc, sd, null), exp.f, false
).expressionSemantic(sc);
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 @@ -6151,6 +6151,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 @@ -1490,8 +1490,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 @@ -2117,6 +2117,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)
if (auto ad = td.onemember.isAggregateDeclaration())
f = ad.getUninstantiatedCtors();

if (!f)
{
if (!tiargs)
Expand Down Expand Up @@ -2362,7 +2366,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 @@ -2373,7 +2378,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))));

//////////////////////////////////////////

0 comments on commit c88e5ee

Please sign in to comment.