New/better macro pragmas, mark some as experimental (#19406)

* New/better macro pragmas, make some experimental

fix #15920, close #18212, close #14781, close #6696,
close https://github.com/nim-lang/RFCs/issues/220

Variable macro pragmas have been changed to
only take a unary section node.
They can now also be applied in sections with multiple variables,
as well as `const` sections. They also accept arguments.

Templates now support macro pragmas, mirroring other routine types.

Type and variable macro pragmas have been made experimental.
Symbols without parentheses instatiating nullary macros or templates
has also been documented in the experimental manual.

A check for a redefinition error based on the left hand side of variable
definitions when using variable macro pragmas was disabled.
This nerfs `byaddr` specifically, however this has been documented as
a consequence of the experimental features `byaddr` uses.

Given how simple these changes are I'm worried if I'm missing something.

* accomodate compiler boot

* allow weird pragmas

* add test for #10994

* remove some control flow, try remove some logic
This commit is contained in:
metagn 2022-01-20 22:57:50 +03:00 • committed by GitHub
commit 2bd1aa186e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 435 additions and 205 deletions

View file

@ -13,8 +13,8 @@ block:
block: # (partial fix) bug #15920
block: # var template pragmas don't work in templates
template foo(lhs, typ, expr) =
let lhs = expr
template foo(expr) =
expr
proc fun1()=
let a {.foo.} = 1
template fun2()=
@ -24,23 +24,22 @@ block: # (partial fix) bug #15920
template foo2() = discard # distractor (template or other symbol kind)
block:
template foo2(lhs, typ, expr) =
let lhs = expr
template foo2(expr) =
expr
proc fun1()=
let a {.foo2.} = 1
template fun2()=
let a {.foo2.} = 1
fun1() # ok
when false: # bug: Error: invalid pragma: foo2
fun2()
fun2() # bug: Error: invalid pragma: foo2
block: # proc template pragmas don't work in templates
block: # template pragmas don't work for templates, #18212
# adapted from $nim/lib/std/private/since.nim
# case without overload
template since3(version: (int, int), body: untyped) {.dirty.} =
when (NimMajor, NimMinor) >= version:
body
when false: # bug
when true: # bug
template fun3(): int {.since3: (1, 3).} = 12
block: # ditto, w
@ -51,7 +50,7 @@ block: # (partial fix) bug #15920
template since2(version: (int, int, int), body: untyped) {.dirty.} =
when (NimMajor, NimMinor, NimPatch) >= version:
body
when false: # bug
when true: # bug
template fun3(): int {.since2: (1, 3).} = 12
when true: # D20210801T100514:here
@ -62,3 +61,10 @@ when true: # D20210801T100514:here
discard ret
fn()
static: discard genSym()
block: # issue #10994
macro foo(x): untyped = x
template bar {.pragma.}
proc a {.bar.} = discard # works
proc b {.bar, foo.} = discard # doesn't

View file

@ -0,0 +1,128 @@
import macros
block: # test usage
macro modify(sec) =
result = copy sec
result[0][0] = ident(repr(result[0][0]) & "Modified")
block:
let foo {.modify.} = 3
doAssert fooModified == 3
block: # in section
let
a = 1
b {.modify.} = 2
c = 3
doAssert (a, bModified, c) == (1, 2, 3)
block: # with single argument
macro appendToName(name: static string, sec) =
result = sec
result[0][0] = ident(repr(result[0][0]) & name)
block:
let foo {.appendToName: "Bar".} = 3
doAssert fooBar == 3
block:
let
a = 1
b {.appendToName("").} = 2
c = 3
doAssert (a, b, c) == (1, 2, 3)
macro appendToNameAndAdd(name: static string, incr: static int, sec) =
result = sec
result[0][0] = ident(repr(result[0][0]) & name)
result[0][2] = infix(result[0][2], "+", newLit(incr))
block: # with multiple arguments
block:
let foo {.appendToNameAndAdd("Bar", 5).} = 3
doAssert fooBar == 8
block:
let
a = 1
b {.appendToNameAndAdd("", 15).} = 2
c = 3
doAssert (a, b, c) == (1, 17, 3)
block: # in other kinds of sections
block:
const
a = 1
b {.appendToNameAndAdd("", 15).} = 2
c = 3
doAssert (a, b, c) == (1, 17, 3)
doAssert static(b) == b
block:
var
a = 1
b {.appendToNameAndAdd("", 15).} = 2
c = 3
doAssert (a, b, c) == (1, 17, 3)
b += a
c += b
doAssert (a, b, c) == (1, 18, 21)
block: # with other pragmas
macro appendToNameAndAdd(name: static string, incr, sec) =
result = sec
result[0][0][0] = ident(repr(result[0][0][0]) & name)
result[0][0][1].add(ident"deprecated")
result[0][2] = infix(result[0][2], "+", incr)
var
a = 1
foo {.exportc: "exportedFooBar", appendToNameAndAdd("Bar", {'0'..'9'}), used.} = {'a'..'z', 'A'..'Z'}
b = 2
doAssert (a, b) == (1, 2)
let importedFooBar {.importc: "exportedFooBar", nodecl.}: set[char]
doAssert importedFooBar == fooBar #[tt.Warning
^ fooBar is deprecated
]#
block: # with stropping
macro `cast`(def) =
let def = def[0]
let
lhs = def[0]
typ = def[1]
ex = def[2]
addrTyp = if typ.kind == nnkEmpty: typ else: newTree(nnkPtrTy, typ)
result = quote do:
let tmp: `addrTyp` = unsafeAddr(`ex`)
template `lhs`: untyped = tmp[]
macro assign(def) =
result = getAst(`cast`(def))
block:
let s = @["foo", "bar"]
let a {.`assign`.} = s[0]
doAssert a == "foo"
doAssert a[0].addr == s[0][0].addr
block:
let
s = @["foo", "bar"]
a {.`cast`.} = s[0]
doAssert a == "foo"
doAssert a[0].addr == s[0][0].addr
block: # bug #15920
macro foo(def) =
result = def
proc fun1()=
let a {.foo.} = 1
template fun2()=
let a {.foo.} = 1
fun1() # ok
fun2() # BUG

View file

@ -13,15 +13,18 @@ template fun() =
var b {.byaddr.}: int = s[0]
doAssert a.addr == b.addr
doAssert not compiles(block:
# redeclaration not allowed
var foo = 0
var foo {.byaddr.} = s[0])
when false:
# template specific redeclaration issue
# see https://github.com/nim-lang/Nim/issues/8275
doAssert not compiles(block:
# redeclaration not allowed
var foo = 0
var foo {.byaddr.} = s[0])
doAssert not compiles(block:
# ditto
var foo {.byaddr.} = s[0]
var foo {.byaddr.} = s[0])
doAssert not compiles(block:
# ditto
var foo {.byaddr.} = s[0]
var foo {.byaddr.} = s[0])
block:
var b {.byaddr.} = s[1] # redeclaration ok in sub scope
@ -44,48 +47,3 @@ fun2()
static: fun2()
when false: # pending bug #13887
static: fun()
## We can define custom pragmas in user code
template byUnsafeAddr(lhs, typ, expr) =
when typ is type(nil):
let tmp = addr(expr)
else:
let tmp: ptr typ = addr(expr)
template lhs: untyped = tmp[]
block:
let s = @["foo", "bar"]
let a {.byUnsafeAddr.} = s[0]
doAssert a == "foo"
doAssert a[0].addr == s[0][0].addr
block: # nkAccQuoted
# shows using a keyword, which requires nkAccQuoted
template `cast`(lhs, typ, expr) =
when typ is type(nil):
let tmp = addr(expr)
else:
let tmp: ptr typ = addr(expr)
template lhs: untyped = tmp[]
block:
let s = @["foo", "bar"]
let a {.`byUnsafeAddr`.} = s[0]
doAssert a == "foo"
doAssert a[0].addr == s[0][0].addr
block:
let s = @["foo", "bar"]
let a {.`cast`.} = s[0]
doAssert a == "foo"
doAssert a[0].addr == s[0][0].addr
block: # bug #15920
template foo(lhs, typ, expr) =
let lhs = expr
proc fun1()=
let a {.foo.} = 1
template fun2()=
let a {.foo.} = 1
fun1() # ok
fun2() # BUG