1discard """
2  cmd: "nim c --gc:arc $file"
3  output: '''5
4(w: 5)
5(w: -5)
6c.text = hello
7c.text = hello
8p.text = hello
9p.toks = @["hello"]
10c.text = hello
11c[].text = hello
12pA.text = hello
13pA.toks = @["hello"]
14c.text = hello
15c.text = hello
16pD.text = hello
17pD.toks = @["hello"]
18c.text = hello
19c.text = hello
20pOD.text = hello
21pOD.toks = @["hello"]
22fff
23fff
242
25fff
26fff
272
28fff
29fff
302
31mmm
32fff
33fff
34fff
353
36mmm
37sink me (sink)
38assign me (not sink)
39sink me (not sink)
40sinked and not optimized to a bitcopy
41sinked and not optimized to a bitcopy
42sinked and not optimized to a bitcopy
43(data: @[0, 0])
44(data: @[0, 0])
45(data: @[0, 0])
46(data: @[0, 0])
47(data: @[0, 0])
48(data: @[0, 0])
49(data: @[0, 0])
50100
51hey
52hey
53(a: "a", b: 2)
54ho
55(a: "b", b: 3)
56(b: "b", a: 2)
57ho
58(b: "a", a: 3)
59hey
60break
61break
62hey
63ho
64hey
65ho
66ho
67king
68live long; long live
69king
70hi
71try
72bye
73()
74()
75()
761
77destroy
781
79destroy
801
81destroy
82copy (self-assign)
831
84destroy
851
86destroy
871
88destroy
89destroy
90copy
91@[(f: 2), (f: 2), (f: 3)]
92destroy
93destroy
94destroy
95sink
96destroy
97copy
98(f: 1)
99destroy
100destroy
101part-to-whole assigment:
102sink
103(children: @[])
104destroy
105sink
106(children: @[])
107destroy
108copy
109destroy
110(f: 1)
111destroy
112'''
113"""
114
115# move bug
116type
117  TMyObj = object
118    p: pointer
119    len: int
120
121var destroyCounter = 0
122
123proc `=destroy`(o: var TMyObj) =
124  if o.p != nil:
125    dealloc o.p
126    o.p = nil
127    inc destroyCounter
128
129proc `=`(dst: var TMyObj, src: TMyObj) =
130  `=destroy`(dst)
131  dst.p = alloc(src.len)
132  dst.len = src.len
133
134proc `=sink`(dst: var TMyObj, src: TMyObj) =
135  `=destroy`(dst)
136  dst.p = src.p
137  dst.len = src.len
138
139type
140  TObjKind = enum Z, A, B
141  TCaseObj = object
142    case kind: TObjKind
143    of Z: discard
144    of A:
145      x1: int # this int plays important role
146      x2: TMyObj
147    of B:
148      y: TMyObj
149
150proc use(a: TCaseObj) = discard
151
152proc moveBug(i: var int) =
153  var a: array[2, TCaseObj]
154  a[i] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5))) # 1
155  a[i+1] = a[i] # 2
156  inc i
157  use(a[i-1])
158
159var x = 0
160moveBug(x)
161
162proc moveBug2(): (TCaseObj, TCaseObj) =
163  var a: array[2, TCaseObj]
164  a[0] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5)))
165  a[1] = a[0] # can move 3
166  result[0] = TCaseObj(kind: A, x1: 5000, x2: TMyObj(len: 5, p: alloc(5))) # 4
167  result[1] = result[0] # 5
168
169proc main =
170  discard moveBug2()
171
172main()
173echo destroyCounter
174
175# bug #13314
176
177type
178  O = object
179    v: int
180  R = ref object
181    w: int
182
183proc `$`(r: R): string = $r[]
184
185proc tbug13314 =
186  var t5 = R(w: 5)
187  var execute = proc () =
188    echo t5
189
190  execute()
191  t5.w = -5
192  execute()
193
194tbug13314()
195
196#-------------------------------------------------------------------------
197# bug #13368
198
199import strutils
200proc procStat() =
201  for line in @["a b", "c d", "e f"]:
202    let cols = line.splitWhitespace(maxSplit=1)
203    let x = cols[0]
204    let (nm, rest) = (cols[0], cols[1])
205procStat()
206
207
208# bug #14269
209
210import sugar, strutils
211
212type
213  Cursor = object
214    text: string
215  Parsed = object
216    text: string
217    toks: seq[string]
218
219proc tokenize(c: var Cursor): seq[string] =
220  dump c.text
221  return c.text.splitWhitespace()
222
223proc parse(): Parsed =
224  var c = Cursor(text: "hello")
225  dump c.text
226  return Parsed(text: c.text, toks: c.tokenize) # note: c.tokenized uses c.text
227
228let p = parse()
229dump p.text
230dump p.toks
231
232
233proc tokenizeA(c: ptr Cursor): seq[string] =
234  dump c[].text
235  return c[].text.splitWhitespace()
236
237proc parseA(): Parsed =
238  var c = Cursor(text: "hello")
239  dump c.text
240  return Parsed(text: c.text, toks: c.addr.tokenizeA) # note: c.tokenized uses c.text
241
242let pA = parseA()
243dump pA.text
244dump pA.toks
245
246
247proc tokenizeD(c: Cursor): seq[string] =
248  dump c.text
249  return c.text.splitWhitespace()
250
251proc parseD(): Parsed =
252  var c = cast[ptr Cursor](alloc0(sizeof(Cursor)))
253  c[] = Cursor(text: "hello")
254  dump c.text
255  return Parsed(text: c.text, toks: c[].tokenizeD) # note: c.tokenized uses c.text
256
257let pD = parseD()
258dump pD.text
259dump pD.toks
260
261# Bug would only pop up with owned refs
262proc tokenizeOD(c: Cursor): seq[string] =
263  dump c.text
264  return c.text.splitWhitespace()
265
266proc parseOD(): Parsed =
267  var c = new Cursor
268  c[] = Cursor(text: "hello")
269  dump c.text
270  return Parsed(text: c.text, toks: c[].tokenizeOD) # note: c.tokenized uses c.text
271
272let pOD = parseOD()
273dump pOD.text
274dump pOD.toks
275
276when false:
277  # Bug would only pop up with owned refs and implicit derefs, but since they don't work together..
278  {.experimental: "implicitDeref".}
279  proc tokenizeOHD(c: Cursor): seq[string] =
280    dump c.text
281    return c.text.splitWhitespace()
282
283  proc parseOHD(): Parsed =
284    var c = new Cursor
285    c[] = Cursor(text: "hello")
286    dump c.text
287    return Parsed(text: c.text, toks: c.tokenizeOHD) # note: c.tokenized uses c.text
288
289  let pOHD = parseOHD()
290  dump pOHD.text
291  dump pOHD.toks
292
293# bug #13456
294
295iterator combinations[T](s: openarray[T], k: int): seq[T] =
296  let n = len(s)
297  assert k >= 0 and k <= n
298  var pos = newSeq[int](k)
299  var current = newSeq[T](k)
300  for i in 0..k-1:
301    pos[k-i-1] = i
302  var done = false
303  while not done:
304    for i in 0..k-1:
305      current[i] = s[pos[k-i-1]]
306    yield current
307    var i = 0
308    while i < k:
309      pos[i] += 1
310      if pos[i] < n-i:
311        for j in 0..i-1:
312          pos[j] = pos[i] + i - j
313        break
314      i += 1
315    if i >= k:
316      break
317
318type
319  UndefEx = object of ValueError
320
321proc main2 =
322  var delayedSyms = @[1, 2, 3]
323  var unp: seq[int]
324  block myb:
325    for a in 1 .. 2:
326      if delayedSyms.len > a:
327        unp = delayedSyms
328        for t in unp.combinations(a + 1):
329          try:
330            var h = false
331            for k in t:
332              echo "fff"
333            if h: continue
334            if true:
335              raise newException(UndefEx, "forward declaration")
336            break myb
337          except UndefEx:
338            echo t.len
339        echo "mmm"
340
341main2()
342
343
344
345type ME = object
346  who: string
347
348proc `=`(x: var ME, y: ME) =
349  if y.who.len > 0: echo "assign ",y.who
350
351proc `=sink`(x: var ME, y: ME) =
352  if y.who.len > 0: echo "sink ",y.who
353
354var dump: ME
355template use(x) = dump = x
356template def(x) = x = dump
357
358var c = true
359
360proc shouldSink() =
361  var x = ME(who: "me (sink)")
362  use(x) # we analyse this
363  if c: def(x)
364  else: def(x)
365  use(x) # ok, with the [else] part.
366
367shouldSink()
368
369dump = ME()
370
371proc shouldNotSink() =
372  var x = ME(who: "me (not sink)")
373  use(x) # we analyse this
374  if c: def(x)
375  use(x) # Not ok without the '[else]'
376
377shouldNotSink()
378
379# bug #14568
380import os
381
382type O2 = object
383  s: seq[int]
384
385proc `=sink`(dest: var O2, src: O2) =
386  echo "sinked and not optimized to a bitcopy"
387
388var testSeq: O2
389
390proc update() =
391  # testSeq.add(0) # uncommenting this line fixes the leak
392  testSeq = O2(s: @[])
393  testSeq.s.add(0)
394
395for i in 1..3:
396  update()
397
398
399# bug #14961
400type
401  Foo = object
402    data: seq[int]
403
404proc initFoo(len: int): Foo =
405  result = (let s = newSeq[int](len); Foo(data: s) )
406
407var f = initFoo(2)
408echo initFoo(2)
409
410proc initFoo2(len: int) =
411  echo   if true:
412             let s = newSeq[int](len); Foo(data: s)
413         else:
414             let s = newSeq[int](len); Foo(data: s)
415
416initFoo2(2)
417
418proc initFoo3(len: int) =
419  echo (block:
420         let s = newSeq[int](len); Foo(data: s))
421
422initFoo3(2)
423
424proc initFoo4(len: int) =
425  echo (let s = newSeq[int](len); Foo(data: s))
426
427initFoo4(2)
428
429proc initFoo5(len: int) =
430  echo (case true
431        of true:
432          let s = newSeq[int](len); Foo(data: s)
433        of false:
434          let s = newSeq[int](len); Foo(data: s))
435
436initFoo5(2)
437
438proc initFoo6(len: int) =
439  echo (block:
440          try:
441            let s = newSeq[int](len); Foo(data: s)
442          finally: discard)
443
444initFoo6(2)
445
446proc initFoo7(len: int) =
447  echo (block:
448          try:
449            raise newException(CatchableError, "sup")
450            let s = newSeq[int](len); Foo(data: s)
451          except CatchableError:
452            let s = newSeq[int](len); Foo(data: s) )
453
454initFoo7(2)
455
456
457# bug #14902
458iterator zip[T](s: openarray[T]): (T, T) =
459  var i = 0
460  while i < 10:
461    yield (s[i mod 2], s[i mod 2 + 1])
462    inc i
463
464var lastMem = int.high
465
466proc leak =
467  const len = 10
468  var x = @[newString(len), newString(len), newString(len)]
469
470  var c = 0
471  for (a, b) in zip(x):
472    let newMem = getOccupiedMem()
473    assert newMem <= lastMem
474    lastMem = newMem
475    c += a.len
476  echo c
477
478leak()
479
480
481proc consume(a: sink string) = echo a
482
483proc weirdScopes =
484  if (let a = "hey"; a.len > 0):
485    echo a
486
487  while (let a = "hey"; a.len > 0):
488    echo a
489    break
490
491  var a = block: (a: "a", b: 2)
492  echo a
493  (discard; a) = (echo "ho"; (a: "b", b: 3))
494  echo a
495
496  var b = try: (b: "b", a: 2)
497          except: raise
498  echo b
499  (discard; b) = (echo "ho"; (b: "a", a: 3))
500  echo b
501
502  var s = "break"
503  consume((echo "hey"; s))
504  echo s
505
506  echo (block:
507          var a = "hey"
508          (echo "hey"; "ho"))
509
510  var b2 = "ho"
511  echo (block:
512          var a = "hey"
513          (echo "hey"; b2))
514  echo b2
515
516  type status = enum
517    alive
518
519  var king = "king"
520  echo (block:
521          var a = "a"
522          when true:
523            var b = "b"
524            case alive
525            of alive:
526              try:
527                var c = "c"
528                if true:
529                  king
530                else:
531                  "the abyss"
532              except:
533                echo "he ded"
534                "dead king")
535  echo "live long; long live"
536  echo king
537
538weirdScopes()
539
540
541# bug #14985
542proc getScope(): string =
543  if true:
544    return "hi"
545  else:
546    "else"
547
548echo getScope()
549
550proc getScope3(): string =
551  try:
552    "try"
553  except:
554    return "except"
555
556echo getScope3()
557
558proc getScope2(): string =
559  case true
560  of true:
561    return "bye"
562  else:
563    "else"
564
565echo getScope2()
566
567
568#--------------------------------------------------------------------
569#bug  #15609
570
571type
572  Wrapper = object
573    discard
574
575proc newWrapper(): ref Wrapper =
576  new(result)
577  result
578
579
580proc newWrapper2(a: int): ref Wrapper =
581  new(result)
582  if a > 0:
583    result
584  else:
585    new(Wrapper)
586
587
588let w1 = newWrapper()
589echo $w1[]
590
591let w2 = newWrapper2(1)
592echo $w2[]
593
594let w3 = newWrapper2(-1)
595echo $w3[]
596
597
598#--------------------------------------------------------------------
599#self-assignments
600
601# Self-assignments that are not statically determinable will get
602# turned into `=copy` calls as caseBracketExprCopy demonstrates.
603# (`=copy` handles self-assignments at runtime)
604
605type
606  OO = object
607    f: int
608  W = object
609    o: OO
610
611proc `=destroy`(x: var OO) =
612  if x.f != 0:
613    echo "destroy"
614    x.f = 0
615
616proc `=sink`(x: var OO, y: OO) =
617  `=destroy`(x)
618  echo "sink"
619  x.f = y.f
620
621proc `=copy`(x: var OO, y: OO) =
622  if x.f != y.f:
623    `=destroy`(x)
624    echo "copy"
625    x.f = y.f
626  else:
627    echo "copy (self-assign)"
628
629proc caseSym =
630  var o = OO(f: 1)
631  o = o # NOOP
632  echo o.f # "1"
633  # "destroy"
634
635caseSym()
636
637proc caseDotExpr =
638  var w = W(o: OO(f: 1))
639  w.o = w.o # NOOP
640  echo w.o.f # "1"
641  # "destroy"
642
643caseDotExpr()
644
645proc caseBracketExpr =
646  var w = [0: OO(f: 1)]
647  w[0] = w[0] # NOOP
648  echo w[0].f # "1"
649  # "destroy"
650
651caseBracketExpr()
652
653proc caseBracketExprCopy =
654  var w = [0: OO(f: 1)]
655  let i = 0
656  w[i] = w[0] # "copy (self-assign)"
657  echo w[0].f # "1"
658  # "destroy"
659
660caseBracketExprCopy()
661
662proc caseDotExprAddr =
663  var w = W(o: OO(f: 1))
664  w.o = addr(w.o)[] # NOOP
665  echo w.o.f # "1"
666  # "destroy"
667
668caseDotExprAddr()
669
670proc caseBracketExprAddr =
671  var w = [0: OO(f: 1)]
672  addr(w[0])[] = addr(addr(w[0])[])[] # NOOP
673  echo w[0].f # "1"
674  # "destroy"
675
676caseBracketExprAddr()
677
678proc caseNotAConstant =
679  var i = 0
680  proc rand: int =
681    result = i
682    inc i
683  var s = @[OO(f: 1), OO(f: 2), OO(f: 3)]
684  s[rand()] = s[rand()] # "destroy" "copy"
685  echo s # @[(f: 2), (f: 2), (f: 3)]
686
687caseNotAConstant()
688
689proc potentialSelfAssign(i: var int) =
690  var a: array[2, OO]
691  a[i] = OO(f: 1) # turned into a memcopy
692  a[1] = OO(f: 2)
693  a[i+1] = a[i] # This must not =sink, but =copy
694  inc i
695  echo a[i-1] # (f: 1)
696
697potentialSelfAssign (var xi = 0; xi)
698
699
700#--------------------------------------------------------------------
701echo "part-to-whole assigment:"
702
703type
704  Tree = object
705    children: seq[Tree]
706
707  TreeDefaultHooks = object
708    children: seq[TreeDefaultHooks]
709
710proc `=destroy`(x: var Tree) = echo "destroy"
711proc `=sink`(x: var Tree, y: Tree) = echo "sink"
712proc `=copy`(x: var Tree, y: Tree) = echo "copy"
713
714proc partToWholeSeq =
715  var t = Tree(children: @[Tree()])
716  t = t.children[0] # This should be sunk, but with the special transform (tmp = t.children[0]; wasMoved(0); `=sink`(t, tmp))
717
718  var tc = TreeDefaultHooks(children: @[TreeDefaultHooks()])
719  tc = tc.children[0] # Ditto; if this were sunk with the normal transform (`=sink`(t, t.children[0]); wasMoved(t.children[0]))
720  echo tc             #        then it would crash because t.children[0] does not exist after the call to `=sink`
721
722partToWholeSeq()
723
724proc partToWholeSeqRTIndex =
725  var i = 0
726  var t = Tree(children: @[Tree()])
727  t = t.children[i] # See comment in partToWholeSeq
728
729  var tc = TreeDefaultHooks(children: @[TreeDefaultHooks()])
730  tc = tc.children[i] # See comment in partToWholeSeq
731  echo tc
732
733partToWholeSeqRTIndex()
734
735type List = object
736  next: ref List
737
738proc `=destroy`(x: var List) = echo "destroy"
739proc `=sink`(x: var List, y: List) = echo "sink"
740proc `=copy`(x: var List, y: List) = echo "copy"
741
742proc partToWholeUnownedRef =
743  var t = List(next: new List)
744  t = t.next[] # Copy because t.next is not an owned ref, and thus t.next[] cannot be moved
745
746partToWholeUnownedRef()
747
748
749#--------------------------------------------------------------------
750# test that nodes that get copied during the transformation
751# (like dot exprs) don't loose their firstWrite/lastRead property
752
753type
754  OOO = object
755    initialized: bool
756
757  C = object
758    o: OOO
759
760proc `=destroy`(o: var OOO) =
761  doAssert o.initialized, "OOO was destroyed before initialization!"
762
763proc initO(): OOO =
764  OOO(initialized: true)
765
766proc initC(): C =
767  C(o: initO())
768
769proc pair(): tuple[a: C, b: C] =
770  result.a = initC() # <- when firstWrite tries to find this node to start its analysis it fails, because injectdestructors uses copyTree/shallowCopy
771  result.b = initC()
772
773discard pair()
774
775
776# bug #17450
777proc noConsume(x: OO) {.nosinks.} = echo x
778
779proc main3 =
780  var i = 1
781  noConsume:
782    block:
783      OO(f: i)
784
785main3()
786
787# misc
788proc smoltest(x: bool): bool =
789  while true:
790    if true: return x
791
792discard smoltest(true)
793
794# bug #18002
795type
796  TTypeAttachedOp = enum
797    attachedAsgn
798    attachedSink
799    attachedTrace
800
801  PNode = ref object
802    discard
803
804proc genAddrOf(n: PNode) =
805  assert n != nil, "moved?!"
806
807proc atomicClosureOp =
808  let x = PNode()
809
810  genAddrOf:
811    block:
812      x
813
814  case attachedTrace
815  of attachedSink: discard
816  of attachedAsgn: discard
817  of attachedTrace: genAddrOf(x)
818
819atomicClosureOp()
820
821