diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 6383e039d77b..f79d1441dda4 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -6787,7 +6787,7 @@ private OverloadSet mergeOverloadSet(ScopeDsymbol sds, Identifier ident, Overloa for (size_t j = 0; j < os.a.length; j++) { Dsymbol s2 = os.a[j]; - if (s.toAlias() == s2.toAlias()) + if (s.toAlias() == s2.toAlias() || areEquivalentMixinMembers(s, s2)) { if (s2.isDeprecated() || (s2.visible() < s.visible() && s.visible().kind != Visibility.Kind.none)) { @@ -6800,6 +6800,63 @@ private OverloadSet mergeOverloadSet(ScopeDsymbol sds, Identifier ident, Overloa return os; } +private bool templateMixinArgsEqual(RootObject o1, RootObject o2) +{ + if (const t1 = isType(o1)) + { + const t2 = isType(o2); + return t2 && t1.equals(t2); + } + if (const e1 = isExpression(o1)) + { + const e2 = isExpression(o2); + return e2 && dmd.expressionsem.equals(e1, e2); + } + if (const s1 = isDsymbol(o1)) + { + const s2 = isDsymbol(o2); + return s1 == s2; + } + return o1 is o2; +} + +private bool isAnonymousTemplateMixin(TemplateMixin tm) +{ + return tm && tm.ident && strncmp(tm.ident.toChars(), "__mixin", 7) == 0; +} + +private bool areEquivalentMixinMembers(Dsymbol s1, Dsymbol s2) +{ + s1 = s1.toAlias(); + s2 = s2.toAlias(); + + auto f1 = s1.isFuncDeclaration(); + auto f2 = s2.isFuncDeclaration(); + if (!f1 || !f2 || !f1.ident.equals(f2.ident)) + return false; + + auto tm1 = f1.parent ? f1.parent.isTemplateMixin() : null; + auto tm2 = f2.parent ? f2.parent.isTemplateMixin() : null; + if (!tm1 || !tm2 || tm1.tempdecl != tm2.tempdecl) + return false; + + if (isAnonymousTemplateMixin(tm1) == isAnonymousTemplateMixin(tm2)) + return false; + + if (!f1.type || !f2.type || !f1.type.equals(f2.type)) + return false; + + if (tm1.tiargs.length != tm2.tiargs.length) + return false; + + foreach (i; 0 .. tm1.tiargs.length) + { + if (!templateMixinArgsEqual((*tm1.tiargs)[i], (*tm2.tiargs)[i])) + return false; + } + return true; +} + /*** * Returns true if any of the symbols `p1` or `p2` resides in the enclosing * instantiation scope of `this`. @@ -7802,7 +7859,8 @@ private extern(C++) class SearchVisitor : Visitor } else if (s2 && s != s2) { - if (s.toAlias() == s2.toAlias() || s.getType() == s2.getType() && s.getType()) + if (s.toAlias() == s2.toAlias() || areEquivalentMixinMembers(s, s2) + || s.getType() == s2.getType() && s.getType()) { /* After following aliases, we found the same * symbol, so it's not an ambiguity. But if one @@ -7828,6 +7886,8 @@ private extern(C++) class SearchVisitor : Visitor */ s = s.toAlias(); s2 = s2.toAlias(); + if (areEquivalentMixinMembers(s, s2)) + continue; /* If both s2 and s are overloadable (though we only * need to check s once) */ @@ -7868,7 +7928,7 @@ private extern(C++) class SearchVisitor : Visitor /* If two imports from C import files, pick first one, as C has global name space */ - if (s.isCsymbol() && s2.isCsymbol()) + if ((s.isCsymbol() && s2.isCsymbol()) || areEquivalentMixinMembers(s, s2)) continue; if (!(flags & SearchOpt.ignoreErrors)) diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index d97cee7769a3..8ca4736f182d 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -8014,6 +8014,57 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor FuncDeclaration resolveOverloadSet(Loc loc, Scope* sc, OverloadSet os, Objects* tiargs, Type tthis, ArgumentList argumentList) { + static bool templateMixinArgsEqual(RootObject o1, RootObject o2) + { + if (const t1 = isType(o1)) + { + const t2 = isType(o2); + return t2 && t1.equals(t2); + } + if (const e1 = isExpression(o1)) + { + const e2 = isExpression(o2); + return e2 && equals(e1, e2); + } + if (const s1 = isDsymbol(o1)) + { + const s2 = isDsymbol(o2); + return s1 == s2; + } + return o1 is o2; + } + + static bool areEquivalentMixinFunctions(FuncDeclaration f1, FuncDeclaration f2) + { + static bool isAnonymousTemplateMixin(TemplateMixin tm) + { + import core.stdc.string; + + return tm && tm.ident && strncmp(tm.ident.toChars(), "__mixin", 7) == 0; + } + + if (!f1 || !f2 || !f1.ident.equals(f2.ident) || !f1.type || !f2.type || !f1.type.equals(f2.type)) + return false; + + auto tm1 = f1.parent ? f1.parent.isTemplateMixin() : null; + auto tm2 = f2.parent ? f2.parent.isTemplateMixin() : null; + if (!tm1 || !tm2 || tm1.tempdecl != tm2.tempdecl) + return false; + + if (isAnonymousTemplateMixin(tm1) == isAnonymousTemplateMixin(tm2)) + return false; + + if (tm1.tiargs.length != tm2.tiargs.length) + return false; + + foreach (i; 0 .. tm1.tiargs.length) + { + if (!templateMixinArgsEqual((*tm1.tiargs)[i], (*tm2.tiargs)[i])) + return false; + } + return true; + } + FuncDeclaration f = null; foreach (s; os.a) { @@ -8038,6 +8089,11 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor // Same function found via two different paths (e.g. direct import and // selective import alias), not an actual conflict. } + else if (areEquivalentMixinFunctions(f, f2)) + { + // Same mixin member found via named and unnamed instances of + // the same template mixin in the same scope. + } else if (f.isCsymbol() && f2.isCsymbol()) { /* C has global name space, so just pick one, such as f. diff --git a/compiler/test/compilable/issue17548.d b/compiler/test/compilable/issue17548.d new file mode 100644 index 000000000000..ab8c455e9be9 --- /dev/null +++ b/compiler/test/compilable/issue17548.d @@ -0,0 +1,27 @@ +mixin template T() +{ + void f() {} +} + +mixin T t0; +mixin T; + +struct A +{ + mixin T t; + mixin T; + + void g() + { + f(); + t.f(); + } +} + +void main() +{ + A a; + a.g(); + f(); + t0.f(); +}