diff options
Diffstat (limited to 'tests/macros')
96 files changed, 5534 insertions, 0 deletions
diff --git a/tests/macros/m18235.nim b/tests/macros/m18235.nim new file mode 100644 index 000000000..6bb4547e0 --- /dev/null +++ b/tests/macros/m18235.nim @@ -0,0 +1,42 @@ +import macros + +# Necessary code to update the AST on a symbol across module boundaries when +# processed by a type macro. Used by a test of a corresponding name of this +# file. + +macro eexport(n: typed): untyped = + result = copyNimTree(n) + # turn exported nnkSym -> nnkPostfix(*, nnkIdent), forcing re-sem + result[0] = nnkPostfix.newTree(ident"*").add: + n.name.strVal.ident + +macro unexport(n: typed): untyped = + result = copyNimTree(n) + # turn nnkSym -> nnkIdent, forcing re-sem and dropping any exported-ness + # that might be present + result[0] = n.name.strVal.ident + +proc foo*() {.unexport.} = discard +proc bar() {.eexport.} = discard + +proc foooof*() {.unexport, eexport, unexport.} = discard +proc barrab() {.eexport, unexport, eexport.} = discard + +macro eexportMulti(n: typed): untyped = + # use the call version of `eexport` macro for one or more decls + result = copyNimTree(n) + for i in 0..<result.len: + result[i] = newCall(ident"eexport", result[i]) + +macro unexportMulti(n: typed): untyped = + # use the call version of `unexport` macro for one or more decls + result = copyNimTree(n) + for i in 0..<result.len: + result[i] = newCall(ident"unexport", result[i]) + +unexportMulti: + proc oof*() = discard + +eexportMulti: + proc rab() = discard + proc baz*() = discard \ No newline at end of file diff --git a/tests/macros/macro_bug.nim b/tests/macros/macro_bug.nim new file mode 100644 index 000000000..c723a4ab6 --- /dev/null +++ b/tests/macros/macro_bug.nim @@ -0,0 +1,18 @@ +import macros + +macro macro_bug*(s: untyped) = + echo s.treeRepr + s.expectKind({nnkProcDef, nnkMethodDef}) + + var params = s.params + + let genericParams = s[2] + result = newNimNode(nnkProcDef).add( + s.name, s[1], genericParams, params, pragma(s), newEmptyNode()) + + # don't really do anything + var body = body(s) + result.add(body) + + echo "result:" + echo result.repr diff --git a/tests/macros/mparsefile.nim b/tests/macros/mparsefile.nim new file mode 100644 index 000000000..8ac99d568 --- /dev/null +++ b/tests/macros/mparsefile.nim @@ -0,0 +1,4 @@ +let a = 1 +let b = 2 +let c = +let d = 4 diff --git a/tests/macros/t14227.nim b/tests/macros/t14227.nim new file mode 100644 index 000000000..4206e2c06 --- /dev/null +++ b/tests/macros/t14227.nim @@ -0,0 +1,23 @@ +discard """ + action: "compile" +""" +import sugar + + +block: + let y = @[@[1, 2], @[2, 4, 6]] + let x = collect(newSeq): + for i in y: + if i.len > 2: + for j in i: + j + echo(x) + +block: + let y = @[@[1, 2], @[2, 4, 6]] + let x = collect(newSeq): + for i in y: + for j in i: + if i.len > 2: + j + echo(x) diff --git a/tests/macros/t14329.nim b/tests/macros/t14329.nim new file mode 100644 index 000000000..b5606424a --- /dev/null +++ b/tests/macros/t14329.nim @@ -0,0 +1,4 @@ +import macros + +macro myMacro(n) = + let x = if true: newLit"test" else: error "error", n diff --git a/tests/macros/t14511.nim b/tests/macros/t14511.nim new file mode 100644 index 000000000..f3b1e2894 --- /dev/null +++ b/tests/macros/t14511.nim @@ -0,0 +1,54 @@ +discard """ + output: "true\n(y: XInt, a: 5)\n(y: XString, b: \"abc\")" +""" + +import macros + +block TEST_1: + # https://github.com/nim-lang/Nim/issues/14511 + + template myPragma() {.pragma.} + + type + XType = enum + XInt, + XString, + XUnused + X = object + case y {.myPragma.}: XType + of XInt, XUnused: + a: int + else: # <-- Else case caused the "Error: index 1 not in 0 .. 0" error + b: string + + var x: X = X(y: XInt, a: 5) + echo x.y.hasCustomPragma(myPragma) + echo x + echo X(y: XString, b: "abc") + + +block TEST_2: + template myDevice(val: string) {.pragma.} + template myKey(val: string) {.pragma.} + template myMouse(val: string) {.pragma.} + + type + Device {.pure.} = enum Keyboard, Mouse + Key = enum Key1, Key2 + Mouse = enum Mouse1, Mouse2 + + type + Obj = object of RootObj + case device {.myDevice: "MyDevicePragmaStr".}: Device + of Device.Keyboard: + key {.myKey: "MyKeyPragmaStr".}: Key + else: # <-- Else case caused the "Error: index 1 not in 0 .. 0" error + mouse {.myMouse: "MyMousePragmaStr".}: Mouse + + var obj: Obj + assert obj.device.hasCustomPragma(myDevice) == true + assert obj.key.hasCustomPragma(myKey) == true + assert obj.mouse.hasCustomPragma(myMouse) == true + assert obj.device.getCustomPragmaVal(myDevice) == "MyDevicePragmaStr" + assert obj.key.getCustomPragmaVal(myKey) == "MyKeyPragmaStr" + assert obj.mouse.getCustomPragmaVal(myMouse) == "MyMousePragmaStr" \ No newline at end of file diff --git a/tests/macros/t14847.nim b/tests/macros/t14847.nim new file mode 100644 index 000000000..0e6d0dd2d --- /dev/null +++ b/tests/macros/t14847.nim @@ -0,0 +1,20 @@ +discard """ + output: "98" +""" +import macros + +#bug #14847 +proc hello*(b: string) = + echo b + +macro dispatch(pro: typed, params: untyped): untyped = + var impl = pro.getImpl + let id = ident(pro.strVal & "_coverage") + impl[0] = id + let call = newCall(id, params) + + result = newStmtList() + result.add(impl) + result.add(call) + +dispatch(hello, "98") diff --git a/tests/macros/t15691.nim b/tests/macros/t15691.nim new file mode 100644 index 000000000..c1e8a8648 --- /dev/null +++ b/tests/macros/t15691.nim @@ -0,0 +1,22 @@ +discard """ + action: compile +""" + +import std/macros + +macro simplifiedExpandMacros(body: typed): untyped = + result = body + +simplifiedExpandMacros: + proc testProc() = discard + +simplifiedExpandMacros: + template testTemplate(): untyped = discard + +# Error: illformed AST: macro testMacro(): untyped = +simplifiedExpandMacros: + macro testMacro(): untyped = discard + +# Error: illformed AST: converter testConverter(x: int): float = +simplifiedExpandMacros: + converter testConverter(x: int): float = discard diff --git a/tests/macros/t15751.nim b/tests/macros/t15751.nim new file mode 100644 index 000000000..fcabb2f9e --- /dev/null +++ b/tests/macros/t15751.nim @@ -0,0 +1,11 @@ +discard """ + cmd: "nim c --hints:off $file" + nimout: "out" +""" + +# bug #15751 +macro print(n: untyped): untyped = + echo n.repr + +print: + out diff --git a/tests/macros/t16758.nim b/tests/macros/t16758.nim new file mode 100644 index 000000000..66b6d42c5 --- /dev/null +++ b/tests/macros/t16758.nim @@ -0,0 +1,38 @@ +discard """ +errormsg: "'blk.p(a)' has nil child at index 1" +action: reject +""" +import macros + +type BlockLiteral[T] = object + p: T + +proc p[T](a:int) = echo 1 +proc p[T](a:string) = echo "a" + +iterator arguments(formalParams: NimNode): NimNode = + var iParam = 0 + for i in 1 ..< formalParams.len: + let pp = formalParams[i] + for j in 0 .. pp.len - 3: + yield pp[j] + inc iParam + +macro implementInvoke(T: typedesc): untyped = + let t = getTypeImpl(T)[1] + + let call = newCall(newDotExpr(ident"blk", ident"p")) + let params = copyNimTree(t[0]) + result = newProc(ident"invoke", body = call) + # result[2] = newTree(nnkGenericParams,T) + for n in arguments(params): + call.add(n) + + params.insert(1, newIdentDefs(ident"blk", newTree(nnkBracketExpr, bindSym"BlockLiteral", T))) + result.params = params + +proc getInvoke(T: typedesc) = + implementInvoke(T) + + +getInvoke(proc(a: int)) diff --git a/tests/macros/t17836.nim b/tests/macros/t17836.nim new file mode 100644 index 000000000..2453637f5 --- /dev/null +++ b/tests/macros/t17836.nim @@ -0,0 +1,15 @@ +import macros + +# Ensure that `isNil` works in the typed macro context when pass procs. + +type + O = object + fn: proc(i: int): int + +var o: O + +macro typedBug(expr: typed) = + doAssert expr[1] != nil + doAssert not expr[1].isNil + +typedBug(o.fn) \ No newline at end of file diff --git a/tests/macros/t18203.nim b/tests/macros/t18203.nim new file mode 100644 index 000000000..aae0a2690 --- /dev/null +++ b/tests/macros/t18203.nim @@ -0,0 +1,15 @@ +discard """ + matrix: "--hint:SuccessX:off --hint:Link:off --hint:Conf:off --hint:CC:off --hint:XDeclaredButNotUsed:on" + nimout: ''' +''' +nimoutFull: true +action: compile +""" + +# bug #18203 +import std/macros + +macro foo(x: typed) = newProc ident"bar" +proc bar() {.foo.} = raise +bar() + diff --git a/tests/macros/t18235.nim b/tests/macros/t18235.nim new file mode 100644 index 000000000..ba5c48a24 --- /dev/null +++ b/tests/macros/t18235.nim @@ -0,0 +1,18 @@ +import m18235 + +# this must error out because it was never actually exported +doAssert(not declared(foo)) +doAssert not compiles(foo()) + +doAssert(not declared(foooof)) +doAssert not compiles(foooof()) + +doAssert(not declared(oof)) +doAssert not compiles(oof()) + +# this should have been exported just fine + +bar() +barrab() +rab() +baz() \ No newline at end of file diff --git a/tests/macros/t19766_20114.nim b/tests/macros/t19766_20114.nim new file mode 100644 index 000000000..ac336f150 --- /dev/null +++ b/tests/macros/t19766_20114.nim @@ -0,0 +1,16 @@ +discard """ + action: compile + nimout: ''' +const + foo {.strdefine.} = "abc" +let hey {.tddd.} = 5 +''' +""" + +import macros + +template tddd {.pragma.} + +expandMacros: + const foo {.strdefine.} = "abc" + let hey {.tddd.} = 5 diff --git a/tests/macros/t20067.nim b/tests/macros/t20067.nim new file mode 100644 index 000000000..0ee3a4712 --- /dev/null +++ b/tests/macros/t20067.nim @@ -0,0 +1,28 @@ +discard """ + output: ''' +b.defaultVal = foo +$c.defaultVal = bar +''' +""" + +import macros + +# #18976 + +macro getString(identifier): string = + result = newLit($identifier) +doAssert getString(abc) == "abc" +doAssert getString(`a b c`) == "abc" + +# #20067 + +template defaultVal*(value : typed) {.pragma.} + +type A = ref object + b {.defaultVal: "foo".}: string + `$c` {.defaultVal: "bar".}: string + +let a = A(b: "a", `$c`: "b") + +echo "b.defaultVal = " & a.b.getCustomPragmaVal(defaultVal) +echo "$c.defaultVal = " & a.`$c`.getCustomPragmaVal(defaultVal) diff --git a/tests/macros/t20435.nim b/tests/macros/t20435.nim new file mode 100644 index 000000000..824282198 --- /dev/null +++ b/tests/macros/t20435.nim @@ -0,0 +1,30 @@ + +#[ + A better test requires matching, so the use of @ working can be showcased + For example: + + proc regularCase[T]() = + case [(1, 3), (3, 4)]: + of [(1, @a), (_, @b)]: + echo a, b + else: discard +]# + +{.experimental: "caseStmtMacros".} + +import macros + +type Foo = object + +macro `case`(obj: Foo) = quote do: discard + +proc notGeneric() = + case Foo() + of a b c d: discard + +proc generic[T]() = + case Foo() + of a b c d: discard + +notGeneric() +generic[int]() diff --git a/tests/macros/t21593.nim b/tests/macros/t21593.nim new file mode 100644 index 000000000..b0b7ebe75 --- /dev/null +++ b/tests/macros/t21593.nim @@ -0,0 +1,13 @@ +discard """ +nimout: ''' +StmtList + UIntLit 18446744073709551615 + IntLit -1''' +""" + +import macros + +dumpTree: + 0xFFFFFFFF_FFFFFFFF'u + 0xFFFFFFFF_FFFFFFFF + diff --git a/tests/macros/t23032_1.nim b/tests/macros/t23032_1.nim new file mode 100644 index 000000000..4e1707414 --- /dev/null +++ b/tests/macros/t23032_1.nim @@ -0,0 +1,19 @@ +import std/macros + +type A[T, H] = object + +proc `%*`(a: A): bool = true +proc `%*`[T](a: A[int, T]): bool = false + +macro collapse(s: untyped) = + result = newStmtList() + result.add quote do: + doAssert(`s`(A[float, int]()) == true) + +macro startHere(n: untyped): untyped = + result = newStmtList() + let s = n[0] + result.add quote do: + `s`.collapse() + +startHere(`a` %* `b`) diff --git a/tests/macros/t23032_2.nim b/tests/macros/t23032_2.nim new file mode 100644 index 000000000..8dde29e10 --- /dev/null +++ b/tests/macros/t23032_2.nim @@ -0,0 +1,20 @@ +discard """ + action: "reject" + errormsg: "ambiguous identifier: '%*'" +""" +import std/macros + +type A[T, H] = object + +proc `%*`[T](a: A) = discard +proc `%*`[T](a: A[int, T]) = discard + +macro collapse(s: typed) = discard + +macro startHere(n: untyped): untyped = + result = newStmtList() + let s = n[0] + result.add quote do: + collapse(`s`.typeof()) + +startHere(`a` %* `b`) diff --git a/tests/macros/t23547.nim b/tests/macros/t23547.nim new file mode 100644 index 000000000..9a2bff9ff --- /dev/null +++ b/tests/macros/t23547.nim @@ -0,0 +1,23 @@ +# https://github.com/nim-lang/Nim/issues/23547 + +type + A[T] = object + x: T + +proc mulCheckSparse[F](dummy: var A[F], xmulchecksparse: static A[F]) = + static: + echo "mulCheckSparse: ", typeof(dummy), ", ", typeof(xmulchecksparse) # when generic params not specified: A[system.int], A + +template sumImpl(xsumimpl: typed) = + static: + echo "sumImpl: ", typeof(xsumimpl) # A + var a = A[int](x: 55) + mulCheckSparse(a, xsumimpl) # fails here + +proc sum[T](xsum: static T) = + static: + echo "sum: ", typeof(xsum) # A[system.int] + sumImpl(xsum) + +const constA = A[int](x : 100) +sum[A[int]](constA) diff --git a/tests/macros/t23784.nim b/tests/macros/t23784.nim new file mode 100644 index 000000000..31b4544c6 --- /dev/null +++ b/tests/macros/t23784.nim @@ -0,0 +1,157 @@ +discard """ + joinable: false +""" + + +# debug ICE: genCheckedRecordField +# apparently after https://github.com/nim-lang/Nim/pull/23477 + +# bug #23784 + +import std/bitops, std/macros + +# -------------------------------------------------------------- + +type Algebra = enum + BN254_Snarks + +type SecretWord* = distinct uint64 +const WordBitWidth* = sizeof(SecretWord) * 8 + +func wordsRequired*(bits: int): int {.inline.} = + const divShiftor = fastLog2(WordBitWidth) + result = (bits + WordBitWidth - 1) shr divShiftor + +type + BigInt*[bits: static int] = object + limbs*: array[bits.wordsRequired, SecretWord] # <--- crash points to here + +# -------------------------------------------------------------- + +const CurveBitWidth = [ + BN254_Snarks: 254 +] + +const BN254_Snarks_Modulus = BigInt[254](limbs: [SecretWord 0x1, SecretWord 0x2, SecretWord 0x3, SecretWord 0x4]) +const BN254_Snarks_Order = BigInt[254](limbs: [SecretWord 0x1, SecretWord 0x1, SecretWord 0x2, SecretWord 0x2]) + +func montyOne*(M: BigInt[254]): BigInt[254] = + ## Returns "1 (mod M)" in the Montgomery domain. + ## This is equivalent to R (mod M) in the natural domain + BigInt[254](limbs: [SecretWord 0x1, SecretWord 0x1, SecretWord 0x1, SecretWord 0x1]) + + +{.experimental: "dynamicBindSym".} + +type + DerivedConstantMode* = enum + kModulus + kOrder + +macro genDerivedConstants*(mode: static DerivedConstantMode): untyped = + ## Generate constants derived from the main constants + ## + ## For example + ## - the Montgomery magic constant "R^2 mod N" in ROM + ## For each curve under the private symbol "MyCurve_R2modP" + ## - the Montgomery magic constant -1/P mod 2^Wordbitwidth + ## For each curve under the private symbol "MyCurve_NegInvModWord + ## - ... + + # Now typedesc are NimNode and there is no way to translate + # NimNode -> typedesc easily so we can't + # "for curve in low(Curve) .. high(Curve):" + # As an ugly workaround, we count + # The item at position 0 is a pragma + result = newStmtList() + + template used(name: string): NimNode = + nnkPragmaExpr.newTree( + ident(name), + nnkPragma.newTree(ident"used") + ) + + let ff = if mode == kModulus: "_Fp" else: "_Fr" + + for curveSym in low(Algebra) .. high(Algebra): + let curve = $curveSym + let M = if mode == kModulus: bindSym(curve & "_Modulus") + else: bindSym(curve & "_Order") + + # const MyCurve_montyOne = montyOne(MyCurve_Modulus) + result.add newConstStmt( + used(curve & ff & "_MontyOne"), newCall( + bindSym"montyOne", + M + ) + ) + +# -------------------------------------------------------------- + +{.experimental: "dynamicBindSym".} + +genDerivedConstants(kModulus) +genDerivedConstants(kOrder) + +proc bindConstant(ff: NimNode, property: string): NimNode = + # Need to workaround https://github.com/nim-lang/Nim/issues/14021 + # which prevents checking if a type FF[Name] = Fp[Name] or Fr[Name] + # was instantiated with Fp or Fr. + # getTypeInst only returns FF and sameType doesn't work. + # so quote do + when checks. + let T = getTypeInst(ff) + T.expectKind(nnkBracketExpr) + doAssert T[0].eqIdent("typedesc") + + let curve = + if T[1].kind == nnkBracketExpr: # typedesc[Fp[BLS12_381]] as used internally + # doAssert T[1][0].eqIdent"Fp" or T[1][0].eqIdent"Fr", "Found ident: '" & $T[1][0] & "' instead of 'Fp' or 'Fr'" + T[1][1].expectKind(nnkIntLit) # static enum are ints in the VM + $Algebra(T[1][1].intVal) + else: # typedesc[bls12381_fp] alias as used for C exports + let T1 = getTypeInst(T[1].getImpl()[2]) + if T1.kind != nnkBracketExpr or + T1[1].kind != nnkIntLit: + echo T.repr() + echo T1.repr() + echo getTypeInst(T1).treerepr() + error "getTypeInst didn't return the full instantiation." & + " Dealing with types in macros is hard, complain at https://github.com/nim-lang/RFCs/issues/44" + $Algebra(T1[1].intVal) + + let curve_fp = bindSym(curve & "_Fp_" & property) + let curve_fr = bindSym(curve & "_Fr_" & property) + result = quote do: + when `ff` is Fp: + `curve_fp` + elif `ff` is Fr: + `curve_fr` + else: + {.error: "Unreachable, received type: " & $`ff`.} + +# -------------------------------------------------------------- + +template matchingBigInt*(Name: static Algebra): untyped = + ## BigInt type necessary to store the prime field Fp + # Workaround: https://github.com/nim-lang/Nim/issues/16774 + # as we cannot do array accesses in type section. + # Due to generic sandwiches, it must be exported. + BigInt[CurveBitWidth[Name]] + +type + Fp*[Name: static Algebra] = object + mres*: matchingBigInt(Name) + +macro getMontyOne*(ff: type Fp): untyped = + ## Get one in Montgomery representation (i.e. R mod P) + result = bindConstant(ff, "MontyOne") + +func getOne*(T: type Fp): T {.noInit, inline.} = + result = cast[ptr T](unsafeAddr getMontyOne(T))[] + +# -------------------------------------------------------------- +proc foo(T: Fp) = + discard T + +let a = Fp[BN254_Snarks].getOne() +foo(a) # oops this was a leftover that broke the bisect. diff --git a/tests/macros/t7454.nim b/tests/macros/t7454.nim new file mode 100644 index 000000000..e527de0c3 --- /dev/null +++ b/tests/macros/t7454.nim @@ -0,0 +1,8 @@ +discard """ +errormsg: "expression has no type:" +line: 8 +""" + +macro p(t: typedesc): typedesc = + discard +var a: p(int) diff --git a/tests/macros/t7875.nim b/tests/macros/t7875.nim new file mode 100644 index 000000000..7b6e47b86 --- /dev/null +++ b/tests/macros/t7875.nim @@ -0,0 +1,22 @@ +discard """ + nimout: "var mysym`gensym0: MyType[float32]" + joinable: false +""" + +import macros + +type + MyType[T] = object + +# this is totally fine +var mysym: MyType[float32] + +macro foobar(): untyped = + let floatSym = bindSym"float32" + + result = quote do: + var mysym: MyType[`floatSym`] + + echo result.repr + +foobar() diff --git a/tests/macros/t8997.nim b/tests/macros/t8997.nim new file mode 100644 index 000000000..b06223717 --- /dev/null +++ b/tests/macros/t8997.nim @@ -0,0 +1,26 @@ +discard """ + errormsg: "illformed AST: " + line: 24 +""" + +import macros + +type + Node* = ref object + children: seq[Node] + +proc newNode*(): Node = + Node(children: newSeq[Node]()) + +macro build*(body: untyped): untyped = + + template appendElement(tmp, childrenBlock) {.dirty.} = + bind newNode + let tmp = newNode() + tmp.children = childrenBlock # this line seems to be the problem + + let tmp = genSym(nskLet, "tmp") + let childrenBlock = newEmptyNode() + result = getAst(appendElement(tmp, childrenBlock)) + +build(body) diff --git a/tests/macros/tastrepr.nim b/tests/macros/tastrepr.nim new file mode 100644 index 000000000..96a37c7a2 --- /dev/null +++ b/tests/macros/tastrepr.nim @@ -0,0 +1,58 @@ +discard """ +output: ''' + +var data = @[(1, "one"), (2, "two")] +for (i, d) in pairs(data): + discard +for i, d in pairs(data): + discard +for i, (x, y) in pairs(data): + discard +var + a = 1 + b = 2 +type + A* = object + +var data = @[(1, "one"), (2, "two")] +for (i, d) in pairs(data): + discard +for i, d in pairs(data): + discard +for i, (x, y) in pairs(data): + discard +var (a, b) = (1, 2) +type + A* = object + +var t04 = 1.0'f128 +t04 = 2.0'f128 +''' +""" + +import macros + +macro echoTypedRepr(arg: typed) = + result = newCall(ident"echo", newLit(arg.repr)) + +macro echoUntypedRepr(arg: untyped) = + result = newCall(ident"echo", newLit(arg.repr)) + +template echoTypedAndUntypedRepr(arg: untyped) = + echoTypedRepr(arg) + echoUntypedRepr(arg) + +echoTypedAndUntypedRepr: + var data = @[(1,"one"), (2,"two")] + for (i, d) in pairs(data): + discard + for i, d in pairs(data): + discard + for i, (x,y) in pairs(data): + discard + var (a,b) = (1,2) + type A* = object # issue #22933 + +echoUntypedRepr: + var t04 = 1'f128 + t04 = 2'f128 diff --git a/tests/macros/tbindsym.nim b/tests/macros/tbindsym.nim new file mode 100644 index 000000000..a493d6a88 --- /dev/null +++ b/tests/macros/tbindsym.nim @@ -0,0 +1,67 @@ +discard """ + nimout: '''initApple +deinitApple +Coral +enum + redCoral, blackCoral''' + output: '''TFoo +TBar''' +""" + +# bug #1319 + +import macros + +type + TTextKind = enum + TFoo, TBar + +macro test: untyped = + var x = @[TFoo, TBar] + result = newStmtList() + for i in x: + result.add newCall(newIdentNode("echo"), + case i + of TFoo: + bindSym("TFoo") + of TBar: + bindSym("TBar")) + +test() + +# issue 7827, bindSym power up +{.experimental: "dynamicBindSym".} +type + Apple = ref object + name: string + color: int + weight: int + +proc initApple(name: string): Apple = + discard + +proc deinitApple(x: Apple) = + discard + +macro wrapObject(obj: typed, n: varargs[untyped]): untyped = + let m = n[0] + for x in m: + var z = bindSym x + echo z.repr + +wrapObject(Apple): + initApple + deinitApple + +type + Coral = enum + redCoral + blackCoral + +macro mixer(): untyped = + let m = "Co" & "ral" + let x = bindSym(m) + echo x.repr + echo getType(x).repr + +mixer() diff --git a/tests/macros/tcasestmtmacro.nim b/tests/macros/tcasestmtmacro.nim new file mode 100644 index 000000000..32019a92a --- /dev/null +++ b/tests/macros/tcasestmtmacro.nim @@ -0,0 +1,33 @@ +discard """ + output: ''' +yes +''' +""" + +import macros + +macro `case`(n: tuple): untyped = + result = newTree(nnkIfStmt) + let selector = n[0] + for i in 1 ..< n.len: + let it = n[i] + case it.kind + of nnkElse, nnkElifBranch, nnkElifExpr, nnkElseExpr: + result.add it + of nnkOfBranch: + for j in 0..it.len-2: + let cond = newCall("==", selector, it[j]) + result.add newTree(nnkElifBranch, cond, it[^1]) + else: + error "custom 'case' for tuple cannot handle this node", it + +var correct = false + +case ("foo", 78) +of ("foo", 78): + correct = true + echo "yes" +of ("bar", 88): echo "no" +else: discard + +doAssert correct diff --git a/tests/macros/tclosuremacro.nim b/tests/macros/tclosuremacro.nim new file mode 100644 index 000000000..44c2411a5 --- /dev/null +++ b/tests/macros/tclosuremacro.nim @@ -0,0 +1,80 @@ +discard """ + output: ''' +noReturn +calling mystuff +yes +calling mystuff +yes +''' +""" + +import sugar, macros + +proc twoParams(x: (int, int) -> int): int = + result = x(5, 5) + +proc oneParam(x: int -> int): int = + x(5) + +proc noParams(x: () -> int): int = + result = x() + +proc noReturn(x: () -> void) = + x() + +proc doWithOneAndTwo(f: (int, int) -> int): int = + f(1,2) + +doAssert twoParams(proc (a, b: auto): auto = a + b) == 10 +doAssert twoParams((x, y) => x + y) == 10 +doAssert oneParam(x => x+5) == 10 +doAssert noParams(() => 3) == 3 +doAssert doWithOneAndTwo((x, y) => x + y) == 3 + +noReturn((() -> void) => echo("noReturn")) + +proc pass2(f: (int, int) -> int): (int) -> int = + ((x: int) -> int) => f(2, x) + +doAssert pass2((x, y) => x + y)(4) == 6 + +const fun = (x, y: int) {.noSideEffect.} => x + y + +doAssert typeof(fun) is (proc (x, y: int): int {.nimcall.}) +doAssert fun(3, 4) == 7 + +proc register(name: string; x: proc()) = + echo "calling ", name + x() + +register("mystuff", proc () = + echo "yes" +) + +proc helper(x: NimNode): NimNode = + if x.kind == nnkProcDef: + result = copyNimTree(x) + result[0] = newEmptyNode() + result = newCall("register", newLit($x[0]), result) + else: + result = copyNimNode(x) + for i in 0..<x.len: + result.add helper(x[i]) + +macro m(x: untyped): untyped = + result = helper(x) + +m: + proc mystuff() = + echo "yes" + +const typedParamAndPragma = (x, y: int) -> int => x + y +doAssert typedParamAndPragma(1, 2) == 3 + +type + Bot = object + call: proc (): string {.noSideEffect.} + +var myBot = Bot() +myBot.call = () {.noSideEffect.} => "I'm a bot." +doAssert myBot.call() == "I'm a bot." diff --git a/tests/macros/tcollect.nim b/tests/macros/tcollect.nim new file mode 100644 index 000000000..ae28ab61b --- /dev/null +++ b/tests/macros/tcollect.nim @@ -0,0 +1,63 @@ +discard """ + output: '''@[2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4] +@[0, 1, 2, 3]''' +""" + +const data = [1,2,3,4,5,6] + +import macros + +macro collect(body): untyped = + # analyse the body, find the deepest expression 'it' and replace it via + # 'result.add it' + let res = genSym(nskVar, "collectResult") + + when false: + proc detectForLoopVar(n: NimNode): NimNode = + if n.kind == nnkForStmt: + result = n[0] + else: + for x in n: + result = detectForLoopVar(x) + if result != nil: return result + return nil + + proc t(n, res: NimNode): NimNode = + case n.kind + of nnkStmtList, nnkStmtListExpr, nnkBlockStmt, nnkBlockExpr, + nnkWhileStmt, + nnkForStmt, nnkIfExpr, nnkIfStmt, nnkTryStmt, nnkCaseStmt, + nnkElifBranch, nnkElse, nnkElifExpr: + result = copyNimTree(n) + if n.len >= 1: + result[^1] = t(n[^1], res) + else: + if true: #n == it: + template adder(res, it) = + res.add it + result = getAst adder(res, n) + else: + result = n + + when false: + let it = detectForLoopVar(body) + if it == nil: error("no for loop in body", body) + + let v = newTree(nnkVarSection, + newTree(nnkIdentDefs, res, newTree(nnkBracketExpr, bindSym"seq", + newCall(bindSym"type", body)), newEmptyNode())) + + result = newTree(nnkStmtListExpr, v, t(body, res), res) + #echo repr result + +let stuff = collect: + var i = -1 + while i < 4: + inc i + for it in data: + if it < 5 and it > 1: + it + +echo stuff + +echo collect(for i in 0..3: i) diff --git a/tests/macros/tcomplexecho.nim b/tests/macros/tcomplexecho.nim new file mode 100644 index 000000000..d58fa561c --- /dev/null +++ b/tests/macros/tcomplexecho.nim @@ -0,0 +1,42 @@ +discard """ + output: '''3 +OK +56 +123 +56 +61''' +""" + +import macros + +# Bug from the forum +macro addEcho1(s: untyped): untyped = + s.body.add(newCall("echo", newStrLitNode("OK"))) + result = s + +proc f1() {.addEcho1.} = + let i = 1+2 + echo i + +f1() + +# bug #537 +proc test(): seq[NimNode] {.compiletime.} = + result = @[] + result.add parseExpr("echo 56") + result.add parseExpr("echo 123") + result.add parseExpr("echo 56") + +proc foo(): seq[NimNode] {.compiletime.} = + result = @[] + result.add test() + result.add parseExpr("echo(5+56)") + +macro bar() = + result = newNimNode(nnkStmtList) + let x = foo() + for xx in x: + result.add xx + echo treeRepr(result) + +bar() diff --git a/tests/macros/tcprag.nim b/tests/macros/tcprag.nim new file mode 100644 index 000000000..71618883f --- /dev/null +++ b/tests/macros/tcprag.nim @@ -0,0 +1,32 @@ +discard """ + output: '''true +true +true +''' +""" + +# issue #7615 +import macros + +template table(name: string) {.pragma.} + +type + User {.table("tuser").} = object + id: int + name: string + age: int + +echo User.hasCustomPragma(table) + + +## crash: Error: internal error: (filename: "sempass2.nim", line: 560, column: 19) +macro m1(T: typedesc): untyped = + getAST hasCustomPragma(T, table) +echo m1(User) # Oops crash + + +## This works +macro m2(T: typedesc): untyped = + result = quote do: + `T`.hasCustomPragma(table) +echo m2(User) diff --git a/tests/macros/tdumpast.nim b/tests/macros/tdumpast.nim new file mode 100644 index 000000000..484b3c2f3 --- /dev/null +++ b/tests/macros/tdumpast.nim @@ -0,0 +1,161 @@ +# Dump the contents of a NimNode + +import macros + +block: + template plus(a, b: untyped): untyped {.dirty} = + a + b + + macro call(e: untyped): untyped = + result = newCall("foo", newStrLitNode("bar")) + + macro dumpAST(n: untyped): string = + var msg = "" + msg.add "lispRepr:\n" & n.lispRepr & "\n" + msg.add "treeRepr:\n" & n.treeRepr & "\n" + + var plusAst = getAst(plus(1, 2)) + msg.add "lispRepr:\n" & n.lispRepr & "\n" + + var callAst = getAst(call(4)) + msg.add "callAst.lispRepr:\n" & callAst.lispRepr & "\n" + + var e = parseExpr("foo(bar + baz)") + msg.add "e.lispRepr:\n" & e.lispRepr & "\n" + result = msg.newLit + + let a = dumpAST: + proc add(x, y: int): int = + return x + y + const foo = 3 + + doAssert a == """ +lispRepr: +(StmtList (ProcDef (Ident "add") (Empty) (Empty) (FormalParams (Ident "int") (IdentDefs (Ident "x") (Ident "y") (Ident "int") (Empty))) (Empty) (Empty) (StmtList (ReturnStmt (Infix (Ident "+") (Ident "x") (Ident "y"))))) (ConstSection (ConstDef (Ident "foo") (Empty) (IntLit 3)))) +treeRepr: +StmtList + ProcDef + Ident "add" + Empty + Empty + FormalParams + Ident "int" + IdentDefs + Ident "x" + Ident "y" + Ident "int" + Empty + Empty + Empty + StmtList + ReturnStmt + Infix + Ident "+" + Ident "x" + Ident "y" + ConstSection + ConstDef + Ident "foo" + Empty + IntLit 3 +lispRepr: +(StmtList (ProcDef (Ident "add") (Empty) (Empty) (FormalParams (Ident "int") (IdentDefs (Ident "x") (Ident "y") (Ident "int") (Empty))) (Empty) (Empty) (StmtList (ReturnStmt (Infix (Ident "+") (Ident "x") (Ident "y"))))) (ConstSection (ConstDef (Ident "foo") (Empty) (IntLit 3)))) +callAst.lispRepr: +(Call (Ident "foo") (StrLit "bar")) +e.lispRepr: +(Call (Ident "foo") (Infix (Ident "+") (Ident "bar") (Ident "baz"))) +""" + +macro fun() = + let n = quote do: + 1+1 == 2 + doAssert n.repr == "1 + 1 == 2", n.repr +fun() + +macro fun2(): untyped = + let n = quote do: + 1 + 2 * 3 == 1 + 6 + doAssert n.repr == "1 + 2 * 3 == 1 + 6", n.repr +fun2() + +macro fun3(): untyped = + let n = quote do: + int | float | array | seq | object | ptr | pointer | float32 + doAssert n.repr == "int | float | array | seq | object | ptr | pointer | float32", n.repr +fun3() + +macro fun4() = + let n = quote do: + (a: 1) + doAssert n.repr == "(a: 1)", n.repr +fun4() + +# nkTupleConstr vs nkPar tests: +block: # lispRepr + macro lispRepr2(a: untyped): string = newLit a.lispRepr + + doAssert lispRepr2(()) == """(TupleConstr)""" + doAssert lispRepr2((a: 1)) == """(TupleConstr (ExprColonExpr (Ident "a") (IntLit 1)))""" + doAssert lispRepr2((a: 1, b: 2)) == """(TupleConstr (ExprColonExpr (Ident "a") (IntLit 1)) (ExprColonExpr (Ident "b") (IntLit 2)))""" + doAssert lispRepr2((1,)) == """(TupleConstr (IntLit 1))""" + doAssert lispRepr2((1, 2)) == """(TupleConstr (IntLit 1) (IntLit 2))""" + doAssert lispRepr2((1, 2, 3.0)) == """(TupleConstr (IntLit 1) (IntLit 2) (FloatLit 3.0))""" + doAssert lispRepr2((1)) == """(Par (IntLit 1))""" + doAssert lispRepr2((1+2)) == """(Par (Infix (Ident "+") (IntLit 1) (IntLit 2)))""" + +block: # repr + macro repr2(a: untyped): string = newLit a.repr + + doAssert repr2(()) == "()" + doAssert repr2((a: 1)) == "(a: 1)" + doAssert repr2((a: 1, b: 2)) == "(a: 1, b: 2)" + doAssert repr2((1,)) == "(1,)" + doAssert repr2((1, 2)) == "(1, 2)" + doAssert repr2((1, 2, 3.0)) == "(1, 2, 3.0)" + doAssert repr2((1)) == "(1)" + doAssert repr2((1+2)) == "(1 + 2)" + +block: # treeRepr + macro treeRepr2(a: untyped): string = newLit a.treeRepr + macro treeRepr3(a: typed): string = newLit a.treeRepr + + doAssert treeRepr2(1+1 == 2) == """ +Infix + Ident "==" + Infix + Ident "+" + IntLit 1 + IntLit 1 + IntLit 2""" + + proc baz() = discard + proc baz(a: int) = discard + proc baz(a: float) = discard + + doAssert treeRepr3(baz()) == """ +Call + Sym "baz"""" + + let a = treeRepr3(block: + proc bar(a: auto) = baz()) + doAssert a == """ +BlockStmt + Empty + ProcDef + Sym "bar" + Empty + GenericParams + Sym "a:type" + FormalParams + Empty + IdentDefs + Sym "a" + Sym "auto" + Empty + Empty + Bracket + Empty + Empty + StmtList + Call + OpenSymChoice 3 "baz"""" diff --git a/tests/macros/tdumpast2.nim b/tests/macros/tdumpast2.nim new file mode 100644 index 000000000..c4c591b2a --- /dev/null +++ b/tests/macros/tdumpast2.nim @@ -0,0 +1,36 @@ +# Dump the contents of a NimNode + +import macros + +proc dumpit(n: NimNode): string {.compileTime.} = + if n == nil: return "nil" + result = $n.kind + add(result, "(") + case n.kind + of nnkEmpty: discard # same as nil node in this representation + of nnkNilLit: add(result, "nil") + of nnkCharLit..nnkInt64Lit: add(result, $n.intVal) + of nnkFloatLit..nnkFloat64Lit: add(result, $n.floatVal) + of nnkStrLit..nnkTripleStrLit: add(result, $n.strVal) + of nnkIdent: add(result, $n.ident) + of nnkSym, nnkNone: assert false + else: + add(result, dumpit(n[0])) + for j in 1..n.len-1: + add(result, ", ") + add(result, dumpit(n[j])) + add(result, ")") + +macro dumpAST(n): untyped = + # dump AST as a side-effect and return the inner node + let n = callsite() + echo dumpit(n) + result = n[1] + +dumpAST: + proc add(x, y: int): int = + return x + y + + proc sub(x, y: int): int = return x - y + + diff --git a/tests/macros/tdumpastgen.nim b/tests/macros/tdumpastgen.nim new file mode 100644 index 000000000..0e0581f6a --- /dev/null +++ b/tests/macros/tdumpastgen.nim @@ -0,0 +1,45 @@ +discard """ +nimout: '''nnkStmtList.newTree( + nnkVarSection.newTree( + nnkIdentDefs.newTree( + newIdentNode("x"), + newEmptyNode(), + nnkCall.newTree( + nnkDotExpr.newTree( + newIdentNode("baz"), + newIdentNode("create") + ), + newLit(56) + ) + ) + ), + nnkProcDef.newTree( + newIdentNode("foo"), + newEmptyNode(), + newEmptyNode(), + nnkFormalParams.newTree( + newEmptyNode() + ), + newEmptyNode(), + newEmptyNode(), + nnkStmtList.newTree( + newCommentStmtNode("This is a docstring"), + nnkCommand.newTree( + newIdentNode("echo"), + newLit("bar") + ) + ) + ) +)''' +""" + +# disabled; can't work as the output is done by the compiler + +import macros + +dumpAstGen: + var x = baz.create(56) + + proc foo() = + ## This is a docstring + echo "bar" diff --git a/tests/macros/tdumptree.nim b/tests/macros/tdumptree.nim new file mode 100644 index 000000000..f540306c4 --- /dev/null +++ b/tests/macros/tdumptree.nim @@ -0,0 +1,27 @@ +discard """ +nimout: ''' +StmtList + VarSection + IdentDefs + Ident "x" + Empty + Call + DotExpr + Ident "foo" + Ident "create" + IntLit 56''' +""" + +# disabled; can't work as the output is done by the compiler + +import macros + +#emit("type\n TFoo = object\n bar: int") + +#var f: TFoo +#f.bar = 5 +#echo(f.bar) + +dumpTree: + var x = foo.create(56) + diff --git a/tests/macros/tescape_var_into_quotedo_as_const.nim b/tests/macros/tescape_var_into_quotedo_as_const.nim new file mode 100644 index 000000000..1ed93f012 --- /dev/null +++ b/tests/macros/tescape_var_into_quotedo_as_const.nim @@ -0,0 +1,36 @@ +discard """ + output: '''ok''' +""" +# bug #9864 +import macros, tables + +proc bar(shOpt: Table[string, int]) = discard + +macro dispatchGen(): untyped = + var shOpt = initTable[string, int]() + shOpt["foo"] = 10 + result = quote do: + bar(`shOpt`) + +dispatchGen() + +type + Foo = object + data: seq[int] + +proc barB(a: Foo) = discard + +proc shOptB(): auto = + var shOpt: Foo + shOpt.data.setLen 1 # fails + shOpt + +macro dispatchGenB(): untyped = + var shOpt = shOptB() # fails + + result = quote do: + barB(`shOpt`) + +dispatchGenB() + +echo "ok" diff --git a/tests/macros/texpectIdent1.nim b/tests/macros/texpectIdent1.nim new file mode 100644 index 000000000..26e52afb5 --- /dev/null +++ b/tests/macros/texpectIdent1.nim @@ -0,0 +1,18 @@ +discard """ +errormsg: "Expected identifier to be `foo` here" +line: 18 +""" + +import macros + +macro testUntyped(arg: untyped): void = + arg.expectKind nnkStmtList + arg.expectLen 2 + arg[0].expectKind nnkCall + arg[0][0].expectIdent "foo" # must pass + arg[1].expectKind nnkCall + arg[1][0].expectIdent "foo" # must fail + +testUntyped: + foo(123) + bar(321) diff --git a/tests/macros/texpectIdent2.nim b/tests/macros/texpectIdent2.nim new file mode 100644 index 000000000..887a6ddc3 --- /dev/null +++ b/tests/macros/texpectIdent2.nim @@ -0,0 +1,24 @@ +discard """ +errormsg: "Expected identifier to be `foo` here" +line: 24 +""" + +import macros + +macro testTyped(arg: typed): void = + arg.expectKind nnkStmtList + arg.expectLen 2 + arg[0].expectKind nnkCall + arg[0][0].expectIdent "foo" # must pass + arg[1].expectKind nnkCall + arg[1][0].expectIdent "foo" # must fail + +proc foo(arg: int) = + discard + +proc bar(arg: int) = + discard + +testTyped: + foo(123) + bar(321) diff --git a/tests/macros/tfail_parse.nim b/tests/macros/tfail_parse.nim new file mode 100644 index 000000000..1925f2b69 --- /dev/null +++ b/tests/macros/tfail_parse.nim @@ -0,0 +1,15 @@ +discard """ +action: "reject" +cmd: "nim check $file" +errormsg: "expected expression, but got multiple statements [ValueError]" +file: "macros.nim" +""" + +import macros +static: + discard parseStmt("'") + discard parseExpr("'") + discard parseExpr(""" +proc foo() +proc foo() = discard +""") diff --git a/tests/macros/tforloop_macro1.nim b/tests/macros/tforloop_macro1.nim new file mode 100644 index 000000000..a8f45c7ac --- /dev/null +++ b/tests/macros/tforloop_macro1.nim @@ -0,0 +1,44 @@ +discard """ + output: '''0 1 +1 2 +2 3 +0 1 +1 2 +2 3 +0 1 +1 2 +2 3 +3 5''' +""" + +import macros + +macro mymacro(): untyped = + result = newLit([1, 2, 3]) + +for a, b in mymacro(): + echo a, " ", b + +macro enumerate(x: ForLoopStmt): untyped = + expectKind x, nnkForStmt + # we strip off the first for loop variable and use + # it as an integer counter: + result = newStmtList() + result.add newVarStmt(x[0], newLit(0)) + var body = x[^1] + if body.kind != nnkStmtList: + body = newTree(nnkStmtList, body) + body.add newCall(bindSym"inc", x[0]) + var newFor = newTree(nnkForStmt) + for i in 1..x.len-3: + newFor.add x[i] + # transform enumerate(X) to 'X' + newFor.add x[^2][1] + newFor.add body + result.add newFor + +for a, b in enumerate(items([1, 2, 3])): + echo a, " ", b + +for a2, b2 in enumerate([1, 2, 3, 5]): + echo a2, " ", b2 diff --git a/tests/macros/tgetimpl.nim b/tests/macros/tgetimpl.nim new file mode 100644 index 000000000..ab33131b0 --- /dev/null +++ b/tests/macros/tgetimpl.nim @@ -0,0 +1,103 @@ +discard """ + nimout: '''foo = "muhaha" +proc poo(x, y: int) = + let y = x + echo ["poo"]''' +""" + +import macros + +const + foo = "muhaha" + +proc poo(x, y: int) = + let y = x + echo "poo" + +macro m(x: typed): untyped = + echo repr x.getImpl + +m(foo) +m(poo) + +#------------ + +macro checkOwner(x: typed, check_id: static[int]): untyped = + let sym = case check_id: + of 0: x + of 1: x.getImpl.body[0][0][0] + of 2: x.getImpl.body[0][0][^1] + of 3: x.getImpl.body[1][0] + else: x + result = newStrLitNode($sym.owner.symKind) + +macro isSameOwner(x, y: typed): untyped = + result = + if x.owner == y.owner: bindSym"true" + else: bindSym"false" + + +static: + doAssert checkOwner(foo, 0) == "nskModule" + doAssert checkOwner(poo, 0) == "nskModule" + doAssert checkOwner(poo, 1) == "nskProc" + doAssert checkOwner(poo, 2) == "nskProc" + doAssert checkOwner(poo, 3) == "nskModule" + doAssert isSameOwner(foo, poo) + proc wrappedScope() = + proc dummyproc() = discard + doAssert isSameOwner(foo, dummyproc) == false + doAssert isSameOwner(poo, dummyproc) == false + wrappedScope() + +macro check_gen_proc(ex: typed): (bool, bool) = + let lenChoice = bindsym"len" + var is_equal = false + var is_instance_of = false + for child in lenChoice: + if not is_equal: + is_equal = ex[0] == child + if not is_instance_of: + is_instance_of = isInstantiationOf(ex[0], child) + + result = nnkTupleConstr.newTree(newLit(is_equal), newLit(is_instance_of)) + +# check that len(seq[int]) is not equal to bindSym"len", but is instance of it +let a = @[1,2,3] +assert: check_gen_proc(len(a)) == (false, true) + + +#--------------------------------------------------------------- +# issue #16110 + +macro check(x: type): untyped = + let z = getType(x) + let y = getImpl(z[1]) + var sym = y[0] + if sym.kind == nnkPragmaExpr: sym = sym[0] + if sym.kind == nnkPostfix: sym = sym[1] + expectKind(z[1], nnkSym) + expectKind(sym, nnkSym) + expectKind(y[2], nnkObjectTy) + doAssert(sym == z[1]) + +type + TirePtr = ptr object + code: int + + TireRef* = ref object + code: int + + TireRef2* {.inheritable.} = ref object + code: int + + TireRef3* {.inheritable.} = object + code: int + +var z1: TirePtr +check(typeof(z1[])) +var z2: TireRef +check(typeof(z2[])) +var z3: TireRef2 +check(typeof(z3[])) +check(TireRef3) diff --git a/tests/macros/tgetraiseslist.nim b/tests/macros/tgetraiseslist.nim new file mode 100644 index 000000000..79694a66f --- /dev/null +++ b/tests/macros/tgetraiseslist.nim @@ -0,0 +1,29 @@ +discard """ + nimout: '''##[ValueError, Gen[string]]## +%%[RootEffect]%% +true true''' +""" + +import macros +import std / effecttraits + +type + Gen[T] = object of CatchableError + x: T + +macro m(call: typed): untyped = + echo "##", repr getRaisesList(call[0]), "##" + echo "%%", repr getTagsList(call[0]), "%%" + echo isGcSafe(call[0]), " ", hasNoSideEffects(call[0]) + result = call + +proc gutenTag() {.tags: RootEffect.} = discard + +proc r(inp: int) = + if inp == 0: + raise newException(ValueError, "bah") + elif inp == 1: + raise newException(Gen[string], "bahB") + gutenTag() + +m r(2) diff --git a/tests/macros/tgettype.nim b/tests/macros/tgettype.nim new file mode 100644 index 000000000..d91efe1fe --- /dev/null +++ b/tests/macros/tgettype.nim @@ -0,0 +1,85 @@ +import std/macros +import stdtest/testutils + +# getType + +block: + type + Model = object of RootObj + User = object of Model + name : string + password : string + + macro testUser: string = + result = newLit(User.getType.lispRepr) + + macro testGeneric(T: typedesc[Model]): string= + result = newLit(T.getType.lispRepr) + + doAssert testUser == """(ObjectTy (Empty) (Sym "Model") (RecList (Sym "name") (Sym "password")))""" + doAssert User.testGeneric == """(BracketExpr (Sym "typeDesc") (Sym "User"))""" + + macro assertVoid(e: typed): untyped = + assert(getTypeInst(e).typeKind == ntyVoid) + + proc voidProc() = discard + + assertVoid voidProc() + +block: + # refs #18220; not an actual solution (yet) but at least shows what's currently + # possible + + type Callable1[R, T, U] = concept fn + fn(default(T)) is R + fn is U + + # note that typetraits.arity doesn't work + macro arity(a: typed): int = + # number of params + # this is not production code! + let a2 = a.getType[1] # this used to crash nim, with: `vmdeps.nim(292, 25) `false`` + newLit a2.len - 1 + + type Callable2[R, T, U] = concept fn + fn(default(T)) is R + fn is U + arity(U) == 2 + + proc map1[T, R, U](a: T, fn: Callable1[R, T, U]): R = + let fn = U(fn) + # `cast[U](fn)` would also work; + # this is currently needed otherwise, sigmatch errors with: + # Error: attempting to call routine: 'fn' + # found 'fn' [param declared in tgettype.nim(53, 28)] + # this can be fixed in future work + fn(a) + + proc map2[T, R, U](a: T, fn: Callable2[R, T, U]): R = + let fn = U(fn) + fn(a) + + proc fn1(a: int, a2 = 'x'): string = $(a, a2, "fn1") + proc fn2(a: int, a2 = "zoo"): string = $(a, a2, "fn2") + proc fn3(a: int, a2 = "zoo2"): string = $(a, a2, "fn3") + proc fn4(a: int): string {.inline.} = $(a, "fn4") + proc fn5(a: int): string = $(a, "fn5") + + assertAll: + # Callable1 + 1.map1(fn1) == """(1, 'x', "fn1")""" + 1.map1(fn2) == """(1, "zoo", "fn2")""" + 1.map1(fn3) == """(1, "zoo", "fn3")""" + # fn3's optional param is not honored, because fn3 and fn2 yield same + # generic instantiation; this is a caveat with this approach + # There are several possible ways to improve things in future work. + 1.map1(fn4) == """(1, "fn4")""" + 1.map1(fn5) == """(1, "fn5")""" + + # Callable2; prevents passing procs with optional params to avoid above + # mentioned caveat, but more restrictive + not compiles(1.map2(fn1)) + not compiles(1.map2(fn2)) + not compiles(1.map2(fn3)) + 1.map2(fn4) == """(1, "fn4")""" + 1.map2(fn5) == """(1, "fn5")""" diff --git a/tests/macros/tgettype2.nim b/tests/macros/tgettype2.nim new file mode 100644 index 000000000..c579cf6ff --- /dev/null +++ b/tests/macros/tgettype2.nim @@ -0,0 +1,99 @@ +discard """ +output: ''' +############ +#### gt #### +############ +gt(Foo): typeDesc[Foo] +gt(Bar): typeDesc[Bar] +gt(Baz): typeDesc[int] +gt(foo): distinct[int] +gt(bar): distinct[int] +gt(baz): int, int +gt(v): seq[int] +gt(vv): seq[float] +gt(t): distinct[tuple[int, int]] +gt(tt): distinct[tuple[float, float]] +gt(s): distinct[tuple[int, int]] +############# +#### gt2 #### +############# +gt2(Foo): Foo +gt2(Bar): Bar +gt2(Baz): Baz +gt2(foo): Foo +gt2(bar): Bar +gt2(baz): Baz +gt2(v): seq[int] +gt2(vv): seq[float] +gt2(t): MyType[system.int] +gt2(tt): MyType[system.float] +gt2(s): MySimpleType +''' +""" + +import macros, typetraits + +type Foo = distinct int +type Bar = distinct int +type Baz = int + +let foo = 0.Foo +let bar = 1.Bar +let baz = 2.Baz + +type MyType[T] = distinct tuple[a,b:T] +type MySimpleType = distinct tuple[a,b: int] + +var v: seq[int] +var vv: seq[float] +var t: MyType[int] +var tt: MyType[float] +var s: MySimpleType + +echo "############" +echo "#### gt ####" +echo "############" + +macro gt(a: typed): string = + let b = a.getType + var str = "gt(" & $a & "):\t" & b.repr + if b.kind == nnkSym: # bad predicat to check weather the type has an implementation + str = str & ", " & b.getType.repr # append the implementation to the result + result = newLit(str) + +echo gt(Foo) # typeDesc[Foo] +echo gt(Bar) # typeDesc[Bar] +echo gt(Baz) # typeDesc[int] shouldn't it be typeDesc[Baz]? +echo gt(foo) # distinct[int] I would prefer Foo, distinct[int] +echo gt(bar) # distinct[int] I would prefer Bar, distinct[int] +echo gt(baz) # int, int I would prefer Baz, int + +echo gt(v) # seq[int], ok +echo gt(vv) # seq[float], ok +echo gt(t) # MyType, distinct[tuple[int, int]] I would prefer MyType[int], distinct[tuple[int, int]] +echo gt(tt) # MyType, distinct[tuple[float, float]] I would prefer MyType[float], distinct[tuple[int, int]] +echo gt(s) # distinct[tuple[int, int]] I would prefer MySimpleType, distinct[tuple[int,int]] + +echo "#############" +echo "#### gt2 ####" +echo "#############" + +# get type name via typetraits + +macro gt2(a: typed): string = + let prefix = "gt2(" & $a & "): \t" + result = quote do: + `prefix` & `a`.type.name + +echo gt2(Foo) # Foo shouldn't this be typeDesc[Foo] ? +echo gt2(Bar) # Bar shouldn't this be typeDesc[Bar] ? +echo gt2(Baz) # Baz shouldn't this be typeDesc[Baz] ? +echo gt2(foo) # Foo +echo gt2(bar) # Bar +echo gt2(baz) # Baz + +echo gt2(v) # seq[int] +echo gt2(vv) # seq[float] +echo gt2(t) # MyType[system.int] why is it system.int and not just int like in seq? +echo gt2(tt) # MyType[system.float] why is it system.float and not just float like in seq? +echo gt2(s) # MySimpleType diff --git a/tests/macros/tgettype3.nim b/tests/macros/tgettype3.nim new file mode 100644 index 000000000..786d09d8b --- /dev/null +++ b/tests/macros/tgettype3.nim @@ -0,0 +1,48 @@ +discard """ + output: "vec2" +""" +# bug #5131 + +import macros + +type + vecBase[I: static[int], T] = distinct array[I, T] + vec2* = vecBase[2, float32] + +proc isRange(n: NimNode, rangeLen: int = -1): bool = + if n.kind == nnkBracketExpr and $(n[0]) == "range": + if rangeLen == -1: + result = true + elif n[2].intVal - n[1].intVal + 1 == rangeLen: + result = true + +proc getTypeName(t: NimNode, skipVar = false): string = + case t.kind + of nnkBracketExpr: + if $(t[0]) == "array" and t[1].isRange(2) and $(t[2]) == "float32": + result = "vec2" + elif $(t[0]) == "array" and t[1].isRange(3) and $(t[2]) == "float32": + result = "vec3" + elif $(t[0]) == "array" and t[1].isRange(4) and $(t[2]) == "float32": + result = "vec4" + elif $(t[0]) == "distinct": + result = getTypeName(t[1], skipVar) + of nnkSym: + case $t + of "vecBase": result = getTypeName(getType(t), skipVar) + of "float32": result = "float" + else: + result = $t + of nnkVarTy: + result = getTypeName(t[0]) + if not skipVar: + result = "inout " & result + else: + echo "UNKNOWN TYPE: ", treeRepr(t) + assert(false, "Unknown type") + +macro typeName(t: typed): string = + result = newLit(getTypeName(getType(t))) + +var tt : vec2 +echo typeName(tt) diff --git a/tests/macros/tgettypeinst.nim b/tests/macros/tgettypeinst.nim new file mode 100644 index 000000000..e722f14aa --- /dev/null +++ b/tests/macros/tgettypeinst.nim @@ -0,0 +1,214 @@ +discard """ +""" + +import macros + +proc symToIdent(x: NimNode): NimNode = + case x.kind: + of nnkCharLit..nnkUInt64Lit: + result = newNimNode(x.kind) + result.intVal = x.intVal + of nnkFloatLit..nnkFloat64Lit: + result = newNimNode(x.kind) + result.floatVal = x.floatVal + of nnkStrLit..nnkTripleStrLit: + result = newNimNode(x.kind) + result.strVal = x.strVal + of nnkIdent, nnkSym: + result = newIdentNode($x) + else: + result = newNimNode(x.kind) + for c in x: + result.add symToIdent(c) + +# check getTypeInst and getTypeImpl for given symbol x +macro testX(x,inst0: typed; recurse: static[bool]; implX: typed) = + # check that getTypeInst(x) equals inst0 + let inst = x.getTypeInst + let instr = inst.symToIdent.treeRepr + let inst0r = inst0.symToIdent.treeRepr + if instr != inst0r: + echo "instr:\n", instr + echo "inst0r:\n", inst0r + doAssert(instr == inst0r) + + # check that getTypeImpl(x) is correct + # if implX is nil then compare to inst0 + # else we expect implX to be a type definition + # and we extract the implementation from that + let impl = x.getTypeImpl + var impl0 = + if implX.kind == nnkNilLit: inst0 + else: implX[0][2] + let implr = impl.symToIdent.treerepr + let impl0r = impl0.symToIdent.treerepr + if implr != impl0r: + echo "implr:\n", implr + echo "impl0r:\n", impl0r + doAssert(implr == impl0r) + + result = newStmtList() + #template echoString(s: string) = echo s.replace("\n","\n ") + #result.add getAst(echoString(" " & inst0.repr)) + #result.add getAst(echoString(" " & inst.repr)) + #result.add getAst(echoString(" " & impl0.repr)) + #result.add getAst(echoString(" " & impl.repr)) + + if recurse: + # now test using a newly formed variable of type getTypeInst(x) + template testDecl(n,m: typed) = + testV(n, false): + type _ = m + result.add getAst(testDecl(inst.symToIdent, impl.symToIdent)) + +# test with a variable (instance) of type +template testV(inst, recurse, impl) = + block: + #echo "testV(" & astToStr(inst) & ", " & $recurse & "):" & astToStr(impl) + var x: inst + testX(x, inst, recurse, impl) + +# test with a newly created typedesc (myType) +# using the passed type as the implementation +template testT(impl, recurse) = + block: + type myType = impl + testV(myType, recurse): + type _ = impl + +# test a built-in type whose instance is equal to the implementation +template test(inst) = + testT(inst, false) + testV(inst, true, nil) + +# test a custom type with provided implementation +template test(inst, impl) = + testV(inst, true, impl) + +type + Model = object of RootObj + User = object of Model + name : string + password : string + + Tree = object of RootObj + value : int + left,right : ref Tree + + MyEnum = enum + valueA, valueB, valueC + + MySet = set[MyEnum] + MySeq = seq[int] + MyIntPtr = ptr int + MyIntRef = ref int + + GenericObject[T] = object + value:T + Foo[N:static[int],T] = object + Bar[N:static[int],T] = object + #baz:Foo[N+1,GenericObject[T]] # currently fails + baz:Foo[N,GenericObject[T]] + + Generic[T] = seq[int] + Concrete = Generic[int] + + Generic2[T1, T2] = seq[T1] + Concrete2 = Generic2[int, float] + + Alias1 = float + Alias2 = Concrete + Alias3 = Concrete2 + + Vec[N: static[int],T] = object + arr: array[N,T] + Vec4[T] = Vec[4,T] + + +test(bool) +test(char) +test(int) +test(float) +test(ptr int) +test(ref int) +test(array[1..10,Bar[2,Foo[3,float]]]) +test(array[MyEnum,Bar[2,Foo[3,float]]]) +test(distinct Bar[2,Foo[3,float]]) +test(tuple[a:int,b:Foo[-1,float]]) +test(seq[int]) +test(set[MyEnum]) +test(proc (a: int, b: Foo[2,float])) +test(proc (a: int, b: Foo[2,float]): Bar[3,int]) + +test(MyEnum): + type _ = enum + valueA, valueB, valueC +test(Bar[2,Foo[3,float]]): + type _ = object + baz: Foo[2, GenericObject[Foo[3, float]]] +test(Model): + type _ = object of RootObj +test(User): + type _ = object of Model + name: string + password: string +test(Tree): + type _ = object of RootObj + value: int + left: ref Tree + right: ref Tree +test(Concrete): + type _ = seq[int] +test(Generic[int]): + type _ = seq[int] +test(Generic[float]): + type _ = seq[int] +test(Concrete2): + type _ = seq[int] +test(Generic2[int,float]): + type _ = seq[int] +test(Alias1): + type _ = float +test(Alias2): + type _ = seq[int] +test(Alias3): + type _ = seq[int] +test(Vec[4,float32]): + type _ = object + arr: array[0..3,float32] +test(Vec4[float32]): + type _ = object + arr: array[0..3,float32] + +# bug #4862 +static: + discard typedesc[(int, int)].getTypeImpl + +# custom pragmas +template myAttr() {.pragma.} +template myAttr2() {.pragma.} +template myAttr3() {.pragma.} +template serializationKey(key: string) {.pragma.} + +type + MyObj {.packed,myAttr,serializationKey: "one".} = object + myField {.myAttr2,serializationKey: "two".}: int + myField2 {.myAttr3,serializationKey: "three".}: float + +# field pragmas not currently supported +test(MyObj): + type + _ {.packed,myAttr,serializationKey: "one".} = object + myField: int + myField2: float + +block t9600: + type + Apple = ref object of RootObj + + macro mixer(x: typed): untyped = + let w = getType(x) + let v = getTypeImpl(w[1]) + + var z: Apple + mixer(z) diff --git a/tests/macros/tgettypeinst7737.nim b/tests/macros/tgettypeinst7737.nim new file mode 100644 index 000000000..e49f82562 --- /dev/null +++ b/tests/macros/tgettypeinst7737.nim @@ -0,0 +1,61 @@ +discard """ + nimout: ''' +seq[int] +CustomSeq[int] +''' +""" + +import macros, typetraits, sequtils + +block: # issue #7737 original + type + CustomSeq[T] = object + data: seq[T] + + proc getSubType(T: NimNode): NimNode = + echo getTypeInst(T).repr + result = getTypeInst(T)[1] + + macro typed_helper(x: varargs[typed]): untyped = + let foo = getSubType(x[0]) + result = quote do: discard + + macro untyped_heavylifting(x: varargs[untyped]): untyped = + var containers = nnkArgList.newTree() + for arg in x: + case arg.kind: + of nnkInfix: + if eqIdent(arg[0], "in"): + containers.add arg[2] + else: + discard + result = quote do: + typed_helper(`containers`) + var a, b, c: seq[int] + untyped_heavylifting z in c, x in a, y in b: + discard + ## The following gives me CustomSeq instead + ## of CustomSeq[int] in getTypeInst + var u, v, w: CustomSeq[int] + untyped_heavylifting z in u, x in v, y in w: + discard + +block: # issue #7737 comment + type + CustomSeq[T] = object + data: seq[T] + # when using just one argument, `foo` and `bar` should be exactly + # identical. + macro foo(arg: typed): string = + result = newLit(arg.getTypeInst.repr) + macro bar(args: varargs[typed]): untyped = + result = newTree(nnkBracket) + for arg in args: + result.add newLit(arg.getTypeInst.repr) + var + a: seq[int] + b: CustomSeq[int] + doAssert foo(a) == "seq[int]" + doAssert bar(a) == ["seq[int]"] + doAssert foo(b) == "CustomSeq[int]" + doAssert bar(b) == ["CustomSeq[int]"] diff --git a/tests/macros/tincremental.nim b/tests/macros/tincremental.nim new file mode 100644 index 000000000..401d6f3f8 --- /dev/null +++ b/tests/macros/tincremental.nim @@ -0,0 +1,150 @@ +discard """ + output: '''heavy_calc_impl is called +sub_calc1_impl is called +sub_calc2_impl is called +** no changes recompute effectively +** change one input and recompute effectively +heavy_calc_impl is called +sub_calc2_impl is called''' +""" + +# sample incremental + +import tables +import macros + +var inputs = initTable[string, float]() +var cache = initTable[string, float]() +var dep_tree {.compileTime.} = initTable[string, string]() + +macro symHash(s: typed{nkSym}): string = + result = newStrLitNode(symBodyHash(s)) + +####################################################################################### + +template graph_node(key: string) {.pragma.} + +proc tag(n: NimNode): NimNode = + ## returns graph node unique name of a function or nil if it is not a graph node + expectKind(n, {nnkProcDef, nnkFuncDef}) + for p in n.pragma: + if p.len > 0 and p[0] == bindSym"graph_node": + return p[1] + return nil + +macro graph_node_key(n: typed{nkSym}): untyped = + result = newStrLitNode(n.symBodyHash) + +macro graph_discovery(n: typed{nkSym}): untyped = + # discovers graph dependency tree and updated dep_tree global var + let mytag = newStrLitNode(n.symBodyHash) + var visited: seq[NimNode] + proc discover(n: NimNode) = + case n.kind: + of nnkNone..pred(nnkSym), succ(nnkSym)..nnkNilLit: discard + of nnkSym: + if n.symKind in {nskFunc, nskProc}: + if n notin visited: + visited.add n + let tag = n.getImpl.tag + if tag != nil: + dep_tree[tag.strVal] = mytag.strVal + else: + discover(n.getImpl.body) + else: + for child in n: + discover(child) + discover(n.getImpl.body) + result = newEmptyNode() + +####################################################################################### + +macro incremental_input(key: static[string], n: untyped{nkFuncDef}): untyped = + # mark leaf nodes of the graph + template getInput(key) {.dirty.} = + {.noSideEffect.}: + inputs[key] + result = n + result.pragma = nnkPragma.newTree(nnkCall.newTree(bindSym"graph_node", newStrLitNode(key))) + result.body = getAst(getInput(key)) + +macro incremental(n: untyped{nkFuncDef}): untyped = + ## incrementalize side effect free computation + ## wraps function into caching layer, mark caching function as a graph_node + ## injects dependency discovery between graph nodes + template cache_func_body(func_name, func_name_str, func_call) {.dirty.} = + {.noSideEffect.}: + graph_discovery(func_name) + let key = graph_node_key(func_name) + if key in cache: + result = cache[key] + else: + echo func_name_str & " is called" + result = func_call + cache[key] = result + + let func_name = n.name.strVal & "_impl" + let func_call = nnkCall.newTree(ident func_name) + for i in 1..<n.params.len: + func_call.add n.params[i][0] + let cache_func = n.copyNimTree + cache_func.body = getAst(cache_func_body(ident func_name, func_name, func_call)) + cache_func.pragma = nnkPragma.newTree(newCall(bindSym"graph_node", + newCall(bindSym"symHash", ident func_name))) + + n.name = ident(func_name) + result = nnkStmtList.newTree(n, cache_func) + +########################################################################### +### Example +########################################################################### + +func input1(): float {.incremental_input("a1").} + +func input2(): float {.incremental_input("a2").} + +func sub_calc1(a: float): float {.incremental.} = + a + input1() + +func sub_calc2(b: float): float {.incremental.} = + b + input2() + +func heavy_calc(a: float, b: float): float {.incremental.} = + sub_calc1(a) + sub_calc2(b) + +########################################################################### +## graph finalize and inputs +########################################################################### + +macro finalize_dep_tree(): untyped = + result = nnkTableConstr.newNimNode + for key, val in dep_tree: + result.add nnkExprColonExpr.newTree(newStrLitNode key, newStrLitNode val) + result = nnkCall.newTree(bindSym"toTable", result) + +const dep_tree_final = finalize_dep_tree() + +proc set_input(key: string, val: float) = + ## set input value + ## all affected nodes of graph are invalidated + inputs[key] = val + var k = key + while k != "": + k = dep_tree_final.getOrDefault(k , "") + cache.del(k) + +########################################################################### +## demo +########################################################################### + +set_input("a1", 5) +set_input("a2", 2) +discard heavy_calc(5.0, 10.0) + +echo "** no changes recompute effectively" +discard heavy_calc(5.0, 10.0) + +echo "** change one input and recompute effectively" + +set_input("a2", 10) +discard heavy_calc(5.0, 10.0) diff --git a/tests/macros/tinvalidtypesym.nim b/tests/macros/tinvalidtypesym.nim new file mode 100644 index 000000000..af6f31d10 --- /dev/null +++ b/tests/macros/tinvalidtypesym.nim @@ -0,0 +1,14 @@ +discard """ +errormsg: "type expected, but symbol 'MyType' has no type." +""" + +import macros + +macro foobar(name) = + let sym = genSym(nskType, "MyType") + + result = quote do: + type + `name` = `sym` + +foobar(MyAlias) diff --git a/tests/macros/tisexported.nim b/tests/macros/tisexported.nim new file mode 100644 index 000000000..53766edf5 --- /dev/null +++ b/tests/macros/tisexported.nim @@ -0,0 +1,10 @@ +import macros + +proc t1* = discard +proc t2 = discard + +macro check(p1: typed, p2: typed) = + doAssert isExported(p1) == true + doAssert isExported(p2) == false + +check t1, t2 diff --git a/tests/macros/tlocktypednode1.nim b/tests/macros/tlocktypednode1.nim new file mode 100644 index 000000000..f760c7cf1 --- /dev/null +++ b/tests/macros/tlocktypednode1.nim @@ -0,0 +1,12 @@ +discard """ +errormsg: "typechecked nodes may not be modified" +""" + +import macros + +macro doSomething(arg: typed): untyped = + echo arg.treeREpr + result = arg + result.add newCall(bindSym"echo", newLit(1)) + +doSomething((echo(1); echo(2))) diff --git a/tests/macros/tlocktypednode2.nim b/tests/macros/tlocktypednode2.nim new file mode 100644 index 000000000..e921772b0 --- /dev/null +++ b/tests/macros/tlocktypednode2.nim @@ -0,0 +1,12 @@ +discard """ +errormsg: "typechecked nodes may not be modified" +""" + +import macros + +macro doSomething(arg: typed): untyped = + echo arg.treeREpr + result = arg + result[0] = newCall(bindSym"echo", newLit(1)) + +doSomething((echo(1); echo(2))) diff --git a/tests/macros/tlocktypednode3.nim b/tests/macros/tlocktypednode3.nim new file mode 100644 index 000000000..0eb9d4467 --- /dev/null +++ b/tests/macros/tlocktypednode3.nim @@ -0,0 +1,15 @@ +discard """ +errormsg: "typechecked nodes may not be modified" +""" + +import macros + +macro doSomething(arg: typed): untyped = + echo arg.treeREpr + result = arg + result.add( + newCall(bindSym"echo", newLit(3)), + newCall(bindSym"echo", newLit(1)) + ) + +doSomething((echo(1); echo(2))) diff --git a/tests/macros/tmacro1.nim b/tests/macros/tmacro1.nim new file mode 100644 index 000000000..18bbeb53d --- /dev/null +++ b/tests/macros/tmacro1.nim @@ -0,0 +1,119 @@ +import macros + +macro test*(a: untyped): untyped = + var nodes: tuple[a, b: int] + nodes.a = 4 + nodes[1] = 45 + + type + TTypeEx = object + x, y: int + case b: bool + of false: nil + of true: z: float + + var t: TTypeEx + t.b = true + t.z = 4.5 + + +test: + "hi" + + +template assertNot(arg: untyped): untyped = + assert(not(arg)) + + +proc foo(arg: int): void = + discard + +proc foo(arg: float): void = + discard + +static: + ## test eqIdent + let a = "abc_def" + let b = "abcDef" + let c = "AbcDef" + let d = nnkBracketExpr.newTree() # not an identifier at all + + assert eqIdent( a , b ) + assert eqIdent(newIdentNode(a), b ) + assert eqIdent( a , newIdentNode(b)) + assert eqIdent(newIdentNode(a), newIdentNode(b)) + + assert eqIdent( a , b ) + assert eqIdent(genSym(nskLet, a), b ) + assert eqIdent( a , genSym(nskLet, b)) + assert eqIdent(genSym(nskLet, a), genSym(nskLet, b)) + + assert eqIdent(newIdentNode( a), newIdentNode( b)) + assert eqIdent(genSym(nskLet, a), newIdentNode( b)) + assert eqIdent(newIdentNode( a), genSym(nskLet, b)) + assert eqIdent(genSym(nskLet, a), genSym(nskLet, b)) + + assertNot eqIdent( c , b ) + assertNot eqIdent(newIdentNode(c), b ) + assertNot eqIdent( c , newIdentNode(b)) + assertNot eqIdent(newIdentNode(c), newIdentNode(b)) + + assertNot eqIdent( c , b ) + assertNot eqIdent(genSym(nskLet, c), b ) + assertNot eqIdent( c , genSym(nskLet, b)) + assertNot eqIdent(genSym(nskLet, c), genSym(nskLet, b)) + + assertNot eqIdent(newIdentNode( c), newIdentNode( b)) + assertNot eqIdent(genSym(nskLet, c), newIdentNode( b)) + assertNot eqIdent(newIdentNode( c), genSym(nskLet, b)) + assertNot eqIdent(genSym(nskLet, c), genSym(nskLet, b)) + + # eqIdent on non identifier at all + assertNot eqIdent(a,d) + + # eqIdent on sym choice + let fooSym = bindSym"foo" + assert fooSym.kind in {nnkOpenSymChoice, nnkClosedSymChoice} + assert fooSym.eqIdent("fOO") + assertNot fooSym.eqIdent("bar") + + # eqIdent on exported and backtick quoted identifiers + let procName = ident("proc") + let quoted = nnkAccQuoted.newTree(procName) + let exported = nnkPostfix.newTree(ident"*", procName) + let exportedQuoted = nnkPostfix.newTree(ident"*", quoted) + + let nodes = @[procName, quoted, exported, exportedQuoted] + + for i in 0 ..< nodes.len: + for j in 0 ..< nodes.len: + doAssert eqIdent(nodes[i], nodes[j]) + + for node in nodes: + doAssert eqIdent(node, "proc") + + + var empty: NimNode + var myLit = newLit("str") + + assert( (empty or myLit) == myLit ) + + empty = newEmptyNode() + + assert( (empty or myLit) == myLit ) + + proc bottom(): NimNode = + quit("may not be evaluated") + + assert( (myLit or bottom()) == myLit ) + +type + Fruit = enum + apple + banana + orange + +macro foo(x: typed) = + doAssert Fruit(x.intVal) == banana + +foo(banana) diff --git a/tests/macros/tmacro2.nim b/tests/macros/tmacro2.nim new file mode 100644 index 000000000..1ad63ec8c --- /dev/null +++ b/tests/macros/tmacro2.nim @@ -0,0 +1,36 @@ +discard """ + output: "ta-da Your value sir: 'HE!!!!o Wor!!d'" +""" + +import macros, strutils + +proc testBlock(): string {.compileTime.} = + block myBlock: + while true: + echo "inner block" + break myBlock + echo "outer block" + result = "ta-da" + +macro mac(n: typed): string = + let n = callsite() + expectKind(n, nnkCall) + expectLen(n, 2) + expectKind(n[1], nnkStrLit) + var s: string = n[1].strVal + s = s.replace("l", "!!") + result = newStrLitNode("Your value sir: '$#'" % [s]) + +const s = testBlock() +const t = mac("HEllo World") +echo s, " ", t + + +#----------------------------------------------------------------------------- +# issue #15326 +macro m(n:typed):auto = + result = n + +proc f[T](x:T): T {.m.} = x + +discard f(3) \ No newline at end of file diff --git a/tests/macros/tmacro3.nim b/tests/macros/tmacro3.nim new file mode 100644 index 000000000..38e8349e7 --- /dev/null +++ b/tests/macros/tmacro3.nim @@ -0,0 +1,30 @@ +discard """ + output: "" +""" + +import macros + +type + TA = tuple[a: int] + PA = ref TA + +macro test*(a: untyped): untyped = + var val: PA + new(val) + val.a = 4 + +test: + "hi" + +macro test2*(a: untyped): untyped = + proc testproc(recurse: int) = + echo "That's weird" + var o : NimNode = nil + echo " no its not!" + o = newNimNode(nnkNone) + if recurse > 0: + testproc(recurse - 1) + testproc(5) + +test2: + "hi" diff --git a/tests/macros/tmacro4.nim b/tests/macros/tmacro4.nim new file mode 100644 index 000000000..cb0399894 --- /dev/null +++ b/tests/macros/tmacro4.nim @@ -0,0 +1,17 @@ +discard """ + output: "after" +""" + +import macros + +macro test_macro*(s: string, n: untyped): untyped = + result = newNimNode(nnkStmtList) + var ass : NimNode = newNimNode(nnkAsgn) + add(ass, newIdentNode("str")) + add(ass, newStrLitNode("after")) + add(result, ass) +when true: + var str: string = "before" + test_macro(str): + var i : integer = 123 + echo str diff --git a/tests/macros/tmacro5.nim b/tests/macros/tmacro5.nim new file mode 100644 index 000000000..802fb28d5 --- /dev/null +++ b/tests/macros/tmacro5.nim @@ -0,0 +1,59 @@ +import macros,json + +var decls{.compileTime.}: seq[NimNode] = @[] +var impls{.compileTime.}: seq[NimNode] = @[] + +macro importImpl_forward(name, returns: untyped): untyped = + result = newNimNode(nnkEmpty) + var func_name = newNimNode(nnkAccQuoted) + func_name.add newIdentNode("import") + func_name.add name + + var res = newNimNode(nnkProcDef) + res.add newNimNode(nnkPostfix) + res[0].add newIdentNode("*") + res[0].add func_name + res.add newNimNode(nnkEmpty) + res.add newNimNode(nnkEmpty) + res.add newNimNode(nnkFormalParams) + res[3].add returns + var p1 = newNimNode(nnkIdentDefs) + p1.add newIdentNode("dat") + p1.add newIdentNOde("JsonNode") + p1.add newNimNode(nnkEmpty) + res[3].add p1 + var p2 = newNimNode(nnkIdentDefs) + p2.add newIdentNode("errors") + p2.add newNimNode(nnkVarTy) + p2.add newNimNode(nnkEmpty) + p2[1].add newNimNode(nnkBracketExpr) + p2[1][0].add newIdentNode("seq") + p2[1][0].add newIdentNode("string") + res[3].add p2 + + res.add newNimNode(nnkEmpty) + res.add newNimNode(nnkEmpty) + res.add newNimNode(nnkEmpty) + + decls.add res + echo(repr(res)) + +macro importImpl(name, returns, body: untyped) = + #var res = getAST(importImpl_forward(name, returns)) + discard getAST(importImpl_forward(name, returns)) + var res = copyNimTree(decls[decls.high]) + res[6] = body + echo repr(res) + impls.add res + +macro okayy() = + result = newNimNode(nnkStmtList) + for node in decls: result.add node + for node in impls: result.add node + +importImpl(Item, int): + echo 42 +importImpl(Foo, int16): + echo 77 + +okayy diff --git a/tests/macros/tmacro6.nim b/tests/macros/tmacro6.nim new file mode 100644 index 000000000..c65d34b6d --- /dev/null +++ b/tests/macros/tmacro6.nim @@ -0,0 +1,75 @@ +discard """ +errormsg: "expression '123' is of type 'int literal(123)' and has to be used (or discarded)" +line: 71 +""" + +import macros + +proc foo(a, b, c: int): int = + result += a + result += b + result += c + +macro bar(a, b, c: int): int = + result = newCall(ident"echo") + result.add a + result.add b + result.add c + +macro baz(a, b, c: int): int = + let stmt = nnkStmtListExpr.newTree() + stmt.add newCall(ident"echo", a) + stmt.add newCall(ident"echo", b) + stmt.add newCall(ident"echo", c) + stmt.add newLit(123) + return c + +# test no result type with explicit return + +macro baz2(a, b, c: int) = + let stmt = nnkStmtListExpr.newTree() + stmt.add newCall(ident"echo", a) + stmt.add newCall(ident"echo", b) + stmt.add newCall(ident"echo", c) + return stmt + +# test explicit void type with explicit return + +macro baz3(a, b, c: int): void = + let stmt = nnkStmtListExpr.newTree() + stmt.add newCall(ident"echo", a) + stmt.add newCall(ident"echo", b) + stmt.add newCall(ident"echo", c) + return stmt + +# test no result type with result variable + +macro baz4(a, b, c: int) = + result = nnkStmtListExpr.newTree() + result.add newCall(ident"echo", a) + result.add newCall(ident"echo", b) + result.add newCall(ident"echo", c) + +# test explicit void type with result variable + +macro baz5(a, b, c: int): void = + let result = nnkStmtListExpr.newTree() + result.add newCall(ident"echo", a) + result.add newCall(ident"echo", b) + result.add newCall(ident"echo", c) + +macro foobar1(): int = + result = quote do: + echo "Hello World" + 1337 + +echo foobar1() + +# this should create an error message, because 123 has to be discarded. + +macro foobar2() = + result = quote do: + echo "Hello World" + 123 + +echo foobar2() diff --git a/tests/macros/tmacro7.nim b/tests/macros/tmacro7.nim new file mode 100644 index 000000000..602191506 --- /dev/null +++ b/tests/macros/tmacro7.nim @@ -0,0 +1,36 @@ +discard """ +output: ''' +calling!stuff +calling!stuff +''' +disabled: true +""" + +# this test modifies an already semchecked ast (bad things happen) +# this test relies on the bug #4547 +# issue #7792 + +import macros + +proc callProc(str: string) = + echo "calling!" & str + +macro testMacro(code: typed): untyped = + let stmtList = newNimNode(nnkStmtList) + + let stmts = code[6] + + for n in stmts.children: + # the error happens here + stmtList.add(newCall(bindSym("callProc"), newLit("stuff"))) + + code[6] = stmtList + + result = newEmptyNode() + +proc main() {.testMacro.} = + echo "test" + echo "test2" + +when isMainModule: + main() diff --git a/tests/macros/tmacro8.nim b/tests/macros/tmacro8.nim new file mode 100644 index 000000000..fdcec4dd4 --- /dev/null +++ b/tests/macros/tmacro8.nim @@ -0,0 +1,35 @@ +# issue #8573 + +import + macros, + strutils, + terminal + +type LogSeverity* = enum + sevError = "Error" + sevWarn = "Warn" + sevInfo = "Info" + sevDebug = "Debug" + +macro log*(severity: static[LogSeverity], group: static[string], m: varargs[typed]): untyped = + let sevStr = align("[" & toUpperAscii($severity) & "] ", 8) + let sevColor = case severity + of sevError: fgRed + of sevWarn: fgYellow + of sevInfo: fgWhite + of sevDebug: fgBlack + + let groupStr = "[" & $group & "] " + + result = quote do: + setStyle({ styleBright }) + setForegroundColor(sevColor) # <== + write(stdout, sevStr) + + setStyle({ styleDim }) + setForegroundColor(fgWhite) + write(stdout, groupStr) + + let wl = newCall(bindSym"styledWriteLine", bindSym"stdout") + for arg in m: wl.add(arg) + result.add(wl) diff --git a/tests/macros/tmacroaspragma.nim b/tests/macros/tmacroaspragma.nim new file mode 100644 index 000000000..5f06f2425 --- /dev/null +++ b/tests/macros/tmacroaspragma.nim @@ -0,0 +1,7 @@ +import macros + +macro foo(x: untyped): untyped = + echo treerepr(callsite()) + result = newNimNode(nnkStmtList) + +proc zoo() {.foo.} = echo "hi" diff --git a/tests/macros/tmacrogenerics.nim b/tests/macros/tmacrogenerics.nim new file mode 100644 index 000000000..c31b5564c --- /dev/null +++ b/tests/macros/tmacrogenerics.nim @@ -0,0 +1,38 @@ +discard """ + nimout: ''' +instantiation 1 with typeDesc[int] and typeDesc[float] +instantiation 2 with typeDesc[float] and typeDesc[string] +instantiation 3 with typeDesc[string] and typeDesc[string] +counter: 3 +''' + output: "int\nfloat\nint\nstring" +""" + +import typetraits, macros + +var counter {.compileTime.} = 0 + +macro makeBar(A, B: typedesc): typedesc = + inc counter + echo "instantiation ", counter, " with ", A.getTypeInst.repr, " and ", B.getTypeInst.repr + result = A + +type + Bar[T, U] = makeBar(T, U) + +var bb1: Bar[int, float] +var bb2: Bar[float, string] +var bb3: Bar[int, float] +var bb4: Bar[string, string] + +proc match(a: int) = echo "int" +proc match(a: string) = echo "string" +proc match(a: float) = echo "float" + +match(bb1) +match(bb2) +match(bb3) +match(bb4) + +static: + echo "counter: ", counter diff --git a/tests/macros/tmacrogensym.nim b/tests/macros/tmacrogensym.nim new file mode 100644 index 000000000..7c0d75f82 --- /dev/null +++ b/tests/macros/tmacrogensym.nim @@ -0,0 +1,65 @@ +import nativesockets, asyncdispatch, macros +var p = newDispatcher() +var sock = createAsyncNativeSocket() + +proc convertReturns(node, retFutureSym: NimNode): NimNode {.compileTime.} = + case node.kind + of nnkReturnStmt: + result = newCall(newIdentNode("complete"), retFutureSym, node[0]) + else: + result = node + for i in 0 ..< node.len: + result[i] = convertReturns(node[i], retFutureSym) + +macro async2(prc: untyped): untyped = + expectKind(prc, nnkProcDef) + + var outerProcBody = newNimNode(nnkStmtList) + + # -> var retFuture = newFuture[T]() + var retFutureSym = newIdentNode("retFuture") #genSym(nskVar, "retFuture") + outerProcBody.add( + newVarStmt(retFutureSym, + newCall( + newNimNode(nnkBracketExpr).add( + newIdentNode("newFuture"), + prc[3][0][1])))) # Get type from return type of this proc. + + # -> iterator nameIter(): FutureBase {.closure.} = <proc_body> + # Changing this line to: newIdentNode($prc[0].ident & "Iter") # will make it work. + var iteratorNameSym = genSym(nskIterator, $prc[0] & "Iter") + assert iteratorNameSym.symKind == nskIterator + #var iteratorNameSym = newIdentNode($prc[0].ident & "Iter") + var procBody = prc[6].convertReturns(retFutureSym) + + var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")], + procBody, nnkIteratorDef) + closureIterator[4] = newNimNode(nnkPragma).add(newIdentNode("closure")) + outerProcBody.add(closureIterator) + + # -> var nameIterVar = nameIter + # -> var first = nameIterVar() + var varNameIterSym = newIdentNode($prc[0] & "IterVar") #genSym(nskVar, $prc[0].ident & "IterVar") + var varNameIter = newVarStmt(varNameIterSym, iteratorNameSym) + outerProcBody.add varNameIter + var varFirstSym = genSym(nskVar, "first") + assert varFirstSym.symKind == nskVar + var varFirst = newVarStmt(varFirstSym, newCall(varNameIterSym)) + outerProcBody.add varFirst + + + result = prc + + # Remove the 'closure' pragma. + for i in 0 ..< result[4].len: + if result[4][i] == newIdentNode("async"): + result[4].del(i) + + result[6] = outerProcBody + +proc readStuff(): Future[string] {.async2.} = + var fut = connect(sock, "irc.freenode.org", Port(6667)) + yield fut + var fut2 = recv(sock, 50) + yield fut2 + return fut2.read diff --git a/tests/macros/tmacrogetimpl.nim b/tests/macros/tmacrogetimpl.nim new file mode 100644 index 000000000..1d996ff29 --- /dev/null +++ b/tests/macros/tmacrogetimpl.nim @@ -0,0 +1,31 @@ +import macros + +# bug #5034 + +macro copyImpl(srsProc: typed, toSym: untyped) = + result = copyNimTree(getImplTransformed(srsProc)) + result[0] = ident $toSym.toStrLit() + +proc foo1(x: float, one: bool = true): float = + if one: + return 1'f + result = x + +proc bar1(what: string): string = + ## be a little more adversarial with `skResult` + proc buzz: string = + result = "lightyear" + if what == "buzz": + result = "buzz " & buzz() + else: + result = what + return result + +copyImpl(foo1, foo2) +doAssert foo1(1'f) == 1.0 +doAssert foo2(10.0, false) == 10.0 +doAssert foo2(10.0) == 1.0 + +copyImpl(bar1, bar2) +doAssert bar1("buzz") == "buzz lightyear" +doAssert bar1("macros") == "macros" diff --git a/tests/macros/tmacros1.nim b/tests/macros/tmacros1.nim new file mode 100644 index 000000000..c588ff7e6 --- /dev/null +++ b/tests/macros/tmacros1.nim @@ -0,0 +1,81 @@ +discard """ + output: '''Got: 'nnkCall' hi +{a} +{b} +{a, b}''' +""" + +import macros + +macro outterMacro*(n, blck: untyped): untyped = + let n = callsite() + var j : string = "hi" + proc innerProc(i: int): string = + echo "Using arg ! " & n.repr + result = "Got: '" & $n.kind & "' " & $j + var callNode = n[0] + expectKind(n, NimNodeKind.nnkCall) + if n.len != 3 or n[1].kind != NimNodeKind.nnkIdent: + error("Macro " & callNode.repr & + " requires the ident passed as parameter (eg: " & callNode.repr & + "(the_name_you_want)): statements.") + result = newNimNode(NimNodeKind.nnkStmtList) + var ass : NimNode = newNimNode(nnkAsgn) + ass.add(newIdentNode(n[1].ident)) + ass.add(newStrLitNode(innerProc(4))) + result.add(ass) + +var str: string +outterMacro(str): + "hellow" +echo str + +type E = enum a b +macro enumerators1(): set[E] = newLit({a}) + +macro enumerators2(): set[E] = + return newLit({b}) + +macro enumerators3(): set[E] = + result = newLit({E.low .. E.high}) + +var myEnums: set[E] + + +myEnums = enumerators1() +echo myEnums +myEnums = enumerators2() +echo myEnums +myEnums = enumerators3() +echo myEnums + +#10751 + +type Tuple = tuple + a: string + b: int + +macro foo(t: static Tuple): untyped = + doAssert t.a == "foo" + doAssert t.b == 12345 + +foo((a: "foo", b: 12345)) + + +# bug #16307 + +macro bug(x: untyped): string = + newLit repr(x) + +let res = bug: + block: + ## one + ## two + ## three + +doAssert res == """ + +block: + ## one + ## two + ## three""" diff --git a/tests/macros/tmacros_issues.nim b/tests/macros/tmacros_issues.nim new file mode 100644 index 000000000..a81c51658 --- /dev/null +++ b/tests/macros/tmacros_issues.nim @@ -0,0 +1,521 @@ +discard """ + nimout: ''' +IntLit 5 +proc (x: int): string => typeDesc[proc[string, int]] +proc (x: int): void => typeDesc[proc[void, int]] +proc (x: int) => typeDesc[proc[void, int]] +x => seq[int] +a +s +d +f +TTaa +TTaa +TTaa +TTaa +true +true +nil +42 +false +true +@[i0, i1, i2, i3, i4] +@[tmp, tmp, tmp, tmp, tmp] +''' + + output: ''' +range[0 .. 100] +array[0 .. 100, int] +10 +test +0o377'i8 +0o000000000755'i32 +1 +2 +3 +foo1 +foo2 +foo3 +true +false +true +false +1.0 +''' +""" + + +import macros, parseutils + + +block t7723: + macro foo1(): untyped = + result = newStmtList() + result.add quote do: + proc init(foo: int, bar: typedesc[int]): int = + foo + + #expandMacros: + foo1() + + doAssert init(1, int) == 1 + + + +block t8706: + macro varargsLen(args:varargs[untyped]): untyped = + doAssert args.kind == nnkArgList + doAssert args.len == 0 + result = newLit(args.len) + + template bar(a0:varargs[untyped]): untyped = + varargsLen(a0) + + template foo(x: int, a0:varargs[untyped]): untyped = + bar(a0) + + doAssert foo(42) == 0 + doAssert bar() == 0 + + + +block t9194: + type + Foo1 = range[0 .. 100] + Foo2 = array[0 .. 100, int] + + macro get(T: typedesc): untyped = + # Get the X out of typedesc[X] + let tmp = getTypeImpl(T) + result = newStrLitNode(getTypeImpl(tmp[1]).repr) + + echo Foo1.get + echo Foo2.get + + + +block t1944: + template t(e: untyped): untyped = + macro m(eNode: untyped): untyped = + echo eNode.treeRepr + m e + + t 5 + + +block t926: + proc test(f: var NimNode) {.compileTime.} = + f = newNimNode(nnkStmtList) + f.add newCall(newIdentNode("echo"), newLit(10)) + + macro blah(prc: untyped): untyped = + result = prc + test(result) + + proc test() {.blah.} = + echo 5 + + + +block t2211: + macro showType(t:typed): untyped = + let ty = t.getType + echo t.repr, " => ", ty.repr + + showType(proc(x:int): string) + showType(proc(x:int): void) + showType(proc(x:int)) + + var x: seq[int] + showType(x) + + + +block t1140: + proc parse_until_symbol(node: NimNode, value: string, index: var int): bool {.compiletime.} = + var splitValue: string + var read = value.parseUntil(splitValue, '$', index) + + # when false: + if false: + var identifier: string + read = value.parseWhile(identifier, {}, index) + node.add newCall("add", ident("result"), newCall("$", ident(identifier))) + + if splitValue.len > 0: + node.insert node.len, newCall("add", ident("result"), newStrLitNode(splitValue)) + + proc parse_template(node: NimNode, value: string) {.compiletime.} = + var index = 0 + while index < value.len and + parse_until_symbol(node, value, index): discard + + macro tmpli(body: untyped): typed = + result = newStmtList() + result.add parseExpr("result = \"\"") + result.parse_template body[1].strVal + + + proc actual: string {.used.} = tmpli html""" + <p>Test!</p> + """ + + proc another: string {.used.} = tmpli html""" + <p>what</p> + """ + + + +block tbugs: + type + Foo = object + s: char + + iterator test2(f: string): Foo = + for i in f: + yield Foo(s: i) + + macro test(): untyped = + for i in test2("asdf"): + echo i.s + + test() + + + # bug 1297 + + type TType = tuple[s: string] + + macro echotest(): untyped = + var t: TType + t.s = "" + t.s.add("test") + result = newCall(newIdentNode("echo"), newStrLitNode(t.s)) + + echotest() + + # bug #1103 + + type + Td = tuple + a:string + b:int + + proc get_data(d: Td) : string {.compileTime.} = + result = d.a # Works if a literal string is used here. + # Bugs if line A or B is active. Works with C + result &= "aa" # A + #result.add("aa") # B + #result = result & "aa" # C + + macro m(s:static[Td]) : untyped = + echo get_data(s) + echo get_data(s) + result = newEmptyNode() + + const s = ("TT", 3) + m(s) + m(s) + + # bug #933 + + proc nilcheck(): NimNode {.compileTime.} = + echo(result == nil) # true + echo(result.isNil) # true + echo(repr(result)) # nil + + macro testnilcheck(): untyped = + result = newNimNode(nnkStmtList) + discard nilcheck() + + testnilcheck() + + # bug #1323 + + proc calc(): array[1, int] = + result[0].inc() + result[0].inc() + + const c = calc() + doAssert c[0] == 2 + + + # bug #3046 + + macro sampleMacroInt(i: int): untyped = + echo i.intVal + + macro sampleMacroBool(b: bool): untyped = + echo b.boolVal + + sampleMacroInt(42) + sampleMacroBool(false) + sampleMacroBool(system.true) + + +# bug #11131 +macro toRendererBug(n): untyped = + result = newLit repr(n) + +echo toRendererBug(0o377'i8) +echo toRendererBug(0o755'i32) + +# bug #12129 +macro foobar() = + var loopVars = newSeq[NimNode](5) + for i, sym in loopVars.mpairs(): + sym = ident("i" & $i) + echo loopVars + for sym in loopVars.mitems(): + sym = ident("tmp") + echo loopVars + +foobar() + + +# bug #13253 +import macros + +type + FooBar = object + a: seq[int] + +macro genFoobar(a: static FooBar): untyped = + result = newStmtList() + for b in a.a: + result.add(newCall(bindSym"echo", newLit b)) + +proc foobar(a: static FooBar) = + genFoobar(a) # removing this make it work + for b in a.a: + echo "foo" & $b + +proc main() = + const a: seq[int] = @[1, 2,3] + # Error: type mismatch: got <array[0..2, int]> but expected 'seq[int]' + const fb = Foobar(a: a) + foobar(fb) +main() + +# bug #13484 + +proc defForward(id, nid: NimNode): NimNode = + result = newProc(id, @[newIdentNode("bool"), newIdentDefs(nid, newIdentNode("int"))], body=newEmptyNode()) + +proc defEven(evenid, oddid, nid: NimNode): NimNode = + result = quote do: + proc `evenid`(`nid`: int): bool = + if `nid` == 0: + return true + else: + return `oddid`(`nid` - 1) + +proc defOdd(evenid, oddid, nid: NimNode): NimNode = + result = quote do: + proc `oddid`(`nid`: int): bool = + if `nid` == 0: + return false + else: + return `evenid`(`nid` - 1) + +proc callNode(funid, param: NimNode): NimNode = + result = quote do: + `funid`(`param`) + +macro testEvenOdd3(): untyped = + let + evenid = newIdentNode("even3") + oddid = newIdentNode("odd3") + nid = newIdentNode("n") + oddForward = defForward(oddid, nid) + even = defEven(evenid, oddid, nid) + odd = defOdd(evenid, oddid, nid) + callEven = callNode(evenid, newLit(42)) + callOdd = callNode(oddid, newLit(42)) + result = quote do: + `oddForward` + `even` + `odd` + echo `callEven` + echo `callOdd` + +macro testEvenOdd4(): untyped = + let + evenid = newIdentNode("even4") + oddid = newIdentNode("odd4") + nid = newIdentNode("n") + oddForward = defForward(oddid, nid) + even = defEven(evenid, oddid, nid) + odd = defOdd(evenid, oddid, nid) + callEven = callNode(evenid, newLit(42)) + callOdd = callNode(oddid, newLit(42)) + # rewrite the body of proc node. + oddForward[6] = newStmtList() + result = quote do: + `oddForward` + `even` + `odd` + echo `callEven` + echo `callOdd` + +macro testEvenOdd5(): untyped = + let + evenid = genSym(nskProc, "even5") + oddid = genSym(nskProc, "odd5") + nid = newIdentNode("n") + oddForward = defForward(oddid, nid) + even = defEven(evenid, oddid, nid) + odd = defOdd(evenid, oddid, nid) + callEven = callNode(evenid, newLit(42)) + callOdd = callNode(oddid, newLit(42)) + result = quote do: + `oddForward` + `even` + `odd` + echo `callEven` + echo `callOdd` + +macro testEvenOdd6(): untyped = + let + evenid = genSym(nskProc, "even6") + oddid = genSym(nskProc, "odd6") + nid = newIdentNode("n") + oddForward = defForward(oddid, nid) + even = defEven(evenid, oddid, nid) + odd = defOdd(evenid, oddid, nid) + callEven = callNode(evenid, newLit(42)) + callOdd = callNode(oddid, newLit(42)) + # rewrite the body of proc node. + oddForward[6] = newStmtList() + result = quote do: + `oddForward` + `even` + `odd` + echo `callEven` + echo `callOdd` + +# it works +testEvenOdd3() + +# it causes an error (redefinition of odd4), which is correct +assert not compiles testEvenOdd4() + +# it caused an error (still forwarded: odd5) +testEvenOdd5() + +# it works, because the forward decl and definition share the symbol and the compiler is forgiving here +#testEvenOdd6() #Don't test it though, the compiler may become more strict in the future + +# bug #15385 +var captured_funcs {.compileTime.}: seq[NimNode] = @[] + +macro aad*(fns: varargs[typed]): typed = + result = newStmtList() + for fn in fns: + captured_funcs.add fn[0] + result.add fn + +func exp*(x: float): float ## get different error if you remove forward declaration + +func exp*(x: float): float {.aad.} = + var x1 = min(max(x, -708.4), 709.8) + var result: float ## looks weird because it is taken from template expansion + result = x1 + 1.0 + result + +template check_accuracy(f: untyped, rng: Slice[float], n: int, verbose = false): auto = + + proc check_accuracy: tuple[avg_ulp: float, max_ulp: int] {.gensym.} = + let k = (rng.b - rng.a) / (float) n + var + res, x: float + i, max_ulp = 0 + avg_ulp = 0.0 + + x = rng.a + while (i < n): + res = f(x) + i.inc + x = x + 0.001 + (avg_ulp, max_ulp) + check_accuracy() + +discard check_accuracy(exp, -730.0..709.4, 4) + +# And without forward decl +macro aad2*(fns: varargs[typed]): typed = + result = newStmtList() + for fn in fns: + captured_funcs.add fn[0] + result.add fn + +func exp2*(x: float): float {.aad2.} = + var x1 = min(max(x, -708.4), 709.8) + var result: float ## looks weird because it is taken from template expansion + result = x1 + 1.0 + result + +template check_accuracy2(f: untyped, rng: Slice[float], n: int, verbose = false): auto = + + proc check_accuracy2: tuple[avg_ulp: float, max_ulp: int] {.gensym.} = + let k = (rng.b - rng.a) / (float) n + var + res, x: float + i, max_ulp = 0 + avg_ulp = 0.0 + + x = rng.a + while (i < n): + res = f(x) + i.inc + x = x + 0.001 + (avg_ulp, max_ulp) + check_accuracy2() + +discard check_accuracy2(exp2, -730.0..709.4, 4) + +# And minimized: +macro aadMin(fn: typed): typed = fn + +func expMin: float + +func expMin: float {.aadMin.} = 1 + +echo expMin() + + +# doubly-typed forward decls +macro noop(x: typed) = x +noop: + proc cally() = discard + +cally() + +noop: + proc barry() + +proc barry() = discard + +# some more: +proc barry2() {.noop.} +proc barry2() = discard + +proc barry3() {.noop.} +proc barry3() {.noop.} = discard + + +# issue #15389 +block double_sem_for_procs: + + macro aad(fns: varargs[typed]): typed = + result = newStmtList() + for fn in fns: + result.add fn + + func exp(x: float): float {.aad.} = + var x1 = min(max(x, -708.4), 709.8) + if x1 > 0.0: + return x1 + 1.0 + result = 10.0 + + discard exp(5.0) diff --git a/tests/macros/tmacros_various.nim b/tests/macros/tmacros_various.nim new file mode 100644 index 000000000..e351b4527 --- /dev/null +++ b/tests/macros/tmacros_various.nim @@ -0,0 +1,391 @@ +discard """ + nimout: ''' +Infix + Ident "=>" + Call + Ident "name" + Ident "a" + ExprColonExpr + Ident "b" + Ident "cint" + NilLit +macrocache ok +''' + + output: ''' +x = 10 +x + y = 30 +proc foo[T, N: static[int]]() +proc foo[T; N: static[int]]() +a[0]: 42 +a[1]: 45 +x: some string +([("key", "val"), ("keyB", "2")], [("val", "key"), ("2", "keyB")]) +([("key", "val"), ("keyB", "2")], [("val", "key"), ("2", "keyB")]) +0 +0 +0 +''' +""" + + +import macros, sugar, macrocache + + +block tdump: + let + x = 10 + y = 20 + dump x + dump(x + y) + + +block texprcolonexpr: + macro def(x): untyped = + echo treeRepr(x) + + def name(a, b:cint) => nil + + + +block tgenericparams: + macro test():string = + let expr0 = "proc foo[T, N: static[int]]()" + let expr1 = "proc foo[T; N: static[int]]()" + + newLit($toStrLit(parseExpr(expr0)) & "\n" & $toStrLit(parseExpr(expr1))) + + echo test() + + + +block tidgen: + # Test compile-time state in same module + var gid {.compileTime.} = 3 + + macro genId(): int = + result = newIntLitNode(gid) + inc gid + + proc Id1(): int {.compileTime.} = return genId() + proc Id2(): int {.compileTime.} = return genId() + + doAssert Id1() == 3 + doAssert Id2() == 4 + + + +block tlexerex: + macro match(s: cstring|string; pos: int; sections: varargs[untyped]): untyped = + for sec in sections: + expectKind sec, nnkOfBranch + expectLen sec, 2 + result = newStmtList() + + var input = "the input" + var pos = 0 + match input, pos: + of r"[a-zA-Z_]\w+": echo "an identifier" + of r"\d+": echo "an integer" + of r".": echo "something else" + + + +block tcopylineinfo: + # issue #5617, feature request + type Test = object + + macro mixer(n: typed): untyped = + let x = newIdentNode("echo") + x.copyLineInfo(n) + result = newLit(x.lineInfo == n.lineInfo) + + var z = mixer(Test) + doAssert z + +block tsetgetlineinfo: + # issue #21098, feature request + type Test = object + + macro mixer1(n: typed): untyped = + let x = newIdentNode("echo") + var lineInfo = n.lineInfoObj + x.setLineInfo lineInfo + result = newLit(x.lineInfo == n.lineInfo) + + macro mixer2(n: typed): untyped = + let x = newIdentNode("echo") + var lineInfo = n.lineInfoObj + lineInfo.line += 1 + x.setLineInfo lineInfo + result = newLit(x.lineInfo != n.lineInfo) + + doAssert mixer1(Test) + + doAssert mixer2(Test) + + + +block tdebugstmt: + macro debug(n: varargs[untyped]): untyped = + result = newNimNode(nnkStmtList, n) + for i in 0..n.len-1: + add(result, newCall("write", newIdentNode("stdout"), toStrLit(n[i]))) + add(result, newCall("write", newIdentNode("stdout"), newStrLitNode(": "))) + add(result, newCall("writeLine", newIdentNode("stdout"), n[i])) + + var + a: array[0..10, int] + x = "some string" + a[0] = 42 + a[1] = 45 + + debug(a[0], a[1], x) + +const + pairs = {"key": "val", "keyB": "2"} + +macro bilookups(arg: static[openArray[(string, string)]]): untyped = + var a = newTree(nnkBracket) + var b = newTree(nnkBracket) + for (k, v) in items(arg): + a.add(newTree(nnkTupleConstr, newLit k, newLit v)) + b.add(newTree(nnkTupleConstr, newLit v, newLit k)) + result = newTree(nnkTupleConstr, a, b) + +macro bilookups2(arg: untyped): untyped = + var a = newTree(nnkBracket) + var b = newTree(nnkBracket) + arg.expectKind(nnkTableConstr) + for x in items(arg): + x.expectKind(nnkExprColonExpr) + a.add(newTree(nnkTupleConstr, x[0], x[1])) + b.add(newTree(nnkTupleConstr, x[1], x[0])) + result = newTree(nnkTupleConstr, a, b) + +const cnst1 = bilookups(pairs) +echo cnst1 +const cnst2 = bilookups2({"key": "val", "keyB": "2"}) +echo cnst2 + + + +# macrocache #11404 +const + mcTable = CacheTable"nimTest" + mcSeq = CacheSeq"nimTest" + mcCounter = CacheCounter"nimTest" + +static: + doAssert(mcCounter.value == 0) # CacheCounter.value + mcCounter.inc # CacheCounter.inc + doAssert(mcCounter.value == 1) # CacheCounter.value + + let a = newLit(1) + let b = newLit(2) + let c = newLit(3) + let d = newLit(4) + + mcSeq.add a # CacheSeq.add + mcSeq.add b # CacheSeq.add + mcSeq.add c # CacheSeq.add + + doAssert(mcSeq.len == 3) # CacheSeq.len + #doAssert(c in mcSeq) # CacheSeq.contains + #doAssert(d notin mcSeq) # CacheSeq.contains + + mcSeq.incl d # CacheSeq.incl + doAssert(mcSeq.len == 4) # CacheSeq.len + + mcSeq.incl c # CacheSeq.incl + doAssert(mcSeq.len == 4) # CacheSeq.len + + doAssert(mcSeq[3] == d) # CacheSeq.[] + + #doAssert(mcSeq.pop() == d)# CacheSeq.pop + #doAssert(mcSeq.len == 3) # CacheSeq.len + + doAssert(mcTable.len == 0) # CacheTable.len + mcTable["a"] = a # CacheTable.[]= + doAssert(mcTable.len == 1) # CacheTable.len + + doAssert(mcTable["a"] == a) # CacheTable.[] + #doAssert("a" in mcTable) # CacheTable.contains + #doAssert(mcTable.hasKey("a"))# CacheTable.hasKey + + for k, v in mcTable: # CacheTable.items + doAssert(k == "a") + doAssert(v == a) + + echo "macrocache ok" + +block tupleNewLitTests: + macro t(): untyped = + result = newLit (1, "foo", (), (1,), (a1: 'x', a2: @["ba"])) + doAssert $t() == """(1, "foo", (), (1,), (a1: 'x', a2: @["ba"]))""" + # this `$` test is needed because tuple equality doesn't distinguish + # between named vs unnamed tuples + doAssert t() == (1, "foo", (), (1, ), (a1: 'x', a2: @["ba"])) + +from strutils import contains +block getImplTransformed: + macro bar(a: typed): string = + # newLit a.getImpl.repr # this would be before code transformation + let b = a.getImplTransformed + newLit b.repr + template toExpand() = + for ai in 0..2: echo ai + proc baz(a=1): int = + defer: discard + toExpand() + 12 + const code = bar(baz) + # sanity check: + doAssert "finally" in code # `defer` is lowered to try/finally + doAssert "while" in code # `for` is lowered to `while` + doAssert "toExpand" notin code + # template is expanded (but that would already be the case with + # `a.getImpl.repr`, unlike the other transformations mentioned above + + +# test macro resemming +macro makeVar(): untyped = + quote: + var tensorY {.inject.}: int + +macro noop(a: typed): untyped = + a + +noop: + makeVar +echo tensorY + +macro xbenchmark(body: typed): untyped = + result = body + +xbenchmark: + proc fastSHA(inputtest: string) = + discard inputtest + fastSHA("hey") + +block: # issue #4547 + macro lazy(stmtList : typed) : untyped = + let decl = stmtList[0] + decl.expectKind nnkLetSection + let name = decl[0][0].strVal + let call = decl[0][2].copy + call.expectKind nnkCall + let ident = newIdentNode("get" & name) + result = quote do: + var value : type(`call`) + proc `ident`() : type(`call`) = + if value.isNil: + value = `call` + value + type MyObject = object + a,b: int + # this part, the macro call and it's result (written in the comment below) is important + lazy: + let y = new(MyObject) + #[ + var value: type(new(MyObject)) + proc gety(): type(new(MyObject)) = + if value.isNil: + value = new(MyObject) + value + ]# + doAssert gety().a == 0 # works and should work + doAssert gety().b == 0 # works and should work + doAssert not declared(y) + doAssert not compiles(y.a) # identifier y should not exist anymore + doAssert not compiles(y.b) # identifier y should not exist anymore + +block: # bug #13511 + type + Builder = ref object + components: seq[Component] + Component = object + + proc add(builder: var Builder, component: Component) {.compileTime.} = + builder.components.add(component) + + macro debugAst(arg: typed): untyped = + ## just for debugging purpose. + discard arg.treeRepr + return arg + + static: + var component = Component() + var builder = Builder() + + template foo(): untyped = + ## WAS: this doc comment causes compilation failure. + builder + + debugAst: + add(foo(), component) + +block: # bug #15118 + macro flop(name: static string) = + let id = genSym(nskType, "env") + let r = + nnkStmtList.newTree( + nnkTypeSection.newTree( + nnkTypeDef.newTree( + id, + newEmptyNode(), + nnkRefTy.newTree( + nnkObjectTy.newTree( + newEmptyNode(), + newEmptyNode(), + nnkRecList.newTree( + nnkIdentDefs.newTree( + newIdentNode(name), + newIdentNode("int"), + newEmptyNode() + ) + ) + ) + ) + ) + ), + + # var f: T + + nnkVarSection.newTree( + nnkIdentDefs.newTree( + newIdentNode("f"), + id, + newEmptyNode() + ) + ), + + # echo f.a + nnkCommand.newTree( + newIdentNode("new"), + newIdentNode("f") + ), + + nnkCommand.newTree( + newIdentNode("echo"), + nnkDotExpr.newTree( + newIdentNode("f"), + newIdentNode(name) + ) + ) + ) + r + + + block: + flop("a") + + block: + flop("b") + +static: + block: + const containsTable = CacheTable"containsTable" + doAssert "foo" notin containsTable + containsTable["foo"] = newLit 42 + doAssert "foo" in containsTable diff --git a/tests/macros/tmacrostmt.nim b/tests/macros/tmacrostmt.nim new file mode 100644 index 000000000..817bc8352 --- /dev/null +++ b/tests/macros/tmacrostmt.nim @@ -0,0 +1,155 @@ +import macros +macro case_token(n: varargs[untyped]): untyped = + # creates a lexical analyzer from regular expressions + # ... (implementation is an exercise for the reader :-) + nil + +case_token: # this colon tells the parser it is a macro statement +of r"[A-Za-z_]+[A-Za-z_0-9]*": + return tkIdentifier +of r"0-9+": + return tkInteger +of r"[\+\-\*\?]+": + return tkOperator +else: + return tkUnknown + +case_token: inc i + +#bug #488 + +macro foo() = + var exp = newCall("whatwhat", newIntLitNode(1)) + if compiles(getAst(exp)): + return exp + else: echo "Does not compute! (test OK)" + +foo() + +#------------------------------------ +# bug #8287 +type MyString = distinct string + +proc `$` (c: MyString): string {.borrow.} + +proc `!!` (c: cstring): int = + c.len + +proc f(name: MyString): int = + !! $ name + +macro repr_and_parse(fn: typed) = + let fn_impl = fn.getImpl + fn_impl.name = genSym(nskProc, $fn_impl.name) + echo fn_impl.repr + result = parseStmt(fn_impl.repr) + +macro repr_to_string(fn: typed): string = + let fn_impl = fn.getImpl + result = newStrLitNode(fn_impl.repr) + +repr_and_parse(f) + + +#------------------------------------ +# bugs #8343 and #8344 +proc one_if_proc(x, y : int): int = + if x < y: result = x + else: result = y + +proc test_block(x, y : int): int = + block label: + result = x + result = y + +#------------------------------------ +# bugs #8348 + +template `>`(x, y: untyped): untyped = + ## "is greater" operator. This is the same as ``y < x``. + y < x + +proc test_cond_stmtlist(x, y: int): int = + result = x + if x > y: + result = x + + +#------------------------------------ +# bug #8762 +proc t2(a, b: int): int = + `+`(a, b) + + +#------------------------------------ +# bug #8761 + +proc fn1(x, y: int):int = + 2 * (x + y) + +proc fn2(x, y: float): float = + (y + 2 * x) / (x - y) + +proc fn3(x, y: int): bool = + (((x and 3) div 4) or (x mod (y xor -1))) == 0 or y notin [1,2] + +proc fn4(x: int): int = + if x mod 2 == 0: return x + 2 + else: return 0 + +proc fn5(a, b: float): float = + result = - a * a / (b * b) + +proc `{}`(x: seq[float], i: int, j: int): float = + x[i + 0 * j] + +proc `{}=`(x: var seq[float], i: int, val: float) = + x[i] = val + +proc fn6() = + var a = @[1.0, 2.0] + let z = a{0, 1} + a{2} = 5.0 + + +#------------------------------------ +# bug #10807 +proc fn_unsafeaddr(x: int): int = + cast[int](unsafeAddr(x)) + +static: + let fn1s = "proc fn1(x, y: int): int =\n result = 2 * (x + y)\n" + let fn2s = "proc fn2(x, y: float): float =\n result = (y + 2 * x) / (x - y)\n" + let fn3s = "proc fn3(x, y: int): bool =\n result = ((x and 3) div 4 or x mod (y xor -1)) == 0 or not contains([1, 2], y)\n" + let fn4s = "proc fn4(x: int): int =\n if x mod 2 == 0:\n return x + 2\n else:\n return 0\n" + let fn5s = "proc fn5(a, b: float): float =\n result = -a * a / (b * b)\n" + let fn6s = "proc fn6() =\n var a = @[1.0, 2.0]\n let z = a{0, 1}\n a{2} = 5.0\n" + let fnAddr = "proc fn_unsafeaddr(x: int): int =\n result = cast[int](addr(x))\n" + + doAssert fn1.repr_to_string == fn1s + doAssert fn2.repr_to_string == fn2s + doAssert fn3.repr_to_string == fn3s + doAssert fn4.repr_to_string == fn4s + doAssert fn5.repr_to_string == fn5s + doAssert fn6.repr_to_string == fn6s + doAssert fn_unsafeaddr.repr_to_string == fnAddr + +#------------------------------------ +# bug #8763 + +type + A {.pure.} = enum + X, Y + B {.pure.} = enum + X, Y + +proc test_pure_enums(a: B) = + case a + of B.X: echo B.X + of B.Y: echo B.Y + +repr_and_parse(one_if_proc) +repr_and_parse(test_block) +repr_and_parse(test_cond_stmtlist) +repr_and_parse(t2) +repr_and_parse(test_pure_enums) diff --git a/tests/macros/tmacrotypes.nim b/tests/macros/tmacrotypes.nim new file mode 100644 index 000000000..13b421303 --- /dev/null +++ b/tests/macros/tmacrotypes.nim @@ -0,0 +1,157 @@ +discard """ + nimout: '''intProc; ntyProc; proc[int, int, float]; proc (a: int; b: float): int {.nimcall.} +void; ntyVoid; void; void +int; ntyInt; int; int +proc () {.nimcall.}; ntyProc; proc[void]; proc () {.nimcall.} +voidProc; ntyProc; proc[void]; proc () {.nimcall.} +listing fields for ObjType +a: string +b: int +listing fields for ObjRef +skipping ref type +a: string +b: int +listing fields for RefType +skipping ref type +a: int +b: float +listing fields for typeof(a) +skipping ref type +a: string +b: int +listing fields for typeof(b) +skipping ref type +a: string +b: int +listing fields for typeof(c) +skipping ref type +a: int +b: float +listing fields for typeof(x) +a: string +b: int +listing fields for typeof(x) +a: int +b: float +typeDesc[range[1 .. 5]]; ntyTypeDesc; typeDesc[range[1, 5]]; typeDesc[range[1 .. 5]] +typeDesc[range]; ntyTypeDesc; typeDesc[range[T]]; typeDesc[range]''' +""" + +import macros, typetraits + +macro checkType(ex: typed): untyped = + echo ex.getTypeInst.repr, "; ", ex.typeKind, "; ", ex.getType.repr, "; ", ex.getTypeImpl.repr + +macro checkProcType(fn: typed): untyped = + if fn.kind == nnkProcDef: + result = fn + let fn_sym = if fn.kind == nnkProcDef: fn[0] else: fn + echo fn_sym, "; ", fn_sym.typeKind, "; ", fn_sym.getType.repr, "; ", fn_sym.getTypeImpl.repr + +proc voidProc = echo "hello" +proc intProc(a: int, b: float): int {.checkProcType.} = 10 + +checkType(voidProc()) +checkType(intProc(10, 20.0)) +checkType(voidProc) +checkProcType(voidProc) + +macro listFields(T: typed) = + echo "listing fields for ", repr(T) + let inputExprType = getType(T) + + var objType = inputExprType[1] + if objType.kind == nnkBracketExpr and objType.len > 1: + if ((objType[0].kind == nnkRefTy) or + (objType[0].kind == nnkSym and eqIdent(objType[0], "ref"))): + echo "skipping ref type" + objType = objType[1] + + let typeAst = objType.getImpl + + var objectDef = typeAst[2] + if objectDef.kind == nnkRefTy: + objectDef = objectDef[0] + + let recList = objectDef[2] + for rec in recList: + echo $rec[0], ": ", $rec[1] + +type + ObjType* = object of RootObj + a: string + b: int + + ObjRef = ref ObjType + + RefType* = ref object of RootObj + a: int + b: float + +listFields ObjType +listFields ObjRef +listFields RefType + +let + a = new ObjType + b = new ObjRef + c = new RefType + +listFields typeOf(a) +listFields typeOf(b) +listFields typeOf(c) + +proc genericProc(x: object) = + listFields typeOf(x) + +genericProc a[] +genericProc b[] +genericProc c[] + +# bug #10548 +block: + var c {.compileTime.} = 0 + + macro meshImpl(arg: typed): untyped = + inc c + result = arg + + type + Blub = int32 + Mesh = meshImpl(Club) + Club = Blub + + static: doAssert(c == 1) + +# bug #10702 +type + VectorElementType = SomeNumber | bool + Vec*[N : static[int], T: VectorElementType] = object + arr*: array[N, T] + +type + Vec4*[T: VectorElementType] = Vec[4,T] + Vec3*[T: VectorElementType] = Vec[3,T] + Vec2*[T: VectorElementType] = Vec[2,T] + +template vecGen(U:untyped,V:typed):typed= + ## ``U`` suffix + ## ``V`` valType + ## + type + `Vec2 U`* {.inject.} = Vec2[V] + `Vec3 U`* {.inject.} = Vec3[V] + `Vec4 U`* {.inject.} = Vec4[V] + +vecGen(f, float32) + +macro foobar(arg: typed): untyped = + let typ = arg.getTypeInst + doAssert typ.getImpl[^1].kind == nnkCall + +var x: Vec2f + +foobar(x) + +checkType(range[1..5]) +checkType(range) diff --git a/tests/macros/tmemit.nim b/tests/macros/tmemit.nim new file mode 100644 index 000000000..6c9f9f935 --- /dev/null +++ b/tests/macros/tmemit.nim @@ -0,0 +1,38 @@ +discard """ + output: ''' +c_func +12 +''' +""" + +import macros, strutils + +# bug #1025 + +macro foo(icname): untyped = + let ic = newStrLitNode($icname) + result = quote do: + proc x* = + proc private {.exportc: `ic`.} = discard + echo `ic` + private() + +foo(c_func) +x() + + +template volatileLoad[T](x: ptr T): T = + var res: T + {.emit: [res, " = (*(", type(x[]), " volatile*)", x, ");"].} + res + +template volatileStore[T](x: ptr T; y: T) = + {.emit: ["*((", type(x[]), " volatile*)(", x, ")) = ", y, ";"].} + +proc main = + var st: int + var foo: ptr int = addr st + volatileStore(foo, 12) + echo volatileLoad(foo) + +main() diff --git a/tests/macros/tmsginfo.nim b/tests/macros/tmsginfo.nim new file mode 100644 index 000000000..f08a231fb --- /dev/null +++ b/tests/macros/tmsginfo.nim @@ -0,0 +1,24 @@ +discard """ + nimout: '''tmsginfo.nim(21, 1) Warning: foo1 [User] +tmsginfo.nim(22, 13) template/generic instantiation of `foo2` from here +tmsginfo.nim(15, 10) Warning: foo2 [User] +tmsginfo.nim(23, 1) Hint: foo3 [User] +tmsginfo.nim(19, 7) Hint: foo4 [User] +''' +""" + +import macros + +macro foo1(y: untyped): untyped = + warning("foo1", y) +macro foo2(y: untyped): untyped = + warning("foo2") +macro foo3(y: untyped): untyped = + hint("foo3", y) +macro foo4(y: untyped): untyped = + hint("foo4") + +proc x1() {.foo1.} = discard +proc x2() {.foo2.} = discard +proc x3() {.foo3.} = discard +proc x4() {.foo4.} = discard diff --git a/tests/macros/tnewlit.nim b/tests/macros/tnewlit.nim new file mode 100644 index 000000000..70683f880 --- /dev/null +++ b/tests/macros/tnewlit.nim @@ -0,0 +1,194 @@ +import macros + +type + MyType = object + a : int + b : string + + RefObject = ref object + x: int + + RegularObject = object + x: int + + ObjectRefAlias = ref RegularObject + +macro test_newLit_MyType: untyped = + let mt = MyType(a: 123, b:"foobar") + result = newLit(mt) + +doAssert test_newLit_MyType == MyType(a: 123, b:"foobar") + +macro test_newLit_array: untyped = + let arr = [1,2,3,4,5] + result = newLit(arr) + +doAssert test_newLit_array == [1,2,3,4,5] + +macro test_newLit_seq_int: untyped = + let s: seq[int] = @[1,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[int] = test_newLit_seq_int + doAssert tmp == @[1,2,3,4,5] + +macro test_newLit_seq_int8: untyped = + let s: seq[int8] = @[1'i8,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[int8] = test_newLit_seq_int8 + doAssert tmp == @[1'i8,2,3,4,5] + +macro test_newLit_seq_int16: untyped = + let s: seq[int16] = @[1'i16,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[int16] = test_newLit_seq_int16 + doAssert tmp == @[1'i16,2,3,4,5] + +macro test_newLit_seq_int32: untyped = + let s: seq[int32] = @[1'i32,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[int32] = test_newLit_seq_int32 + doAssert tmp == @[1'i32,2,3,4,5] + +macro test_newLit_seq_int64: untyped = + let s: seq[int64] = @[1'i64,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[int64] = test_newLit_seq_int64 + doAssert tmp == @[1'i64,2,3,4,5] + +macro test_newLit_seq_uint: untyped = + let s: seq[uint] = @[1u,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[uint] = test_newLit_seq_uint + doAssert tmp == @[1u,2,3,4,5] + +macro test_newLit_seq_uint8: untyped = + let s: seq[uint8] = @[1'u8,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[uint8] = test_newLit_seq_uint8 + doAssert tmp == @[1'u8,2,3,4,5] + +macro test_newLit_seq_uint16: untyped = + let s: seq[uint16] = @[1'u16,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[uint16] = test_newLit_seq_uint16 + doAssert tmp == @[1'u16,2,3,4,5] + +macro test_newLit_seq_uint32: untyped = + let s: seq[uint32] = @[1'u32,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[uint32] = test_newLit_seq_uint32 + doAssert tmp == @[1'u32,2,3,4,5] + +macro test_newLit_seq_uint64: untyped = + let s: seq[uint64] = @[1'u64,2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[uint64] = test_newLit_seq_uint64 + doAssert tmp == @[1'u64,2,3,4,5] + +macro test_newLit_seq_float: untyped = + let s: seq[float] = @[1.0, 2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[float] = test_newLit_seq_float + doAssert tmp == @[1.0, 2,3,4,5] + +macro test_newLit_seq_float32: untyped = + let s: seq[float32] = @[1.0'f32, 2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[float32] = test_newLit_seq_float32 + doAssert tmp == @[1.0'f32, 2,3,4,5] + +macro test_newLit_seq_float64: untyped = + let s: seq[float64] = @[1.0'f64, 2,3,4,5] + result = newLit(s) + +block: + let tmp: seq[float64] = test_newLit_seq_float64 + doAssert tmp == @[1.0'f64, 2,3,4,5] + +macro test_newLit_tuple: untyped = + let tup: tuple[a:int,b:string] = (a: 123, b: "223") + result = newLit(tup) + +doAssert test_newLit_tuple == (a: 123, b: "223") + +type + ComposedType = object + mt: MyType + arr: array[4,int] + data: seq[byte] + +macro test_newLit_ComposedType: untyped = + let ct = ComposedType(mt: MyType(a: 123, b:"abc"), arr: [1,2,3,4], data: @[1.byte, 3, 7, 127]) + result = newLit(ct) + +doAssert test_newLit_ComposedType == ComposedType(mt: MyType(a: 123, b:"abc"), arr: [1,2,3,4], data: @[1.byte, 3, 7, 127]) + +macro test_newLit_empty_seq_string: untyped = + var strSeq = newSeq[string](0) + result = newLit(strSeq) + +block: + # x needs to be of type seq[string] + var x = test_newLit_empty_seq_string + x.add("xyz") + +type + MyEnum = enum + meA + meB + +macro test_newLit_Enum: untyped = + result = newLit(meA) + +block: + let tmp: MyEnum = meA + doAssert tmp == test_newLit_Enum + +macro test_newLit_set: untyped = + let myset = {MyEnum.low .. MyEnum.high} + result = newLit(myset) + +block: + let tmp: set[MyEnum] = {MyEnum.low .. MyEnum.high} + doAssert tmp == test_newLit_set + +macro test_newLit_ref_object: untyped = + var x = RefObject(x: 10) + return newLit(x) + +block: + let x = test_newLit_ref_object() + doAssert $(x[]) == "(x: 10)" + +macro test_newLit_object_ref_alias: untyped = + var x = ObjectRefAlias(x: 10) + return newLit(x) + +block: + let x = test_newLit_object_ref_alias() + doAssert $(x[]) == "(x: 10)" + diff --git a/tests/macros/tnewproc.nim b/tests/macros/tnewproc.nim new file mode 100644 index 000000000..a5bfd6dca --- /dev/null +++ b/tests/macros/tnewproc.nim @@ -0,0 +1,51 @@ +import macros + +macro test(a: untyped): untyped = + # proc hello*(x: int = 3, y: float32): int {.inline.} = discard + let + nameNode = nnkPostfix.newTree( + newIdentNode("*"), + newIdentNode("hello") + ) + params = @[ + newIdentNode("int"), + nnkIdentDefs.newTree( + newIdentNode("x"), + newIdentNode("int"), + newLit(3) + ), + nnkIdentDefs.newTree( + newIdentNode("y"), + newIdentNode("float32"), + newEmptyNode() + ) + ] + paramsNode = nnkFormalParams.newTree(params) + pragmasNode = nnkPragma.newTree( + newIdentNode("inline") + ) + bodyNode = nnkStmtList.newTree( + nnkDiscardStmt.newTree( + newEmptyNode() + ) + ) + + var + expected = nnkProcDef.newTree( + nameNode, + newEmptyNode(), + newEmptyNode(), + paramsNode, + pragmasNode, + newEmptyNode(), + bodyNode + ) + + doAssert expected == newProc(name=nameNode, params=params, + body = bodyNode, pragmas=pragmasNode) + expected.pragma = newEmptyNode() + doAssert expected == newProc(name=nameNode, params=params, + body = bodyNode) + +test: + 42 diff --git a/tests/macros/tnodecompare.nim b/tests/macros/tnodecompare.nim new file mode 100644 index 000000000..5ffb495b1 --- /dev/null +++ b/tests/macros/tnodecompare.nim @@ -0,0 +1,39 @@ +import macros + +static: + let nodeA = newCommentStmtNode("this is a comment") + doAssert nodeA.repr == "## this is a comment" + doAssert nodeA.strVal == "this is a comment" + doAssert $nodeA == "this is a comment" + + let nodeB = newCommentStmtNode("this is a comment") + doAssert nodeA == nodeB + nodeB.strVal = "this is a different comment" + doAssert nodeA != nodeB + +macro test(a: typed, b: typed): untyped = + newLit(a == b) + +doAssert test(1, 1) == true +doAssert test(1, 2) == false + +type + Obj = object of RootObj + Other = object of RootObj + +doAssert test(Obj, Obj) == true +doAssert test(Obj, Other) == false + +var a, b: int + +doAssert test(a, a) == true +doAssert test(a, b) == false + +macro test2: untyped = + newLit(bindSym"Obj" == bindSym"Obj") + +macro test3: untyped = + newLit(bindSym"Obj" == bindSym"Other") + +doAssert test2() == true +doAssert test3() == false diff --git a/tests/macros/tparsefile.nim b/tests/macros/tparsefile.nim new file mode 100644 index 000000000..a41223f80 --- /dev/null +++ b/tests/macros/tparsefile.nim @@ -0,0 +1,11 @@ +import macros + +static: + let fn = "mparsefile.nim" + var raised = false + try: + discard parseStmt(staticRead(fn), filename = fn) + except ValueError as e: + raised = true + doAssert e.msg == "mparsefile.nim(4, 1) Error: invalid indentation" + doAssert raised diff --git a/tests/macros/tprocgettype.nim b/tests/macros/tprocgettype.nim new file mode 100644 index 000000000..0c1cc4270 --- /dev/null +++ b/tests/macros/tprocgettype.nim @@ -0,0 +1,28 @@ +discard """ + nimout: ''' +var x: proc () {.cdecl.} = foo +var x: iterator (): int {.closure.} = bar +''' +""" + +# issue #19010 + +import macros + +macro createVar(x: typed): untyped = + result = nnkVarSection.newTree: + newIdentDefs(ident"x", getTypeInst(x), copy(x)) + + echo repr result + +block: + proc foo() {.cdecl.} = discard + + createVar(foo) + x() + +block: + iterator bar(): int {.closure.} = discard + + createVar(bar) + for a in x(): discard diff --git a/tests/macros/tprochelpers.nim b/tests/macros/tprochelpers.nim new file mode 100644 index 000000000..d95a2ced8 --- /dev/null +++ b/tests/macros/tprochelpers.nim @@ -0,0 +1,22 @@ +import std/macros +import stdtest/testutils + +macro test1(prc: untyped): untyped = + assertAll: + prc.params.len == 2 + prc.params[1].len == 4 + prc.pragma.len == 2 + + prc.params = nnkFormalParams.newTree( + ident("int") + ) + prc.pragma = newEmptyNode() + + assertAll: + prc.params.len == 1 + prc.pragma.len == 0 + prc + +proc test(a, b: int): int {.gcsafe, raises: [], test1.} = 5 + +type hello = proc(a, b: int): int {.gcsafe, raises: [], test1.} diff --git a/tests/macros/tquotedo.nim b/tests/macros/tquotedo.nim new file mode 100644 index 000000000..6acb8ef4e --- /dev/null +++ b/tests/macros/tquotedo.nim @@ -0,0 +1,51 @@ +discard """ +output: ''' +123 +Hallo Welt +Hallo Welt +1 +() +''' +""" + +import macros + +macro mac(): untyped = + quote do: + proc test(): int = + (proc(): int = result = 123)() + +mac() +echo test() + +macro foobar(arg: untyped): untyped = + result = arg + result.add quote do: + `result` + +foobar: + echo "Hallo Welt" + +# bug #3744 +import macros +macro t(): untyped = + return quote do: + proc tp(): int = + result = 1 +t() + +echo tp() + + +# https://github.com/nim-lang/Nim/issues/9866 +type + # Foo = int # works + Foo = object # fails + +macro dispatchGen(): untyped = + var shOpt: Foo + result = quote do: + let baz = `shOpt` + echo `shOpt` + +dispatchGen() diff --git a/tests/macros/tquotewords.nim b/tests/macros/tquotewords.nim new file mode 100644 index 000000000..e718d25f9 --- /dev/null +++ b/tests/macros/tquotewords.nim @@ -0,0 +1,22 @@ +discard """ + output: "thisanexample" +""" +# Test an idea I recently had: + +import macros + +macro quoteWords(n: varargs[untyped]): untyped = + let n = callsite() + result = newNimNode(nnkBracket, n) + for i in 1..n.len-1: + expectKind(n[i], nnkIdent) + result.add(toStrLit(n[i])) + +const + myWordList = quoteWords(this, an, example) + +var s = "" +for w in items(myWordList): + s.add(w) + +echo s #OUT thisanexample diff --git a/tests/macros/trecmacro.nim b/tests/macros/trecmacro.nim new file mode 100644 index 000000000..d804178bc --- /dev/null +++ b/tests/macros/trecmacro.nim @@ -0,0 +1,14 @@ +discard """ + errormsg: "recursive dependency: 'dump'" + file: "trecmacro.nim" + line: 8 +""" + +macro dump(n: untyped): untyped = + dump(n) + if kind(n) == nnkNone: + nil + else: + hint($kind(n)) + for i in countUp(0, len(n)-1): + nil diff --git a/tests/macros/treturnsempty.nim b/tests/macros/treturnsempty.nim new file mode 100644 index 000000000..a5a678af3 --- /dev/null +++ b/tests/macros/treturnsempty.nim @@ -0,0 +1,11 @@ +discard """ + errormsg: "type mismatch" + line: 11 +""" +# bug #2372 +macro foo(dummy: int): untyped = + discard + +proc takeStr(s: string) = echo s + +takeStr foo(12) diff --git a/tests/macros/tsame_name_497.nim b/tests/macros/tsame_name_497.nim new file mode 100644 index 000000000..9f26a4024 --- /dev/null +++ b/tests/macros/tsame_name_497.nim @@ -0,0 +1,6 @@ + +import macro_bug + +type TObj = object + +proc f(o: TObj) {.macro_bug.} = discard diff --git a/tests/macros/tsametype.nim b/tests/macros/tsametype.nim new file mode 100644 index 000000000..5219a3767 --- /dev/null +++ b/tests/macros/tsametype.nim @@ -0,0 +1,42 @@ +discard """ +output: '''true +false +true +false +true +false +true +false +true +false''' +joinable: false +""" + +import macros + +macro same(a: typedesc, b: typedesc): untyped = + newLit(a.getType[1].sameType b.getType[1]) + +echo same(int, int) +echo same(int, float) + +type + SomeInt = int + DistinctInt = distinct int + SomeFloat = float + DistinctFloat = distinct float + +echo same(int, SomeInt) +echo same(int, DistinctInt) +echo same(float, SomeFloat) +echo same(float, DistinctFloat) + +type + Obj = object of RootObj + SubObj = object of Obj + Other = object of RootObj + +echo same(Obj, Obj) +echo same(int, Obj) +echo same(SubObj, SubObj) +echo same(Other, Obj) diff --git a/tests/macros/tslice.nim b/tests/macros/tslice.nim new file mode 100644 index 000000000..c64289ec6 --- /dev/null +++ b/tests/macros/tslice.nim @@ -0,0 +1,38 @@ +import macros + +macro test(): untyped = + result = nnkStmtList.newTree() + let n = nnkStmtList.newTree( + newIdentNode("one"), + newIdentNode("two"), + newIdentNode("three"), + newIdentNode("four"), + newIdentNode("five"), + newIdentNode("six") + ) + + var i = 1 + for x in n[1 .. ^2]: + assert x == n[i] + i.inc + assert i == 5 + + i = 3 + for x in n[3..^1]: + assert x == n[i] + i.inc + assert i == 6 + + i = 0 + for x in n[0..3]: + assert x == n[i] + i.inc + assert i == 4 + + i = 0 + for x in n[0..5]: + assert x == n[i] + i.inc + assert i == 6 + +test() diff --git a/tests/macros/tstaticparamsmacro.nim b/tests/macros/tstaticparamsmacro.nim new file mode 100644 index 000000000..2632ca730 --- /dev/null +++ b/tests/macros/tstaticparamsmacro.nim @@ -0,0 +1,75 @@ +discard """ + nimout: ''' +letters +aa +bb +numbers +11 +22 +AST a +@[(c: 11, d: 22), (c: 33, d: 44)] +AST b +(e: @[55, 66], f: @[77, 88]) +55 +10 +20Test +20 +''' +""" + +import macros + +type + TConfig = tuple + letters: seq[string] + numbers:seq[int] + +const data: Tconfig = (@["aa", "bb"], @[11, 22]) + +macro mymacro(data: static[TConfig]): untyped = + echo "letters" + for s in items(data.letters): + echo s + echo "numbers" + for n in items(data.numbers): + echo n + +mymacro(data) + +type + Ta = seq[tuple[c:int, d:int]] + Tb = tuple[e:seq[int], f:seq[int]] + +const + a : Ta = @[(11, 22), (33, 44)] + b : Tb = (@[55,66], @[77, 88]) + +macro mA(data: static[Ta]): untyped = + echo "AST a\n", repr(data) + +macro mB(data: static[Tb]): untyped = + echo "AST b\n", repr(data) + echo data.e[0] + +mA(a) +mB(b) + +type + Foo[N: static[int], Z: static[string]] = object + +macro staticIntMacro(f: static[int]): untyped = echo f +staticIntMacro 10 + +var + x: Foo[20, "Test"] + +macro genericMacro[N; Z: static[string]](f: Foo[N, Z], ll = 3, zz = 12): untyped = + echo N, Z + +genericMacro x + +template genericTemplate[N, Z](f: Foo[N, Z], ll = 3, zz = 12): int = N + +static: + echo genericTemplate(x) + diff --git a/tests/macros/tstringinterp.nim b/tests/macros/tstringinterp.nim new file mode 100644 index 000000000..74c73599b --- /dev/null +++ b/tests/macros/tstringinterp.nim @@ -0,0 +1,73 @@ +discard """ + output: "Hello Alice, 64 | Hello Bob, 10$" +""" + +import macros, parseutils, strutils + +proc concat(strings: varargs[string]): string = + result = newString(0) + for s in items(strings): result.add(s) + +template processInterpolations(e) = + var s = e[1].strVal + for f in interpolatedFragments(s): + case f.kind + of ikStr: addString(f.value) + of ikDollar: addDollar() + of ikVar, ikExpr: addExpr(newCall("$", parseExpr(f.value))) + +macro formatStyleInterpolation(e: untyped): untyped = + let e = callsite() + var + formatString = "" + arrayNode = newNimNode(nnkBracket) + idx = 1 + + proc addString(s: string) = + formatString.add(s) + + proc addExpr(e: NimNode) = + arrayNode.add(e) + formatString.add("$" & $(idx)) + inc idx + + proc addDollar() = + formatString.add("$$") + + processInterpolations(e) + + result = parseExpr("\"x\" % [y]") + result[1].strVal = formatString + result[2] = arrayNode + +macro concatStyleInterpolation(e: untyped): untyped = + let e = callsite() + var args: seq[NimNode] + newSeq(args, 0) + + proc addString(s: string) = args.add(newStrLitNode(s)) + proc addExpr(e: NimNode) = args.add(e) + proc addDollar() = args.add(newStrLitNode"$") + + processInterpolations(e) + + result = newCall("concat", args) + +### + +proc sum(a, b, c: int): int = + return (a + b + c) + +var + alice = "Alice" + bob = "Bob" + a = 10 + b = 20 + c = 34 + +var + s1 = concatStyleInterpolation"Hello ${alice}, ${sum(a, b, c)}" + s2 = formatStyleInterpolation"Hello ${bob}, ${sum(alice.len, bob.len, 2)}$$" + +write(stdout, s1 & " | " & s2) +write(stdout, "\n") diff --git a/tests/macros/tstructuredlogging.nim b/tests/macros/tstructuredlogging.nim new file mode 100644 index 000000000..649c6c0bd --- /dev/null +++ b/tests/macros/tstructuredlogging.nim @@ -0,0 +1,154 @@ +discard """ +output: ''' +main started: a=10, b=inner-b, c=10, d=some-d, x=16, z=20 +exiting: a=12, b=overridden-b, c=100, msg=bye bye, x=16 +''' +""" + +import macros, tables + +template scopeHolder = + 0 # scope revision number + +type + BindingsSet = Table[string, NimNode] + +proc actualBody(n: NimNode): NimNode = + # skip over the double StmtList node introduced in `mergeScopes` + result = n.body + if result.kind == nnkStmtList and result[0].kind == nnkStmtList: + result = result[0] + +iterator bindings(n: NimNode, skip = 0): (string, NimNode) = + for i in skip ..< n.len: + let child = n[i] + if child.kind in {nnkAsgn, nnkExprEqExpr}: + let name = $child[0] + let value = child[1] + yield (name, value) + +proc scopeRevision(scopeHolder: NimNode): int = + # get the revision number from a scopeHolder sym + assert scopeHolder.kind == nnkSym + var revisionNode = scopeHolder.getImpl.actualBody[0] + result = int(revisionNode.intVal) + +proc lastScopeHolder(scopeHolders: NimNode): NimNode = + # get the most recent scopeHolder from a symChoice node + if scopeHolders.kind in {nnkClosedSymChoice, nnkOpenSymChoice}: + var bestScopeRev = 0 + assert scopeHolders.len > 0 + for scope in scopeHolders: + let rev = scope.scopeRevision + if result == nil or rev > bestScopeRev: + result = scope + bestScopeRev = rev + else: + result = scopeHolders + + assert result.kind == nnkSym + +macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped = + var + bestScope = scopeHolders.lastScopeHolder + bestScopeRev = bestScope.scopeRevision + + var finalBindings = initTable[string, NimNode]() + for k, v in bindings(bestScope.getImpl.actualBody, skip = 1): + finalBindings[k] = v + + for k, v in bindings(newBindings): + finalBindings[k] = v + + var newScopeDefinition = newStmtList(newLit(bestScopeRev + 1)) + + for k, v in finalBindings: + newScopeDefinition.add newAssignment(newIdentNode(k), v) + + result = quote: + template scopeHolder {.redefine.} = `newScopeDefinition` + +template scope(newBindings: untyped) {.dirty.} = + mergeScopes(bindSym"scopeHolder", newBindings) + +type + TextLogRecord = object + line: string + + StdoutLogRecord = object + +template setProperty(r: var TextLogRecord, key: string, val: string, isFirst: bool) = + if not first: r.line.add ", " + r.line.add key + r.line.add "=" + r.line.add val + +template setEventName(r: var StdoutLogRecord, name: string) = + stdout.write(name & ": ") + +template setProperty(r: var StdoutLogRecord, key: string, val: auto, isFirst: bool) = + when not isFirst: stdout.write ", " + stdout.write key + stdout.write "=" + stdout.write $val + +template flushRecord(r: var StdoutLogRecord) = + stdout.write "\n" + stdout.flushFile + +macro logImpl(scopeHolders: typed, + logStmtProps: varargs[untyped]): untyped = + let lexicalScope = scopeHolders.lastScopeHolder.getImpl.actualBody + var finalBindings = initOrderedTable[string, NimNode]() + + for k, v in bindings(lexicalScope, skip = 1): + finalBindings[k] = v + + for k, v in bindings(logStmtProps, skip = 1): + finalBindings[k] = v + + finalBindings.sort(system.cmp) + + let eventName = logStmtProps[0] + assert eventName.kind in {nnkStrLit} + let record = genSym(nskVar, "record") + + result = quote: + var `record`: StdoutLogRecord + setEventName(`record`, `eventName`) + + var isFirst = true + for k, v in finalBindings: + result.add newCall(newIdentNode"setProperty", + record, newLit(k), v, newLit(isFirst)) + isFirst = false + + result.add newCall(newIdentNode"flushRecord", record) + +template log(props: varargs[untyped]) {.dirty.} = + logImpl(bindSym"scopeHolder", props) + +scope: + a = 12 + b = "original-b" + +scope: + x = 16 + b = "overridden-b" + +scope: + c = 100 + +proc main = + scope: + c = 10 + + scope: + z = 20 + + log("main started", a = 10, b = "inner-b", d = "some-d") + +main() + +log("exiting", msg = "bye bye") + diff --git a/tests/macros/ttemplatesymbols.nim b/tests/macros/ttemplatesymbols.nim new file mode 100644 index 000000000..3182de79d --- /dev/null +++ b/tests/macros/ttemplatesymbols.nim @@ -0,0 +1,171 @@ +import + macros, algorithm, strutils + +proc normalProc(x: int) = + echo x + +template templateWithtouParams = + echo 10 + +proc overloadedProc(x: int) = + echo x + +proc overloadedProc(x: string) = + echo x + +proc overloadedProc[T](x: T) = + echo x + +template normalTemplate(x: int) = + echo x + +template overloadedTemplate(x: int) = + echo x + +template overloadedTemplate(x: string) = + echo x + +macro normalMacro(x: int): untyped = + discard + +macro macroWithoutParams: untyped = + discard + +macro inspectSymbol(sym: typed, expected: static[string]): untyped = + if sym.kind == nnkSym: + echo "Symbol node:" + let res = sym.getImpl.repr & "\n" + echo res + # echo "|", res, "|" + # echo "|", expected, "|" + if expected.len > 0: assert res == expected + elif sym.kind in {nnkClosedSymChoice, nnkOpenSymChoice}: + echo "Begin sym choice:" + var results = newSeq[string](0) + for innerSym in sym: + results.add innerSym.getImpl.repr + sort(results, cmp[string]) + let res = results.join("\n") & "\n" + echo res + if expected.len > 0: assert res == expected + echo "End symchoice." + else: + echo "Non-symbol node: ", sym.kind + if expected.len > 0: assert $sym.kind == expected + +macro inspectUntyped(sym: untyped, expected: static[string]): untyped = + let res = sym.repr + echo "Untyped node: ", res + assert res == expected + +inspectSymbol templateWithtouParams, "nnkCommand" + # this template is expanded, because bindSym was not used + # the end result is the template body (nnkCommand) + +inspectSymbol bindSym("templateWithtouParams"), """ +template templateWithtouParams() = + echo 10 + +""" + +inspectSymbol macroWithoutParams, "nnkEmpty" + # Just like the template above, the macro was expanded + +inspectSymbol bindSym("macroWithoutParams"), """ +macro macroWithoutParams(): untyped = + discard + +""" + +inspectSymbol normalMacro, """ +macro normalMacro(x: int): untyped = + discard + +""" + # Since the normalMacro has params, it's automatically + # treated as a symbol here (no need for `bindSym`) + +inspectSymbol bindSym("normalMacro"), """ +macro normalMacro(x: int): untyped = + discard + +""" + +inspectSymbol normalTemplate, """ +template normalTemplate(x: int) = + echo x + +""" + +inspectSymbol bindSym("normalTemplate"), """ +template normalTemplate(x: int) = + echo x + +""" + +inspectSymbol overloadedTemplate, """ +template overloadedTemplate(x: int) = + echo x + +template overloadedTemplate(x: string) = + echo x + +""" + +inspectSymbol bindSym("overloadedTemplate"), """ +template overloadedTemplate(x: int) = + echo x + +template overloadedTemplate(x: string) = + echo x + +""" + +inspectUntyped bindSym("overloadedTemplate"), """bindSym("overloadedTemplate")""" + # binSym is active only in the presence of `typed` params. + # `untyped` params still get the raw AST + +inspectSymbol normalProc, """ +proc normalProc(x: int) = + echo [x] + +""" + +inspectSymbol bindSym("normalProc"), """ +proc normalProc(x: int) = + echo [x] + +""" + +inspectSymbol overloadedProc, """ +proc overloadedProc(x: int) = + echo [x] + +proc overloadedProc(x: string) = + echo [x] + +proc overloadedProc[T](x: T) = + echo x + +""" + +inspectSymbol overloadedProc[float], """ +proc overloadedProc(x: T) = + echo [x] + +""" + # As expected, when we select a specific generic, the + # AST is no longer a symChoice + +inspectSymbol bindSym("overloadedProc"), """ +proc overloadedProc(x: int) = + echo [x] + +proc overloadedProc(x: string) = + echo [x] + +proc overloadedProc[T](x: T) = + echo x + +""" + diff --git a/tests/macros/ttryparseexpr.nim b/tests/macros/ttryparseexpr.nim new file mode 100644 index 000000000..e6e9e9880 --- /dev/null +++ b/tests/macros/ttryparseexpr.nim @@ -0,0 +1,24 @@ +discard """ + outputsub: '''Error: expression expected, but found '[EOF]' 45''' +""" + +# feature request #1473 +import macros + +macro test(text: string): untyped = + try: + result = parseExpr(text.strVal) + except ValueError: + result = newLit getCurrentExceptionMsg() + +const + valid = 45 + a = test("foo&&") + b = test("valid") + c = test("\"") # bug #2504 + +echo a, " ", b + +static: + # Issue #9918 + discard parseStmt("echo(1+1);") diff --git a/tests/macros/ttypenodes.nim b/tests/macros/ttypenodes.nim new file mode 100644 index 000000000..233ea9780 --- /dev/null +++ b/tests/macros/ttypenodes.nim @@ -0,0 +1,16 @@ +import macros + +macro makeEnum(): untyped = + newTree(nnkEnumTy, newEmptyNode(), ident"a", ident"b", ident"c") + +macro makeObject(): untyped = + newTree(nnkObjectTy, newEmptyNode(), newEmptyNode(), newTree(nnkRecList, + newTree(nnkIdentDefs, ident"x", ident"y", ident"int", newEmptyNode()))) + +type + Foo = makeEnum() + Bar = makeObject() + +doAssert {a, b, c} is set[Foo] +let bar = Bar(x: 3, y: 4) +doAssert (bar.x, bar.y) == (3, 4) diff --git a/tests/macros/tvarargsuntyped.nim b/tests/macros/tvarargsuntyped.nim new file mode 100644 index 000000000..5a06adcca --- /dev/null +++ b/tests/macros/tvarargsuntyped.nim @@ -0,0 +1,108 @@ +discard """ + output: '''Let's go! +(left: 2, r: 7, x: 8, height: 4, s: test, width: 3, y: 9, top: 1, g: 7, b: 8) +(left: 2, r: 7, x: 8, height: 4, s: text, width: 3, y: 9, top: 1, g: 7, b: 8) +(left: 2, r: 7, x: 8, height: 4, s: text, width: 3, y: 9, top: 4, g: 7, b: 8) +(left: 2, r: 7, x: 8, height: 4, s: test, width: 3, y: 9, top: 1, g: 7, b: 8) +10 +hello 18.0''' +""" + +import macros + +proc internalBar(top, left, width, height: cint, s: string, x, y: int, r,g,b: int) = + echo "(left: ", left, ", r: ", r, ", x: ", x, ", height: ", height, ", s: ", s, + ", width: ", width, ", y: ", y, ", top: ", top, ", g: ", g, ", b: ", b, ")" + +# we need these dummy constructors due to the wrong implementation +# of 'varargs[untyped]' in the compiler: + +proc point(x, y: int): int = discard +proc color(r, g, b: int): int = discard +proc rect(a, b, c, d: int): int = discard + +template declareUnpackingMacro(nimname,extname) = + macro nimname(n: varargs[untyped]): untyped = + var s: string = astToStr(extname) & "(" + var first = true + echo repr n + for x in n.children: + var unpack = false + if x.kind in nnkCallKinds: + case $x[0] + of "point": + expectLen(x, 3) + unpack = true + of "rect": + expectLen(x, 5) + unpack = true + of "color": + expectLen(x, 4) + unpack = true + else: discard + if unpack: + for i in 1..<x.len: + if first: + first = false + else: + add(s, ", ") + add(s, repr(x[i])) + else: + if first: + first = false + else: + add(s, ", ") + add(s, repr(x)) + + add(s, ")") + echo s + result = parseStmt(s) + +declareUnpackingMacro(bar,internalBar) + +type MyInt = distinct int + +proc myInt(i: int): MyInt = cast[MyInt](i) + +converter toCInt(mi: MyInt): cint = cast[cint](mi) + +echo "Let's go!" + +bar(rect(1, 2, 3, 4), "test", point(8, 9), color(7,7,8)) + +bar(1,2,3,4,"text",8,9,7,7,8) + +bar(myInt(4),2,3,4,"text",8,9,7,7,8) + +let top: cint = 1 +let left: cint = 2 +let width: cint = 3 +let height: cint = 4 + +bar(rect(top, left, width, height), "test", point(8, 9), color(7,7,8)) + + +# bug #10075 + +import macros + +proc convert_hidden_stdconv(args: NimNode): NimNode = + var n = args + while n.len == 1 and n[0].kind == nnkHiddenStdConv: + n = n[0][1] + return n + +macro t2(s: int, v: varargs[untyped]): untyped = + let v = convert_hidden_stdconv(v) + echo v.treeRepr + let (v1, v2) = (v[0], v[1]) + quote do: + echo `v1`, " ", `v2` + +template t1(s: int, v: varargs[typed]) = + #static: + # dumpTree v + echo s + t2(s, v) + +t1(10, "hello", 18.0) diff --git a/tests/macros/tvtable.nim b/tests/macros/tvtable.nim new file mode 100644 index 000000000..0f322b7d5 --- /dev/null +++ b/tests/macros/tvtable.nim @@ -0,0 +1,74 @@ +discard """ + output: ''' +OBJ 1 foo +10 +OBJ 1 bar +OBJ 2 foo +5 +OBJ 2 bar +''' +""" + +type + # these are the signatures of the virtual procs for each type + fooProc[T] = proc (o: var T): int {.nimcall.} + barProc[T] = proc (o: var T) {.nimcall.} + + # an untyped table to store the proc pointers + # it's also possible to use a strongly typed tuple here + VTable = array[0..1, pointer] + + TBase {.inheritable.} = object + vtbl: ptr VTable + + TUserObject1 = object of TBase + x: int + + TUserObject2 = object of TBase + y: int + +proc foo(o: var TUserObject1): int = + echo "OBJ 1 foo" + return 10 + +proc bar(o: var TUserObject1) = + echo "OBJ 1 bar" + +proc foo(o: var TUserObject2): int = + echo "OBJ 2 foo" + return 5 + +proc bar(o: var TUserObject2) = + echo "OBJ 2 bar" + +proc getVTable(T: typedesc): ptr VTable = + # pay attention to what's going on here + # this will initialize the vtable for each type at program start-up + # + # fooProc[T](foo) is a type coercion - it looks for a proc named foo + # matching the signature fooProc[T] (e.g. proc (o: var TUserObject1): int) + var vtbl {.global.} = [ + cast[pointer](fooProc[T](foo)), + cast[pointer](barProc[T](bar)) + ] + + return vtbl.addr + +proc create(T: typedesc): T = + result.vtbl = getVTable(T) + +proc baseFoo(o: var TBase): int = + return cast[fooProc[TBase]](o.vtbl[0])(o) + +proc baseBar(o: var TBase) = + cast[barProc[TBase]](o.vtbl[1])(o) + +var a = TUserObject1.create +var b = TUserObject2.create + +echo a.baseFoo +a.baseBar + +echo b.baseFoo +b.baseBar + diff --git a/tests/macros/twrapiterator.nim b/tests/macros/twrapiterator.nim new file mode 100644 index 000000000..e153ae980 --- /dev/null +++ b/tests/macros/twrapiterator.nim @@ -0,0 +1,19 @@ + +import macros + +# bug #7093 + +macro foobar(arg: untyped): untyped = + let procDef = quote do: + proc foo(): void = + echo "bar" + + + result = newStmtList( + arg, procDef + ) + + echo result.repr + +iterator bar(): int {.foobar.} = + discard diff --git a/tests/macros/typesafeprintf.nim b/tests/macros/typesafeprintf.nim new file mode 100644 index 000000000..daf213bd3 --- /dev/null +++ b/tests/macros/typesafeprintf.nim @@ -0,0 +1,49 @@ +discard """ + output: '''test 10''' +""" + +# bug #1152 + +import macros, typetraits +proc printfImpl(formatstr: cstring) {.importc: "printf", varargs.} + +iterator tokenize(format: string): char = + var i = 0 + while i < format.len: + case format[i] + of '%': + case format[i+1] + of '\0': break + else: yield format[i+1] + i.inc + of '\0': break + else: discard + i.inc + +macro printf(formatString: string{lit}, args: varargs[typed]): untyped = + var i = 0 + let err = getType(bindSym"ValueError") + for c in tokenize(formatString.strVal): + var expectedType = case c + of 'c': getType(bindSym"char") + of 'd', 'i', 'x', 'X': getType(bindSym"int") + of 'f', 'e', 'E', 'g', 'G': getType(bindSym"float") + of 's': getType(bindSym"string") + of 'p': getType(bindSym"pointer") + else: err + + var actualType = getType(args[i]) + inc i + + if sameType(expectedType, err): + error c & " is not a valid format character" + elif not sameType(expectedType, actualType): + error "type mismatch for argument " & $i & ". expected type: " & + $expectedType & ", actual type: " & $actualType + + # keep the original callsite, but use cprintf instead + result = newCall(bindSym"printfImpl") + result.add formatString + for a in args: result.add a + +printf("test %d\n", 10) diff --git a/tests/macros/typesapi2.nim b/tests/macros/typesapi2.nim new file mode 100644 index 000000000..0130049c0 --- /dev/null +++ b/tests/macros/typesapi2.nim @@ -0,0 +1,48 @@ +# tests to see if a symbol returned from macros.getType() can +# be used as a type +import macros + +macro testTypesym (t:typed): untyped = + var ty = t.getType + if ty.typekind == ntyTypedesc: + # skip typedesc get to the real type + ty = ty[1].getType + + if ty.kind == nnkSym: return ty + assert ty.kind == nnkBracketExpr + assert ty[0].kind == nnkSym + result = ty[0] + return + +type TestFN = proc(a,b:int):int +var iii: testTypesym(TestFN) +static: assert iii is TestFN + +proc foo11 : testTypesym(void) = + echo "HI!" +static: assert foo11 is (proc():void {.nimcall.}) + +var sss: testTypesym(seq[int]) +static: assert sss is seq[int] +# very nice :> + +static: assert array[2,int] is testTypesym(array[2,int]) +static: assert(ref int is testTypesym(ref int)) +static: assert(void is testTypesym(void)) + + +macro tts2 (t:typed, idx:int): untyped = + var ty = t.getType + if ty.typekind == ntyTypedesc: + # skip typedesc get to the real type + ty = ty[1].getType + + if ty.kind == nnkSym: return ty + assert ty.kind == nnkBracketExpr + return ty[idx.intval.int] +type TestFN2 = proc(a:int,b:float):string +static: + assert(tts2(TestFN2, 0) is TestFN2) + assert(tts2(TestFN2, 1) is string) + assert(tts2(TestFN2, 2) is int) + assert(tts2(TestFN2, 3) is float) |