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

"undeclared identifier" error when using fmt from strformat on devel inside a template #10977

Open
dawkot opened this issue Apr 7, 2019 · 9 comments

Comments

@dawkot
Copy link

dawkot commented Apr 7, 2019

import strformat

template foo: untyped =
  var b = "abc"
  assert declared b
  assert not compiles (block: discard fmt"{b}") # undeclared identifier: 'b'

static: foo

Nim Compiler Version 0.19.9 [Linux: amd64]
Compiled at 2019-04-07

@bluenote10
Copy link
Contributor

Hm, it looks like the fundamental problem behind #7632 hasn't been solved actually? The most basic example still doesn't compile for me:

import strformat

template formatMsg(s: string): string =
  &"test {s}"

echo formatMsg("x")

@Araq
Copy link
Member

Araq commented Apr 8, 2019

@bluenote10 That would require a new language feature like untyped{strlit} that causes & to be expanded within the template declaration. Here is a workaround:

template t(s): untyped =
  let x {.inject.} = s
  &"test {x}"

@krux02
Copy link
Contributor

krux02 commented Apr 8, 2019

I recommend this workaround:

template t(s): untyped =
  block:
    let x {.inject.} = s
    &"test {x}"

Inject makes the template non hygienic. The block makes the template hygienic again.

@Araq
Copy link
Member

Araq commented Apr 8, 2019

The block makes the template hygienic again.

Btw it's only semi-hygienic with block otherwise we would never have added .gensym.

@krux02
Copy link
Contributor

krux02 commented Apr 8, 2019

@Araq how would you formalite "semi-hygienic" is this PR: #10982

@narimiran
Copy link
Member

While this hasn't been enabled, the limitations of strformat inside of a template (and workarounds) are now well-documented.

@timotheecour
Copy link
Member

timotheecour commented May 23, 2021

re-opening because this is a common gotcha, and, even though the problem was documented, it's still not fixed, and it's a solvable problem using a compiler extension.
It's likely the same root cause as:

It's worth fixing IMO.

minimized example using only parseExpr, not strformat.fmt:

when true:
  import macros
  macro fmt2(a: static string): untyped =
    let ret1 = parseExpr(a)
    result = quote do:
      var ret = "{"
      add(ret, $`ret1`)
      add(ret, "}")
      ret
  template main()= # fails: Error: undeclared identifier: 'x1'
  # proc main()= # works
    let x1 = 123
    let a = fmt2("x1")
  main()

@timotheecour timotheecour reopened this May 23, 2021
@kaushalmodi
Copy link
Contributor

kaushalmodi commented May 23, 2021

@timotheecour Thanks! Just last week, I was working with templates, and I needed to make a conscious effort to use strutils.% instead of strformat.& only when printing stuff from inside templates.

E.g. https://github.com/kaushalmodi/nim-svvpi/blob/6868848889f92c7ae4c892be4f3eaa2c6a18901c/svvpi.nim#L186-L189

@timotheecour
Copy link
Member

timotheecour commented May 25, 2021

my proposal:

option 1

add a magic proc (handled in semMagic or semTemplBody handler for nkCallKinds) which gets special cased so that it gets called before template body gets gensym'd, so that everything works.

# in macros.nim:
proc interp*(a: string, fmt: string): untyped {.magic.}

interp("foo {a} {2*2} bar", "{}") is replaced by: ["foo ", a, " ", 2*2, " bar"] (untyped AST)

this will allow code like this:

template bar() =
  let a = 3
  echo "foo {a}".interp.fmt
  {.emit: "void fn1(int {a});".interp.}
  {.emit:"void fn2(int `a`){}".interp("``").} # custom {}
  asm "auto x = {a}".interp

to work, thus fixing all related issues (and avoiding the need for this style of emits: {.emit: ["foo", a, "bar"].} which are arguably less user friendly)

option 2

A variant of this proposal is a pragma {.pregensym.} (somewhat reminiscent of {.immediate.}) which would cause a template/macro to be called before it gets gensym'd, eg:

macro fmt(args...) {.pregensym.} = ...

template bar() =
  let a = 3
  echo fmt"foo {a}" # gets expanded before bar is gensym'd in `semTemplBody` 

but this only makes sense for macros that have only untyped/litteral arguments

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

8 participants