discard """
  matrix: "--mm:refc; --mm:orc"
"""

#[
xxx macros tests need to be reorganized to makes sure each API is tested once
See also:
  tests/macros/tdumpast.nim for treeRepr + friends
]#

import std/macros
import std/assertions

block: # hasArgOfName
  macro m(u: untyped): untyped =
    for name in ["s","i","j","k","b","xs","ys"]:
      doAssert hasArgOfName(params u,name)
    doAssert not hasArgOfName(params u,"nonexistent")

  proc p(s: string; i,j,k: int; b: bool; xs,ys: seq[int] = @[]) {.m.} = discard

block: # bug #17454
  proc f(v: NimNode): string {.raises: [].} = $v

block: # unpackVarargs
  block:
    proc bar1(a: varargs[int]): string =
      for ai in a: result.add " " & $ai
    proc bar2(a: varargs[int]) =
      let s1 = bar1(a)
      let s2 = unpackVarargs(bar1, a) # `unpackVarargs` makes no difference here
      doAssert s1 == s2
    bar2(1, 2, 3)
    bar2(1)
    bar2()

  block:
    template call1(fun: typed; args: varargs[untyped]): untyped =
      unpackVarargs(fun, args)
    template call2(fun: typed; args: varargs[untyped]): untyped =
      # fun(args) # works except for last case with empty `args`, pending bug #9996
      when varargsLen(args) > 0: fun(args)
      else: fun()

    proc fn1(a = 0, b = 1) = discard (a, b)

    call1(fn1)
    call1(fn1, 10)
    call1(fn1, 10, 11)

    call2(fn1)
    call2(fn1, 10)
    call2(fn1, 10, 11)

  block:
    template call1(fun: typed; args: varargs[typed]): untyped =
      unpackVarargs(fun, args)
    template call2(fun: typed; args: varargs[typed]): untyped =
      # xxx this would give a confusing error message:
      # required type for a: varargs[typed] [varargs] but expression '[10]' is of type: varargs[typed] [varargs]
      when varargsLen(args) > 0: fun(args)
      else: fun()
    macro toString(a: varargs[typed, `$`]): string =
      var msg = genSym(nskVar, "msg")
      result = newStmtList()
      result.add quote do:
        var `msg` = ""
      for ai in a:
        result.add quote do: `msg`.add $`ai`
      result.add quote do: `msg`
    doAssert call1(toString) == ""
    doAssert call1(toString, 10) == "10"
    doAssert call1(toString, 10, 11) == "1011"

block: # SameType
  type
    A = int
    B = distinct int
    C = object
    Generic[T, Y] = object
  macro isSameType(a, b: typed): untyped =
    newLit(sameType(a, b))

  static:
    assert Generic[int, int].isSameType(Generic[int, int])
    assert Generic[A, string].isSameType(Generic[int, string])
    assert not Generic[A, string].isSameType(Generic[B, string])
    assert not Generic[int, string].isSameType(Generic[int, int])
    assert isSameType(int, A)
    assert isSameType(10, 20)
    assert isSameType("Hello", "world")
    assert not isSameType("Hello", cstring"world")
    assert not isSameType(int, B)
    assert not isSameType(int, Generic[int, int])
    assert not isSameType(C, string)
    assert not isSameType(C, int)


  #[
    # compiler sameType fails for the following, read more in `types.nim`'s `sameTypeAux`.
    type
      D[T] = C
      G[T] = T
    static:
      assert isSameType(D[int], C)
      assert isSameType(D[int], D[float])
      assert isSameType(G[float](1.0), float(1.0))
      assert isSameType(float(1.0), G[float](1.0))
  ]#

  type Tensor[T] = object
    data: T

  macro testTensorInt(x: typed): untyped =
    let
      tensorIntType = getTypeInst(Tensor[int])[1]
      xTyp = x.getTypeInst
    
    newLit(xTyp.sameType(tensorIntType))

  var
    x: Tensor[int]
    x1 = Tensor[float]()
    x2 = Tensor[A]()
    x3 = Tensor[B]()

  static: 
    assert testTensorInt(x)
    assert not testTensorInt(x1)
    assert testTensorInt(x2)
    assert not testTensorInt(x3)

block: # extractDocCommentsAndRunnables
  macro checkRunnables(prc: untyped) =
    let runnables = prc.body.extractDocCommentsAndRunnables()
    doAssert runnables[0][0].eqIdent("runnableExamples")

  macro checkComments(comment: static[string], prc: untyped) =
    let comments = prc.body.extractDocCommentsAndRunnables()
    doAssert comments[0].strVal == comment
    
  proc a() {.checkRunnables.} =
    runnableExamples: discard
    discard

  proc b() {.checkRunnables.} =
    runnableExamples "-d:ssl": discard
    discard
    
  proc c() {.checkComments("Hello world").} =
    ## Hello world

block: # bug #19020
  type
    foo = object

  template typ(T:typedesc) {.pragma.}

  proc bar() {.typ: foo.} = discard

  static:
    doAssert $bar.getCustomPragmaVal(typ) == "foo"
  doAssert $bar.getCustomPragmaVal(typ) == "foo"

block hasCustomPragmaGeneric:
  template examplePragma() {.pragma.}
  type
    Foo[T] {.examplePragma.} = object
      x {.examplePragma.}: T
  var f: Foo[string]
  doAssert f.hasCustomPragma(examplePragma)
  doAssert f.x.hasCustomPragma(examplePragma)

block getCustomPragmaValGeneric:
  template examplePragma(x: int) {.pragma.}
  type
    Foo[T] {.examplePragma(42).} = object
      x {.examplePragma(25).}: T
  var f: Foo[string]
  doAssert f.getCustomPragmaVal(examplePragma) == 42
  doAssert f.x.getCustomPragmaVal(examplePragma) == 25

block: # bug #21326
  macro foo(body: untyped): untyped =
    let a = body.lineInfoObj()
    let aLit = a.newLit
    result = quote do:
      doAssert $`a` == $`aLit`

  foo:
    let c = 1

  template name(a: LineInfo): untyped =
    discard a # `aLit` works though

  macro foo3(body: untyped): untyped =
    let a = body.lineInfoObj()
    # let ax = newLit(a)
    result = getAst(name(a))

  foo3:
    let c = 1

block: # bug #7375
  macro fails(b: static[bool]): untyped =
    doAssert b == false
    result = newStmtList()

  macro foo(): untyped =

    var b = false

    ## Fails
    result = quote do:
      fails(`b`)

  foo()

  macro someMacro(): untyped =
    template tmpl(boolean: bool) =
      when boolean:
        discard "it's true!"
      else:
        doAssert false
    result = getAst(tmpl(true))

  someMacro()

block:
  macro foo(): untyped =
    result = quote do: `littleEndian`

  doAssert littleEndian == foo()

block:
  macro eqSym(x, y: untyped): untyped =
    let eq = $x == $y # Unfortunately eqIdent compares to string.
    result = quote do: `eq`

  var r, a, b: int

  template fma(result: var int, a, b: int, op: untyped) =
    # fused multiple-add
    when eqSym(op, `+=`):
      discard "+="
    else:
      discard "+"

  fma(r, a, b, `+=`)

block:
  template test(boolArg: bool) =
    static:
      doAssert typeof(boolArg) is bool
    let x: bool = boolArg # compile error here, because boolArg became an int

  macro testWrapped1(boolArg: bool): untyped =
    # forwarding boolArg directly works
    result = getAst(test(boolArg))

  macro testWrapped2(boolArg: bool): untyped =
    # forwarding boolArg via a local variable also works
    let b = boolArg
    result = getAst(test(b))

  macro testWrapped3(boolArg: bool): untyped =
    # but using a literal `true` as a local variable will be converted to int
    let b = true
    result = getAst(test(b))

  test(true) # ok
  testWrapped1(true) # ok
  testWrapped2(true) # ok
  testWrapped3(true) 

block:
  macro foo(): untyped =
    var s = { 'a', 'b' }
    quote do:              
      let t = `s`         
      doAssert $typeof(t) == "set[char]"

  foo()

block: # bug #9607
  proc fun1(info:LineInfo): string = "bar"
  proc fun2(info:int): string = "bar"

  macro echoL(args: varargs[untyped]): untyped =
    let info = args.lineInfoObj
    let fun1 = bindSym"fun1"
    let fun2 = bindSym"fun2"

    # this would work instead
    # result = newCall(bindSym"fun2", info.line.newLit)

    result = quote do:

      # BUG1: ???(0, 0) Error: internal error: genLiteral: ty is nil
      `fun1`(`info`)

  macro echoM(args: varargs[untyped]): untyped =
    let info = args.lineInfoObj
    let fun1 = bindSym"fun1"
    let fun2 = bindSym"fun2"

    # this would work instead
    # result = newCall(bindSym"fun2", info.line.newLit)

    result = quote do:

      # BUG1: ???(0, 0) Error: internal error: genLiteral: ty is nil
      `fun2`(`info`.line)


  doAssert echoL() == "bar"
  doAssert echoM() == "bar"

block:
  macro hello[T](x: T): untyped =
    result = quote do:
      let m: `T` = `x`
      discard m

  hello(12)

block:
  proc hello(x: int, y: typedesc) =
    discard

  macro main =
    let x = 12
    result = quote do:
      `hello`(12, type(x))

  main()

block: # bug #22947
  macro bar[N: static int](a: var array[N, int]) =
    result = quote do:
      for i in 0 ..< `N`:
        `a`[i] = i

  func foo[N: static int](a: var array[N, int]) =
    bar(a)


  var a: array[4, int]
  foo(a)
