From 9dfd4cb443d1968b43b3df4b70665d56b1f3755b Mon Sep 17 00:00:00 2001 From: zero-vector Date: Thu, 11 Dec 2025 18:13:35 +0100 Subject: [PATCH 1/5] `opOpAssign` now tries to match on `opImplicitCast` --- compiler/src/dmd/opover.d | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 32197ded62..6b3df75c4c 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -1257,6 +1257,10 @@ Expression op_overload(Expression e, Scope* sc, EXP* pop = null) if (s) { + enum MAX_RETRY_COUNT = 3; + int retryCount = 0; + RETRY_MATCH: + /* Try: * a.opOpAssign(b) */ @@ -1276,6 +1280,36 @@ Expression op_overload(Expression e, Scope* sc, EXP* pop = null) } else if (m.last == MATCH.nomatch) { + // NOTE(mojo): Try opImplicitCast + if (++retryCount < MAX_RETRY_COUNT) { + if (auto ad = isAggregate(e.e2.type)) { + if (auto fd = search_function(ad, Id._cast_impl)) { + + auto td = s.isTemplateDeclaration(); + + if (td && td.funcroot) { + // TODO (mojo): Handle this, it dod not triggrer on my tests. + } + else { + td = td.isTemplateDeclaration(); + auto f = td.onemember ? td.onemember.isFuncDeclaration() : null; + if (f && f.type) { + + auto tf = f.type.toTypeFunction(); + // Expecting one parameter: a.opOpAssign(b) and try `implicitCastTo` + if (tf.parameterList.length) { + import dmd.dcast : implicitCastTo; + e.e2 = implicitCastTo(e.e2, sc, tf.parameterList[0].type); + + // e.e2 is now casted, rerun the opOpAssign masching. + goto RETRY_MATCH; + } + } + } + } + } + } + if (tiargs) goto L1; m.lastf = null; From 193766bf86eb6c3fcad6f7b84103821e2d01a9d2 Mon Sep 17 00:00:00 2001 From: zero-vector Date: Thu, 11 Dec 2025 22:57:38 +0100 Subject: [PATCH 2/5] - Reworked to error out if more than one match is found. - Removed call to `implicitCastTo` as it was checking more then we need. --- compiler/src/dmd/opover.d | 68 +++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 6b3df75c4c..25cedb8241 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -1283,28 +1283,62 @@ Expression op_overload(Expression e, Scope* sc, EXP* pop = null) // NOTE(mojo): Try opImplicitCast if (++retryCount < MAX_RETRY_COUNT) { if (auto ad = isAggregate(e.e2.type)) { - if (auto fd = search_function(ad, Id._cast_impl)) { + if (auto implicitCastFd = search_function(ad, Id._cast_impl)) { auto td = s.isTemplateDeclaration(); - if (td && td.funcroot) { - // TODO (mojo): Handle this, it dod not triggrer on my tests. - } - else { - td = td.isTemplateDeclaration(); - auto f = td.onemember ? td.onemember.isFuncDeclaration() : null; - if (f && f.type) { - - auto tf = f.type.toTypeFunction(); - // Expecting one parameter: a.opOpAssign(b) and try `implicitCastTo` - if (tf.parameterList.length) { - import dmd.dcast : implicitCastTo; - e.e2 = implicitCastTo(e.e2, sc, tf.parameterList[0].type); - - // e.e2 is now casted, rerun the opOpAssign masching. - goto RETRY_MATCH; + // IMPORTANT: How to approach this: + // If s.isTemplateDeclaration we should probe all overloads + // Get the parameter type (for `opOpAssign` its just one!) and + // try to `implicitCastTo` on it, if succeesful retry with `RETRY_MATCH` + // if not do next overload. + // Problem is, if more than one matches, we should error out on ambiguity, + // now first one wins (and god knows what "first" means). + + // We need to check `overnext` for other opOpAssign types and try to match. + if (td) { + int implicitCastMeatchCount = 0; + Expression matched_ex = null; + if (td.overroot) td = td.overroot; + for (; td; td = td.overnext) + { + if (td.funcroot) { + // TODO (mojo): Handle this, it did not triggrer on my tests. + } + else { + auto f = td.onemember ? td.onemember.isFuncDeclaration() : null; + if (f && f.type) { + auto tf = f.type.toTypeFunction(); + // Expecting one parameter: a.opOpAssign(b) and try `implicitCastTo` + if (tf.parameterList.length) { + auto t = tf.parameterList[0].type; + + auto tiargs2 = new Objects(); + tiargs2.push(t); + auto dti = new DotTemplateInstanceExp(e.e2.loc, e.e2, implicitCastFd.ident, tiargs2); + auto ce = new CallExp(e.e2.loc, dti); + + if (sc && .trySemantic(ce, sc)) { + matched_ex = ce.expressionSemantic(sc); + implicitCastMeatchCount += 1; + + // We dont need to do this beyond 2 + if (implicitCastMeatchCount > 1) break; + } + } + } } } + + if (implicitCastMeatchCount == 1) { + e.e2 = matched_ex; + goto RETRY_MATCH; + } + else if (implicitCastMeatchCount > 1) { + error(e.loc, "ambiguous `opImplicitCast` in `%s` while trying to append to `%s`", ad.toChars(), e.e1.type.toChars()); + return ErrorExp.get(); + } + } } } From a4a4063c1ea4beeedb5e940f26ce41997a5028cf Mon Sep 17 00:00:00 2001 From: zero-vector Date: Thu, 11 Dec 2025 23:01:32 +0100 Subject: [PATCH 3/5] Fixed comment --- compiler/src/dmd/opover.d | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 25cedb8241..36ed8c217e 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -1292,8 +1292,6 @@ Expression op_overload(Expression e, Scope* sc, EXP* pop = null) // Get the parameter type (for `opOpAssign` its just one!) and // try to `implicitCastTo` on it, if succeesful retry with `RETRY_MATCH` // if not do next overload. - // Problem is, if more than one matches, we should error out on ambiguity, - // now first one wins (and god knows what "first" means). // We need to check `overnext` for other opOpAssign types and try to match. if (td) { From 08291f21fef07bc5c44b044c841c22ec3c268e01 Mon Sep 17 00:00:00 2001 From: zero-vector Date: Sat, 13 Dec 2025 20:37:10 +0100 Subject: [PATCH 4/5] Changed the way probing of `opImplicitCast` is done. --- compiler/src/dmd/opover.d | 89 +++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 36ed8c217e..7855ae50f1 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -1282,49 +1282,65 @@ Expression op_overload(Expression e, Scope* sc, EXP* pop = null) { // NOTE(mojo): Try opImplicitCast if (++retryCount < MAX_RETRY_COUNT) { - if (auto ad = isAggregate(e.e2.type)) { - if (auto implicitCastFd = search_function(ad, Id._cast_impl)) { + if (auto ad2 = isAggregate(e.e2.type)) { + if (auto implicitCastFd = search_function(ad2, Id._cast_impl)) { - auto td = s.isTemplateDeclaration(); - - // IMPORTANT: How to approach this: + // IMPORTANT: How to approach this v2: // If s.isTemplateDeclaration we should probe all overloads - // Get the parameter type (for `opOpAssign` its just one!) and - // try to `implicitCastTo` on it, if succeesful retry with `RETRY_MATCH` - // if not do next overload. + // We can not just probe parameter type as it can be some template param (`opOpAssign(string op: "~")(T rhs)`) + // Instead we check all `implicitCastTo` of e2.type. + // Cast OPs are ion style of `T : int` so we shoult be able to try match on + // the `TemplateTypeParameter`, If we findf exactly one match we costruct this `opImplicitCast` + // and re-run the matching. + + if (auto tdIc = implicitCastFd.isTemplateDeclaration()) { - // We need to check `overnext` for other opOpAssign types and try to match. - if (td) { int implicitCastMeatchCount = 0; Expression matched_ex = null; - if (td.overroot) td = td.overroot; - for (; td; td = td.overnext) + + for (; tdIc; tdIc = tdIc.overnext) { - if (td.funcroot) { - // TODO (mojo): Handle this, it did not triggrer on my tests. - } - else { - auto f = td.onemember ? td.onemember.isFuncDeclaration() : null; - if (f && f.type) { - auto tf = f.type.toTypeFunction(); - // Expecting one parameter: a.opOpAssign(b) and try `implicitCastTo` - if (tf.parameterList.length) { - auto t = tf.parameterList[0].type; - - auto tiargs2 = new Objects(); - tiargs2.push(t); - auto dti = new DotTemplateInstanceExp(e.e2.loc, e.e2, implicitCastFd.ident, tiargs2); - auto ce = new CallExp(e.e2.loc, dti); - - if (sc && .trySemantic(ce, sc)) { - matched_ex = ce.expressionSemantic(sc); - implicitCastMeatchCount += 1; - - // We dont need to do this beyond 2 - if (implicitCastMeatchCount > 1) break; - } + if (!tdIc.parameters) continue; + auto tp = (*tdIc.parameters)[0]; + + if (auto ttp = tp.isTemplateTypeParameter()) { + auto implicitCastType = ttp.specType; + + // printf("Probing: %s\n", implicitCastType.toChars()); + + // We try functionResolve on e1.opOpAssign(implicitCastType) + auto args3 = new Expressions(); + args3.setDim(1); + (*args3)[0] = new TypeExp(e.loc, implicitCastType); + expandTuples(args3); + + MatchAccumulator implicitOpMatch; + functionResolve(implicitOpMatch, s, e.loc, sc, tiargs, e.e1.type, ArgumentList(args3)); + + if (implicitOpMatch.count == 1) { + // If exactly one match, we rewrite e2 as e2.opImplicitCast!implicitCastType + auto tiargs2 = new Objects(); + tiargs2.push(implicitCastType); + auto dti = new DotTemplateInstanceExp(e.e2.loc, e.e2, implicitCastFd.ident, tiargs2); + auto ce = new CallExp(e.e2.loc, dti); + + // in theory we dont need`trySemantic`, as we contruct the call from implicitCastType + if (sc && .trySemantic(ce, sc)) { + matched_ex = ce.expressionSemantic(sc); + implicitCastMeatchCount += 1; + + // Here it would be nice to exit early, + // but we still have to check if other `opImplicitCast1` dfoes not match. + + // We dont need to do this beyond 2. cleatly ambiguity error + if (implicitCastMeatchCount > 1) break; } } + else if (implicitOpMatch.count > 1) { + // NOTE: isk how this could be more than 1, but I frer to asser. + error(e.loc, "ambiguous `opImplicitCast` in `%s` while trying to append to `%s`", ad2.toChars(), e.e1.type.toChars()); + return ErrorExp.get(); + } } } @@ -1333,10 +1349,9 @@ Expression op_overload(Expression e, Scope* sc, EXP* pop = null) goto RETRY_MATCH; } else if (implicitCastMeatchCount > 1) { - error(e.loc, "ambiguous `opImplicitCast` in `%s` while trying to append to `%s`", ad.toChars(), e.e1.type.toChars()); + error(e.loc, "ambiguous `opImplicitCast` in `%s` while trying to append to `%s`", ad2.toChars(), e.e1.type.toChars()); return ErrorExp.get(); } - } } } From f9adbfbb5c445dcb28a4678088f30ac15ba3df92 Mon Sep 17 00:00:00 2001 From: zero-vector Date: Sat, 13 Dec 2025 20:38:30 +0100 Subject: [PATCH 5/5] ... --- compiler/src/dmd/opover.d | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index 7855ae50f1..3ef93845b5 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -1289,9 +1289,11 @@ Expression op_overload(Expression e, Scope* sc, EXP* pop = null) // If s.isTemplateDeclaration we should probe all overloads // We can not just probe parameter type as it can be some template param (`opOpAssign(string op: "~")(T rhs)`) // Instead we check all `implicitCastTo` of e2.type. - // Cast OPs are ion style of `T : int` so we shoult be able to try match on - // the `TemplateTypeParameter`, If we findf exactly one match we costruct this `opImplicitCast` + // Cast OPs are done like this `(T : int)` so we shoult be able to try match on + // the `TemplateTypeParameter`, If we find exactly one match we costruct this `opImplicitCast` // and re-run the matching. + // TODO: we check first parameter for TemplateTypeParameter, should be good enough for `implicitCastTo` + // but better make more robust checks in th future. if (auto tdIc = implicitCastFd.isTemplateDeclaration()) {