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

symbols of no argument templates can't be passed to macros #11870

Open
krux02 opened this issue Aug 2, 2019 · 0 comments
Open

symbols of no argument templates can't be passed to macros #11870

krux02 opened this issue Aug 2, 2019 · 0 comments

Comments

@krux02
Copy link
Contributor

krux02 commented Aug 2, 2019

When a template, macro or const does not require arguments to be substituted, It is done instantly. This makes it impossible to pass this symbol to a typed macro as a symbol.

Example

import macros

# Here are just a bunch of definitions to test if it is possible to pass their 
# symbol to a macro as an ``nnkSym``.

template argsTemplate(arg1, arg2: untyped): untyped =
  echo "something"

template noArgsTemplate(): untyped =
  echo "something"

template defaultArgsTemplate(arg1: int = 123, arg2: string = "abc"): untyped =
  echo "something"


macro argsMacro(arg1, arg2: untyped): untyped =
  result = quote do:
    echo "something"

macro noArgsMacro(): untyped =
  result = quote do:
    echo "something"

macro defaultArgsMacro(arg1: int = 123, arg2: string = "abc"): untyped =
  result = quote do:
    echo "something"


proc argsProc(arg1, arg2: int) =
  echo "something"

proc noArgsProc() =
  echo "something"

proc defaultArgsProc(arg1: int = 123, arg2: string = "abc") =
  echo "something"


const myConst = 123


iterator argsIterator(a,b: int): int =
  yield a+b

iterator noArgsIterator(): int =
  yield 123

iterator defaultArgsIterator(a: int = 12; b: int = 23): int =
  yield a+b

macro processSymbol(arg: typed): untyped =
  echo arg.lispRepr

# Here starts the test to pass all the symbols to the `processSymbol` macro.

# pass template symbol
processSymbol(argsTemplate)        # (Sym "argsTemplate")                 -> OK
processSymbol(noArgsTemplate)      # (Command (Sym "echo") ...)           -> not ok
processSymbol(defaultArgsTemplate) # (Command (Sym "echo") ...)           -> not ok

# pass macros symbol
processSymbol(argsMacro)           # (Sym "argsMacro")                    -> OK
processSymbol(noArgsMacro)         # (Command (Sym "echo") ...)           -> not OK
# Error: in call 'defaultArgsMacro' got -1, but expected 2 argument(s)
# processSymbol(defaultArgsMacro)  #                                      -> not ok

# pass proc symbol (works)
processSymbol(argsProc)            # (Sym "argsProc")                     -> OK
processSymbol(noArgsProc)          # (Sym "noArgsProc")                   -> OK
processSymbol(defaultArgsProc)     # (Sym "defaultArgsProc")              -> OK

# pass module symbol
processSymbol(macros)              # (Sym "macros")                       -> OK
processSymbol(macros.newLit)       # (ClosedSymChoice (Sym "newLit") ...) -> OK

# pass const symbol                #
processSymbol(myConst)             # (IntLit 123)                         -> not OK

# pass iterator symbol
processSymbol(argsIterator)        # (Sym "argsIterator")        -> OK
processSymbol(noArgsIterator)      # (Sym "noArgsIterator")      -> OK
processSymbol(defaultArgsIterator) # (Sym "defaultArgsIterator") -> OK

Possible Solution

resolve symbol at callsite

If there would be a proc in Macros to convert an identifier into a symbol by looking the symbol up in the context of the callsite, this problem could be avoided.

macro setSymAlias(newName, oldName: untyped) =
  let oldSym = resolveAtCallsite(oldName)
  case oldSym.symKind
  ...

distinct between myTempl() and myTempl (no args)

when a template that is declared with () in the declaration cannot be called without (), there is much less of a problem.

template foo() = discard
let x = foo # <-- error `()` required

Provide a syntax that prevents symbol substitution

In Scala methods can be call without braces. Since the this parameter for local methodes also doesn't need to be provided, function calls can be nothing more than the function name on it's own. But in some contexts, when you want to pass the function symbol as a function object, there is a syntax for it that prevents function execution:

processCallback(argumentLessMethod _)

Remove Feature that the () for argument less templates are optional.

To call a procedure, the () are required to distinguish it from the expression where just the function symbol as a value should be used. I don't know who thought that for templates and macros we don't need this distinction, but if we din't have it, this problem would not exist. This breaks a lot. I don't think it is feasible. I just add this as a reminder that careless addition of features and rules in the compiler really can break things and cause problems in the long run.

Additional Information

This issue came up when I tried to implement symbol aliasing as a pure macro implementation. My approach looks promising, but it does require that I can pass all sorts of symbol types to a macro which I currently don't know how to do it:

import macros

proc forwardParams(call, params: NimNode): NimNode =
  case params.kind
  of nnkFormalParams:
    for i in 1 ..< params.len:
      discard forwardParams(call, params[i])
  of nnkIdentDefs:
    for i in 0 ..< params.len - 2:
      call.add params[i]
  else:
    discard
  result = call

macro setSymAlias(newName: untyped; oldName: typed) =
  echo "set sym alias: ", newName.repr, " = ", oldName.repr
  ## Sets a symbol alias for symbol `oldName` to `newName`.
  echo newName.lispRepr
  echo oldName.lispRepr
  oldName.expectKind nnkSym
  case oldName.symKind
  of nskProc, nskTemplate, nskMacro, nskMethod:
    let params = oldName.getImpl[3]
    result = nnkTemplateDef.newTree(
      newName,
      newEmptyNode(),
      newEmptyNode(),
      params,
      newEmptyNode(),
      newEmptyNode(),
      forwardParams(newCall(oldName), params)
    )
  of nskLet, nskVar, nskParam, nskConst, nskResult:
    result = nnkTemplateDef.newTree(
      newName,
      newEmptyNode(),
      newEmptyNode(),
      nnkFormalParams.newTree(
        newIdentNode("untyped")
      ),
      newEmptyNode(),
      newEmptyNode(),
      oldName
    )
  else:
    error("incompatible symbol kind " & $oldName.symKind, oldName)

  when isMainModule:
    echo result.repr

Here is a usage example of that symbol alias implementation:

proc t(arg: int = 123): int =
  42 + arg

setSymAlias(ta,t)

let value = ta()

setSymAlias(valuea,value)

echo valuea
echo ta(arg = 337)

macro fun2(a = 1,b=2): untyped = quote do: (`a`,`b`)
setSymAlias(fun2a, fun2) # error with fun2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants