From 802bcf5a2d0d46ba1af055b9a02228e6d2b34de1 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sat, 29 Aug 2026 20:40:46 +0800 Subject: [PATCH] fixes #26132; =destroy should accept non-parametrized generic (#26142) fixes #26132 --- compiler/semstmts.nim | 117 +++++++++++-------------------- tests/destructor/tdestructor.nim | 96 +++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 75 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 6f9696cea6..094127d23b 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2160,47 +2160,54 @@ proc checkedForDestructor(t: PType): bool = return true result = false -proc whereToBindTypeHook(c: PContext; t: PType): PType = +proc normalizeTypeHook(t: PType; markAsgn = false): PType = result = t while true: - if result.kind in {tyGenericBody, tyGenericInst}: result = result.skipModifier - elif result.kind == tyGenericInvocation: result = result[0] - else: break + if markAsgn: + incl(result, tfHasAsgn) + if result.kind == tyCompositeTypeClass and result.base.kind == tyGenericBody: + result = result.base + elif result.kind in {tyGenericBody, tyGenericInst}: + result = result.skipModifier + elif result.kind == tyGenericInvocation: + result = result.genericHead + else: + break + +proc whereToBindTypeHook(c: PContext; t: PType): PType = + result = normalizeTypeHook(t) if result.kind in {tyObject, tyDistinct, tySequence, tyString}: result = canonType(c, result) +proc bindHookToType(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp; + typeToBind: PType): bool = + var obj = typeToBind + if obj.kind notin {tyObject, tyDistinct, tySequence, tyString}: + return false + obj = canonType(c, obj) + let ao = getAttachedOp(c.graph, obj, op) + if ao == s: + discard "forward declared hook" + elif ao.isNil and not checkedForDestructor(obj): + setAttachedOp(c.graph, c.module.position, obj, op, s) + else: + prevDestructor(c, op, ao, obj, n.info) + if obj.owner.getModule != s.getModule: + localError(c.config, n.info, errGenerated, + "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") + result = true + proc bindDupHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) = let t = s.typ var noError = false let cond = t.len == 2 and t.returnType != nil if cond: - var obj = t.firstParamType - while true: - incl(obj, tfHasAsgn) - if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier - elif obj.kind == tyGenericInvocation: obj = obj.genericHead - else: break + var obj = normalizeTypeHook(t.firstParamType, markAsgn = true) + let res = normalizeTypeHook(t.returnType) - var res = t.returnType - while true: - if res.kind in {tyGenericBody, tyGenericInst}: res = res.skipModifier - elif res.kind == tyGenericInvocation: res = res.genericHead - else: break - - if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, res): - obj = canonType(c, obj) - let ao = getAttachedOp(c.graph, obj, op) - if ao == s: - discard "forward declared destructor" - elif ao.isNil and not checkedForDestructor(obj): - setAttachedOp(c.graph, c.module.position, obj, op, s) - else: - prevDestructor(c, op, ao, obj, n.info) - noError = true - if obj.owner.getModule != s.getModule: - localError(c.config, n.info, errGenerated, - "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") + if sameType(obj, res): + noError = bindHookToType(c, s, n, op, obj) if not noError and sfSystemModule notin s.owner.flags: localError(c.config, n.info, errGenerated, @@ -2230,25 +2237,8 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) = t.len >= 2 and t.returnType == nil if cond: - var obj = t.firstParamType.skipTypes({tyVar}) - while true: - incl(obj, tfHasAsgn) - if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.skipModifier - elif obj.kind == tyGenericInvocation: obj = obj.genericHead - else: break - if obj.kind in {tyObject, tyDistinct, tySequence, tyString}: - obj = canonType(c, obj) - let ao = getAttachedOp(c.graph, obj, op) - if ao == s: - discard "forward declared destructor" - elif ao.isNil and not checkedForDestructor(obj): - setAttachedOp(c.graph, c.module.position, obj, op, s) - else: - prevDestructor(c, op, ao, obj, n.info) - noError = true - if obj.owner.getModule != s.getModule: - localError(c.config, n.info, errGenerated, - "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") + var obj = normalizeTypeHook(t.firstParamType.skipTypes({tyVar}), markAsgn = true) + noError = bindHookToType(c, s, n, op, obj) if not noError and sfSystemModule notin s.owner.flags: case op of attachedTrace: @@ -2315,35 +2305,12 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = message(c.config, n.info, warnDeprecated, "Overriding `=` hook is deprecated; Override `=copy` hook instead") let t = s.typ if t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar: - var obj = t.firstParamType.elementType - while true: - incl(obj, tfHasAsgn) - if obj.kind == tyGenericBody: obj = obj.skipModifier - elif obj.kind == tyGenericInvocation: obj = obj.genericHead - else: break - var objB = t[2] - while true: - if objB.kind == tyGenericBody: objB = objB.skipModifier - elif objB.kind in {tyGenericInvocation, tyGenericInst}: - objB = objB.genericHead - else: break - if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB): + var obj = normalizeTypeHook(t.firstParamType.elementType, markAsgn = true) + let objB = normalizeTypeHook(t[2]) + if sameType(obj, objB): # attach these ops to the canonical tySequence - obj = canonType(c, obj) - #echo "ATTACHING TO ", obj.id, " ", s.name.s, " ", cast[int](obj) let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink - let ao = getAttachedOp(c.graph, obj, k) - if ao == s: - discard "forward declared op" - elif ao.isNil and not checkedForDestructor(obj): - setAttachedOp(c.graph, c.module.position, obj, k, s) - else: - prevDestructor(c, k, ao, obj, n.info) - if obj.owner.getModule != s.getModule: - localError(c.config, n.info, errGenerated, - "type bound operation `" & name & "` can be defined only in the same module with its type (" & obj.typeToString() & ")") - - return + if bindHookToType(c, s, n, k, obj): return if sfSystemModule notin s.owner.flags: localError(c.config, n.info, errGenerated, "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)") diff --git a/tests/destructor/tdestructor.nim b/tests/destructor/tdestructor.nim index bb5889f345..d44d54365b 100644 --- a/tests/destructor/tdestructor.nim +++ b/tests/destructor/tdestructor.nim @@ -166,3 +166,99 @@ type Vector*[T] = object # proc `=destroy`*(x: var Vector[int]) = discard # this will remove error proc `=destroy`*[T](x: var Vector[T]) = discard var a: Vector[int] # Error: unresolved generic parameter + +# issue #26132 + +block: + type UnparameterizedGeneric[T] = object + + proc `=destroy`(x: var UnparameterizedGeneric) = discard + proc `=wasMoved`(x: var UnparameterizedGeneric) = discard + proc `=trace`(x: var UnparameterizedGeneric; env: pointer) = discard + + var x: UnparameterizedGeneric[int] + discard x + +# Exercise every type-bound hook with the generic parameter omitted. +block: + type + Generic[T] = object + value: T + + var destroys, moves, traces, copies, sinks, dups: int + + proc `=destroy`(x: var Generic) = inc destroys + proc `=wasMoved`(x: var Generic) = + inc moves + x.value = default(typeof(x.value)) + proc `=trace`(x: var Generic; env: pointer) = inc traces + proc `=copy`(dest: var Generic; src: Generic) = + inc copies + dest.value = src.value + proc `=sink`(dest: var Generic; src: Generic) = + inc sinks + dest.value = src.value + proc `=dup`(src: Generic): Generic = + inc dups + Generic(value: src.value) + proc deepCopy(src: ref Generic): ref Generic = src + + proc exercise[T]() = + var first = Generic[T](value: default(T)) + var second = Generic[T](value: default(T)) + second = first + doAssert second.value == first.value + second = Generic[T](value: default(T)) + doAssert second.value == default(T) + `=trace`(first, nil) + `=wasMoved`(first) + let implicitDuplicate = first + discard implicitDuplicate + let duplicate = `=dup`(first) + discard duplicate + let original = new(Generic[T]) + doAssert deepCopy(original) == original + + exercise[string]() + exercise[int]() + exercise[seq[int]]() + + doAssert copies > 0 + doAssert sinks > 0 + doAssert dups > 0 + doAssert moves > 0 + doAssert traces > 0 + doAssert destroys > 0 + + block: + type GenericDistinct[T] = distinct Generic[T] + + proc `=destroy`(x: var GenericDistinct) = discard + proc `=wasMoved`(x: var GenericDistinct) = discard + proc `=trace`(x: var GenericDistinct; env: pointer) = discard + proc `=copy`(dest: var GenericDistinct; src: GenericDistinct) = discard + proc `=sink`(dest: var GenericDistinct; src: GenericDistinct) = discard + proc `=dup`(src: GenericDistinct): GenericDistinct = src + proc deepCopy(src: ref GenericDistinct): ref GenericDistinct = src + + var first = GenericDistinct[string](Generic[string](value: "first")) + var second = GenericDistinct[string](Generic[string](value: "second")) + second = first + second = GenericDistinct[string](Generic[string](value: "third")) + `=trace`(first, nil) + `=wasMoved`(first) + let moved = move(first) + let duplicate = `=dup`(moved) + discard duplicate + let original = new(GenericDistinct[string]) + doAssert deepCopy(original) == original + + block: + type GenericPair[A, B] = object + left: A + right: B + + proc `=destroy`(x: var GenericPair) = discard + + var pair = GenericPair[int, string](left: 42, right: "pair") + discard pair