From 1bb7817f65ce5569e17c8249d7acc39acd5a0b5c Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 11 May 2026 19:05:05 +0800 Subject: [PATCH] `move` now uses its declaration for overridden =wasMoved --- compiler/ccgexprs.nim | 3 +++ compiler/semcall.nim | 13 +++++++++++++ lib/system.nim | 2 +- tests/ccgbugs2/t25800.nim | 30 ++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/ccgbugs2/t25800.nim diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 2cf187e687..0708607c96 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2842,6 +2842,9 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) = var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved) if op == nil: resetLoc(p, a) + elif sfOverridden in op.flags: + internalError(p.config, n.info, + "overridden =wasMoved should be emitted by instantiated move") else: var b = initLocExpr(p, newSymNode(op)) case skipTypes(a.t, abstractVar+{tyStatic}).kind diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 48d29610e5..a43a13d2d3 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -853,6 +853,12 @@ proc compactVoidArgs(n: PNode): PNode = if n[i] != nil: result.add n[i] +proc hasOverriddenWasMoved(c: PContext; call: PNode): bool = + if call.len != 2 or call[1].typ == nil: return false + let typ = call[1].typ.skipTypes({tyAlias, tyVar, tySink}) + let op = getAttachedOp(c.graph, typ, attachedWasMoved) + result = op != nil and sfOverridden in op.flags + proc semResolvedCall(c: PContext, x: var TCandidate, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = @@ -880,6 +886,13 @@ proc semResolvedCall(c: PContext, x: var TCandidate, else: c.inheritBindings(x, expectedType) finalCallee = generateInstance(c, x.calleeSym, x.bindings, n.info) + if x.calleeSym.magic == mMove and hasOverriddenWasMoved(c, x.call): + # Let system.move[T]'s instantiated body perform the overridable + # =wasMoved call. The mMove codegen path stays the compact default + # implementation for non-overridden hooks. + ensureMutable finalCallee + finalCallee.magic = mNone + setOwner(finalCallee, c.module) else: # For macros and templates, the resolved generic params # are added as normal params. diff --git a/lib/system.nim b/lib/system.nim index c76d096426..8e586e57fe 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -166,7 +166,7 @@ proc wasMoved*[T](obj: var T) {.magic: "WasMoved", noSideEffect.} ## it was "moved" and to signify its destructor should do nothing and ## ideally be optimized away. -proc move*[T](x: var T): T {.magic: "Move", noSideEffect.} = +proc move*[T](x: var T): T {.magic: "Move", noSideEffect, nodestroy.} = result = x {.cast(raises: []), cast(tags: []).}: `=wasMoved`(x) diff --git a/tests/ccgbugs2/t25800.nim b/tests/ccgbugs2/t25800.nim new file mode 100644 index 0000000000..eb97d30b22 --- /dev/null +++ b/tests/ccgbugs2/t25800.nim @@ -0,0 +1,30 @@ +discard """ + cmd: "nim cpp $file" + action: "compile" +""" + +# Bug Report 1: {.importcpp.} on =wasMoved generates invalid preprocessor directive #. +{.emit: """/*TYPESECTION*/ +struct CppRef { + int* data; + CppRef() : data(new int(42)) {} + ~CppRef() { delete data; data = nullptr; } + void reset() { delete data; data = nullptr; } +}; +""".} + +type CppRef* {.importcpp, bycopy, noInit.} = object + +proc `=destroy`(x: var CppRef) {.importcpp: "#.~CppRef()".} +proc `=wasMoved`(x: var CppRef) {.importcpp: "#.reset()".} +proc `=copy`(dest: var CppRef; src: CppRef) {.importcpp: "dest = src".} +proc `=sink`(dest: var CppRef; src: CppRef) {.importcpp: "dest = std::move(src)".} + +# This triggers =wasMoved when passing to sink parameter +proc consume(x: sink CppRef) = discard + +proc test() = + var x: CppRef + consume(move(x)) # =wasMoved MUST be called here after the move + +test() \ No newline at end of file