From a35614e5395732cc56bf3243ec9401c29009a63e Mon Sep 17 00:00:00 2001 From: metagn Date: Sun, 12 Apr 2026 08:05:48 +0300 Subject: [PATCH] make explicit copy and hook calls keep their symbol (#25731) fixes #25730 As mentioned in the issue this results in less optimized output, it always generates the explicitly called hook as a proc rather than an inline assignment. But maybe this is a reasonable trade since it only happens on explicit `=sink`/`=copy` calls. Any way to optimize it requires detecting either the type or the found hook as a trivial assignment. I am not sure how to do these, the hook isn't like destructors that propagate empty statements in `liftdestructors` (which is what `isTrivial` checks for), it needs to propagate simple assignments instead. And there is no logic for the type, `tfHasAsgn` is misleading since it only checks if the destructor is trivial, because there is no check for a trivial assignment. Did not mark as backported but the only issue I can think of is the performance issue above, otherwise it would be more correct if anything. --- compiler/semdata.nim | 12 +++++-- compiler/semmagic.nim | 4 +-- tests/arc/tnodestroyexplicithook2.nim | 45 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/arc/tnodestroyexplicithook2.nim diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 4710d7c0d6..d133f2fee9 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -775,9 +775,17 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode = result[0] = newSymNode(op) analyseIfAddressTakenInCall(c, result, false) of attachedSink: - result = c.semAsgnOpr(c, n, nkSinkAsgn) + result = n + let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink}) + let op = getAttachedOp(c.graph, t, kind) + if op != nil: + result[0] = newSymNode(op) of attachedAsgn: - result = c.semAsgnOpr(c, n, nkAsgn) + result = n + let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink}) + let op = getAttachedOp(c.graph, t, kind) + if op != nil: + result[0] = newSymNode(op) of attachedDeepCopy: result = n let t = n[1].typ.skipTypes({tyAlias, tyVar, tySink}) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index bf2ff85a06..88d7512373 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -615,9 +615,9 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode, of mAsgn: case n[0].sym.name.s of "=", "=copy": - result = semAsgnOpr(c, n, nkAsgn) + result = replaceHookMagic(c, n, attachedAsgn) of "=sink": - result = semAsgnOpr(c, n, nkSinkAsgn) + result = replaceHookMagic(c, n, attachedSink) else: result = semShallowCopy(c, n, flags) of mIsPartOf: result = semIsPartOf(c, n, flags) diff --git a/tests/arc/tnodestroyexplicithook2.nim b/tests/arc/tnodestroyexplicithook2.nim new file mode 100644 index 0000000000..a05eee0e31 --- /dev/null +++ b/tests/arc/tnodestroyexplicithook2.nim @@ -0,0 +1,45 @@ +discard """ + output: ''' +246 +246 +''' +""" + +# issue #25730 + +type + Inner[T] = object + x: T + Foo[T] = object + inner: Inner[T] + Bar[T] = object + foo: Foo[T] + +proc `=sink`[T](a: var Inner[T], b: Inner[T]) {.nodestroy.} = + a.x = b.x * 2 + +proc `=copy`[T](a: var Inner[T], b: Inner[T]) {.nodestroy.} = + a.x = b.x * 2 + +when true: + proc `=sink`[T](a: var Bar[T], b: Bar[T]) {.nodestroy.} = + `=sink`(a.foo, b.foo) + + proc `=copy`[T](a: var Bar[T], b: Bar[T]) {.nodestroy.} = + `=copy`(a.foo, b.foo) + +proc useSink() = + let a = Bar[int](foo: Foo[int](inner: Inner[int](x: 123))) + var b: Bar[int] + `=sink`(b, a) + echo b.foo.inner.x + +useSink() + +proc useCopy() = + let a = Bar[int](foo: Foo[int](inner: Inner[int](x: 123))) + var b: Bar[int] + `=copy`(b, a) + echo b.foo.inner.x + +useCopy()