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

Macro generated generic proc doesnt work #11877

Open
SolitudeSF opened this issue Aug 4, 2019 · 5 comments
Open

Macro generated generic proc doesnt work #11877

SolitudeSF opened this issue Aug 4, 2019 · 5 comments
Labels
Documentation Content Related to documentation content (not generation). Generics Language Design Macros

Comments

@SolitudeSF
Copy link
Contributor

I have following macro that generates the procedure out of existing procedure. It essentially just strips a return value and adds var to a first argument. I also had to move some generic related fields, because getImpl for some reason provides illformed AST.

Example

import macros

macro genMutating*(p: typed, name: untyped) =
  result = p.getImpl

  var call = nnkCall.newTree(result[0].strVal.ident)
  for i in 1..<result[3].len:
    for c in result[3][i]:
      if c.kind == nnkIdent: call.add c

  result[0] = nnkPostfix.newTree(ident "*", ident name.strVal)
  result[2] = result[5][1]
  result[3][0] = newEmptyNode()
  result[3][1][1] = nnkVarTy.newTree(result[3][1][1])
  result[5] = newEmptyNode()
  result[6] = nnkStmtList.newTree(nnkAsgn.newTree(result[3][1][0], call))

type
  Color* = array[3, int] | array[4, int]
  Image*[T: Color] = seq[T]

func test*[T: Color](img: Image[T]): Image[T] = discard

test.genMutating res
# func res[T](img: var Image[T]) =
#   img = test(img)

var i = Image[array[3, int]](@[[1, 2, 3]])
i.res

Current Output

Compilation fails with

test.nim(29, 2) template/generic instantiation of `res` from here
test.nim(22, 12) Error: cannot instantiate: 'T'

But if i use --expandMacro or repr result and place the output into the code instead of macro the program compiles and runs fine.

Expected Output

Should compile and run successfully.

Tested on 0.20.2 and latest devel.

@SolitudeSF SolitudeSF changed the title Macro generated generics proc doesnt work Macro generated generic proc doesnt work Aug 4, 2019
@mratsim
Copy link
Collaborator

mratsim commented Aug 4, 2019

Welcome to the fabulous land of nim-lang/RFCs#44.

Take a chair and a glass and let me provide you with a magic fix:

proc replaceNodes*(ast: NimNode): NimNode =
  # Replace NimIdent and NimSym by a fresh ident node
  proc inspect(node: NimNode): NimNode =
    case node.kind:
    of {nnkIdent, nnkSym}:
      return ident($node)
    of nnkEmpty:
      return node
    of nnkLiterals:
      return node
    else:
      var rTree = node.kind.newTree()
      for child in node:
        rTree.add inspect(child)
      return rTree
  result = inspect(ast)

plug it in your macros, and surprise it works:

import macros

proc replaceNodes*(ast: NimNode): NimNode =
  # Replace NimIdent and NimSym by a fresh ident node
  proc inspect(node: NimNode): NimNode =
    case node.kind:
    of {nnkIdent, nnkSym}:
      return ident($node)
    of nnkEmpty:
      return node
    of nnkLiterals:
      return node
    else:
      var rTree = node.kind.newTree()
      for child in node:
        rTree.add inspect(child)
      return rTree
  result = inspect(ast)

macro genMutating*(p: typed, name: untyped) =
  result = p.getImpl

  var call = nnkCall.newTree(result[0].strVal.ident)
  for i in 1..<result[3].len:
    for c in result[3][i]:
      if c.kind == nnkIdent: call.add c

  result[0] = nnkPostfix.newTree(ident "*", ident name.strVal)
  result[2] = result[5][1]
  result[3][0] = newEmptyNode()
  result[3][1][1] = nnkVarTy.newTree(result[3][1][1])
  result[5] = newEmptyNode()
  result[6] = nnkStmtList.newTree(nnkAsgn.newTree(result[3][1][0], call))

  result = replaceNodes(result)

type
  Color* = array[3, int] | array[4, int]
  Image*[T: Color] = seq[T]

func test*[T: Color](img: Image[T]): Image[T] = discard

test.genMutating res
# func res[T](img: var Image[T]) =
#   img = test(img)

var i = Image[array[3, int]](@[[1, 2, 3]])
i.res

What is happening?

In some cases, typed macro, or macro nested inside a generic or template (#11091), you will get symbols instead of idents for types. Symbols are already bound to a type T and you need to regenerate a fresh ident to avoid that, see detailed explanation in #8531 (comment).

@mratsim
Copy link
Collaborator

mratsim commented Aug 4, 2019

Tagging langage design.

Either getImpl is working as intended: i.e. when passed a generic typed proc it "instantiates" T to an abstract type and returns a symbol "T", in that case we should close this bug in favor of nim-lang/RFCs#44.

Or getImpl (and maybe getTypeImpl) should not instantiate generics and in that case this is an actual implementation bug.

@SolitudeSF
Copy link
Contributor Author

it its working as intended, then maybe the replaceNodes helper, probably under a different name, should be added to macros.

@mratsim
Copy link
Collaborator

mratsim commented Aug 4, 2019

The proposed solution is to introduce a toUntypedAST that undo semchecks #11091 (comment).

I don't think it's working as intended but at this point the generic machine seems to be too time-consuming to change in-depth. A proper fix of the recurrent instantiation and semcheck issues reported would as far as I know require such in-depth changes.

@krux02 krux02 added the Documentation Content Related to documentation content (not generation). label Aug 4, 2019
@krux02
Copy link
Contributor

krux02 commented Aug 4, 2019

@Araq, I think we should really document better that semchecked may not be modified. I think about the following solution: A pnode has the flags field where we can detect if a node is already semchecked or not. In opcNSetChild we can then check if nfSem is not part of the flags of that node. If it is, emit an error. This way result[0] = nnkPostfix.newTree(ident "*", ident name.strVal) would already emit an error message. A clear error message that explains that nodes that are already typechecked should be used read only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation Content Related to documentation content (not generation). Generics Language Design Macros
Projects
None yet
Development

No branches or pull requests

3 participants