From e877c39cd7644920dec896a046ee818406cd83d8 Mon Sep 17 00:00:00 2001 From: Vladimir Panteleev Date: Thu, 18 Dec 2025 23:24:40 +0000 Subject: [PATCH] Fix #19905 - expand type tuples in delegate params during IFTI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When matching a lambda against a delegate type like `R delegate(Args)` during IFTI, parameter types that resolve to TypeTuples (e.g. from AliasSeq) were not being expanded before comparing parameter counts. This caused IFTI to fail for lambdas like `(a) => a.m()` when matched against `R delegate(AliasSeq!S)`, even though explicit instantiation `fun!void((a) => a.m())` worked correctly. The fix resolves and expands TypeTuple parameters before comparing counts, similar to what TypeFunction.typeSemantic does, but only for parameters that don't rely on template parameters being deduced. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- compiler/src/dmd/templatesem.d | 50 ++++++++++++++++++++++++---- compiler/test/compilable/test19905.d | 29 ++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 compiler/test/compilable/test19905.d diff --git a/compiler/src/dmd/templatesem.d b/compiler/src/dmd/templatesem.d index ac107b049241..886fd487aa6c 100644 --- a/compiler/src/dmd/templatesem.d +++ b/compiler/src/dmd/templatesem.d @@ -7598,7 +7598,39 @@ MATCH deduceType(scope RootObject o, scope Scope* sc, scope Type tparam, //printf("\ttf = %s\n", tf.toChars()); const dim = tf.parameterList.length; - if (tof.parameterList.length != dim || tof.parameterList.varargs != tf.parameterList.varargs) + if (tof.parameterList.varargs != tf.parameterList.varargs) + return; + + // https://github.com/dlang/dmd/issues/19905 + // Resolve and expand TypeTuples (e.g. from AliasSeq) in tof's parameters + // before comparing counts. We can't call TypeFunction.typeSemantic because + // the return type may contain unresolved template parameters. + Types* expandedTypes; + foreach (pto; *tof.parameterList.parameters) + { + Type pt = pto.type; + if (!reliesOnTemplateParameters(pt, parameters[inferStart .. parameters.length])) + { + pt = pt.syntaxCopy().typeSemantic(e.loc, sc); + if (pt.ty == Terror) + return; + } + if (auto tt = pt.isTypeTuple()) + { + if (!expandedTypes) + expandedTypes = new Types(); + foreach (arg; *tt.arguments) + expandedTypes.push(arg.type); + } + else + { + if (!expandedTypes) + expandedTypes = new Types(); + expandedTypes.push(pt); + } + } + + if (!expandedTypes || expandedTypes.length != dim) return; auto tiargs = new Objects(); @@ -7614,15 +7646,19 @@ MATCH deduceType(scope RootObject o, scope Scope* sc, scope Type tparam, ++u; } assert(u < dim); - Parameter pto = tof.parameterList[u]; - if (!pto) + Type t = (*expandedTypes)[u]; + if (!t) break; - Type t = pto.type.syntaxCopy(); // https://issues.dlang.org/show_bug.cgi?id=11774 if (reliesOnTemplateParameters(t, parameters[inferStart .. parameters.length])) return; - t = t.typeSemantic(e.loc, sc); - if (t.ty == Terror) - return; + // https://issues.dlang.org/show_bug.cgi?id=11774 + t = t.syntaxCopy(); + if (!t.deco) + { + t = t.typeSemantic(e.loc, sc); + if (t.ty == Terror) + return; + } tiargs.push(t); } diff --git a/compiler/test/compilable/test19905.d b/compiler/test/compilable/test19905.d new file mode 100644 index 000000000000..f8c1757c8653 --- /dev/null +++ b/compiler/test/compilable/test19905.d @@ -0,0 +1,29 @@ +// https://github.com/dlang/dmd/issues/19905 +// Type list (tuple) not expanded in delegate during IFTI + +alias AliasSeq(TList...) = TList; + +struct S +{ + void m() {} +} + +void fun1(R)(R delegate(AliasSeq!S) dg) {} +void fun2(R)(R delegate(AliasSeq!(S, S)) dg) {} +void fun3(R)(R delegate(AliasSeq!(S, S, S)) dg) {} +void fun0(R)(R delegate(AliasSeq!()) dg) {} + +void main() +{ + // Explicit template argument always worked + fun1!void((a) { a.m(); }); + fun2!void((a, b) { a.m(); b.m(); }); + fun3!void((a, b, c) { a.m(); b.m(); c.m(); }); + fun0!void(() {}); + + // IFTI with tuple expansion - was broken, now fixed + fun1((a) { a.m(); }); + fun2((a, b) { a.m(); b.m(); }); + fun3((a, b, c) { a.m(); b.m(); c.m(); }); + fun0(() {}); +}