summary refs log tree commit diff stats
path: root/tests/destructor
diff options
context:
space:
mode:
Diffstat (limited to 'tests/destructor')
-rw-r--r--tests/destructor/const_smart_ptr.nim10
-rw-r--r--tests/destructor/t23748.nim31
-rw-r--r--tests/destructor/t23837.nim51
-rw-r--r--tests/destructor/tatomicptrs.nim12
-rw-r--r--tests/destructor/tdistinctseq.nim8
-rw-r--r--tests/destructor/tdont_return_unowned_from_owned.nim35
-rw-r--r--tests/destructor/tgotoexc_leak.nim19
-rw-r--r--tests/destructor/tgotoexceptions7.nim3
-rw-r--r--tests/destructor/tmatrix.nim5
-rw-r--r--tests/destructor/tmove.nim18
-rw-r--r--tests/destructor/tmove_objconstr.nim35
-rw-r--r--tests/destructor/tnonvardestructor.nim247
-rw-r--r--tests/destructor/tprevent_assign2.nim7
-rw-r--r--tests/destructor/tprevent_assign3.nim7
-rw-r--r--tests/destructor/tsink.nim70
-rw-r--r--tests/destructor/tv2_cast.nim70
-rw-r--r--tests/destructor/twasmoved.nim14
-rw-r--r--tests/destructor/twasmoved_error.nim37
18 files changed, 579 insertions, 100 deletions
diff --git a/tests/destructor/const_smart_ptr.nim b/tests/destructor/const_smart_ptr.nim
index 4d8c7c9a3..25dd46500 100644
--- a/tests/destructor/const_smart_ptr.nim
+++ b/tests/destructor/const_smart_ptr.nim
@@ -2,13 +2,12 @@ type
   ConstPtr*[T] = object
     val: ptr T
 
-proc `=destroy`*[T](p: var ConstPtr[T]) =
+proc `=destroy`*[T](p: ConstPtr[T]) =
   if p.val != nil:
     `=destroy`(p.val[])
     dealloc(p.val)
-    p.val = nil
 
-proc `=`*[T](dest: var ConstPtr[T], src: ConstPtr[T]) {.error.}
+proc `=copy`*[T](dest: var ConstPtr[T], src: ConstPtr[T]) {.error.}
 
 proc `=sink`*[T](dest: var ConstPtr[T], src: ConstPtr[T]) {.inline.} =
   if dest.val != nil and dest.val != src.val:
@@ -31,12 +30,11 @@ type
     len: int
     data: ptr UncheckedArray[float]
 
-proc `=destroy`*(m: var MySeqNonCopyable) {.inline.} =
+proc `=destroy`*(m: MySeqNonCopyable) {.inline.} =
   if m.data != nil:
     deallocShared(m.data)
-    m.data = nil
 
-proc `=`*(m: var MySeqNonCopyable, m2: MySeqNonCopyable) {.error.}
+proc `=copy`*(m: var MySeqNonCopyable, m2: MySeqNonCopyable) {.error.}
 
 proc `=sink`*(m: var MySeqNonCopyable, m2: MySeqNonCopyable) {.inline.} =
   if m.data != m2.data:
diff --git a/tests/destructor/t23748.nim b/tests/destructor/t23748.nim
new file mode 100644
index 000000000..a3738733e
--- /dev/null
+++ b/tests/destructor/t23748.nim
@@ -0,0 +1,31 @@
+discard """
+  matrix: "--gc:refc; --gc:arc"
+  output: '''
+hello 42
+hello 42
+len = 2
+'''
+"""
+
+# bug #23748
+
+type
+  O = ref object
+    s: string
+    cb: seq[proc()]
+
+proc push1(o: O, i: int) =
+  let o = o
+  echo o.s, " ", i
+  o.cb.add(proc() = echo o.s, " ", i)
+
+proc push2(o: O, i: int) =
+  let o = o
+  echo o.s, " ", i
+  proc p() = echo o.s, " ", i
+  o.cb.add(p)
+
+let o = O(s: "hello", cb: @[])
+o.push1(42)
+o.push2(42)
+echo "len = ", o.cb.len
diff --git a/tests/destructor/t23837.nim b/tests/destructor/t23837.nim
new file mode 100644
index 000000000..e219dd6b5
--- /dev/null
+++ b/tests/destructor/t23837.nim
@@ -0,0 +1,51 @@
+discard """
+  output: '''
+Deallocating OwnedString
+HelloWorld
+'''
+  matrix: "--cursorinference:on; --cursorinference:off"
+  target: "c"
+"""
+
+# bug #23837
+{.
+  emit: [
+    """
+#include <stdlib.h>
+#include <string.h>
+char *allocCString() {
+    char *result = (char *) malloc(10 + 1);
+    strcpy(result, "HelloWorld");
+    return result;
+}
+
+"""
+  ]
+.}
+
+proc rawWrapper(): cstring {.importc: "allocCString", cdecl.}
+proc free(p: pointer) {.importc: "free", cdecl.}
+
+# -------------------------
+
+type OwnedString = distinct cstring
+
+proc `=destroy`(s: OwnedString) =
+  free(cstring s)
+  echo "Deallocating OwnedString"
+
+func `$`(s: OwnedString): string {.borrow.}
+
+proc leakyWrapper(): string =
+  let ostring = rawWrapper().OwnedString
+  $ostring
+
+# -------------------------
+
+proc main() =
+  # destructor not called - definitely lost: 11 bytes in 1 blocks
+  # doesn't leak with --cursorInference:off
+  let s = leakyWrapper()
+  echo s
+
+main()
\ No newline at end of file
diff --git a/tests/destructor/tatomicptrs.nim b/tests/destructor/tatomicptrs.nim
index 88f84d67c..82870ac82 100644
--- a/tests/destructor/tatomicptrs.nim
+++ b/tests/destructor/tatomicptrs.nim
@@ -39,7 +39,7 @@ proc `=destroy`*[T](dest: var SharedPtr[T]) =
     echo "deallocating"
     dest.x = nil
 
-proc `=`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
+proc `=copy`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
   var s = src.x
   if s != nil: incRef(s)
   #atomicSwap(dest, s)
@@ -50,6 +50,9 @@ proc `=`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
     deallocShared(s)
     echo "deallocating"
 
+proc `=dup`*[T](src: SharedPtr[T]): SharedPtr[T] =
+  `=copy`(result, src)
+
 proc `=sink`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
   ## XXX make this an atomic store:
   if dest.x != src.x:
@@ -120,7 +123,7 @@ proc `=destroy`*[T](m: var MySeq[T]) {.inline.} =
     deallocShared(m.data)
     m.data = nil
 
-proc `=`*[T](m: var MySeq[T], m2: MySeq[T]) =
+proc `=copy`*[T](m: var MySeq[T], m2: MySeq[T]) =
   if m.data == m2.data: return
   if m.data != nil:
     `=destroy`(m)
@@ -131,17 +134,22 @@ proc `=`*[T](m: var MySeq[T], m2: MySeq[T]) =
     m.data = cast[ptr UncheckedArray[T]](allocShared(bytes))
     copyMem(m.data, m2.data, bytes)
 
+proc `=dup`*[T](m: MySeq[T]): MySeq[T] =
+  `=copy`[T](result, m)
+
 proc `=sink`*[T](m: var MySeq[T], m2: MySeq[T]) {.inline.} =
   if m.data != m2.data:
     if m.data != nil:
       `=destroy`(m)
     m.len = m2.len
     m.data = m2.data
+    m.refcount = m2.refcount
 
 proc len*[T](m: MySeq[T]): int {.inline.} = m.len
 
 proc newMySeq*[T](size: int, initial_value: T): MySeq[T] =
   result.len = size
+  result.refcount = 1
   if size > 0:
     result.data = cast[ptr UncheckedArray[T]](allocShared(sizeof(T) * size))
 
diff --git a/tests/destructor/tdistinctseq.nim b/tests/destructor/tdistinctseq.nim
new file mode 100644
index 000000000..5a2ac5ead
--- /dev/null
+++ b/tests/destructor/tdistinctseq.nim
@@ -0,0 +1,8 @@
+discard """
+  matrix: "-u:nimPreviewNonVarDestructor;"
+"""
+type DistinctSeq* = distinct seq[int]
+
+# `=destroy`(cast[ptr DistinctSeq](0)[])
+var x = @[].DistinctSeq
+`=destroy`(x)
diff --git a/tests/destructor/tdont_return_unowned_from_owned.nim b/tests/destructor/tdont_return_unowned_from_owned.nim
index d27626dea..ffe87cd76 100644
--- a/tests/destructor/tdont_return_unowned_from_owned.nim
+++ b/tests/destructor/tdont_return_unowned_from_owned.nim
@@ -1,9 +1,12 @@
 discard """
   cmd: "nim check --newruntime --hints:off $file"
   nimout: '''
-tdont_return_unowned_from_owned.nim(36, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(Obj)' as the return type
-tdont_return_unowned_from_owned.nim(39, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(Obj)' as the return type
-tdont_return_unowned_from_owned.nim(42, 6) Error: type mismatch: got <Obj>
+tdont_return_unowned_from_owned.nim(26, 13) Error: assignment produces a dangling ref: the unowned ref lives longer than the owned ref
+tdont_return_unowned_from_owned.nim(27, 13) Error: assignment produces a dangling ref: the unowned ref lives longer than the owned ref
+tdont_return_unowned_from_owned.nim(31, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(RootRef)' as the return type
+tdont_return_unowned_from_owned.nim(43, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(Obj)' as the return type
+tdont_return_unowned_from_owned.nim(46, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(Obj)' as the return type
+tdont_return_unowned_from_owned.nim(49, 6) Error: type mismatch: got <Obj>
 but expected one of:
 proc new[T](a: var ref T; finalizer: proc (x: ref T) {.nimcall.})
   first type mismatch at position: 2
@@ -11,17 +14,21 @@ proc new[T](a: var ref T; finalizer: proc (x: ref T) {.nimcall.})
 2 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them
 
 expression: new(result)
-tdont_return_unowned_from_owned.nim(42, 6) Error: illformed AST:
-tdont_return_unowned_from_owned.nim(50, 13) Error: assignment produces a dangling ref: the unowned ref lives longer than the owned ref
-tdont_return_unowned_from_owned.nim(51, 13) Error: assignment produces a dangling ref: the unowned ref lives longer than the owned ref
-tdont_return_unowned_from_owned.nim(55, 10) Error: cannot return an owned pointer as an unowned pointer; use 'owned(RootRef)' as the return type
+tdont_return_unowned_from_owned.nim(49, 6) Error: illformed AST: 
 '''
-  errormsg: "cannot return an owned pointer as an unowned pointer; use 'owned(RootRef)' as the return type"
+  errormsg: "illformed AST:"
 """
 
 
 
+proc testA(result: var (RootRef, RootRef)) =
+  let r: owned RootRef = RootRef()
+  result[0] = r
+  result[1] = RootRef()
 
+proc testB(): RootRef =
+  let r: owned RootRef = RootRef()
+  result = r
 
 
 
@@ -39,17 +46,11 @@ proc newObjB(): Obj =
   result = Obj()
 
 proc newObjC(): Obj =
-  new(result)
+  new(result) # illFormedAst raises GlobalError,
+              # without pipeline parsing, it needs to placed at the end
+              # in case that it disturbs other errors
 
 let a = newObjA()
 let b = newObjB()
 let c = newObjC()
 
-proc testA(result: var (RootRef, RootRef)) =
-  let r: owned RootRef = RootRef()
-  result[0] = r
-  result[1] = RootRef()
-
-proc testB(): RootRef =
-  let r: owned RootRef = RootRef()
-  result = r
diff --git a/tests/destructor/tgotoexc_leak.nim b/tests/destructor/tgotoexc_leak.nim
new file mode 100644
index 000000000..c8a234085
--- /dev/null
+++ b/tests/destructor/tgotoexc_leak.nim
@@ -0,0 +1,19 @@
+discard """
+  output: '''0
+true'''
+  cmd: "nim c --gc:arc $file"
+"""
+
+# bug #22398
+
+for i in 0 ..< 10_000:
+  try:
+    try:
+      raise newException(ValueError, "")
+    except CatchableError:
+      discard
+      raise newException(ValueError, "") # or raise getCurrentException(), just raise works ok
+  except ValueError:
+    discard
+echo getOccupiedMem()
+echo getCurrentException() == nil
diff --git a/tests/destructor/tgotoexceptions7.nim b/tests/destructor/tgotoexceptions7.nim
index 6e564a044..c04bd6ba0 100644
--- a/tests/destructor/tgotoexceptions7.nim
+++ b/tests/destructor/tgotoexceptions7.nim
@@ -25,7 +25,8 @@ proc helper = doAssert(false)
 
 proc main(i: int) =
   var obj = Obj(kind: kindA, s: "abc")
-  obj.kind = kindB
+  {.cast(uncheckedAssign).}:
+    obj.kind = kindB
   obj.i = 2
   try:
     var objA = ObjA()
diff --git a/tests/destructor/tmatrix.nim b/tests/destructor/tmatrix.nim
index 98ca95c94..2fd5af789 100644
--- a/tests/destructor/tmatrix.nim
+++ b/tests/destructor/tmatrix.nim
@@ -31,7 +31,7 @@ proc `=sink`*(a: var Matrix; b: Matrix) =
   a.m = b.m
   a.n = b.n
 
-proc `=`*(a: var Matrix; b: Matrix) =
+proc `=copy`*(a: var Matrix; b: Matrix) =
   if a.data != nil and a.data != b.data:
     dealloc(a.data)
     deallocCount.inc
@@ -43,6 +43,9 @@ proc `=`*(a: var Matrix; b: Matrix) =
     allocCount.inc
     copyMem(a.data, b.data, b.m * b.n * sizeof(float))
 
+proc `=dup`*(a: Matrix): Matrix =
+  `=copy`(result, a)
+
 proc matrix*(m, n: int, s: float): Matrix =
   ## Construct an m-by-n constant matrix.
   result.m = m
diff --git a/tests/destructor/tmove.nim b/tests/destructor/tmove.nim
new file mode 100644
index 000000000..2762aff90
--- /dev/null
+++ b/tests/destructor/tmove.nim
@@ -0,0 +1,18 @@
+discard """
+  targets: "c cpp"
+"""
+
+block:
+  var called = 0
+
+  proc bar(a: var int): var int =
+    inc called
+    result = a
+
+  proc foo =
+    var a = 2
+    var s = move bar(a)
+    doAssert called == 1
+    doAssert s == 2
+
+  foo()
diff --git a/tests/destructor/tmove_objconstr.nim b/tests/destructor/tmove_objconstr.nim
index a583a1704..cdc1eb1c0 100644
--- a/tests/destructor/tmove_objconstr.nim
+++ b/tests/destructor/tmove_objconstr.nim
@@ -137,28 +137,29 @@ doAssert seq3[0] == 1.0
 var seq4, seq5: MySeqNonCopyable
 (seq4, i, seq5) = myfunc2(2, 3)
 
-seq4 = block:
-  var tmp = newMySeq(4, 1.0)
-  tmp[0] = 3.0
-  tmp
+proc foo =
+  seq4 = block:
+    var tmp = newMySeq(4, 1.0)
+    tmp[0] = 3.0
+    tmp
 
-doAssert seq4[0] == 3.0
+  doAssert seq4[0] == 3.0
 
-import macros
 
-seq4 =
-  if i > 0: newMySeq(2, 5.0)
-  elif i < -100: raise newException(ValueError, "Parse Error")
-  else: newMySeq(2, 3.0)
+  seq4 =
+    if i > 0: newMySeq(2, 5.0)
+    elif i < -100: raise newException(ValueError, "Parse Error")
+    else: newMySeq(2, 3.0)
 
-seq4 =
-  case (char) i:
-    of 'A', {'W'..'Z'}: newMySeq(2, 5.0)
-    of 'B': quit(-1)
-    else:
-      let (x1, x2, x3) = myfunc2(2, 3)
-      x3
+  seq4 =
+    case (char) i:
+      of 'A', {'W'..'Z'}: newMySeq(2, 5.0)
+      of 'B': quit(-1)
+      else:
+        let (x1, x2, x3) = myfunc2(2, 3)
+        x3
 
+foo()
 
 #------------------------------------------------------------
 #-- Move into array constructor
diff --git a/tests/destructor/tnonvardestructor.nim b/tests/destructor/tnonvardestructor.nim
new file mode 100644
index 000000000..1b4413790
--- /dev/null
+++ b/tests/destructor/tnonvardestructor.nim
@@ -0,0 +1,247 @@
+discard """

+  targets: "c cpp"

+  matrix: "--mm:arc; --mm:orc"

+"""

+

+block:

+  type

+    PublicKey = array[32, uint8]

+    PrivateKey = array[64, uint8]

+

+  proc ed25519_create_keypair(publicKey: ptr PublicKey; privateKey: ptr PrivateKey) =

+    publicKey[][0] = uint8(88)

+

+  type

+    KeyPair = object

+      public: PublicKey

+      private: PrivateKey

+

+  proc initKeyPair(): KeyPair =

+    ed25519_create_keypair(result.public.addr, result.private.addr)

+

+  let keys = initKeyPair()

+  doAssert keys.public[0] == 88

+

+

+template minIndexByIt: untyped =

+  var other = 3

+  other

+

+proc bug20303() =

+  var hlibs = @["hello", "world", "how", "are", "you"]

+  let res = hlibs[minIndexByIt()]

+  doAssert res == "are"

+

+bug20303()

+

+proc main() = # todo bug with templates

+  block: # bug #11267

+    var a: seq[char] = block: @[]

+    doAssert a == @[]

+    # 2

+    proc b: seq[string] =

+      discard

+      @[]

+    doAssert b() == @[]

+static: main()

+main()

+

+

+type Obj = tuple

+  value: int

+  arr: seq[int]

+

+proc bug(): seq[Obj] =

+  result.add (value: 0, arr: @[])

+  result[^1].value = 1

+  result[^1].arr.add 1

+

+# bug #19990

+let s = bug()

+doAssert s[0] == (value: 1, arr: @[1])

+

+block: # bug #21974

+  type Test[T] = ref object

+      values : seq[T]

+      counter: int

+

+  proc newTest[T](): Test[T] =

+      result         = new(Test[T])

+      result.values  = newSeq[T](16)

+      result.counter = 0

+

+  proc push[T](self: Test[T], value: T) =

+      self.counter += 1

+      if self.counter >= self.values.len:

+          self.values.setLen(self.values.len * 2)

+      self.values[self.counter - 1] = value

+

+  proc pop[T](self: Test[T]): T =

+      result         = self.values[0]

+      self.values[0] = self.values[self.counter - 1] # <--- This line

+      self.counter  -= 1

+

+

+  type X = tuple

+      priority: int

+      value   : string

+

+  var a = newTest[X]()

+  a.push((1, "One"))

+  doAssert a.pop.value == "One"

+

+# bug #21987

+

+type

+  EmbeddedImage* = distinct Image

+  Image = object

+    len: int

+

+proc imageCopy*(image: Image): Image {.nodestroy.}

+

+proc `=destroy`*(x: Image) =

+  discard

+

+proc `=sink`*(dest: var Image; source: Image) =

+  `=destroy`(dest)

+  wasMoved(dest)

+

+proc `=dup`*(source: Image): Image {.nodestroy.} =

+  result = imageCopy(source)

+

+proc `=copy`*(dest: var Image; source: Image) =

+  dest = imageCopy(source) # calls =sink implicitly

+

+proc `=destroy`*(x: EmbeddedImage) = discard

+

+proc `=dup`*(source: EmbeddedImage): EmbeddedImage {.nodestroy.} = source

+

+proc `=copy`*(dest: var EmbeddedImage; source: EmbeddedImage) {.nodestroy.} =

+  dest = source

+

+proc imageCopy*(image: Image): Image =

+  result = image

+

+proc main2 =

+  block:

+    var a = Image(len: 2).EmbeddedImage

+    var b = Image(len: 1).EmbeddedImage

+    b = a

+    doAssert Image(a).len == 2

+    doAssert Image(b).len == 2

+

+  block:

+    var a = Image(len: 2)

+    var b = Image(len: 1)

+    b = a

+    doAssert a.len == 2

+    doAssert b.len == 0

+

+main2()

+

+type

+  Edge = object

+    neighbor {.cursor.}: Node

+

+  NodeObj = object

+    neighbors: seq[Edge]

+    label: string

+    visited: bool

+  Node = ref NodeObj

+

+  Graph = object

+    nodes: seq[Node]

+

+proc `=destroy`(x: NodeObj) =

+  `=destroy`(x.neighbors)

+  `=destroy`(x.label)

+

+proc addNode(self: var Graph; label: string): Node =

+  self.nodes.add(Node(label: label))

+  result = self.nodes[^1]

+

+proc addEdge(self: Graph; source, neighbor: Node) =

+  source.neighbors.add(Edge(neighbor: neighbor))

+

+block:

+  proc main =

+    var graph: Graph

+    let nodeA = graph.addNode("a")

+    let nodeB = graph.addNode("b")

+    let nodeC = graph.addNode("c")

+

+    graph.addEdge(nodeA, neighbor = nodeB)

+    graph.addEdge(nodeA, neighbor = nodeC)

+

+  main()

+

+block:

+  type RefObj = ref object

+

+  proc `[]`(val: static[int]) = # works with different name/overload or without static arg

+    discard

+

+  template noRef(T: typedesc): typedesc = # works without template indirection

+    typeof(default(T)[])

+

+  proc `=destroy`(x: noRef(RefObj)) =

+    discard

+

+  proc foo =

+    var x = new RefObj

+    doAssert $(x[]) == "()"

+

+  # bug #11705

+  foo()

+

+block: # bug #22197

+  type

+    H5IdObj = object

+    H5Id = ref H5IdObj

+

+    FileID = distinct H5Id

+

+    H5GroupObj = object

+      file_id: FileID

+    H5Group = ref H5GroupObj

+

+  ## This would make it work!

+  #proc `=destroy`*(x: FileID) = `=destroy`(cast[H5Id](x))

+  ## If this does not exist, it also works!

+  proc newFileID(): FileID = FileID(H5Id())

+

+  proc `=destroy`(grp: H5GroupObj) =

+    ## Closes the group and resets all references to nil.

+    if cast[pointer](grp.fileId) != nil:

+      `=destroy`(grp.file_id)

+

+  var grp = H5Group()

+  reset(grp.file_id)

+  reset(grp)

+

+import std/tables

+

+block: # bug #22286

+  type

+    A = object

+    B = object

+      a: A

+    C = object

+      b: B

+

+  proc `=destroy`(self: A) =

+    echo "destroyed"

+

+  proc `=destroy`(self: C) =

+    `=destroy`(self.b)

+

+  var c = C()

+

+block: # https://forum.nim-lang.org/t/10642

+  type AObj = object

+    name: string

+    tableField: Table[string, string]

+

+  proc `=destroy`(x: AObj) =

+    `=destroy`(x.name)

+    `=destroy`(x.tableField)

diff --git a/tests/destructor/tprevent_assign2.nim b/tests/destructor/tprevent_assign2.nim
index fd1a711db..eb5588b1a 100644
--- a/tests/destructor/tprevent_assign2.nim
+++ b/tests/destructor/tprevent_assign2.nim
@@ -1,7 +1,7 @@
 discard """
-  errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
+  errormsg: "'=dup' is not available for type <Foo>, which is inferred from unavailable '=copy'; requires a copy because it's not the last read of 'otherTree'; another read is done here: tprevent_assign2.nim(51, 31); routine: preventThis"
   file: "tprevent_assign2.nim"
-  line: 48
+  line: 49
 """
 
 type
@@ -9,7 +9,8 @@ type
     x: int
 
 proc `=destroy`(f: var Foo) = f.x = 0
-proc `=`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+proc `=copy`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+
 proc `=sink`(a: var Foo; b: Foo) = a.x = b.x
 
 proc createTree(x: int): Foo =
diff --git a/tests/destructor/tprevent_assign3.nim b/tests/destructor/tprevent_assign3.nim
index b9bd40fa9..aa834a66c 100644
--- a/tests/destructor/tprevent_assign3.nim
+++ b/tests/destructor/tprevent_assign3.nim
@@ -1,7 +1,7 @@
 discard """
-  errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
+  errormsg: "'=dup' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
   file: "tprevent_assign3.nim"
-  line: 46
+  line: 47
 """
 
 type
@@ -9,7 +9,8 @@ type
     x: int
 
 proc `=destroy`(f: var Foo) = f.x = 0
-proc `=`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+proc `=copy`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+proc `=dup`(a: Foo): Foo {.error.}
 proc `=sink`(a: var Foo; b: Foo) = a.x = b.x
 
 proc createTree(x: int): Foo =
diff --git a/tests/destructor/tsink.nim b/tests/destructor/tsink.nim
new file mode 100644
index 000000000..e8750ad7c
--- /dev/null
+++ b/tests/destructor/tsink.nim
@@ -0,0 +1,70 @@
+discard """
+  matrix: "--mm:arc"
+"""
+
+type AnObject = object of RootObj
+  value*: int
+
+proc mutate(shit: sink AnObject) =
+  shit.value = 1
+
+proc foo = # bug #23359
+  var bar = AnObject(value: 42)
+  mutate(bar)
+  doAssert bar.value == 42
+
+foo()
+
+block: # bug #23902
+  proc foo(a: sink string): auto = (a, a)
+
+  proc bar(a: sink int): auto = return a
+
+  proc foo(a: sink string) =
+    var x = (a, a)
+
+block: # bug #24175
+  block:
+    func mutate(o: sink string): string =
+      o[1] = '1'
+      result = o
+
+    static:
+      let s = "999"
+      let m = mutate(s)
+      doAssert s == "999"
+      doAssert m == "919"
+
+    func foo() =
+      let s = "999"
+      let m = mutate(s)
+      doAssert s == "999"
+      doAssert m == "919"
+
+    static:
+      foo()
+    foo()
+
+  block:
+    type O = object
+      a: int
+
+    func mutate(o: sink O): O =
+      o.a += 1
+      o
+
+    static:
+      let x = O(a: 1)
+      let y = mutate(x)
+      doAssert x.a == 1
+      doAssert y.a == 2
+
+    proc foo() =
+      let x = O(a: 1)
+      let y = mutate(x)
+      doAssert x.a == 1
+      doAssert y.a == 2
+
+    static:
+      foo()
+    foo()
diff --git a/tests/destructor/tv2_cast.nim b/tests/destructor/tv2_cast.nim
index 917cf0eb3..48bdf67dd 100644
--- a/tests/destructor/tv2_cast.nim
+++ b/tests/destructor/tv2_cast.nim
@@ -3,81 +3,51 @@ discard """
 @[116, 101, 115, 116]
 @[1953719668, 875770417]
 destroying O1'''
-  cmd: '''nim c --gc:arc --expandArc:main --expandArc:main1 --expandArc:main2 --expandArc:main3 --hints:off --assertions:off $file'''
-  nimout: '''--expandArc: main
+  cmd: '''nim c --mm:arc --expandArc:main --expandArc:main1 --expandArc:main2 --expandArc:main3 --hints:off --assertions:off $file'''
+  nimout: '''
+--expandArc: main
 
 var
   data
   :tmpD
-  :tmpD_1
-  :tmpD_2
-data =
-  wasMoved(:tmpD)
-  `=copy`(:tmpD, cast[string](
-    :tmpD_2 = encode(cast[seq[byte]](
-      :tmpD_1 = newString(100)
-      :tmpD_1))
-    :tmpD_2))
-  :tmpD
-`=destroy`(:tmpD_2)
-`=destroy_1`(:tmpD_1)
-`=destroy_1`(data)
+data = cast[string](encode(cast[seq[byte]](
+  :tmpD = newString(100)
+  :tmpD)))
+`=destroy`(:tmpD)
+`=destroy`(data)
 -- end of expandArc ------------------------
 --expandArc: main1
 
 var
   s
   data
-  :tmpD
-  :tmpD_1
 s = newString(100)
-data =
-  wasMoved(:tmpD)
-  `=copy`(:tmpD, cast[string](
-    :tmpD_1 = encode(toOpenArrayByte(s, 0, len(s) - 1))
-    :tmpD_1))
-  :tmpD
-`=destroy`(:tmpD_1)
-`=destroy_1`(data)
-`=destroy_1`(s)
+data = cast[string](encode(toOpenArrayByte(s, 0, len(s) - 1)))
+`=destroy`(data)
+`=destroy`(s)
 -- end of expandArc ------------------------
 --expandArc: main2
 
 var
   s
   data
-  :tmpD
-  :tmpD_1
 s = newSeq(100)
-data =
-  wasMoved(:tmpD)
-  `=copy`(:tmpD, cast[string](
-    :tmpD_1 = encode(s)
-    :tmpD_1))
-  :tmpD
-`=destroy`(:tmpD_1)
-`=destroy_1`(data)
-`=destroy`(s)
+data = cast[string](encode(s))
+`=destroy`(data)
+`=destroy_1`(s)
 -- end of expandArc ------------------------
 --expandArc: main3
 
 var
   data
   :tmpD
-  :tmpD_1
-  :tmpD_2
-data =
-  wasMoved(:tmpD)
-  `=copy`(:tmpD, cast[string](
-    :tmpD_2 = encode do:
-      :tmpD_1 = newSeq(100)
-      :tmpD_1
-    :tmpD_2))
-  :tmpD
-`=destroy`(:tmpD_2)
-`=destroy`(:tmpD_1)
+data = cast[string](encode do:
+  :tmpD = newSeq(100)
+  :tmpD)
+`=destroy`(:tmpD)
 `=destroy_1`(data)
--- end of expandArc ------------------------'''
+-- end of expandArc ------------------------
+'''
 """
 
 func encode*(src: openArray[byte]): seq[byte] =
diff --git a/tests/destructor/twasmoved.nim b/tests/destructor/twasmoved.nim
new file mode 100644
index 000000000..566322702
--- /dev/null
+++ b/tests/destructor/twasmoved.nim
@@ -0,0 +1,14 @@
+type
+  Foo = object
+    id: int
+
+proc `=wasMoved`(x: var Foo) =
+  x.id = -1
+
+proc foo =
+  var s = Foo(id: 999)
+  var m = move s
+  doAssert s.id == -1
+  doAssert m.id == 999
+
+foo()
diff --git a/tests/destructor/twasmoved_error.nim b/tests/destructor/twasmoved_error.nim
new file mode 100644
index 000000000..1cd57e3df
--- /dev/null
+++ b/tests/destructor/twasmoved_error.nim
@@ -0,0 +1,37 @@
+discard """
+  cmd: '''nim c --mm:arc $file'''
+  errormsg: "'=wasMoved' is not available for type <Game>; routine: main"
+"""
+
+# bug #19291
+
+const
+  screenWidth = 800
+  screenHeight = 450
+
+var
+  ready = false
+type
+  Game = object
+
+proc `=destroy`(x: var Game) =
+  assert ready, "Window is already opened"
+  ready = false
+
+proc `=sink`(x: var Game; y: Game) {.error.}
+proc `=copy`(x: var Game; y: Game) {.error.}
+proc `=wasMoved`(x: var Game) {.error.}
+
+proc initGame(width, height: int32, title: string): Game =
+  assert not ready, "Window is already closed"
+  ready = true
+
+proc update(x: Game) = discard
+
+proc main =
+  var g = initGame(screenWidth, screenHeight, "Tetris raylib")
+  g.update()
+  var g2 = g
+  echo "hello"
+
+main()