Showing with 119 additions and 0 deletions.
  1. +23 −0 src/template.c
  2. +54 −0 test/runnable/imports/link14541traits.d
  3. +42 −0 test/runnable/link14541.d
23 changes: 23 additions & 0 deletions src/template.c
Original file line number Diff line number Diff line change
Expand Up @@ -5925,6 +5925,7 @@ void TemplateInstance::semantic(Scope *sc, Expressions *fargs)
* implements the typeargs. If so, just refer to that one instead.
*/
inst = tempdecl->findExistingInstance(this, fargs);
TemplateInstance *errinst = NULL;
if (!inst)
{
// So, we need to implement 'this' instance.
Expand All @@ -5933,6 +5934,7 @@ void TemplateInstance::semantic(Scope *sc, Expressions *fargs)
{
// If the first instantiation had failed, re-run semantic,
// so that error messages are shown.
errinst = inst;
}
else
{
Expand Down Expand Up @@ -6333,6 +6335,27 @@ void TemplateInstance::semantic(Scope *sc, Expressions *fargs)
symtab = NULL;
}
}
else if (errinst)
{
/* Bugzilla 14541: If the previous gagged instance had failed by
* circular references, currrent "error reproduction instantiation"
* might succeed, because of the difference of instantiated context.
* On such case, the cached error instance needs to be overridden by the
* succeeded instance.
*/
size_t bi = hash % tempdecl->buckets.dim;
TemplateInstances *instances = tempdecl->buckets[bi];
assert(instances);
for (size_t i = 0; i < instances->dim; i++)
{
TemplateInstance *ti = (*instances)[i];
if (ti == errinst)
{
(*instances)[i] = this; // override
break;
}
}
}

#if LOG
printf("-TemplateInstance::semantic('%s', this=%p)\n", toChars(), this);
Expand Down
54 changes: 54 additions & 0 deletions test/runnable/imports/link14541traits.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module imports.link14541traits;

template hasElaborateAssign(S)
{
static if (is(S == struct))
{
extern __gshared S lvalue;

enum hasElaborateAssign = is(typeof(S.init.opAssign(S.init))) ||
is(typeof(S.init.opAssign(lvalue)));
}
else
{
enum bool hasElaborateAssign = false;
}
}

void swap(T)(ref T lhs, ref T rhs) @trusted pure nothrow @nogc
{
static if (hasElaborateAssign!T)
{
}
else
{
}
}

template Tuple(Types...)
{
struct Tuple
{
Types field;
alias field this;

this(Types values)
{
field[] = values[];
}

void opAssign(R)(auto ref R rhs)
{
static if (is(R : Tuple!Types) && !__traits(isRef, rhs))
{
// Use swap-and-destroy to optimize rvalue assignment
swap!(Tuple!Types)(this, rhs);
}
else
{
// Do not swap; opAssign should be called on the fields.
field[] = rhs.field[];
}
}
}
}
42 changes: 42 additions & 0 deletions test/runnable/link14541.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import imports.link14541traits;

void main()
{
Tuple!(int, int) result;

alias T = typeof(result);
static assert(hasElaborateAssign!T);
// hasElaborateAssign!(Tuple(int, int)):
// 1. instantiates Tuple!(int, int).opAssign!(Tuple!(int, int)) [auto ref = Rvalue]
// 2. instantiates swap!(Tuple!(int, int))
// 3. instantiates hasElaborateAssign!(Tuple!(int, int))
// --> forward reference error
// --> swap!(Tuple!(int, int)) fails to instantiate
// --> Tuple!(int, int).opAssign!(Tuple!(int, int)) [auto ref = rvalue] fails to instantiate
// 4. instantiates Tuple!(int, int).opAssign!(Tuple!(int, int)) [auto ref = Lvalue]
// --> succeeds
// hasElaborateAssign!(Tuple(int, int)) succeeds to instantiate (result is 'true')

// Instantiates Tuple!(int, int).opAssign!(Tuple!(int, int)) [auto ref = Rvalue], but
// it's already done in gagged context, so this is made an error reproduction instantiation.
// But, the forward reference of hasElaborateAssign!(Tuple(int, int)) is already resolved, so
// the instantiation will succeeds.
result = Tuple!(int, int)(0, 0); // --> 1st error reproduction instantiation
result = Tuple!(int, int)(0, 0); // --> 2nd error reproduction instantiation

// The two error reproduction instantiations generate the function:
// Tuple!(int, int).opAssign!(Tuple!(int, int)) [auto ref = Rvalue]
// twice, then it will cause duplicate COMDAT error in Win64 platform.
}

/+
The point is, if instantiated contexts are different, two instantiations may cause different result.
- The 1st Tuple.opAssign instantiation is invoked from hasElaborateAssign template with gagging.
So it has failed, because of the circular reference of hasElaborateAssign template..
- The 2nd Tuple.opAssign instantiation is invoked from main() without gagging.
It does not have circular reference, so the instantiation should succeed.
Therefore, the gagged failure should be overridden by the ungagged success.
+/