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

Importing macros can change template behavior #18691

Open
alaviss opened this issue Aug 13, 2021 · 1 comment
Open

Importing macros can change template behavior #18691

alaviss opened this issue Aug 13, 2021 · 1 comment

Comments

@alaviss
Copy link
Collaborator

alaviss commented Aug 13, 2021

Example

This was written to test some concepts, please don't read too much into the implementation.

when not defined(normal):
  import macros

import xmltree

type
  Html = distinct XmlNode
  Body = distinct Html
  P = distinct Html
  Head = distinct Html
  Title = distinct Html
  Text = distinct XmlNode

converter toXmlNode(n: Html): XmlNode = XmlNode(n)
converter toXmlNode(n: Body): XmlNode = XmlNode(n)
converter toXmlNode(n: P): XmlNode = XmlNode(n)
converter toXmlNode(n: Text): XmlNode = XmlNode(n)
converter toXmlNode(n: Head): XmlNode = XmlNode(n)
converter toXmlNode(n: Title): XmlNode = XmlNode(n)

converter toHtml(n: Body): Html = Html(n)
converter toHtml(n: P): Html = Html(n)
converter toHtml(n: Head): Html = Html(n)
converter toHtml(n: Title): Html = Html(n)

func newHtml(init: proc (x: Html)): Html {.discardable.} =
  result = Html newElement("html")
  init(result)

template html(init: untyped): untyped =
  newHtml(proc (x {.inject.}: Html) = init)

template html(): untyped =
  html:
    discard

func newBody(parent: Html, init: proc (x: Body)): Body {.discardable.} =
  result = Body newElement("body")
  init(result)
  parent.add(result)

template body(init: untyped): untyped =
  mixin x
  newBody(x, proc (x {.inject.}: Body) = init)

func newHead(parent: Html, init: proc (x: Head)): Head {.discardable.} =
  result = Head newElement("head")
  init(result)
  parent.add(result)

template head(init: untyped): untyped =
  mixin x
  newHead(x, proc (x {.inject.}: Head) = init)

func newP(parent: Html, init: proc (x: P)): P {.discardable.} =
  result = P newElement("p")
  init(result)
  parent.add(result)

template p(init: untyped): untyped =
  mixin x
  newP(x, proc (x {.inject.}: P) = init)

func newTitle(parent: Html, init: proc (x: Title)): Title {.discardable.} =
  result = Title newElement("title")
  init(result)
  parent.add(result)

template title(init: untyped): untyped =
  mixin x
  newTitle(x, proc (x {.inject.}: Title) = init)

func newText(parent: XmlNode, text: sink string): Text {.discardable.} =
  result = Text newText(text)
  parent.add(result)

template text(text: string): untyped =
  mixin x
  newText(x, text)

echo:
  html:
    head:
      title:
        text "Hello, world!"
    body:
      p:
        text "Hello, "
        text "World!"

Current Output

$ nim c -r test.nim

<html>
  <head>
    <title>Hello, world!</title>
  </head>
  <p>Hello, World!</p>
  <body />
</html>

Expected Output

$ nim c -d:normal -r test.nim
<html>
  <head>
    <title>Hello, world!</title>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

Additional Information

$ nim -v
Nim Compiler Version 1.5.1 [Linux: amd64]
Compiled at 2021-08-13
Copyright (c) 2006-2021 by Andreas Rumpf

git hash: 4463a3c3d75daabfef52a139221f729e8af9e81e
active boot switches: -d:release -d:nimUseLinenoise
@can-lehmann
Copy link
Contributor

I would argue that this might actually be expected behavior in the current version of Nim. The macros module defines a procedure called body which may be used to access the body of a proc. This causes multiple overloads to be bound to the symbol body. One of them has an untyped argument (the template defined above) and the other one has an argument of type NimNode (the proc defined in the macros module). According to the manual, multiple overloads of the same symbol might trigger the resolution of an untyped argument. This is why at the time of symbol resolution, the binding to the argument x from the template body is not yet available and x therefore still refers to the argument from the html template. As a result the paragraph node is added to the <html> node.
In order to check this hypothesis, we can add a proc definition proc body(x: int): int = discard at the top of the source code. Since this overload triggers the resolution of the argument, the same behaviour as above can be observed. Renaming the body template also works.
There has been previous discussion on changing this behavior in nim-lang/RFCs#402, although the initially proposed change to symbol resolution would not help in this case as far as I know.

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