summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSaem Ghani <saemghani+github@gmail.com>2021-03-17 11:51:50 -0700
committerGitHub <noreply@github.com>2021-03-17 19:51:50 +0100
commit72b89eff82c54e771b6842674a4de3cbf714eae3 (patch)
tree9dc9cb41b4f07c097809dbf1566daa6f624cde4e
parent144e338abb33debfa6ec6abdf3a110aade779485 (diff)
downloadNim-72b89eff82c54e771b6842674a4de3cbf714eae3.tar.gz
semLambda removed, semProcAux reworked (#17379)
* simplified proc-like name ident to symbol code

* wip - reworking generic param sem

* wip - closer to removing nkEmpty generic params

* it's hacky but tests pass

* slowly tweaking semProcAux to take on semLambda

* fix pragma superset check proto vs current

* Set the symbol owner earlier

* partial progress reworking proto

found bug where default values between forward and impl lead to overload
resolution issues.

* simplified pragma handling and callConv checks

Co-authored-by: Clyybber <Clyybber@users.noreply.github.com>

* partially working

* cgexprs issue

* It works!

* comment clean-up

* clean-up asserts, comments, and other bits

* add isGenericParams, inline isGeneric queries

* seeing if this is sufficiently consistent
* can use this approach or continue it in a further PR

* commentary about nullary generics and clean-ups

* fixed a mistake in PNode isGenericRoutine

* Some small cleanups

* Small cleanup

* for func lambdas ensure we use lambda pragmas

* add some basic compileTime func tests

* [ci skip] remove comments

Co-authored-by: Clyybber <Clyybber@users.noreply.github.com>
Co-authored-by: Clyybber <darkmine956@gmail.com>
-rw-r--r--compiler/ast.nim33
-rw-r--r--compiler/injectdestructors.nim4
-rw-r--r--compiler/liftdestructors.nim10
-rw-r--r--compiler/lookups.nim1
-rw-r--r--compiler/pragmas.nim19
-rw-r--r--compiler/sem.nim1
-rw-r--r--compiler/semcall.nim4
-rw-r--r--compiler/semexprs.nim2
-rw-r--r--compiler/seminst.nim2
-rw-r--r--compiler/semstmts.nim340
-rw-r--r--compiler/semtempl.nim7
-rw-r--r--compiler/semtypes.nim6
-rw-r--r--compiler/vmgen.nim2
-rw-r--r--tests/constr/tnocompiletimefunc.nim14
-rw-r--r--tests/constr/tnocompiletimefunclambda.nim6
-rw-r--r--tests/converter/tconverter.nim11
-rw-r--r--tests/generics/tnullary_generics.nim26
-rw-r--r--tests/statictypes/tstatictypes.nim6
18 files changed, 284 insertions, 210 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 2b1f76e2d..d82420519 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -1040,6 +1040,7 @@ const
   declarativeDefs* = {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef}
   routineDefs* = declarativeDefs + {nkMacroDef, nkTemplateDef}
   procDefs* = nkLambdaKinds + declarativeDefs
+  callableDefs* = nkLambdaKinds + routineDefs
 
   nkSymChoices* = {nkClosedSymChoice, nkOpenSymChoice}
   nkStrKinds* = {nkStrLit..nkTripleStrLit}
@@ -1760,12 +1761,32 @@ proc getStrOrChar*(a: PNode): string =
     #internalError(a.info, "getStrOrChar")
     #result = ""
 
-proc isGenericRoutine*(s: PSym): bool =
-  case s.kind
-  of skProcKinds:
-    result = sfFromGeneric in s.flags or
-             (s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty)
-  else: discard
+proc isGenericParams*(n: PNode): bool {.inline.} =
+  ## used to judge whether a node is generic params.
+  n != nil and n.kind == nkGenericParams
+
+proc isGenericRoutine*(n: PNode): bool  {.inline.} =
+  n != nil and n.kind in callableDefs and n[genericParamsPos].isGenericParams
+
+proc isGenericRoutineStrict*(s: PSym): bool {.inline.} =
+  ## determines if this symbol represents a generic routine
+  ## the unusual name is so it doesn't collide and eventually replaces
+  ## `isGenericRoutine`
+  s.kind in skProcKinds and s.ast.isGenericRoutine
+
+proc isGenericRoutine*(s: PSym): bool {.inline.} =
+  ## determines if this symbol represents a generic routine or an instance of
+  ## one. This should be renamed accordingly and `isGenericRoutineStrict`
+  ## should take this name instead.
+  ##
+  ## Warning/XXX: Unfortunately, it considers a proc kind symbol flagged with
+  ## sfFromGeneric as a generic routine. Instead this should likely not be the
+  ## case and the concepts should be teased apart:
+  ## - generic definition
+  ## - generic instance
+  ## - either generic definition or instance
+  s.kind in skProcKinds and (sfFromGeneric in s.flags or
+                             s.ast.isGenericRoutine)
 
 proc skipGenericOwner*(s: PSym): PSym =
   ## Generic instantiations are owned by their originating generic
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index 6010ba43d..d20ed8e26 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -268,7 +268,7 @@ proc genOp(c: var Con; op: PSym; dest: PNode): PNode =
 
 proc genOp(c: var Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode =
   var op = getAttachedOp(c.graph, t, kind)
-  if op == nil or op.ast[genericParamsPos].kind != nkEmpty:
+  if op == nil or op.ast.isGenericRoutine:
     # give up and find the canonical type instead:
     let h = sighashes.hashType(t, {CoType, CoConsiderOwned, CoDistinct})
     let canon = c.graph.canonTypes.getOrDefault(h)
@@ -278,7 +278,7 @@ proc genOp(c: var Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode
     #echo dest.typ.id
     globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
       "' operator not found for type " & typeToString(t))
-  elif op.ast[genericParamsPos].kind != nkEmpty:
+  elif op.ast.isGenericRoutine:
     globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
       "' operator is generic")
   dbg:
diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim
index db31b632a..889c65cc0 100644
--- a/compiler/liftdestructors.nim
+++ b/compiler/liftdestructors.nim
@@ -354,7 +354,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
     #  markUsed(c.g.config, c.info, op, c.g.usageSym)
     onUse(c.info, op)
     # We also now do generic instantiations in the destructor lifting pass:
-    if op.ast[genericParamsPos].kind != nkEmpty:
+    if op.ast.isGenericRoutine:
       op = instantiateGeneric(c, op, t, t.typeInst)
       field = op
       #echo "trying to use ", op.ast
@@ -370,7 +370,7 @@ proc addDestructorCall(c: var TLiftCtx; orig: PType; body, x: PNode) =
   var op = t.destructor
 
   if op != nil and sfOverriden in op.flags:
-    if op.ast[genericParamsPos].kind != nkEmpty:
+    if op.ast.isGenericRoutine:
       # patch generic destructor:
       op = instantiateGeneric(c, op, t, t.typeInst)
       setAttachedOp(c.g, c.idgen.module, t, attachedDestructor, op)
@@ -394,7 +394,7 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
     var op = t.destructor
     if op != nil and sfOverriden in op.flags:
 
-      if op.ast[genericParamsPos].kind != nkEmpty:
+      if op.ast.isGenericRoutine:
         # patch generic destructor:
         op = instantiateGeneric(c, op, t, t.typeInst)
         setAttachedOp(c.g, c.idgen.module, t, attachedDestructor, op)
@@ -1021,7 +1021,7 @@ proc patchBody(g: ModuleGraph; c: PContext; n: PNode; info: TLineInfo; idgen: Id
 
       let op = getAttachedOp(g, t, attachedDestructor)
       if op != nil:
-        if op.ast[genericParamsPos].kind != nkEmpty:
+        if op.ast.isGenericRoutine:
           internalError(g.config, info, "resolved destructor is generic")
         if op.magic == mDestroy:
           internalError(g.config, info, "patching mDestroy with mDestroy?")
@@ -1031,7 +1031,7 @@ proc patchBody(g: ModuleGraph; c: PContext; n: PNode; info: TLineInfo; idgen: Id
 proc inst(g: ModuleGraph; c: PContext; t: PType; kind: TTypeAttachedOp; idgen: IdGenerator;
           info: TLineInfo) =
   let op = getAttachedOp(g, t, kind)
-  if op != nil and op.ast != nil and op.ast[genericParamsPos].kind != nkEmpty:
+  if op != nil and op.ast != nil and op.ast.isGenericRoutine:
     if t.typeInst != nil:
       var a: TLiftCtx
       a.info = info
diff --git a/compiler/lookups.nim b/compiler/lookups.nim
index 0f6ec151a..e928f707e 100644
--- a/compiler/lookups.nim
+++ b/compiler/lookups.nim
@@ -287,6 +287,7 @@ proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) =
 
 proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string;
                         conflictsWith: TLineInfo) =
+  ## Emit a redefinition error if in non-interactive mode
   if c.config.cmd != cmdInteractive:
     localError(c.config, info,
       "redefinition of '$1'; previous declaration here: $2" %
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 8fbdd3579..e0a468419 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -60,7 +60,7 @@ const
     wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
     wThread, wAsmNoStackFrame,
     wRaises, wLocks, wTags, wRequires, wEnsures,
-    wGcSafe, wCodegenDecl, wNoInit}
+    wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
   typePragmas* = declPragmas + {wMagic, wAcyclic,
     wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
     wIncompleteStruct, wCompleteStruct, wByCopy, wByRef,
@@ -1245,23 +1245,23 @@ proc mergePragmas(n, pragmas: PNode) =
   else:
     for p in pragmas: n[pragmasPos].add p
 
-proc implicitPragmas*(c: PContext, sym: PSym, n: PNode,
+proc implicitPragmas*(c: PContext, sym: PSym, info: TLineInfo,
                       validPragmas: TSpecialWords) =
   if sym != nil and sym.kind != skModule:
     for it in c.optionStack:
       let o = it.otherPragmas
       if not o.isNil and sfFromGeneric notin sym.flags: # see issue #12985
-        pushInfoContext(c.config, n.info)
+        pushInfoContext(c.config, info)
         var i = 0
         while i < o.len:
           if singlePragma(c, sym, o, i, validPragmas, true, false):
-            internalError(c.config, n.info, "implicitPragmas")
+            internalError(c.config, info, "implicitPragmas")
           inc i
         popInfoContext(c.config)
         if sym.kind in routineKinds and sym.ast != nil: mergePragmas(sym.ast, o)
 
     if lfExportLib in sym.loc.flags and sfExportc notin sym.flags:
-      localError(c.config, n.info, ".dynlib requires .exportc")
+      localError(c.config, info, ".dynlib requires .exportc")
     var lib = c.optionStack[^1].dynlib
     if {lfDynamicLib, lfHeader} * sym.loc.flags == {} and
         sfImportc in sym.flags and lib != nil:
@@ -1291,4 +1291,11 @@ proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords;
             isStatement: bool) =
   if n == nil: return
   pragmaRec(c, sym, n, validPragmas, isStatement)
-  implicitPragmas(c, sym, n, validPragmas)
+  # XXX: in the case of a callable def, this should use its info
+  implicitPragmas(c, sym, n.info, validPragmas)
+
+proc pragmaCallable*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords,
+                    isStatement: bool = false) =
+  if n == nil: return
+  if n[pragmasPos].kind != nkEmpty:
+    pragmaRec(c, sym, n[pragmasPos], validPragmas, isStatement)
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 70fd7b894..5593f8b02 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -36,7 +36,6 @@ proc semProcBody(c: PContext, n: PNode): PNode
 proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode
 proc changeType(c: PContext; n: PNode, newType: PType, check: bool)
 
-proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode
 proc semTypeNode(c: PContext, n: PNode, prev: PType): PType
 proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode
 proc semOpAux(c: PContext, n: PNode)
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index 0e79dec26..99979c382 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -445,7 +445,7 @@ proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
   let a = if a.kind == nkHiddenDeref: a[0] else: a
   if a.kind == nkHiddenCallConv and a[0].kind == nkSym:
     let s = a[0].sym
-    if s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty:
+    if s.isGenericRoutineStrict:
       let finalCallee = generateInstance(c, s, x.bindings, a.info)
       a[0].sym = finalCallee
       a[0].typ = finalCallee.typ
@@ -524,7 +524,7 @@ proc semResolvedCall(c: PContext, x: TCandidate,
       if result.typ.kind == tyError: incl result.typ.flags, tfCheckedForDestructor
     return
   let gp = finalCallee.ast[genericParamsPos]
-  if gp.kind != nkEmpty:
+  if gp.isGenericParams:
     if x.calleeSym.kind notin {skMacro, skTemplate}:
       if x.calleeSym.magic in {mArrGet, mArrPut}:
         finalCallee = x.calleeSym
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index a7cc235c0..7bd903657 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -2856,7 +2856,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   of nkCurly: result = semSetConstr(c, n)
   of nkBracket: result = semArrayConstr(c, n, flags)
   of nkObjConstr: result = semObjConstr(c, n, flags)
-  of nkLambdaKinds: result = semLambda(c, n, flags)
+  of nkLambdaKinds: result = semProcAux(c, n, skProc, lambdaPragmas, flags)
   of nkDerefExpr: result = semDeref(c, n)
   of nkAddr:
     result = n
diff --git a/compiler/seminst.nim b/compiler/seminst.nim
index 62cbdbcc1..68ab2a310 100644
--- a/compiler/seminst.nim
+++ b/compiler/seminst.nim
@@ -358,7 +358,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
 
   openScope(c)
   let gp = n[genericParamsPos]
-  internalAssert c.config, gp.kind != nkEmpty
+  internalAssert c.config, gp.kind == nkGenericParams
   n[namePos] = newSymNode(result)
   pushInfoContext(c.config, info, fn.detailedInfo)
   var entry = TInstantiation.new
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 1fe540a65..a98103478 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1523,75 +1523,9 @@ proc semProcAnnotation(c: PContext, prc: PNode;
 
     return
 
-proc setGenericParamsMisc(c: PContext; n: PNode): PNode =
-  let orig = n[genericParamsPos]
-  # we keep the original params around for better error messages, see
-  # issue https://github.com/nim-lang/Nim/issues/1713
-  result = semGenericParamList(c, orig)
-  if n[miscPos].kind == nkEmpty:
-    n[miscPos] = newTree(nkBracket, c.graph.emptyNode, orig)
-  else:
-    n[miscPos][1] = orig
-  n[genericParamsPos] = result
-
-proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
-  # XXX semProcAux should be good enough for this now, we will eventually
-  # remove semLambda
-  result = semProcAnnotation(c, n, lambdaPragmas)
-  if result != nil: return result
-  result = n
-  checkSonsLen(n, bodyPos + 1, c.config)
-  var s: PSym
-  if n[namePos].kind != nkSym:
-    s = newSym(skProc, c.cache.idAnon, nextSymId c.idgen, getCurrOwner(c), n.info)
-    s.ast = n
-    n[namePos] = newSymNode(s)
-  else:
-    s = n[namePos].sym
-  pushOwner(c, s)
-  openScope(c)
-  var gp: PNode
-  if n[genericParamsPos].kind != nkEmpty:
-    gp = setGenericParamsMisc(c, n)
-  else:
-    gp = newNodeI(nkGenericParams, n.info)
-
-  if n[paramsPos].kind != nkEmpty:
-    semParamList(c, n[paramsPos], gp, s)
-    # paramsTypeCheck(c, s.typ)
-    if gp.len > 0 and n[genericParamsPos].kind == nkEmpty:
-      # we have a list of implicit type parameters:
-      n[genericParamsPos] = gp
-  else:
-    s.typ = newProcType(c, n.info)
-  if n[pragmasPos].kind != nkEmpty:
-    pragma(c, s, n[pragmasPos], lambdaPragmas)
-  s.options = c.config.options
-  if n[bodyPos].kind != nkEmpty:
-    if sfImportc in s.flags:
-      localError(c.config, n[bodyPos].info, errImplOfXNotAllowed % s.name.s)
-    #if efDetermineType notin flags:
-    # XXX not good enough; see tnamedparamanonproc.nim
-    if gp.len == 0 or (gp.len == 1 and tfRetType in gp[0].typ.flags):
-      pushProcCon(c, s)
-      addResult(c, n, s.typ[0], skProc)
-      s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos]))
-      trackProc(c, s, s.ast[bodyPos])
-      popProcCon(c)
-    elif efOperand notin flags:
-      localError(c.config, n.info, errGenericLambdaNotAllowed)
-    sideEffectsCheck(c, s)
-  else:
-    localError(c.config, n.info, errImplOfXexpected % s.name.s)
-  closeScope(c)           # close scope for parameters
-  popOwner(c)
-  result.typ = s.typ
-  if optOwnedRefs in c.config.globalOptions:
-    result.typ = makeVarType(c, result.typ, tyOwned)
-
 proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode {.nosinks.} =
+  ## used for resolving 'auto' in lambdas based on their callsite 
   var n = n
-
   let original = n[namePos].sym
   let s = original #copySym(original, false)
   #incl(s.flags, sfFromGeneric)
@@ -1798,11 +1732,6 @@ proc cursorInProc(conf: ConfigRef; n: PNode): bool =
   if n.info.fileIndex == conf.m.trackPos.fileIndex:
     result = cursorInProcAux(conf, n)
 
-type
-  TProcCompilationSteps = enum
-    stepRegisterSymbol,
-    stepDetermineType,
-
 proc hasObjParam(s: PSym): bool =
   var t = s.typ
   for col in 1..<t.len:
@@ -1814,7 +1743,7 @@ proc finishMethod(c: PContext, s: PSym) =
     methodDef(c.graph, c.idgen, s)
 
 proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
-  if isGenericRoutine(s):
+  if s.isGenericRoutine:
     let tt = s.typ
     var foundObj = false
     # we start at 1 for now so that tparsecombnum continues to compile.
@@ -1840,28 +1769,45 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
     else:
       localError(c.config, n.info, "'method' needs a parameter that has an object type")
 
+proc setGenericParamsMisc(c: PContext; n: PNode) =
+  let orig = n[genericParamsPos]
+
+  doAssert orig.kind in {nkEmpty, nkGenericParams}
+
+  if n[genericParamsPos].kind == nkEmpty:
+    n[genericParamsPos] = newNodeI(nkGenericParams, n.info)
+  else:
+    # we keep the original params around for better error messages, see
+    # issue https://github.com/nim-lang/Nim/issues/1713
+    n[genericParamsPos] = semGenericParamList(c, orig)
+
+  if n[miscPos].kind == nkEmpty:
+    n[miscPos] = newTree(nkBracket, c.graph.emptyNode, orig)
+  else:
+    n[miscPos][1] = orig
+
 proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
-                validPragmas: TSpecialWords,
-                phase = stepRegisterSymbol): PNode =
+                validPragmas: TSpecialWords, flags: TExprFlags = {}): PNode =
   result = semProcAnnotation(c, n, validPragmas)
   if result != nil: return result
   result = n
   checkMinSonsLen(n, bodyPos + 1, c.config)
+
+  let isAnon = n[namePos].kind == nkEmpty
+
   var s: PSym
-  var typeIsDetermined = false
-  var isAnon = false
-  if n[namePos].kind != nkSym:
-    assert phase == stepRegisterSymbol
 
-    if n[namePos].kind == nkEmpty:
-      s = newSym(kind, c.cache.idAnon, nextSymId c.idgen, getCurrOwner(c), n.info)
-      incl(s.flags, sfUsed)
-      isAnon = true
-    else:
-      s = semIdentDef(c, n[0], kind)
+  case n[namePos].kind
+  of nkEmpty:
+    s = newSym(kind, c.cache.idAnon, nextSymId c.idgen, c.getCurrOwner, n.info)
+    s.flags.incl sfUsed
+    n[namePos] = newSymNode(s)
+  of nkSym:
+    s = n[namePos].sym
+    s.owner = c.getCurrOwner
+  else:
+    s = semIdentDef(c, n[namePos], kind)
     n[namePos] = newSymNode(s)
-    s.ast = n
-    #s.scope = c.currentScope
     when false:
       # disable for now
       if sfNoForward in c.module.flags and
@@ -1869,37 +1815,50 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
         addInterfaceOverloadableSymAt(c, c.currentScope, s)
         s.flags.incl sfForward
         return
-  else:
-    s = n[namePos].sym
-    s.owner = getCurrOwner(c)
-    typeIsDetermined = s.typ == nil
-    s.ast = n
-    #s.scope = c.currentScope
 
+  assert s.kind in skProcKinds
+
+  s.ast = n
   s.options = c.config.options
+  #s.scope = c.currentScope
 
-  # before compiling the proc body, set as current the scope
+  # before compiling the proc params & body, set as current the scope
   # where the proc was declared
-  let oldScope = c.currentScope
-  #c.currentScope = s.scope
+  let delcarationScope = c.currentScope
   pushOwner(c, s)
   openScope(c)
-  var gp: PNode
-  if n[genericParamsPos].kind != nkEmpty:
-    gp = setGenericParamsMisc(c, n)
-  else:
-    gp = newNodeI(nkGenericParams, n.info)
+
   # process parameters:
+  # generic parameters, parameters, and also the implicit generic parameters
+  # within are analysed. This is often the entirety of their semantic analysis
+  # but later we will have to do a check for forward declarations, which can by
+  # way of pragmas, default params, and so on invalidate this parsing.
+  # Nonetheless, we need to carry out this analysis to perform the search for a
+  # potential forward declaration.
+  setGenericParamsMisc(c, n)
+
   if n[paramsPos].kind != nkEmpty:
-    semParamList(c, n[paramsPos], gp, s)
-    if gp.len > 0:
-      if n[genericParamsPos].kind == nkEmpty:
-        # we have a list of implicit type parameters:
-        n[genericParamsPos] = gp
-        # check for semantics again:
-        # semParamList(c, n[ParamsPos], nil, s)
+    semParamList(c, n[paramsPos], n[genericParamsPos], s)
+    # we maybe have implicit type parameters:
   else:
     s.typ = newProcType(c, n.info)
+
+  if n[genericParamsPos].safeLen == 0:
+    # if there exist no explicit or implicit generic parameters, then this is
+    # at most a nullary generic (generic with no type params). Regardless of
+    # whether it's a nullary generic or non-generic, we restore the original.
+    # In the case of `nkEmpty` it's non-generic and an empty `nkGeneircParams`
+    # is a nullary generic.
+    #
+    # Remarks about nullary generics vs non-generics:
+    # The difference between a non-generic and nullary generic is minor in
+    # most cases but there are subtle and significant differences as well.
+    # Due to instantiation that generic procs go through, a static echo in the
+    # body of a nullary  generic will not be executed immediately, as it's
+    # instantiated and not immediately evaluated.
+    n[genericParamsPos] = n[miscPos][1]
+    n[miscPos] = c.graph.emptyNode
+
   if tfTriggersCompileTime in s.typ.flags: incl(s.flags, sfCompileTime)
   if n[patternPos].kind != nkEmpty:
     n[patternPos] = semPattern(c, n[patternPos])
@@ -1908,47 +1867,66 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
   elif s.kind == skFunc:
     incl(s.flags, sfNoSideEffect)
     incl(s.typ.flags, tfNoSideEffect)
-  var (proto, comesFromShadowScope) = if isAnon: (nil, false)
-                                      else: searchForProc(c, oldScope, s)
+
+  var (proto, comesFromShadowScope) =
+      if isAnon: (nil, false)
+      else: searchForProc(c, delcarationScope, 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
+    ## In cases such as a macro generating a proc with a gensymmed name we
+    ## know `searchForProc` will not find it and sfForward will be set. In
+    ## such scenarios the sym is shared between forward declaration and we
+    ## can treat the `s` as the proto.
     proto = s
-  if proto == nil:
-    if s.kind == skIterator:
-      if s.typ.callConv != ccClosure:
-        s.typ.callConv = if isAnon: ccClosure else: ccInline
-    else:
+  let hasProto = proto != nil
+
+  # set the default calling conventions
+  case s.kind
+  of skIterator:
+    if s.typ.callConv != ccClosure:
+      s.typ.callConv = if isAnon: ccClosure else: ccInline
+  of skMacro, skTemplate:
+    # we don't bother setting calling conventions for macros and templates
+    discard
+  else:
+    # NB: procs with a forward decl have theirs determined by the forward decl
+    if not hasProto:
+      # in this case we're either a forward declaration or we're an impl without
+      # a forward decl. We set the calling convention or will be set during
+      # pragma analysis further down.
       s.typ.callConv = lastOptionEntry(c).defaultCC
-    # add it here, so that recursive procs are possible:
-    if sfGenSym in s.flags:
-      if s.owner == nil: s.owner = getCurrOwner(c)
-    elif kind in OverloadableSyms:
-      if not typeIsDetermined:
-        addInterfaceOverloadableSymAt(c, oldScope, s)
-    else:
-      if not typeIsDetermined:
-        addInterfaceDeclAt(c, oldScope, s)
-    if n[pragmasPos].kind != nkEmpty:
-      pragma(c, s, n[pragmasPos], validPragmas)
+
+  if not hasProto and sfGenSym notin s.flags: #and not isAnon:
+    if s.kind in OverloadableSyms:
+      addInterfaceOverloadableSymAt(c, delcarationScope, s)
     else:
-      implicitPragmas(c, s, n, validPragmas)
-    styleCheckDef(c.config, s)
-    onDef(n[namePos].info, s)
-  else:
-    if n[pragmasPos].kind != nkEmpty:
-      pragma(c, s, n[pragmasPos], validPragmas)
-      # To ease macro generation that produce forwarded .async procs we now
-      # allow a bit redundancy in the pragma declarations. The rule is
-      # a prototype's pragma list must be a superset of the current pragma
-      # list.
-      # XXX This needs more checks eventually, for example that external
-      # linking names do agree:
-      if proto.typ.callConv != s.typ.callConv or proto.typ.flags < s.typ.flags:
-        localError(c.config, n[pragmasPos].info, errPragmaOnlyInHeaderOfProcX %
-          ("'" & proto.name.s & "' from " & c.config$proto.info))
-    styleCheckDef(c.config, s)
+      addInterfaceDeclAt(c, delcarationScope, s)
+
+  pragmaCallable(c, s, n, validPragmas)
+  if not hasProto:
+    implicitPragmas(c, s, n.info, validPragmas)
+
+  # To ease macro generation that produce forwarded .async procs we now
+  # allow a bit redundancy in the pragma declarations. The rule is
+  # a prototype's pragma list must be a superset of the current pragma
+  # list.
+  # XXX This needs more checks eventually, for example that external
+  # linking names do agree:
+  if hasProto and (
+      # calling convention mismatch
+      tfExplicitCallConv in s.typ.flags and proto.typ.callConv != s.typ.callConv or
+      # implementation has additional pragmas
+      proto.typ.flags < s.typ.flags):
+    localError(c.config, n[pragmasPos].info, errPragmaOnlyInHeaderOfProcX %
+      ("'" & proto.name.s & "' from " & c.config$proto.info &
+        " '" & s.name.s & "' from " & c.config$s.info))
+
+  styleCheckDef(c.config, s)
+  if hasProto:
     onDefResolveForward(n[namePos].info, proto)
+  else:
+    onDef(n[namePos].info, s)
+
+  if hasProto:
     if sfForward notin proto.flags and proto.magic == mNone:
       wrongRedefinition(c, n.info, proto.name.s, proto.info)
     if not comesFromShadowScope:
@@ -1957,7 +1935,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     suggestSym(c.graph, s.info, proto, c.graph.usageSym)
     closeScope(c)         # close scope with wrong parameter symbols
     openScope(c)          # open scope for old (correct) parameter symbols
-    if proto.ast[genericParamsPos].kind != nkEmpty:
+    if proto.ast[genericParamsPos].isGenericParams:
       addGenericParamListToScope(c, proto.ast[genericParamsPos])
     addParams(c, proto.typ.n, proto.kind)
     proto.info = s.info       # more accurate line information
@@ -1974,31 +1952,42 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     popOwner(c)
     pushOwner(c, s)
 
-  if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n)
-  if s.name.s[0] in {'.', '('}:
-    if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}:
-      localError(c.config, n.info, "the overloaded " & s.name.s &
-        " operator has to be enabled with {.experimental: \"dotOperators\".}")
-    elif s.name.s == "()" and callOperator notin c.features:
-      localError(c.config, n.info, "the overloaded " & s.name.s &
-        " operator has to be enabled with {.experimental: \"callOperator\".}")
+  if not isAnon:
+    if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n)
+    elif s.name.s[0] in {'.', '('}:
+      if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}:
+        localError(c.config, n.info, "the overloaded " & s.name.s &
+          " operator has to be enabled with {.experimental: \"dotOperators\".}")
+      elif s.name.s == "()" and callOperator notin c.features:
+        localError(c.config, n.info, "the overloaded " & s.name.s &
+          " operator has to be enabled with {.experimental: \"callOperator\".}")
 
   if n[bodyPos].kind != nkEmpty and sfError notin s.flags:
     # for DLL generation we allow sfImportc to have a body, for use in VM
     if sfBorrow in s.flags:
       localError(c.config, n[bodyPos].info, errImplOfXNotAllowed % s.name.s)
-    let usePseudoGenerics = kind in {skMacro, skTemplate}
-    # Macros and Templates can have generic parameters, but they are
-    # only used for overload resolution (there is no instantiation of
-    # the symbol, so we must process the body now)
-    if not usePseudoGenerics and c.config.ideCmd in {ideSug, ideCon} and not
+    if c.config.ideCmd in {ideSug, ideCon} and s.kind notin {skMacro, skTemplate} and not
         cursorInProc(c.config, n[bodyPos]):
-      discard "speed up nimsuggest"
+      # speed up nimsuggest
       if s.kind == skMethod: semMethodPrototype(c, s, n)
+    elif isAnon:
+      let gp = n[genericParamsPos]
+      if gp.kind == nkEmpty or (gp.len == 1 and tfRetType in gp[0].typ.flags):
+        # absolutely no generics (empty) or a single generic return type are
+        # allowed, everything else, including a nullary generic is an error.
+        pushProcCon(c, s)
+        addResult(c, n, s.typ[0], skProc)
+        s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos]))
+        trackProc(c, s, s.ast[bodyPos])
+        popProcCon(c)
+      elif efOperand notin flags:
+        localError(c.config, n.info, errGenericLambdaNotAllowed)
     else:
       pushProcCon(c, s)
-      if n[genericParamsPos].kind == nkEmpty or usePseudoGenerics:
-        if not usePseudoGenerics and s.magic == mNone: paramsTypeCheck(c, s.typ)
+      if n[genericParamsPos].kind == nkEmpty or s.kind in {skMacro, skTemplate}:
+        # Macros and Templates can have generic parameters, but they are only
+        # used for overload resolution (there is no instantiation of the symbol)
+        if s.kind notin {skMacro, skTemplate} and s.magic == mNone: paramsTypeCheck(c, s.typ)
 
         maybeAddResult(c, s, n)
         # semantic checking also needed with importc in case used in VM
@@ -2006,9 +1995,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
         # unfortunately we cannot skip this step when in 'system.compiles'
         # context as it may even be evaluated in 'system.compiles':
         trackProc(c, s, s.ast[bodyPos])
-        if s.kind == skMethod: semMethodPrototype(c, s, n)
       else:
-        if (s.typ[0] != nil and kind != skIterator) or kind == skMacro:
+        if (s.typ[0] != nil and s.kind != skIterator):
           addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nextSymId c.idgen, nil, n.info))
 
         openScope(c)
@@ -2016,20 +2004,17 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
         closeScope(c)
         if s.magic == mNone:
           fixupInstantiatedSymbols(c, s)
-        if s.kind == skMethod: semMethodPrototype(c, s, n)
-      if sfImportc in s.flags:
-        # don't ignore the body in case used in VM
-        # n[bodyPos] = c.graph.emptyNode
-        discard
+      if s.kind == skMethod: semMethodPrototype(c, s, n)
       popProcCon(c)
   else:
-    if s.kind in {skProc, skFunc} and s.typ[0] != nil and s.typ[0].kind == tyUntyped:
-      # `auto` is represented as `tyUntyped` at this point in compilation.
-      localError(c.config, n[paramsPos][0].info, "return type 'auto' cannot be used in forward declarations")
-
     if s.kind == skMethod: semMethodPrototype(c, s, n)
-    if proto != nil: localError(c.config, n.info, errImplOfXexpected % proto.name.s)
+    if hasProto: localError(c.config, n.info, errImplOfXexpected % proto.name.s)
     if {sfImportc, sfBorrow, sfError} * s.flags == {} and s.magic == mNone:
+      # this is a forward declaration and we're building the prototype
+      if s.kind in {skProc, skFunc} and s.typ[0] != nil and s.typ[0].kind == tyUntyped:
+        # `auto` is represented as `tyUntyped` at this point in compilation.
+        localError(c.config, n[paramsPos][0].info, "return type 'auto' cannot be used in forward declarations")
+
       incl(s.flags, sfForward)
       incl(s.flags, sfWasForwarded)
     elif sfBorrow in s.flags: semBorrow(c, n, s)
@@ -2044,15 +2029,14 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     result.typ = s.typ
     if optOwnedRefs in c.config.globalOptions:
       result.typ = makeVarType(c, result.typ, tyOwned)
-  if isTopLevel(c) and s.kind != skIterator and
-      s.typ.callConv == ccClosure:
+  elif isTopLevel(c) and s.kind != skIterator and s.typ.callConv == ccClosure:
     localError(c.config, s.info, "'.closure' calling convention for top level routines is invalid")
 
 proc determineType(c: PContext, s: PSym) =
   if s.typ != nil: return
   #if s.magic != mNone: return
   #if s.ast.isNil: return
-  discard semProcAux(c, s.ast, s.kind, {}, stepDetermineType)
+  discard semProcAux(c, s.ast, s.kind, {})
 
 proc semIterator(c: PContext, n: PNode): PNode =
   # gensym'ed iterator?
@@ -2086,7 +2070,9 @@ proc semProc(c: PContext, n: PNode): PNode =
   result = semProcAux(c, n, skProc, procPragmas)
 
 proc semFunc(c: PContext, n: PNode): PNode =
-  result = semProcAux(c, n, skFunc, procPragmas)
+  let validPragmas = if n[namePos].kind != nkEmpty: procPragmas
+                     else: lambdaPragmas
+  result = semProcAux(c, n, skFunc, validPragmas)
 
 proc semMethod(c: PContext, n: PNode): PNode =
   if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "method")
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 2f87fb8f2..568873269 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -636,10 +636,9 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
       param.flags.incl sfTemplateParam
       param.flags.excl sfGenSym
       if param.typ.kind != tyUntyped: allUntyped = false
-    if gp.len > 0:
-      if n[genericParamsPos].kind == nkEmpty:
-        # we have a list of implicit type parameters:
-        n[genericParamsPos] = gp
+    if gp.len > 0 and n[genericParamsPos].kind == nkEmpty:
+      # we have a list of implicit type parameters:
+      n[genericParamsPos] = gp
   else:
     s.typ = newTypeS(tyProc, c)
     # XXX why do we need tyTyped as a return type again?
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index b8dd19c51..dcab9a884 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -1233,7 +1233,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
     if hasDefault:
       def = a[^1]
       block determineType:
-        if genericParams != nil and genericParams.len > 0:
+        if genericParams.isGenericParams:
           def = semGenericStmt(c, def)
           if hasUnresolvedArgs(c, def):
             def.typ = makeTypeFromExpr(c, def.copyTree)
@@ -1361,7 +1361,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
           result.flags.excl tfHasMeta
       result.n.typ = r
 
-  if genericParams != nil and genericParams.len > 0:
+  if genericParams.isGenericParams:
     for n in genericParams:
       if {sfUsed, sfAnon} * n.sym.flags == {}:
         result.flags.incl tfUnresolved
@@ -1666,7 +1666,7 @@ proc semProcTypeWithScope(c: PContext, n: PNode,
     # we construct a fake 'nkProcDef' for the 'mergePragmas' inside 'implicitPragmas'...
     s.ast = newTree(nkProcDef, newNodeI(nkEmpty, n.info), newNodeI(nkEmpty, n.info),
         newNodeI(nkEmpty, n.info), newNodeI(nkEmpty, n.info), newNodeI(nkEmpty, n.info))
-    implicitPragmas(c, s, n, {wTags, wRaises})
+    implicitPragmas(c, s, n.info, {wTags, wRaises})
     when useEffectSystem: setEffectsForProcType(c.graph, result, s.ast[pragmasPos])
   closeScope(c)
 
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 392fe9737..7d7382d18 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -2265,7 +2265,7 @@ proc genProc(c: PCtx; s: PSym): int =
     genParams(c, s.typ.n)
 
     # allocate additional space for any generically bound parameters
-    if s.kind == skMacro and s.ast[genericParamsPos].kind != nkEmpty:
+    if s.kind == skMacro and s.isGenericRoutineStrict:
       genGenericParams(c, s.ast[genericParamsPos])
 
     if tfCapturesEnv in s.typ.flags:
diff --git a/tests/constr/tnocompiletimefunc.nim b/tests/constr/tnocompiletimefunc.nim
new file mode 100644
index 000000000..a95648c0f
--- /dev/null
+++ b/tests/constr/tnocompiletimefunc.nim
@@ -0,0 +1,14 @@
+discard """
+  errormsg: "request to generate code for .compileTime proc: foo"
+"""
+
+# ensure compileTime funcs can't be called from runtime
+
+func foo(a: int): int {.compileTime.} =
+  a * a
+
+proc doAThing(): int =
+  for i in 0..2:
+    result += foo(i)
+
+echo doAThing()
diff --git a/tests/constr/tnocompiletimefunclambda.nim b/tests/constr/tnocompiletimefunclambda.nim
new file mode 100644
index 000000000..d134eea40
--- /dev/null
+++ b/tests/constr/tnocompiletimefunclambda.nim
@@ -0,0 +1,6 @@
+discard """
+  errormsg: "request to generate code for .compileTime proc: :anonymous"
+"""
+
+let a = func(a: varargs[int]) {.compileTime, closure.} =
+  discard a[0]
\ No newline at end of file
diff --git a/tests/converter/tconverter.nim b/tests/converter/tconverter.nim
new file mode 100644
index 000000000..0bf067c55
--- /dev/null
+++ b/tests/converter/tconverter.nim
@@ -0,0 +1,11 @@
+discard """
+  output: '''fooo fooo'''
+"""
+
+converter intToString[T](i: T): string = "fooo"
+
+let
+  foo: string = 1
+  bar: string = intToString(2)
+
+echo foo, " ", bar
\ No newline at end of file
diff --git a/tests/generics/tnullary_generics.nim b/tests/generics/tnullary_generics.nim
new file mode 100644
index 000000000..c79558ee3
--- /dev/null
+++ b/tests/generics/tnullary_generics.nim
@@ -0,0 +1,26 @@
+discard """
+  nimout: '''
+hah
+hey
+hey
+hah
+'''
+"""
+
+# non-generic
+proc foo(s: string) =
+  static: echo "hah"
+  echo s
+
+static: echo "hey"
+
+foo("hoo")
+
+# nullary generic
+proc bar[](s: string) =
+  static: echo "hah"
+  echo s
+
+static: echo "hey"
+
+bar("hoo")
diff --git a/tests/statictypes/tstatictypes.nim b/tests/statictypes/tstatictypes.nim
index 41c060138..a76276d2c 100644
--- a/tests/statictypes/tstatictypes.nim
+++ b/tests/statictypes/tstatictypes.nim
@@ -2,6 +2,8 @@ discard """
 nimout: '''
 staticAlialProc instantiated with 358
 staticAlialProc instantiated with 368
+0: Foo
+1: Bar
 '''
 output: '''
 16
@@ -289,8 +291,10 @@ macro fooParam(x: static array[2, string]): untyped =
     echo i, ": ", val
 
 macro barParam(x: static Table[int, string]): untyped =
-  for i, val in x:
+  let barParamInsides = proc(i: int, val: string): NimNode =
     echo i, ": ", val
+  for i, val in x:
+    discard barParamInsides(i, val)
 
 fooM()
 barM()