Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[superseded] new macros.genAst: sidesteps issues with quote do #11722

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@

- Added `os.isRelativeTo` to tell whether a path is relative to another

- Added `macros.genAst` that fixes all issues with `quote do` (#11722)

## Library changes

- `asyncdispatch.drain` now properly takes into account `selector.hasPendingOperations`
Expand All @@ -73,7 +75,8 @@
- `httpclient.maxredirects` changed from `int` to `Natural`, because negative values serve no purpose whatsoever.
- `httpclient.newHttpClient` and `httpclient.newAsyncHttpClient` added `headers` argument to set initial HTTP Headers,
instead of a hardcoded empty `newHttpHeader()`.

- Added `macros.genAst` that avoids the problems inherent with `quote do` and can
be used as a replacement (#11722)

## Language additions

Expand All @@ -83,7 +86,6 @@
- `=sink` type bound operator is now optional. Compiler can now use combination
of `=destroy` and `copyMem` to move objects efficiently.


## Language changes

- Unsigned integer operators have been fixed to allow promotion of the first operand.
Expand All @@ -106,6 +108,11 @@
pass specific compiler options to the C(++) backend for the C(++) file
that was produced from the current Nim module.

- VM FFI now works with {.importc, dynlib.}, when using -d:nimHasLibFFI (#11635)

- importc procs with a body are now executed in the VM as if importc wasn't specified,
this allows using {.rtl.} procs at CT, making -d:useNimRtl work in more cases,
e.g. compiling nim itself (#11635)

## Bugfixes

Expand Down
74 changes: 74 additions & 0 deletions lib/core/macros.nim
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ proc parseStmt*(s: string): NimNode {.noSideEffect, compileTime.} =

proc getAst*(macroOrTemplate: untyped): NimNode {.magic: "ExpandToAst", noSideEffect.}
## Obtains the AST nodes returned from a macro or template invocation.
## See also `genAst`.
## Example:
##
## .. code-block:: nim
Expand All @@ -574,6 +575,9 @@ proc getAst*(macroOrTemplate: untyped): NimNode {.magic: "ExpandToAst", noSideEf
## var ast = getAst(BarTemplate())

proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.}
## Caution: `quote` has many caveats, see https://github.com/nim-lang/RFCs/issues/122
## Consider using the new `genAst` instead, which avoids those issues.
##
## Quasi-quoting operator.
## Accepts an expression or a block and returns the AST that represents it.
## Within the quoted AST, you are able to interpolate NimNode expressions
Expand Down Expand Up @@ -1375,6 +1379,76 @@ proc copy*(node: NimNode): NimNode {.compileTime.} =
## An alias for `copyNimTree<#copyNimTree,NimNode>`_.
return node.copyNimTree()

type GenAstOpt* = enum
kDirtyTemplate,
# When set, uses a dirty template in implementation of `genAst`. This
# is occasionally useful as workaround for issues such as #8220, see
# `strformat limitations <strformat.html#limitations>`_ for details.
# Default is unset, to avoid hijacking of uncaptured local symbols by
# symbols in caller scope.
kNoNewLit,
# don't call call newLit automatically in `genAst` capture parameters

macro genAstOpt*(options: static set[GenAstOpt], args: varargs[untyped]): untyped =
## Accepts a list of captured variables `a=b` or `a` and a block and returns the
## AST that represents it. Local `{.inject.}` symbols (e.g. procs) are captured
## unless `kDirtyTemplate in options`.
runnableExamples:
macro fun(a: bool, b: static bool): untyped =
let c = false # doesn't override parameter `c`
var d = 11 # var => gensym'd
proc localFun(): auto = 12 # proc => inject'd
genAst(a, b, c = true):
# echo d # not captured => gives `var not init`
(a, b, c, localFun())
doAssert fun(true, false) == (true, false, true, 12)

let params = newTree(nnkFormalParams, newEmptyNode())
let pragmas =
if kDirtyTemplate in options:
nnkPragma.newTree(ident"dirty")
else:
newEmptyNode()

template newLitMaybe(a): untyped =
when (a is type) or (typeof(a) is (proc | iterator | func | NimNode)):
a # `proc` actually also covers template, macro
else: newLit(a)

# using `_` as workaround, see https://github.com/nim-lang/Nim/issues/2465#issuecomment-511076669
let name = genSym(nskTemplate, "_fun")
let call = newCall(name)
for a in args[0..^2]:
var varName: NimNode
var varVal: NimNode
case a.kind
of nnkExprEqExpr:
varName = a[0]
varVal = a[1]
of nnkIdent:
varName = a
varVal = a
else: error("invalid argument kind: " & $a.kind, a)
if kNoNewLit notin options: varVal = newCall(bindSym"newLitMaybe", varVal)

params.add newTree(nnkIdentDefs, varName, newEmptyNode(), newEmptyNode())
call.add varVal

result = newStmtList()
result.add nnkTemplateDef.newTree(
name,
newEmptyNode(),
newEmptyNode(),
params,
pragmas,
newEmptyNode(),
args[^1])
result.add newCall(bindSym"getAst", call)

template genAst*(args: varargs[untyped]): untyped =
## convenience wrapper around `genAstOpt`
genAstOpt({}, args)

when defined(nimVmEqIdent):
proc eqIdent*(a: string; b: string): bool {.magic: "EqIdent", noSideEffect.}
## Style insensitive comparison.
Expand Down
52 changes: 52 additions & 0 deletions tests/macros/mgenast.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import std/macros

## Using a enum instead of, say, int, to make apparent potential bugs related to
## forgetting converting to NimNode via newLit, see https://github.com/nim-lang/Nim/issues/9607

type Foo* = enum kfoo0, kfoo1, kfoo2, kfoo3, kfoo4

proc myLocalPriv(): auto = kfoo1
proc myLocalPriv2(): auto = kfoo1
macro bindme2*(): untyped =
genAst: myLocalPriv()
macro bindme3*(): untyped =
## myLocalPriv must be captured explicitly
genAstOpt({kDirtyTemplate}, myLocalPriv): myLocalPriv()

macro bindme4*(): untyped =
## calling this won't compile because `myLocalPriv` isn't captured
genAstOpt({kDirtyTemplate}): myLocalPriv()

macro bindme5UseExpose*(): untyped =
genAst: myLocalPriv2()

macro bindme5UseExposeFalse*(): untyped =
genAstOpt({kDirtyTemplate}): myLocalPriv2()

## example from https://github.com/nim-lang/Nim/issues/7889
from std/streams import newStringStream, readData, writeData

macro bindme6UseExpose*(): untyped =
genAst:
var tst = "sometext"
var ss = newStringStream("anothertext")
writeData(ss, tst[0].addr, 2)
discard readData(ss, tst[0].addr, 2)

macro bindme6UseExposeFalse*(): untyped =
## with `kDirtyTemplate`, requires passing all referenced symbols
## which can be tedious
genAstOpt({kDirtyTemplate}, newStringStream, writeData, readData):
var tst = "sometext"
var ss = newStringStream("anothertext")
writeData(ss, tst[0].addr, 2)
discard readData(ss, tst[0].addr, 2)


proc locafun1(): auto = "in locafun1"
proc locafun2(): auto = "in locafun2"
# locafun3 in caller scope only
macro mixinExample*(): untyped =
genAst:
mixin locafun1
(locafun1(), locafun2(), locafun3())
Loading