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

Identifiers in templates not gensym'ed when declared in nested template invocation #24590

Open
MariusDoe opened this issue Dec 30, 2024 · 0 comments

Comments

@MariusDoe
Copy link

MariusDoe commented Dec 30, 2024

Description

This produces a "redefinition of a" error:

template declare1(x: untyped) =
    let x = 1

template declare2(x: untyped) =
    declare1 a
    let x = a + 1

declare2 a
assert a == 2

An explicit {.gensym.} pragma does not help (still causes a redefinition error):

template declare1(x: untyped) =
    let x = 1

template declare2(x: untyped) =
    declare1(a {.gensym.})
    let x = a + 1

declare2 a
assert a == 2

Investigation

Implementing declare1 as a macro and inspecting x shows that it is given as a simple ident instead of a gensym symbol, even with the explicit {.gensym.} pragma from before:

import macros

macro declare1(x: untyped) =
    echo x.lispRepr # outputs (PragmaExpr (Ident "a") (Pragma (Ident "gensym")))
    result = quote do:
        let `x` = 1

template declare2(x: untyped) =
    declare1(a {.gensym.})
    let x = a + 1

declare2 a
assert a == 2

Discussion

First of all, note that this issue is similar to #24260, but the focus there was on how templates ignore gensyms in their names, while this issue is focused on identifiers not being gensym'ed.

I originally expected that every identifier that is "created" in a template, in the sense that it does not refer to an existing symbol, should be gensym'ed. In this way, everything that would later be derived from that identifier (like a let declaration in declare1) would only be visible in the template that created the identifier (declare2), not the surrounding scope. This is currently only the case for identifiers that are directly used in declarations like let a = 1.

However, as @metagn noted, an automatic gensymming of identifiers cannot be done in templates, because identifiers could be bound at the instantiation site.

Even if not every identifier should be gensym'ed, I still think identifiers that are "later" used in declarations should/could be retroactively gensym'ed. Although I'm not sure whether this can be implemented (easily).

Another alternative that was mentioned by @metagn is what I tried to do myself: using the {.gensym.} pragma to force identifiers to be gensym'ed, even if they aren't immediately used in a declaration.

Nim Version

Nim Compiler Version 2.0.8 [Linux: amd64]
Compiled at 2024-07-03
Copyright (c) 2006-2023 by Andreas Rumpf

git hash: 5935c3b
active boot switches: -d:release

Current Output

/tmp/declare.nim(8, 10) Error: redefinition of 'a'; previous declaration here: /tmp/declare.nim(5, 14)

Expected Output

(empty)

Known Workarounds

  • use a different name for the temporary variable. note that this still (unintentionally) leaks the temporary:

    template declare1(x: untyped) =
        let x = 1
    
    template declare2(x: untyped) =
        declare1 b
        let x = b + 1
    
    declare2 a
    assert a == 2
    assert b == 1 # works! (not good)
  • do not use nested template invocations:

    template declare(x: untyped) =
        let a = 1
        let x = a + 1
    
    declare a
    assert a == 2

Additional Information

Also checked with versions (same error everywhere):

  • 1.0.0
  • 1.2.0
  • 1.4.0
  • 1.6.0
  • 2.0.0
  • devel (b9de2bb)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant