summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md1
-rw-r--r--compiler/commands.nim2
-rw-r--r--compiler/evaltempl.nim10
-rw-r--r--compiler/lookups.nim3
-rw-r--r--compiler/options.nim1
-rw-r--r--compiler/procfind.nim45
-rw-r--r--compiler/sem.nim3
-rw-r--r--compiler/semdata.nim2
-rw-r--r--compiler/semexprs.nim13
-rw-r--r--compiler/semstmts.nim6
-rw-r--r--compiler/semtempl.nim27
-rw-r--r--compiler/types.nim2
-rw-r--r--compiler/vm.nim5
-rw-r--r--compiler/vmdef.nim1
-rw-r--r--testament/important_packages.nim4
-rw-r--r--tests/macros/tmacros_issues.nim114
-rw-r--r--tests/template/tparams_gensymed.nim126
17 files changed, 270 insertions, 95 deletions
diff --git a/changelog.md b/changelog.md
index d9eaa5823..517919de9 100644
--- a/changelog.md
+++ b/changelog.md
@@ -264,6 +264,7 @@ proc mydiv(a, b): int {.raises: [].} =
 - Removed the `--oldNewlines` switch.
 - Removed the `--laxStrings` switch for mutating the internal zero terminator on strings.
 - Removed the `--oldast` switch.
+- Removed the `--oldgensym` switch
 - `$getType(untyped)` is now "untyped" instead of "expr", `$getType(typed)` is
   now "typed" instead of "stmt".
 - Sink inference is now disabled per default and has to enabled explicitly via
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 504550045..b67cd461f 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -871,8 +871,6 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
   of "expandarc":
     expectArg(conf, switch, arg, pass, info)
     conf.arcToExpand[arg] = "T"
-  of "oldgensym":
-    processOnOffSwitchG(conf, {optNimV019}, arg, pass, info)
   of "useversion":
     expectArg(conf, switch, arg, pass, info)
     case arg
diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim
index 2a01a7911..c1024e2fe 100644
--- a/compiler/evaltempl.nim
+++ b/compiler/evaltempl.nim
@@ -21,6 +21,7 @@ type
                       # new symbol
     config: ConfigRef
     ic: IdentCache
+    instID: int
 
 proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
   result = copyNode(a)
@@ -53,8 +54,8 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
           #if x.kind == skParam and x.owner.kind == skModule:
           #  internalAssert c.config, false
           idTablePut(c.mapping, s, x)
-        if sfGenSym in s.flags and optNimV019 notin c.config.globalOptions:
-          result.add newIdentNode(getIdent(c.ic, x.name.s & "`gensym" & $x.id),
+        if sfGenSym in s.flags:
+          result.add newIdentNode(getIdent(c.ic, x.name.s & "`gensym" & $c.instID),
             if c.instLines: actual.info else: templ.info)
         else:
           result.add newSymNode(x, if c.instLines: actual.info else: templ.info)
@@ -166,7 +167,7 @@ proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
 
 proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
                    conf: ConfigRef;
-                   ic: IdentCache;
+                   ic: IdentCache; instID: ref int;
                    fromHlo=false): PNode =
   inc(conf.evalTemplateCounter)
   if conf.evalTemplateCounter > evalTemplateLimit:
@@ -181,6 +182,7 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
   ctx.config = conf
   ctx.ic = ic
   initIdTable(ctx.mapping)
+  ctx.instID = instID[]
 
   let body = tmpl.getBody
   #echo "instantion of ", renderTree(body, {renderIds})
@@ -203,3 +205,5 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
   #if ctx.debugActive:
   #  echo "instantion of ", renderTree(result, {renderIds})
   dec(conf.evalTemplateCounter)
+  # The instID must be unique for every template instantiation, so we increment it here
+  inc instID[]
diff --git a/compiler/lookups.nim b/compiler/lookups.nim
index fe0e6fe7c..a3a2bf49c 100644
--- a/compiler/lookups.nim
+++ b/compiler/lookups.nim
@@ -316,8 +316,7 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
       fixSpelling(n, ident, searchInScopes)
       errorUndeclaredIdentifier(c, n.info, ident.s)
       result = errorSym(c, n)
-    elif checkAmbiguity in flags and result != nil and
-        contains(c.ambiguousSymbols, result.id):
+    elif checkAmbiguity in flags and result != nil and result.id in c.ambiguousSymbols:
       errorUseQualifier(c, n.info, result)
   of nkSym:
     result = n.sym
diff --git a/compiler/options.nim b/compiler/options.nim
index 7beabe5e8..b5fb546ef 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -89,7 +89,6 @@ type                          # please make sure we have under 32 options
                               # implementation
     optOwnedRefs              # active if the Nim compiler knows about 'owned'.
     optMultiMethods
-    optNimV019
     optBenchmarkVM            # Enables cpuTime() in the VM
     optProduceAsm             # produce assembler code
     optPanics                 # turn panics (sysFatal) into a process termination
diff --git a/compiler/procfind.nim b/compiler/procfind.nim
index f7dc6c379..1d897758a 100644
--- a/compiler/procfind.nim
+++ b/compiler/procfind.nim
@@ -28,40 +28,7 @@ proc equalGenericParams(procA, procB: PNode): bool =
       if not exprStructuralEquivalent(a.ast, b.ast): return
   result = true
 
-proc searchForProcOld*(c: PContext, scope: PScope, fn: PSym): PSym =
-  # Searches for a forward declaration or a "twin" symbol of fn
-  # in the symbol table. If the parameter lists are exactly
-  # the same the sym in the symbol table is returned, else nil.
-  var it: TIdentIter
-  result = initIdentIter(it, scope.symbols, fn.name)
-  if isGenericRoutine(fn):
-    # we simply check the AST; this is imprecise but nearly the best what
-    # can be done; this doesn't work either though as type constraints are
-    # not kept in the AST ..
-    while result != nil:
-      if result.kind == fn.kind and isGenericRoutine(result):
-        let genR = result.ast[genericParamsPos]
-        let genF = fn.ast[genericParamsPos]
-        if exprStructuralEquivalent(genR, genF) and
-           exprStructuralEquivalent(result.ast[paramsPos],
-                                    fn.ast[paramsPos]) and
-           equalGenericParams(genR, genF):
-            return
-      result = nextIdentIter(it, scope.symbols)
-  else:
-    while result != nil:
-      if result.kind == fn.kind and not isGenericRoutine(result):
-        case equalParams(result.typ.n, fn.typ.n)
-        of paramsEqual:
-          return
-        of paramsIncompatible:
-          localError(c.config, fn.info, "overloaded '$1' leads to ambiguous calls" % fn.name.s)
-          return
-        of paramsNotEqual:
-          discard
-      result = nextIdentIter(it, scope.symbols)
-
-proc searchForProcNew(c: PContext, scope: PScope, fn: PSym): PSym =
+proc searchForProc*(c: PContext, scope: PScope, fn: PSym): PSym =
   const flags = {ExactGenericParams, ExactTypeDescValues,
                  ExactConstraints, IgnoreCC}
   var it: TIdentIter
@@ -83,16 +50,6 @@ proc searchForProcNew(c: PContext, scope: PScope, fn: PSym): PSym =
         discard
     result = nextIdentIter(it, scope.symbols)
 
-proc searchForProc*(c: PContext, scope: PScope, fn: PSym): PSym =
-  result = searchForProcNew(c, scope, fn)
-  when false:
-    let old = searchForProcOld(c, scope, fn)
-    if old != result:
-      echo "Mismatch in searchForProc: ", fn.info
-      debug fn.typ
-      debug if result != nil: result.typ else: nil
-      debug if old != nil: old.typ else: nil
-
 when false:
   proc paramsFitBorrow(child, parent: PNode): bool =
     result = false
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 1f98b4f87..a07f62ca0 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -472,7 +472,7 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
 
   #if c.evalContext == nil:
   #  c.evalContext = c.createEvalContext(emStatic)
-  result = evalMacroCall(c.module, c.graph, n, nOrig, sym)
+  result = evalMacroCall(c.module, c.graph, c.templInstCounter, n, nOrig, sym)
   if efNoSemCheck notin flags:
     result = semAfterMacroCall(c, n, result, sym, flags)
   if c.config.macrosToExpand.hasKey(sym.name.s):
@@ -521,6 +521,7 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext {.nosinks.} =
   c.semTypeNode = semTypeNode
   c.instTypeBoundOp = sigmatch.instTypeBoundOp
   c.hasUnresolvedArgs = hasUnresolvedArgs
+  c.templInstCounter = new int
 
   pushProcCon(c, module)
   pushOwner(c, c.module)
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index 8680fcac3..5bd6c4cf2 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -35,7 +35,6 @@ type
     inTryStmt*: int           # whether we are in a try statement; works also
                               # in standalone ``except`` and ``finally``
     next*: PProcCon           # used for stacking procedure contexts
-    wasForwarded*: bool       # whether the current proc has a separate header
     mappingExists*: bool
     mapping*: TIdTable
     caseContext*: seq[tuple[n: PNode, idx: int]]
@@ -85,6 +84,7 @@ type
                                # this is used so that generic instantiations
                                # can access private object fields
     instCounter*: int          # to prevent endless instantiations
+    templInstCounter*: ref int # gives every template instantiation a unique id
 
     ambiguousSymbols*: IntSet  # ids of all ambiguous symbols (cannot
                                # store this info in the syms themselves!)
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index fd9203f34..cdd0f61dd 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -33,7 +33,7 @@ proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
   # Note: This is n.info on purpose. It prevents template from creating an info
   # context when called from an another template
   pushInfoContext(c.config, n.info, s.detailedInfo)
-  result = evalTemplate(n, s, getCurrOwner(c), c.config, c.cache, efFromHlo in flags)
+  result = evalTemplate(n, s, getCurrOwner(c), c.config, c.cache, c.templInstCounter, efFromHlo in flags)
   if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, s, flags)
   popInfoContext(c.config)
 
@@ -1203,17 +1203,6 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
     elif sfGenSym in s.flags:
       # the owner should have been set by now by addParamOrResult
       internalAssert c.config, s.owner != nil
-      if c.p.wasForwarded:
-        # gensym'ed parameters that nevertheless have been forward declared
-        # need a special fixup:
-        let realParam = c.p.owner.typ.n[s.position+1]
-        internalAssert c.config, realParam.kind == nkSym and realParam.sym.kind == skParam
-        return newSymNode(c.p.owner.typ.n[s.position+1].sym, n.info)
-      elif c.p.owner.kind == skMacro:
-        # gensym'ed macro parameters need a similar hack (see bug #1944):
-        var u = searchInScopes(c, s.name)
-        internalAssert c.config, u != nil and u.kind == skParam and u.owner == s.owner
-        return newSymNode(u, n.info)
     result = newSymNode(s, n.info)
   of skVar, skLet, skResult, skForVar:
     if s.magic == mNimvm:
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index c88ff3609..68f0cee64 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1898,6 +1898,10 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     incl(s.typ.flags, tfNoSideEffect)
   var proto: PSym = if isAnon: nil
                     else: searchForProc(c, oldScope, s)
+  if proto == nil and sfForward in s.flags:
+    #This is a definition that shares its sym with its forward declaration (generated by a macro),
+    #if the symbol is also gensymmed we won't find it with searchForProc, so we check here
+    proto = s
   if proto == nil:
     if s.kind == skIterator:
       if s.typ.callConv != ccClosure:
@@ -1943,7 +1947,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
       addGenericParamListToScope(c, proto.ast[genericParamsPos])
     addParams(c, proto.typ.n, proto.kind)
     proto.info = s.info       # more accurate line information
-    s.typ = proto.typ
     proto.options = s.options
     s = proto
     n[genericParamsPos] = proto.ast[genericParamsPos]
@@ -1983,7 +1986,6 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
       if n[genericParamsPos].kind == nkEmpty or usePseudoGenerics:
         if not usePseudoGenerics and s.magic == mNone: paramsTypeCheck(c, s.typ)
 
-        c.p.wasForwarded = proto != nil
         maybeAddResult(c, s, n)
         # semantic checking also needed with importc in case used in VM
         s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos]))
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index c9cd82833..e771e17c4 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -145,9 +145,6 @@ proc getIdentNode(c: var TemplCtx, n: PNode): PNode =
     illFormedAst(n, c.c.config)
     result = n
 
-template oldCheck(cx: TemplCtx; cond: bool): bool =
-  (optNimV019 notin cx.c.config.globalOptions or cond)
-
 proc isTemplParam(c: TemplCtx, n: PNode): bool {.inline.} =
   result = n.kind == nkSym and n.sym.kind == skParam and
            n.sym.owner == c.owner and sfTemplateParam in n.sym.flags
@@ -215,21 +212,7 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) =
         closeScope(c)
     let ident = getIdentNode(c, n)
     if not isTemplParam(c, ident):
-      # fix #2670, consider:
-      #
-      # when b:
-      #    var a = "hi"
-      # else:
-      #    var a = 5
-      # echo a
-      #
-      # We need to ensure that both 'a' produce the same gensym'ed symbol.
-      # So we need only check the *current* scope.
-      let s = localSearchInScope(c.c, considerQuotedIdent(c.c, ident))
-      if s != nil and s.owner == c.owner and sfGenSym in s.flags:
-        onUse(n.info, s)
-        replaceIdentBySym(c.c, n, newSymNode(s, n.info))
-      elif n.kind != nkSym:
+      if n.kind != nkSym:
         let local = newGenSym(k, ident, c)
         addPrelimDecl(c.c, local)
         styleCheckDef(c.c.config, n.info, local)
@@ -560,16 +543,16 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
     if n.kind == nkDotExpr:
       result = n
       result[0] = semTemplBody(c, n[0])
-      if optNimV019 notin c.c.config.globalOptions: inc c.noGenSym
+      inc c.noGenSym
       result[1] = semTemplBody(c, n[1])
-      if optNimV019 notin c.c.config.globalOptions: dec c.noGenSym
+      dec c.noGenSym
     else:
       result = semTemplBodySons(c, n)
   of nkExprColonExpr, nkExprEqExpr:
     if n.len == 2:
-      if optNimV019 notin c.c.config.globalOptions: inc c.noGenSym
+      inc c.noGenSym
       result[0] = semTemplBody(c, n[0])
-      if optNimV019 notin c.c.config.globalOptions: dec c.noGenSym
+      dec c.noGenSym
       result[1] = semTemplBody(c, n[1])
     else:
       result = semTemplBodySons(c, n)
diff --git a/compiler/types.nim b/compiler/types.nim
index b0581e96a..94fe8fd54 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -936,7 +936,7 @@ proc equalParams(a, b: PNode): TParamsEquality =
         discard
       of paramsIncompatible:
         result = paramsIncompatible
-      if (m.name.id != n.name.id):
+      if m.name.id != n.name.id:
         # BUGFIX
         return paramsNotEqual # paramsIncompatible;
       # continue traversal! If not equal, we can return immediately; else
diff --git a/compiler/vm.nim b/compiler/vm.nim
index 1edcace82..bb217c4db 100644
--- a/compiler/vm.nim
+++ b/compiler/vm.nim
@@ -1233,7 +1233,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
           let node = regs[rb+i].regToNode
           node.info = c.debug[pc]
           macroCall.add(node)
-        var a = evalTemplate(macroCall, prc, genSymOwner, c.config, c.cache)
+        var a = evalTemplate(macroCall, prc, genSymOwner, c.config, c.cache, c.templInstCounter)
         if a.kind == nkStmtList and a.len == 1: a = a[0]
         a.recSetFlagIsRef
         ensureKind(rkNode)
@@ -2232,7 +2232,7 @@ proc errorNode(owner: PSym, n: PNode): PNode =
   result.typ = newType(tyError, owner)
   result.typ.flags.incl tfCheckedForDestructor
 
-proc evalMacroCall*(module: PSym; g: ModuleGraph;
+proc evalMacroCall*(module: PSym; g: ModuleGraph; templInstCounter: ref int;
                     n, nOrig: PNode, sym: PSym): PNode =
   if g.config.errorCounter > 0: return errorNode(module, n)
 
@@ -2253,6 +2253,7 @@ proc evalMacroCall*(module: PSym; g: ModuleGraph;
   c.mode = emStaticStmt
   c.comesFromHeuristic.line = 0'u16
   c.callsite = nOrig
+  c.templInstCounter = templInstCounter
   let start = genProc(c, sym)
 
   var tos = PStackFrame(prc: sym, comesFrom: 0, next: nil)
diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim
index 9228ce5eb..59ffe9581 100644
--- a/compiler/vmdef.nim
+++ b/compiler/vmdef.nim
@@ -262,6 +262,7 @@ type
     graph*: ModuleGraph
     oldErrorCount*: int
     profiler*: Profiler
+    templInstCounter*: ref int # gives every template instantiation a unique ID, needed here for getAst
 
   PStackFrame* = ref TStackFrame
   TStackFrame* = object
diff --git a/testament/important_packages.nim b/testament/important_packages.nim
index 8fa7c99a9..a4187f64c 100644
--- a/testament/important_packages.nim
+++ b/testament/important_packages.nim
@@ -12,7 +12,7 @@ var packages2*: seq[tuple[name, cmd: string; hasDeps: bool; url: string, useHead
 # pkg1 "alea", true
 pkg1 "argparse"
 pkg1 "arraymancer", true, "nim c tests/tests_cpu.nim"
-pkg1 "ast_pattern_matching", false, "nim c -r --oldgensym:on tests/test1.nim"
+#pkg1 "ast_pattern_matching", false, "nim c -r --oldgensym:on tests/test1.nim"
 pkg1 "awk", true
 pkg1 "bigints"
 pkg1 "binaryheap", false, "nim c -r binaryheap.nim"
@@ -95,7 +95,7 @@ pkg2 "optionsutils"
 pkg2 "ormin", true, "nim c -o:orminn ormin.nim"
 pkg2 "parsetoml"
 pkg2 "patty"
-pkg2 "plotly", true, "nim c --oldgensym:on examples/all.nim"
+pkg2 "plotly", true, "nim c examples/all.nim"
 pkg2 "pnm"
 pkg2 "polypbren"
 pkg2 "prologue", true, "nim c -d:release -r tests/test_compile/test_compile.nim"
diff --git a/tests/macros/tmacros_issues.nim b/tests/macros/tmacros_issues.nim
index 255a98ba9..882c0735d 100644
--- a/tests/macros/tmacros_issues.nim
+++ b/tests/macros/tmacros_issues.nim
@@ -35,6 +35,10 @@ test
 foo1
 foo2
 foo3
+true
+false
+true
+false
 '''
 """
 
@@ -289,3 +293,113 @@ proc main() =
   const fb = Foobar(a: a)
   foobar(fb)
 main()
+
+# bug #13484
+
+proc defForward(id, nid: NimNode): NimNode =
+  result = newProc(id, @[newIdentNode("bool"), newIdentDefs(nid, newIdentNode("int"))], body=newEmptyNode())
+
+proc defEven(evenid, oddid, nid: NimNode): NimNode =
+  result = quote do:
+    proc `evenid`(`nid`: int): bool =
+      if `nid` == 0:
+        return true
+      else:
+        return `oddid`(`nid` - 1)
+
+proc defOdd(evenid, oddid, nid: NimNode): NimNode =
+  result = quote do:
+    proc `oddid`(`nid`: int): bool =
+      if `nid` == 0:
+        return false
+      else:
+        return `evenid`(`nid` - 1)
+
+proc callNode(funid, param: NimNode): NimNode =
+  result = quote do:
+    `funid`(`param`)
+
+macro testEvenOdd3(): untyped =
+  let
+    evenid = newIdentNode("even3")
+    oddid = newIdentNode("odd3")
+    nid = newIdentNode("n")
+    oddForward = defForward(oddid, nid)
+    even = defEven(evenid, oddid, nid)
+    odd = defOdd(evenid, oddid, nid)
+    callEven = callNode(evenid, newLit(42))
+    callOdd = callNode(oddid, newLit(42))
+  result = quote do:
+    `oddForward`
+    `even`
+    `odd`
+    echo `callEven`
+    echo `callOdd`
+
+macro testEvenOdd4(): untyped =
+  let
+    evenid = newIdentNode("even4")
+    oddid = newIdentNode("odd4")
+    nid = newIdentNode("n")
+    oddForward = defForward(oddid, nid)
+    even = defEven(evenid, oddid, nid)
+    odd = defOdd(evenid, oddid, nid)
+    callEven = callNode(evenid, newLit(42))
+    callOdd = callNode(oddid, newLit(42))
+  # rewrite the body of proc node.
+  oddForward[6] = newStmtList()
+  result = quote do:
+    `oddForward`
+    `even`
+    `odd`
+    echo `callEven`
+    echo `callOdd`
+
+macro testEvenOdd5(): untyped =
+  let
+    evenid = genSym(nskProc, "even5")
+    oddid = genSym(nskProc, "odd5")
+    nid = newIdentNode("n")
+    oddForward = defForward(oddid, nid)
+    even = defEven(evenid, oddid, nid)
+    odd = defOdd(evenid, oddid, nid)
+    callEven = callNode(evenid, newLit(42))
+    callOdd = callNode(oddid, newLit(42))
+  result = quote do:
+    `oddForward`
+    `even`
+    `odd`
+    echo `callEven`
+    echo `callOdd`
+
+macro testEvenOdd6(): untyped =
+  let
+    evenid = genSym(nskProc, "even6")
+    oddid = genSym(nskProc, "odd6")
+    nid = newIdentNode("n")
+    oddForward = defForward(oddid, nid)
+    even = defEven(evenid, oddid, nid)
+    odd = defOdd(evenid, oddid, nid)
+    callEven = callNode(evenid, newLit(42))
+    callOdd = callNode(oddid, newLit(42))
+  # rewrite the body of proc node.
+  oddForward[6] = newStmtList()
+  result = quote do:
+    `oddForward`
+    `even`
+    `odd`
+    echo `callEven`
+    echo `callOdd`
+
+# it works
+testEvenOdd3()
+
+# it causes an error (redefinition of odd4), which is correct
+assert not compiles testEvenOdd4()
+
+# it caused an error (still forwarded: odd5)
+testEvenOdd5()
+
+# it works, because the forward decl and definition share the symbol and the compiler is forgiving here
+#testEvenOdd6() #Don't test it though, the compiler may become more strict in the future
+
diff --git a/tests/template/tparams_gensymed.nim b/tests/template/tparams_gensymed.nim
index a35d878d5..cfd354a74 100644
--- a/tests/template/tparams_gensymed.nim
+++ b/tests/template/tparams_gensymed.nim
@@ -195,3 +195,129 @@ mystate_machine:
   state_push(S1)
   echo state_current()
   state_pop()
+
+# bug #15075
+block: #Doesn't work
+  template genGenTempl: untyped =
+    proc loop(locals: int)
+    proc loop(locals: int) = discard
+  genGenTempl()
+  let pool = loop
+
+block: #Doesn't work
+  macro genGenMacro: untyped =
+    quote do:
+      proc loop(locals: int)
+      proc loop(locals: int) = discard
+  genGenMacro()
+  let pool = loop
+
+block: #This works
+  proc loop(locals: int)
+  proc loop(locals: int) = discard
+  let pool = loop
+
+#Now somewhat recursive:
+type Cont = ref object of RootObj
+  fn*: proc(c: Cont): Cont {.nimcall.}
+
+block: #Doesn't work
+  template genGenTempl(): untyped =
+    proc loop(locals: Cont): Cont
+    proc loop(locals: Cont): Cont =
+      return Cont(fn: loop)
+    proc doServer(): Cont =
+      return Cont(fn: loop)
+  genGenTempl()
+  discard doServer()
+
+block: #Doesn't work
+  macro genGenMacro(): untyped =
+    quote:
+      proc loop(locals: Cont): Cont
+      proc loop(locals: Cont): Cont =
+        return Cont(fn: loop)
+      proc doServer(): Cont =
+        return Cont(fn: loop)
+  genGenMacro()
+  discard doServer()
+
+block: #This works
+  proc loop(locals: Cont): Cont
+  proc loop(locals: Cont): Cont =
+    return Cont(fn: loop)
+  proc doServer(): Cont =
+    return Cont(fn: loop)
+  discard doServer()
+
+#And fully recursive:
+block: #Doesn't work
+  template genGenTempl: untyped =
+    proc loop(locals: int)
+    proc loop(locals: int) = loop(locals)
+  genGenTempl()
+  let pool = loop
+
+block: #Doesn't work
+  macro genGenMacro: untyped =
+    quote do:
+      proc loop(locals: int)
+      proc loop(locals: int) = loop(locals)
+  genGenMacro()
+  let pool = loop
+
+block: #This works
+  proc loop(locals: int)
+  proc loop(locals: int) = loop(locals)
+  let pool = loop
+
+block:
+  template genAndCallLoop: untyped =
+    proc loop() {.gensym.}
+    proc loop() {.gensym.} =
+      discard
+    loop()
+  genAndCallLoop
+
+block: #Fully recursive and gensymmed:
+  template genGenTempl: untyped =
+    proc loop(locals: int) {.gensym.}
+    proc loop(locals: int) {.gensym.} = loop(locals)
+    let pool = loop
+  genGenTempl()
+
+block: #Make sure gensymmed symbol doesn't overwrite the forward decl
+  proc loop()
+  proc loop() = discard
+  template genAndCallLoop: untyped =
+    proc loop() {.gensym.} =
+      discard
+    loop()
+  genAndCallLoop()
+
+template genLoopDecl: untyped =
+  proc loop()
+template genLoopDef: untyped =
+  proc loop() = discard
+block:
+  genLoopDecl
+  genLoopDef
+  loop()
+block:
+  proc loop()
+  genLoopDef
+  loop()
+block:
+  genLoopDecl
+  proc loop() = discard
+  loop()
+
+block: #Gensymmed sym sharing forward decl
+  macro genGenMacro: untyped =
+    let sym = genSym(nskProc, "loop")
+    nnkStmtList.newTree(
+      newProc(sym, body = newEmptyNode()),
+      newCall(sym),
+      newProc(sym, body = newStmtList()),
+    )
+  genGenMacro