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 () () () 1 destroy 1 destroy 1 destroy copy (self-assign) 1 destroy 1 destroy 1 destroy destroy copy @[(f: 2), (f: 2), (f: 3)] destroy destroy destroy sink sink destroy copy (f: 1) destroy destroy part-to-whole assigment: sink (children: @[]) destroy sink (children: @[]) destroy copy destroy (f: 1) destroy ''' """ # 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 `=`(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 `=`(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: return "hi" else: "else" echo getScope() proc getScope3(): string = try: "try" except: return "except" echo getScope3() proc getScope2(): string = case true of true: return "bye" else: "else" echo getScope2() #-------------------------------------------------------------------- #bug #15609 type Wrapper = object discard proc newWrapper(): ref Wrapper = new(result) result proc newWrapper2(a: int): ref Wrapper = new(result) if a > 0: result else: new(Wrapper) let w1 = newWrapper() echo $w1[] let w2 = newWrapper2(1) echo $w2[] let w3 = newWrapper2(-1) echo $w3[] #-------------------------------------------------------------------- #self-assignments # Self-assignments that are not statically determinable will get # turned into `=copy` calls as caseBracketExprCopy demonstrates. # (`=copy` handles self-assignments at runtime) type OO = object f: int W = object o: OO proc `=destroy`(x: var OO) = if x.f != 0: echo "destroy" x.f = 0 proc `=sink`(x: var OO, y: OO) = `=destroy`(x) echo "sink" x.f = y.f proc `=copy`(x: var OO, y: OO) = if x.f != y.f: `=destroy`(x) echo "copy" x.f = y.f else: echo "copy (self-assign)" proc caseSym = var o = OO(f: 1) o = o # NOOP echo o.f # "1" # "destroy" caseSym() proc caseDotExpr = var w = W(o: OO(f: 1)) w.o = w.o # NOOP echo w.o.f # "1" # "destroy" caseDotExpr() proc caseBracketExpr = var w = [0: OO(f: 1)] w[0] = w[0] # NOOP echo w[0].f # "1" # "destroy" caseBracketExpr() proc caseBracketExprCopy = var w = [0: OO(f: 1)] let i = 0 w[i] = w[0] # "copy (self-assign)" echo w[0].f # "1" # "destroy" caseBracketExprCopy() proc caseDotExprAddr = var w = W(o: OO(f: 1)) w.o = addr(w.o)[] # NOOP echo w.o.f # "1" # "destroy" caseDotExprAddr() proc caseBracketExprAddr = var w = [0: OO(f: 1)] addr(w[0])[] = addr(addr(w[0])[])[] # NOOP echo w[0].f # "1" # "destroy" caseBracketExprAddr() proc caseNotAConstant = var i = 0 proc rand: int = result = i inc i var s = @[OO(f: 1), OO(f: 2), OO(f: 3)] s[rand()] = s[rand()] # "destroy" "copy" echo s # @[(f: 2), (f: 2), (f: 3)] caseNotAConstant() proc potentialSelfAssign(i: var int) = var a: array[2, OO] a[i] = OO(f: 1) # turned into a memcopy a[1] = OO(f: 2) a[i+1] = a[i] # This must not =sink, but =copy inc i echo a[i-1] # (f: 1) potentialSelfAssign (var xi = 0; xi) #-------------------------------------------------------------------- echo "part-to-whole assigment:" type Tree = object children: seq[Tree] TreeDefaultHooks = object children: seq[TreeDefaultHooks] proc `=destroy`(x: var Tree) = echo "destroy" proc `=sink`(x: var Tree, y: Tree) = echo "sink" proc `=copy`(x: var Tree, y: Tree) = echo "copy" proc partToWholeSeq = var t = Tree(children: @[Tree()]) t = t.children[0] # This should be sunk, but with the special transform (tmp = t.children[0]; wasMoved(0); `=sink`(t, tmp)) var tc = TreeDefaultHooks(children: @[TreeDefaultHooks()]) tc = tc.children[0] # Ditto; if this were sunk with the normal transform (`=sink`(t, t.children[0]); wasMoved(t.children[0])) echo tc # then it would crash because t.children[0] does not exist after the call to `=sink` partToWholeSeq() proc partToWholeSeqRTIndex = var i = 0 var t = Tree(children: @[Tree()]) t = t.children[i] # See comment in partToWholeSeq var tc = TreeDefaultHooks(children: @[TreeDefaultHooks()]) tc = tc.children[i] # See comment in partToWholeSeq echo tc partToWholeSeqRTIndex() type List = object next: ref List proc `=destroy`(x: var List) = echo "destroy" proc `=sink`(x: var List, y: List) = echo "sink" proc `=copy`(x: var List, y: List) = echo "copy" proc partToWholeUnownedRef = var t = List(next: new List) t = t.next[] # Copy because t.next is not an owned ref, and thus t.next[] cannot be moved partToWholeUnownedRef() #-------------------------------------------------------------------- # test that nodes that get copied during the transformation # (like dot exprs) don't loose their firstWrite/lastRead property type OOO = object initialized: bool C = object o: OOO proc `=destroy`(o: var OOO) = doAssert o.initialized, "OOO was destroyed before initialization!" proc initO(): OOO = OOO(initialized: true) proc initC(): C = C(o: initO()) proc pair(): tuple[a: C, b: C] = result = (a: initC(), b: initC())# <- when firstWrite tries to find this node to start its analysis it fails, because injectdestructors uses copyTree/shallowCopy discard pair() # bug #17450 proc noConsume(x: OO) {.nosinks.} = echo x proc main3 = var i = 1 noConsume: block: OO(f: i) main3() # misc proc smoltest(x: bool): bool = while true: if true: return x discard smoltest(true) # bug #18002 type TTypeAttachedOp = enum attachedAsgn attachedSink attachedTrace PNode = ref object discard proc genAddrOf(n: PNode) = assert n != nil, "moved?!" proc atomicClosureOp = let x = PNode() genAddrOf: block: x case attachedTrace of attachedSink: discard of attachedAsgn: discard of attachedTrace: genAddrOf(x) atomicClosureOp() template assertEq(a, b: untyped): untyped = block: let aval = a bval = b if aval != bval: quit "bug!" proc convoluted = let _ = (; var val1: string; if true: val1 = "22" true ) assertEq val1, "22" assertEq val1, "22" convoluted()