summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md4
-rw-r--r--compiler/ast.nim2
-rw-r--r--compiler/injectdestructors.nim4
-rw-r--r--compiler/sempass2.nim3
-rw-r--r--compiler/semstmts.nim4
-rw-r--r--doc/destructors.rst22
-rw-r--r--tests/arc/tcaseobjcopy.nim246
-rw-r--r--tests/arc/tcomputedgotocopy.nim41
-rw-r--r--tests/arc/tmovebugcopy.nim526
-rw-r--r--tests/arc/topt_no_cursor.nim12
-rw-r--r--tests/arc/topt_wasmoved_destroy_pairs.nim2
-rw-r--r--tests/arc/tweavecopy.nim154
-rw-r--r--tests/destructor/tconsume_twice.nim2
-rw-r--r--tests/destructor/tprevent_assign.nim2
-rw-r--r--tests/destructor/tprevent_assign2.nim2
-rw-r--r--tests/destructor/tprevent_assign3.nim2
-rw-r--r--tests/destructor/tuse_ownedref_after_move.nim2
17 files changed, 1001 insertions, 29 deletions
diff --git a/changelog.md b/changelog.md
index 2c8f9b5e2..14bcbe608 100644
--- a/changelog.md
+++ b/changelog.md
@@ -215,6 +215,10 @@
 
 - The `=destroy` hook no longer has to reset its target, as the compiler now automatically inserts
   `wasMoved` calls where needed.
+- The `=` hook is now called `=copy` for clarity. The old name `=` is still available so there
+  is no need to update your code. This change was backported to 1.2 too so you can use the
+  more readability `=copy` without loss of compatibility.
+
 - In the newruntime it is now allowed to assign to the discriminator field
   without restrictions as long as case object doesn't have custom destructor.
   The discriminator value doesn't have to be a constant either. If you have a
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 8adbfa15c..f796e64c2 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -1331,7 +1331,7 @@ const
   MaxLockLevel* = 1000'i16
   UnknownLockLevel* = TLockLevel(1001'i16)
   AttachedOpToStr*: array[TTypeAttachedOp, string] = [
-    "=destroy", "=", "=sink", "=trace", "=dispose", "=deepcopy"]
+    "=destroy", "=copy", "=sink", "=trace", "=dispose", "=deepcopy"]
 
 proc `$`*(x: TLockLevel): string =
   if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>"
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index b18bcf34e..ea90299e2 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -235,7 +235,7 @@ template isUnpackedTuple(n: PNode): bool =
 
 proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
   var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">"
-  if opname == "=" and ri != nil:
+  if (opname == "=" or opname == "=copy") and ri != nil:
     m.add "; requires a copy because it's not the last read of '"
     m.add renderTree(ri)
     m.add '\''
@@ -319,7 +319,7 @@ proc genCopy(c: var Con; dest, ri: PNode): PNode =
   if tfHasOwned in t.flags and ri.kind != nkNilLit:
     # try to improve the error message here:
     if c.otherRead == nil: discard isLastRead(ri, c)
-    c.checkForErrorPragma(t, ri, "=")
+    c.checkForErrorPragma(t, ri, "=copy")
   result = c.genCopyNoCheck(dest, ri)
 
 proc genDiscriminantAsgn(c: var Con; s: var Scope; n: PNode): PNode =
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 5eb464bb8..aa04f7451 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -809,7 +809,8 @@ proc trackCall(tracked: PEffects; n: PNode) =
 
   if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and
         tracked.owner.kind != skMacro:
-    let opKind = find(AttachedOpToStr, a.sym.name.s.normalize)
+    var opKind = find(AttachedOpToStr, a.sym.name.s.normalize)
+    if a.sym.name.s.normalize == "=": opKind = attachedAsgn.int
     if opKind != -1:
       # rebind type bounds operations after createTypeBoundOps call
       let t = n[1].typ.skipTypes({tyAlias, tyVar})
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 27cd9019f..b5e69c135 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1732,7 +1732,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
                  "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T")
     incl(s.flags, sfUsed)
     incl(s.flags, sfOverriden)
-  of "=", "=sink":
+  of "=", "=copy", "=sink":
     if s.magic == mAsgn: return
     incl(s.flags, sfUsed)
     incl(s.flags, sfOverriden)
@@ -1754,7 +1754,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
         # 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 == "=": attachedAsgn else: attachedSink
+        let k = if name == "=" or name == "=copy": attachedAsgn else: attachedSink
         if obj.attachedOps[k] == s:
           discard "forward declared op"
         elif obj.attachedOps[k].isNil and tfCheckedForDestructor notin obj.flags:
diff --git a/doc/destructors.rst b/doc/destructors.rst
index b433c38d7..b581fce3e 100644
--- a/doc/destructors.rst
+++ b/doc/destructors.rst
@@ -40,7 +40,7 @@ written as:
       for i in 0..<x.len: `=destroy`(x[i])
       dealloc(x.data)
 
-  proc `=`*[T](a: var myseq[T]; b: myseq[T]) =
+  proc `=copy`*[T](a: var myseq[T]; b: myseq[T]) =
     # do nothing for self-assignments:
     if a.data == b.data: return
     `=destroy`(a)
@@ -134,7 +134,7 @@ not free the resources afterwards by setting the object to its default value
 default value is written as ``wasMoved(x)``. When not provided the compiler
 is using a combination of `=destroy` and `copyMem` instead. This is efficient
 hence users rarely need to implement their own `=sink` operator, it is enough to
-provide `=destroy` and `=`, compiler will take care about the rest.
+provide `=destroy` and `=copy`, compiler will take care about the rest.
 
 The prototype of this hook for a type ``T`` needs to be:
 
@@ -157,10 +157,10 @@ The general pattern in ``=sink`` looks like:
 How self-assignments are handled is explained later in this document.
 
 
-`=` (copy) hook
+`=copy` hook
 ---------------
 
-The ordinary assignment in Nim conceptually copies the values. The ``=`` hook
+The ordinary assignment in Nim conceptually copies the values. The ``=copy`` hook
 is called for assignments that couldn't be transformed into ``=sink``
 operations.
 
@@ -168,14 +168,14 @@ The prototype of this hook for a type ``T`` needs to be:
 
 .. code-block:: nim
 
-  proc `=`(dest: var T; source: T)
+  proc `=copy`(dest: var T; source: T)
 
 
-The general pattern in ``=`` looks like:
+The general pattern in ``=copy`` looks like:
 
 .. code-block:: nim
 
-  proc `=`(dest: var T; source: T) =
+  proc `=copy`(dest: var T; source: T) =
     # protect against self-assignments:
     if dest.field != source.field:
       `=destroy`(dest)
@@ -183,7 +183,7 @@ The general pattern in ``=`` looks like:
       dest.field = duplicateResource(source.field)
 
 
-The ``=`` proc can be marked with the ``{.error.}`` pragma. Then any assignment
+The ``=copy`` proc can be marked with the ``{.error.}`` pragma. Then any assignment
 that otherwise would lead to a copy is prevented at compile-time.
 
 
@@ -201,7 +201,7 @@ Swap
 ====
 
 The need to check for self-assignments and also the need to destroy previous
-objects inside ``=`` and ``=sink`` is a strong indicator to treat
+objects inside ``=copy`` and ``=sink`` is a strong indicator to treat
 ``system.swap`` as a builtin primitive of its own that simply swaps every
 field in the involved objects via ``copyMem`` or a comparable mechanism.
 In other words, ``swap(a, b)`` is **not** implemented
@@ -326,7 +326,7 @@ destroyed at the scope exit.
 
   x = y
   ------------------          (copy)
-  `=`(x, y)
+  `=copy`(x, y)
 
 
   f_sink(g())
@@ -336,7 +336,7 @@ destroyed at the scope exit.
 
   f_sink(notLastReadOf y)
   --------------------------     (copy-to-sink)
-  (let tmp; `=`(tmp, y);
+  (let tmp; `=copy`(tmp, y);
   f_sink(tmp))
 
 
diff --git a/tests/arc/tcaseobjcopy.nim b/tests/arc/tcaseobjcopy.nim
new file mode 100644
index 000000000..ed07b404e
--- /dev/null
+++ b/tests/arc/tcaseobjcopy.nim
@@ -0,0 +1,246 @@
+discard """
+  valgrind: true
+  cmd: "nim c --gc:arc -d:useMalloc $file"
+  output: '''myobj destroyed
+myobj destroyed
+myobj destroyed
+A
+B
+begin
+end
+prevented
+(ok: true, value: "ok")
+myobj destroyed
+'''
+"""
+
+# bug #13102
+
+type
+  D = ref object
+  R = object
+    case o: bool
+    of false:
+      discard
+    of true:
+      field: D
+
+iterator things(): R =
+  when true:
+    var
+      unit = D()
+    while true:
+      yield R(o: true, field: unit)
+  else:
+    while true:
+      var
+        unit = D()
+      yield R(o: true, field: unit)
+
+proc main =
+  var i = 0
+  for item in things():
+    discard item.field
+    inc i
+    if i == 2: break
+
+main()
+
+# bug #13149
+
+type
+  TMyObj = object
+    p: pointer
+    len: int
+
+proc `=destroy`(o: var TMyObj) =
+  if o.p != nil:
+    dealloc o.p
+    o.p = nil
+    echo "myobj destroyed"
+
+proc `=copy`(dst: var TMyObj, src: TMyObj) =
+  `=destroy`(dst)
+  dst.p = alloc(src.len)
+  dst.len = src.len
+
+proc `=sink`(dst: var TMyObj, src: TMyObj) =
+  `=destroy`(dst)
+  dst.p = src.p
+  dst.len = src.len
+
+type
+  TObjKind = enum Z, A, B
+  TCaseObj = object
+    case kind: TObjKind
+    of Z: discard
+    of A:
+      x1: int # this int plays important role
+      x2: TMyObj
+    of B:
+      y: TMyObj
+
+proc testSinks: TCaseObj =
+  result = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
+  result = TCaseObj(kind: B, y: TMyObj(len: 3, p: alloc(3)))
+
+proc use(x: TCaseObj) = discard
+
+proc testCopies(i: int) =
+  var a: array[2, TCaseObj]
+  a[i] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
+  a[i+1] = a[i] # copy, cannot move
+  use(a[i])
+
+let x1 = testSinks()
+testCopies(0)
+
+# bug #12957
+
+type
+  PegKind* = enum
+    pkCharChoice,
+    pkSequence
+  Peg* = object ## type that represents a PEG
+    case kind: PegKind
+    of pkCharChoice: charChoice: ref set[char]
+    else: discard
+    sons: seq[Peg]
+
+proc charSet*(s: set[char]): Peg =
+  ## constructs a PEG from a character set `s`
+  result = Peg(kind: pkCharChoice)
+  new(result.charChoice)
+  result.charChoice[] = s
+
+proc len(a: Peg): int {.inline.} = return a.sons.len
+proc myadd(d: var Peg, s: Peg) {.inline.} = add(d.sons, s)
+
+proc sequence*(a: openArray[Peg]): Peg =
+  result = Peg(kind: pkSequence, sons: @[])
+  when false:
+    #works too:
+    result.myadd(a[0])
+    result.myadd(a[1])
+  for x in items(a):
+    # works:
+    #result.sons.add(x)
+    # fails:
+    result.myadd x
+  if result.len == 1:
+    result = result.sons[0] # this must not move!
+
+when true:
+  # bug #12957
+
+  proc p =
+    echo "A"
+    let x = sequence([charSet({'a'..'z', 'A'..'Z', '_'}),
+              charSet({'a'..'z', 'A'..'Z', '0'..'9', '_'})])
+    echo "B"
+  p()
+
+  proc testSubObjAssignment =
+    echo "begin"
+    # There must be extactly one element in the array constructor!
+    let x = sequence([charSet({'a'..'z', 'A'..'Z', '_'})])
+    echo "end"
+  testSubObjAssignment()
+
+
+#------------------------------------------------
+
+type
+  MyObject = object
+    x1: string
+    case kind1: bool
+      of false: y1: string
+      of true:
+          y2: seq[string]
+          case kind2: bool
+              of true: z1: string
+              of false:
+                z2: seq[string]
+                flag: bool
+    x2: string
+
+proc test_myobject =
+  var x: MyObject
+  x.x1 = "x1"
+  x.x2 = "x2"
+  x.y1 = "ljhkjhkjh"
+  x.kind1 = true
+  x.y2 = @["1", "2"]
+  x.kind2 = true
+  x.z1 = "yes"
+  x.kind2 = false
+  x.z2 = @["1", "2"]
+  x.kind2 = true
+  x.z1 = "yes"
+  x.kind2 = true # should be no effect
+  doAssert(x.z1 == "yes")
+  x.kind2 = false
+  x.kind1 = x.kind2 # support self assignment with effect
+
+  try:
+    x.kind1 = x.flag # flag is not accesible
+  except FieldDefect:
+    echo "prevented"
+
+  doAssert(x.x1 == "x1")
+  doAssert(x.x2 == "x2")
+
+
+test_myobject()
+
+
+#------------------------------------------------
+# bug #14244
+
+type
+  RocksDBResult*[T] = object
+    case ok*: bool
+    of true:
+      value*: T
+    else:
+      error*: string
+
+proc init(): RocksDBResult[string] =
+  result.ok = true
+  result.value = "ok"
+
+echo init()
+
+
+#------------------------------------------------
+# bug #14312
+
+type MyObj = object
+  case kind: bool
+    of false: x0: int # would work with a type like seq[int]; value would be reset
+    of true: x1: string
+
+var a = MyObj(kind: false, x0: 1234)
+a.kind = true
+doAssert(a.x1 == "")
+
+block:
+  # bug #15532
+  type Kind = enum
+    k0, k1
+
+  type Foo = object
+    y: int
+    case kind: Kind
+    of k0: x0: int
+    of k1: x1: int
+
+  const j0 = Foo(y: 1, kind: k0, x0: 2)
+  const j1 = Foo(y: 1, kind: k1, x1: 2)
+
+  doAssert j0.y == 1
+  doAssert j0.kind == k0
+  doAssert j1.kind == k1
+
+  doAssert j1.x1 == 2
+  doAssert j0.x0 == 2
diff --git a/tests/arc/tcomputedgotocopy.nim b/tests/arc/tcomputedgotocopy.nim
new file mode 100644
index 000000000..78cb6c5c0
--- /dev/null
+++ b/tests/arc/tcomputedgotocopy.nim
@@ -0,0 +1,41 @@
+discard """
+  cmd: '''nim c --newruntime $file'''
+  output: '''2
+2'''
+"""
+
+type
+  ObjWithDestructor = object
+    a: int
+proc `=destroy`(self: var ObjWithDestructor) =
+  echo "destroyed"
+
+proc `=copy`(self: var ObjWithDestructor, other: ObjWithDestructor) =
+  echo "copied"
+
+proc test(a: range[0..1], arg: ObjWithDestructor) =
+  var iteration = 0
+  while true:
+    {.computedGoto.}
+
+    let
+      b = int(a) * 2
+      c = a
+      d = arg
+      e = arg
+
+    discard c
+    discard d
+    discard e
+
+    inc iteration
+
+    case a
+    of 0:
+      assert false
+    of 1:
+      echo b
+      if iteration == 2:
+        break
+
+test(1, ObjWithDestructor())
diff --git a/tests/arc/tmovebugcopy.nim b/tests/arc/tmovebugcopy.nim
new file mode 100644
index 000000000..7c5228147
--- /dev/null
+++ b/tests/arc/tmovebugcopy.nim
@@ -0,0 +1,526 @@
+discard """
+  cmd: "nim c --gc:arc $file"
+  output: '''5
+(w: 5)
+(w: -5)
+c.text = hello
+c.text = hello
+p.text = hello
+p.toks = @["hello"]
+c.text = hello
+c[].text = hello
+pA.text = hello
+pA.toks = @["hello"]
+c.text = hello
+c.text = hello
+pD.text = hello
+pD.toks = @["hello"]
+c.text = hello
+c.text = hello
+pOD.text = hello
+pOD.toks = @["hello"]
+fff
+fff
+2
+fff
+fff
+2
+fff
+fff
+2
+mmm
+fff
+fff
+fff
+3
+mmm
+sink me (sink)
+assign me (not sink)
+sink me (not sink)
+sinked and not optimized to a bitcopy
+sinked and not optimized to a bitcopy
+sinked and not optimized to a bitcopy
+(data: @[0, 0])
+(data: @[0, 0])
+(data: @[0, 0])
+(data: @[0, 0])
+(data: @[0, 0])
+(data: @[0, 0])
+(data: @[0, 0])
+100
+hey
+hey
+(a: "a", b: 2)
+ho
+(a: "b", b: 3)
+(b: "b", a: 2)
+ho
+(b: "a", a: 3)
+hey
+break
+break
+hey
+ho
+hey
+ho
+ho
+king
+live long; long live
+king
+hi
+try
+bye
+'''
+"""
+
+# move bug
+type
+  TMyObj = object
+    p: pointer
+    len: int
+
+var destroyCounter = 0
+
+proc `=destroy`(o: var TMyObj) =
+  if o.p != nil:
+    dealloc o.p
+    o.p = nil
+    inc destroyCounter
+
+proc `=copy`(dst: var TMyObj, src: TMyObj) =
+  `=destroy`(dst)
+  dst.p = alloc(src.len)
+  dst.len = src.len
+
+proc `=sink`(dst: var TMyObj, src: TMyObj) =
+  `=destroy`(dst)
+  dst.p = src.p
+  dst.len = src.len
+
+type
+  TObjKind = enum Z, A, B
+  TCaseObj = object
+    case kind: TObjKind
+    of Z: discard
+    of A:
+      x1: int # this int plays important role
+      x2: TMyObj
+    of B:
+      y: TMyObj
+
+proc use(a: TCaseObj) = discard
+
+proc moveBug(i: var int) =
+  var a: array[2, TCaseObj]
+  a[i] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5))) # 1
+  a[i+1] = a[i] # 2
+  inc i
+  use(a[i-1])
+
+var x = 0
+moveBug(x)
+
+proc moveBug2(): (TCaseObj, TCaseObj) =
+  var a: array[2, TCaseObj]
+  a[0] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
+  a[1] = a[0] # can move 3
+  result[0] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5))) # 4
+  result[1] = result[0] # 5
+
+proc main =
+  discard moveBug2()
+
+main()
+echo destroyCounter
+
+# bug #13314
+
+type
+  O = object
+    v: int
+  R = ref object
+    w: int
+
+proc `$`(r: R): string = $r[]
+
+proc tbug13314 =
+  var t5 = R(w: 5)
+  var execute = proc () =
+    echo t5
+
+  execute()
+  t5.w = -5
+  execute()
+
+tbug13314()
+
+#-------------------------------------------------------------------------
+# bug #13368
+
+import strutils
+proc procStat() =
+  for line in @["a b", "c d", "e f"]:
+    let cols = line.splitWhitespace(maxSplit=1)
+    let x = cols[0]
+    let (nm, rest) = (cols[0], cols[1])
+procStat()
+
+
+# bug #14269
+
+import sugar, strutils
+
+type
+  Cursor = object
+    text: string
+  Parsed = object
+    text: string
+    toks: seq[string]
+
+proc tokenize(c: var Cursor): seq[string] =
+  dump c.text
+  return c.text.splitWhitespace()
+
+proc parse(): Parsed =
+  var c = Cursor(text: "hello")
+  dump c.text
+  return Parsed(text: c.text, toks: c.tokenize) # note: c.tokenized uses c.text
+
+let p = parse()
+dump p.text
+dump p.toks
+
+
+proc tokenizeA(c: ptr Cursor): seq[string] =
+  dump c[].text
+  return c[].text.splitWhitespace()
+
+proc parseA(): Parsed =
+  var c = Cursor(text: "hello")
+  dump c.text
+  return Parsed(text: c.text, toks: c.addr.tokenizeA) # note: c.tokenized uses c.text
+
+let pA = parseA()
+dump pA.text
+dump pA.toks
+
+
+proc tokenizeD(c: Cursor): seq[string] =
+  dump c.text
+  return c.text.splitWhitespace()
+
+proc parseD(): Parsed =
+  var c = cast[ptr Cursor](alloc0(sizeof(Cursor)))
+  c[] = Cursor(text: "hello")
+  dump c.text
+  return Parsed(text: c.text, toks: c[].tokenizeD) # note: c.tokenized uses c.text
+
+let pD = parseD()
+dump pD.text
+dump pD.toks
+
+# Bug would only pop up with owned refs
+proc tokenizeOD(c: Cursor): seq[string] =
+  dump c.text
+  return c.text.splitWhitespace()
+
+proc parseOD(): Parsed =
+  var c = new Cursor
+  c[] = Cursor(text: "hello")
+  dump c.text
+  return Parsed(text: c.text, toks: c[].tokenizeOD) # note: c.tokenized uses c.text
+
+let pOD = parseOD()
+dump pOD.text
+dump pOD.toks
+
+when false:
+  # Bug would only pop up with owned refs and implicit derefs, but since they don't work together..
+  {.experimental: "implicitDeref".}
+  proc tokenizeOHD(c: Cursor): seq[string] =
+    dump c.text
+    return c.text.splitWhitespace()
+
+  proc parseOHD(): Parsed =
+    var c = new Cursor
+    c[] = Cursor(text: "hello")
+    dump c.text
+    return Parsed(text: c.text, toks: c.tokenizeOHD) # note: c.tokenized uses c.text
+
+  let pOHD = parseOHD()
+  dump pOHD.text
+  dump pOHD.toks
+
+# bug #13456
+
+iterator combinations[T](s: openarray[T], k: int): seq[T] =
+  let n = len(s)
+  assert k >= 0 and k <= n
+  var pos = newSeq[int](k)
+  var current = newSeq[T](k)
+  for i in 0..k-1:
+    pos[k-i-1] = i
+  var done = false
+  while not done:
+    for i in 0..k-1:
+      current[i] = s[pos[k-i-1]]
+    yield current
+    var i = 0
+    while i < k:
+      pos[i] += 1
+      if pos[i] < n-i:
+        for j in 0..i-1:
+          pos[j] = pos[i] + i - j
+        break
+      i += 1
+    if i >= k:
+      break
+
+type
+  UndefEx = object of ValueError
+
+proc main2 =
+  var delayedSyms = @[1, 2, 3]
+  var unp: seq[int]
+  block myb:
+    for a in 1 .. 2:
+      if delayedSyms.len > a:
+        unp = delayedSyms
+        for t in unp.combinations(a + 1):
+          try:
+            var h = false
+            for k in t:
+              echo "fff"
+            if h: continue
+            if true:
+              raise newException(UndefEx, "forward declaration")
+            break myb
+          except UndefEx:
+            echo t.len
+        echo "mmm"
+
+main2()
+
+
+
+type ME = object
+  who: string
+
+proc `=copy`(x: var ME, y: ME) =
+  if y.who.len > 0: echo "assign ",y.who
+
+proc `=sink`(x: var ME, y: ME) =
+  if y.who.len > 0: echo "sink ",y.who
+
+var dump: ME
+template use(x) = dump = x
+template def(x) = x = dump
+
+var c = true
+
+proc shouldSink() =
+  var x = ME(who: "me (sink)")
+  use(x) # we analyse this
+  if c: def(x)
+  else: def(x)
+  use(x) # ok, with the [else] part.
+
+shouldSink()
+
+dump = ME()
+
+proc shouldNotSink() =
+  var x = ME(who: "me (not sink)")
+  use(x) # we analyse this
+  if c: def(x)
+  use(x) # Not ok without the '[else]'
+
+shouldNotSink()
+
+# bug #14568
+import os
+
+type O2 = object
+  s: seq[int]
+
+proc `=sink`(dest: var O2, src: O2) =
+  echo "sinked and not optimized to a bitcopy"
+
+var testSeq: O2
+
+proc update() =
+  # testSeq.add(0) # uncommenting this line fixes the leak
+  testSeq = O2(s: @[])
+  testSeq.s.add(0)
+
+for i in 1..3:
+  update()
+
+
+# bug #14961
+type
+  Foo = object
+    data: seq[int]
+
+proc initFoo(len: int): Foo =
+  result = (let s = newSeq[int](len); Foo(data: s) )
+
+var f = initFoo(2)
+echo initFoo(2)
+
+proc initFoo2(len: int) =
+  echo   if true:
+             let s = newSeq[int](len); Foo(data: s)
+         else:
+             let s = newSeq[int](len); Foo(data: s)
+
+initFoo2(2)
+
+proc initFoo3(len: int) =
+  echo (block:
+         let s = newSeq[int](len); Foo(data: s))
+
+initFoo3(2)
+
+proc initFoo4(len: int) =
+  echo (let s = newSeq[int](len); Foo(data: s))
+
+initFoo4(2)
+
+proc initFoo5(len: int) =
+  echo (case true
+        of true:
+          let s = newSeq[int](len); Foo(data: s)
+        of false:
+          let s = newSeq[int](len); Foo(data: s))
+
+initFoo5(2)
+
+proc initFoo6(len: int) =
+  echo (block:
+          try:
+            let s = newSeq[int](len); Foo(data: s)
+          finally: discard)
+
+initFoo6(2)
+
+proc initFoo7(len: int) =
+  echo (block:
+          try:
+            raise newException(CatchableError, "sup")
+            let s = newSeq[int](len); Foo(data: s)
+          except CatchableError:
+            let s = newSeq[int](len); Foo(data: s) )
+
+initFoo7(2)
+
+
+# bug #14902
+iterator zip[T](s: openarray[T]): (T, T) =
+  var i = 0
+  while i < 10:
+    yield (s[i mod 2], s[i mod 2 + 1])
+    inc i
+
+var lastMem = int.high
+
+proc leak =
+  const len = 10
+  var x = @[newString(len), newString(len), newString(len)]
+
+  var c = 0
+  for (a, b) in zip(x):
+    let newMem = getOccupiedMem()
+    assert newMem <= lastMem
+    lastMem = newMem
+    c += a.len
+  echo c
+
+leak()
+
+
+proc consume(a: sink string) = echo a
+
+proc weirdScopes =
+  if (let a = "hey"; a.len > 0):
+    echo a
+
+  while (let a = "hey"; a.len > 0):
+    echo a
+    break
+
+  var a = block: (a: "a", b: 2)
+  echo a
+  (discard; a) = (echo "ho"; (a: "b", b: 3))
+  echo a
+
+  var b = try: (b: "b", a: 2)
+          except: raise
+  echo b
+  (discard; b) = (echo "ho"; (b: "a", a: 3))
+  echo b
+
+  var s = "break"
+  consume((echo "hey"; s))
+  echo s
+
+  echo (block:
+          var a = "hey"
+          (echo "hey"; "ho"))
+
+  var b2 = "ho"
+  echo (block:
+          var a = "hey"
+          (echo "hey"; b2))
+  echo b2
+
+  type status = enum
+    alive
+
+  var king = "king"
+  echo (block:
+          var a = "a"
+          when true:
+            var b = "b"
+            case alive
+            of alive:
+              try:
+                var c = "c"
+                if true:
+                  king
+                else:
+                  "the abyss"
+              except:
+                echo "he ded"
+                "dead king")
+  echo "live long; long live"
+  echo king
+
+weirdScopes()
+
+
+# bug #14985
+proc getScope(): string =
+  if true:
+    "hi"
+  else:
+    "else"
+
+echo getScope()
+
+proc getScope3(): string =
+  try:
+    "try"
+  except:
+    "except"
+
+echo getScope3()
+
+proc getScope2(): string =
+  case true
+  of true:
+    "bye"
+  else:
+    "else"
+
+echo getScope2()
diff --git a/tests/arc/topt_no_cursor.nim b/tests/arc/topt_no_cursor.nim
index 5fd21439a..d5811e91e 100644
--- a/tests/arc/topt_no_cursor.nim
+++ b/tests/arc/topt_no_cursor.nim
@@ -33,9 +33,9 @@ result = (
 var
   sibling
   saved
-`=`(sibling, target.parent.left)
-`=`(saved, sibling.right)
-`=`(sibling.right, saved.left)
+`=copy`(sibling, target.parent.left)
+`=copy`(saved, sibling.right)
+`=copy`(sibling.right, saved.left)
 `=sink`(sibling.parent, saved)
 `=destroy`(sibling)
 -- end of expandArc ------------------------
@@ -46,7 +46,7 @@ var
   lvalue
   lnext
   _
-`=`(lresult, [123])
+`=copy`(lresult, [123])
 _ = (
   let blitTmp = lresult
   blitTmp, ";")
@@ -67,10 +67,10 @@ try:
   var it_cursor = x
   a = (
     wasMoved(:tmpD)
-    `=`(:tmpD, it_cursor.key)
+    `=copy`(:tmpD, it_cursor.key)
     :tmpD,
     wasMoved(:tmpD_1)
-    `=`(:tmpD_1, it_cursor.val)
+    `=copy`(:tmpD_1, it_cursor.val)
     :tmpD_1)
   echo [
     :tmpD_2 = `$`(a)
diff --git a/tests/arc/topt_wasmoved_destroy_pairs.nim b/tests/arc/topt_wasmoved_destroy_pairs.nim
index 2a6391dd2..2f971f112 100644
--- a/tests/arc/topt_wasmoved_destroy_pairs.nim
+++ b/tests/arc/topt_wasmoved_destroy_pairs.nim
@@ -38,7 +38,7 @@ try:
           return
         add(a):
           wasMoved(:tmpD)
-          `=`(:tmpD, x)
+          `=copy`(:tmpD, x)
           :tmpD
         inc i_1, 1
   if cond:
diff --git a/tests/arc/tweavecopy.nim b/tests/arc/tweavecopy.nim
new file mode 100644
index 000000000..fc796b352
--- /dev/null
+++ b/tests/arc/tweavecopy.nim
@@ -0,0 +1,154 @@
+discard """
+  outputsub: '''Success'''
+  cmd: '''nim c --gc:arc --threads:on $file'''
+  disabled: "bsd"
+"""
+
+# bug #13936
+
+import std/atomics
+
+const MemBlockSize = 256
+
+type
+  ChannelSPSCSingle* = object
+    full{.align: 128.}: Atomic[bool]
+    itemSize*: uint8
+    buffer*{.align: 8.}: UncheckedArray[byte]
+
+proc `=copy`(
+    dest: var ChannelSPSCSingle,
+    source: ChannelSPSCSingle
+  ) {.error: "A channel cannot be copied".}
+
+proc initialize*(chan: var ChannelSPSCSingle, itemsize: SomeInteger) {.inline.} =
+  ## If ChannelSPSCSingle is used intrusive another data structure
+  ## be aware that it should be the last part due to ending by UncheckedArray
+  ## Also due to 128 bytes padding, it automatically takes half
+  ## of the default MemBlockSize
+  assert itemsize.int in 0 .. int high(uint8)
+  assert itemSize.int +
+          sizeof(chan.itemsize) +
+          sizeof(chan.full) < MemBlockSize
+
+  chan.itemSize = uint8 itemsize
+  chan.full.store(false, moRelaxed)
+
+func isEmpty*(chan: var ChannelSPSCSingle): bool {.inline.} =
+  not chan.full.load(moAcquire)
+
+func tryRecv*[T](chan: var ChannelSPSCSingle, dst: var T): bool {.inline.} =
+  ## Try receiving the item buffered in the channel
+  ## Returns true if successful (channel was not empty)
+  ##
+  ## ⚠ Use only in the consumer thread that reads from the channel.
+  assert (sizeof(T) == chan.itemsize.int) or
+          # Support dummy object
+          (sizeof(T) == 0 and chan.itemsize == 1)
+
+  let full = chan.full.load(moAcquire)
+  if not full:
+    return false
+  dst = cast[ptr T](chan.buffer.addr)[]
+  chan.full.store(false, moRelease)
+  return true
+
+func trySend*[T](chan: var ChannelSPSCSingle, src: sink T): bool {.inline.} =
+  ## Try sending an item into the channel
+  ## Reurns true if successful (channel was empty)
+  ##
+  ## ⚠ Use only in the producer thread that writes from the channel.
+  assert (sizeof(T) == chan.itemsize.int) or
+          # Support dummy object
+          (sizeof(T) == 0 and chan.itemsize == 1)
+
+  let full = chan.full.load(moAcquire)
+  if full:
+    return false
+  cast[ptr T](chan.buffer.addr)[] = src
+  chan.full.store(true, moRelease)
+  return true
+
+# Sanity checks
+# ------------------------------------------------------------------------------
+when isMainModule:
+
+  when not compileOption("threads"):
+    {.error: "This requires --threads:on compilation flag".}
+
+  template sendLoop[T](chan: var ChannelSPSCSingle,
+                       data: sink T,
+                       body: untyped): untyped =
+    while not chan.trySend(data):
+      body
+
+  template recvLoop[T](chan: var ChannelSPSCSingle,
+                       data: var T,
+                       body: untyped): untyped =
+    while not chan.tryRecv(data):
+      body
+
+  type
+    ThreadArgs = object
+      ID: WorkerKind
+      chan: ptr ChannelSPSCSingle
+
+    WorkerKind = enum
+      Sender
+      Receiver
+
+  template Worker(id: WorkerKind, body: untyped): untyped {.dirty.} =
+    if args.ID == id:
+      body
+
+  proc thread_func(args: ThreadArgs) =
+
+    # Worker RECEIVER:
+    # ---------
+    # <- chan
+    # <- chan
+    # <- chan
+    #
+    # Worker SENDER:
+    # ---------
+    # chan <- 42
+    # chan <- 53
+    # chan <- 64
+    Worker(Receiver):
+      var val: int
+      for j in 0 ..< 10:
+        args.chan[].recvLoop(val):
+          # Busy loop, in prod we might want to yield the core/thread timeslice
+          discard
+        echo "                  Receiver got: ", val
+        doAssert val == 42 + j*11
+
+    Worker(Sender):
+      doAssert args.chan.full.load(moRelaxed) == false
+      for j in 0 ..< 10:
+        let val = 42 + j*11
+        args.chan[].sendLoop(val):
+          # Busy loop, in prod we might want to yield the core/thread timeslice
+          discard
+        echo "Sender sent: ", val
+
+  proc main() =
+    echo "Testing if 2 threads can send data"
+    echo "-----------------------------------"
+    var threads: array[2, Thread[ThreadArgs]]
+
+    var chan = cast[ptr ChannelSPSCSingle](allocShared(MemBlockSize))
+    chan[].initialize(itemSize = sizeof(int))
+
+    createThread(threads[0], thread_func, ThreadArgs(ID: Receiver, chan: chan))
+    createThread(threads[1], thread_func, ThreadArgs(ID: Sender, chan: chan))
+
+    joinThread(threads[0])
+    joinThread(threads[1])
+
+    freeShared(chan)
+
+    echo "-----------------------------------"
+    echo "Success"
+
+  main()
diff --git a/tests/destructor/tconsume_twice.nim b/tests/destructor/tconsume_twice.nim
index 0030267f8..b0a039e9b 100644
--- a/tests/destructor/tconsume_twice.nim
+++ b/tests/destructor/tconsume_twice.nim
@@ -1,6 +1,6 @@
 discard """
   cmd: "nim c --newruntime $file"
-  errormsg: "'=' is not available for type <owned Foo>; requires a copy because it's not the last read of 'a'; another read is done here: tconsume_twice.nim(13, 10); routine: consumeTwice"
+  errormsg: "'=copy' is not available for type <owned Foo>; requires a copy because it's not the last read of 'a'; another read is done here: tconsume_twice.nim(13, 10); routine: consumeTwice"
   line: 11
 """
 type
diff --git a/tests/destructor/tprevent_assign.nim b/tests/destructor/tprevent_assign.nim
index 108ccc371..4c484ebc1 100644
--- a/tests/destructor/tprevent_assign.nim
+++ b/tests/destructor/tprevent_assign.nim
@@ -1,5 +1,5 @@
 discard """
-  errormsg: "'=' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
+  errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
   line: 29
 """
 
diff --git a/tests/destructor/tprevent_assign2.nim b/tests/destructor/tprevent_assign2.nim
index 0e4481710..ef20672d5 100644
--- a/tests/destructor/tprevent_assign2.nim
+++ b/tests/destructor/tprevent_assign2.nim
@@ -1,5 +1,5 @@
 discard """
-  errormsg: "'=' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
+  errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
   file: "tprevent_assign2.nim"
   line: 48
 """
diff --git a/tests/destructor/tprevent_assign3.nim b/tests/destructor/tprevent_assign3.nim
index a8a35ea5e..0577aa5ff 100644
--- a/tests/destructor/tprevent_assign3.nim
+++ b/tests/destructor/tprevent_assign3.nim
@@ -1,5 +1,5 @@
 discard """
-  errormsg: "'=' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
+  errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
   file: "tprevent_assign3.nim"
   line: 46
 """
diff --git a/tests/destructor/tuse_ownedref_after_move.nim b/tests/destructor/tuse_ownedref_after_move.nim
index 46540837c..ce96b741e 100644
--- a/tests/destructor/tuse_ownedref_after_move.nim
+++ b/tests/destructor/tuse_ownedref_after_move.nim
@@ -1,6 +1,6 @@
 discard """
   cmd: '''nim c --newruntime $file'''
-  errormsg: "'=' is not available for type <owned Button>; requires a copy because it's not the last read of ':envAlt.b1'; another read is done here: tuse_ownedref_after_move.nim(52, 4)"
+  errormsg: "'=copy' is not available for type <owned Button>; requires a copy because it's not the last read of ':envAlt.b1'; another read is done here: tuse_ownedref_after_move.nim(52, 4)"
   line: 48
 """