summary refs log tree commit diff stats
path: root/compiler/pragmas.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/pragmas.nim')
-rw-r--r--compiler/pragmas.nim1578
1 files changed, 984 insertions, 594 deletions
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index bc3771700..9a298cd90 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -10,219 +10,316 @@
 # This module implements semantic checking for pragmas
 
 import
-  os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer,
-  wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees,
-  rodread, types, lookups
+  condsyms, ast, astalgo, idents, semdata, msgs, renderer,
+  wordrecg, ropes, options, extccomp, magicsys, trees,
+  types, lookups, lineinfos, pathutils, linter, modulepaths
+
+from sigmatch import trySuggestPragmas
+
+import std/[os, math, strutils]
+
+when defined(nimPreviewSlimSystem):
+  import std/assertions
+
+from ic / ic import addCompilerProc
 
 const
   FirstCallConv* = wNimcall
   LastCallConv* = wNoconv
 
 const
-  procPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
-    wMagic, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader,
-    wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge,
-    wBorrow, wExtern, wImportCompilerProc, wThread, wImportCpp, wImportObjC,
-    wAsmNoStackFrame, wError, wDiscardable, wNoInit, wDestructor, wCodegenDecl,
-    wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe,
-    wOverride, wConstructor, wExportNims, wUsed}
+  declPragmas = {wImportc, wImportObjC, wImportCpp, wImportJs, wExportc, wExportCpp,
+    wExportNims, wExtern, wDeprecated, wNodecl, wError, wUsed}
+    ## common pragmas for declarations, to a good approximation
+  procPragmas* = declPragmas + {FirstCallConv..LastCallConv,
+    wMagic, wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
+    wCompilerProc, wNonReloadable, wCore, wProcVar, wVarargs, wCompileTime,
+    wBorrow, wImportCompilerProc, wThread,
+    wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
+    wGensym, wInject, wRaises, wEffectsOf, wTags, wForbids, wLocks, wDelegator, wGcSafe,
+    wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
+    wRequires, wEnsures, wEnforceNoRaises, wSystemRaisesDefect, wVirtual, wQuirky, wMember}
   converterPragmas* = procPragmas
   methodPragmas* = procPragmas+{wBase}-{wImportCpp}
-  templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty,
-    wDelegator, wExportNims, wUsed}
-  macroPragmas* = {FirstCallConv..LastCallConv, wImmediate, wImportc, wExportc,
-    wNodecl, wMagic, wNosideeffect, wCompilerproc, wDeprecated, wExtern,
-    wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wDelegator,
-    wExportNims, wUsed}
-  iteratorPragmas* = {FirstCallConv..LastCallConv, wNosideeffect, wSideeffect,
-    wImportc, wExportc, wNodecl, wMagic, wDeprecated, wBorrow, wExtern,
-    wImportCpp, wImportObjC, wError, wDiscardable, wGensym, wInject, wRaises,
-    wTags, wLocks, wGcSafe, wExportNims, wUsed}
-  exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe}
-  stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks,
-    wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
-    wLinedir, wStacktrace, wLinetrace, wOptimization, wHint, wWarning, wError,
+  templatePragmas* = {wDeprecated, wError, wGensym, wInject, wDirty,
+    wDelegator, wExportNims, wUsed, wPragma, wRedefine, wCallsite}
+  macroPragmas* = declPragmas + {FirstCallConv..LastCallConv,
+    wMagic, wNoSideEffect, wCompilerProc, wNonReloadable, wCore,
+    wDiscardable, wGensym, wInject, wDelegator}
+  iteratorPragmas* = declPragmas + {FirstCallConv..LastCallConv, wNoSideEffect, wSideEffect,
+    wMagic, wBorrow,
+    wDiscardable, wGensym, wInject, wRaises, wEffectsOf,
+    wTags, wForbids, wLocks, wGcSafe, wRequires, wEnsures}
+  exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNoSideEffect}
+  stmtPragmas* = {
+    wHint, wWarning, wError,
     wFatal, wDefine, wUndef, wCompile, wLink, wLinksys, wPure, wPush, wPop,
-    wBreakpoint, wWatchPoint, wPassl, wPassc, wDeadCodeElim, wDeprecated,
-    wFloatchecks, wInfChecks, wNanChecks, wPragma, wEmit, wUnroll,
-    wLinearScanEnd, wPatterns, wEffects, wNoForward, wReorder, wComputedGoto,
-    wInjectStmt, wDeprecated, wExperimental, wThis}
-  lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
-    wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader,
-    wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame,
-    wRaises, wLocks, wTags, wGcSafe}
-  typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl,
-    wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow,
-    wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef,
+    wPassl, wPassc, wLocalPassc,
+    wDeadCodeElimUnused,  # deprecated, always on
+    wDeprecated,
+    wPragma, wEmit, wUnroll,
+    wLinearScanEnd, wPatterns, wTrMacros, wEffects, wNoForward, wReorder, wComputedGoto,
+    wExperimental, wDoctype, wThis, wUsed, wInvariant, wAssume, wAssert}
+  stmtPragmasTopLevel* = {wChecks, wObjChecks, wFieldChecks, wRangeChecks,
+    wBoundChecks, wOverflowChecks, wNilChecks, wStaticBoundchecks,
+    wStyleChecks, wAssertions,
+    wWarnings, wHints,
+    wLineDir, wStackTrace, wLineTrace, wOptimization,
+    wFloatChecks, wInfChecks, wNanChecks}
+  lambdaPragmas* = {FirstCallConv..LastCallConv,
+    wNoSideEffect, wSideEffect, wNoreturn, wNosinks, wDynlib, wHeader,
+    wThread, wAsmNoStackFrame,
+    wRaises, wLocks, wTags, wForbids, wRequires, wEnsures, wEffectsOf,
+    wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
+  typePragmas* = declPragmas + {wMagic, wAcyclic,
+    wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
+    wIncompleteStruct, wCompleteStruct, wByCopy, wByRef,
     wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
-    wBorrow, wGcSafe, wExportNims, wPartial, wUsed, wExplain}
-  fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern,
-    wImportCpp, wImportObjC, wError, wGuard, wBitsize, wUsed}
-  varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl,
-    wMagic, wHeader, wDeprecated, wCompilerproc, wDynlib, wExtern,
-    wImportCpp, wImportObjC, wError, wNoInit, wCompileTime, wGlobal,
-    wGensym, wInject, wCodegenDecl, wGuard, wGoto, wExportNims, wUsed}
-  constPragmas* = {wImportc, wExportc, wHeader, wDeprecated, wMagic, wNodecl,
-    wExtern, wImportCpp, wImportObjC, wError, wGensym, wInject, wExportNims,
-    wIntDefine, wStrDefine, wUsed}
+    wCppNonPod, wBorrow, wGcSafe, wPartial, wExplain, wPackage, wCodegenDecl,
+    wSendable, wNoInit}
+  fieldPragmas* = declPragmas + {wGuard, wBitsize, wCursor,
+    wRequiresInit, wNoalias, wAlign, wNoInit} - {wExportNims, wNodecl} # why exclude these?
+  varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar,
+    wMagic, wHeader, wCompilerProc, wCore, wDynlib,
+    wNoInit, wCompileTime, wGlobal, wLiftLocals,
+    wGensym, wInject, wCodegenDecl,
+    wGuard, wGoto, wCursor, wNoalias, wAlign}
+  constPragmas* = declPragmas + {wHeader, wMagic,
+    wGensym, wInject,
+    wIntDefine, wStrDefine, wBoolDefine, wDefine,
+    wCompilerProc, wCore}
+  paramPragmas* = {wNoalias, wInject, wGensym, wByRef, wByCopy, wCodegenDecl, wExportc, wExportCpp}
   letPragmas* = varPragmas
-  procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect,
-                      wThread, wRaises, wLocks, wTags, wGcSafe}
+  procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect,
+                      wThread, wRaises, wEffectsOf, wLocks, wTags, wForbids, wGcSafe,
+                      wRequires, wEnsures}
+  forVarPragmas* = {wInject, wGensym}
   allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
+  enumFieldPragmas* = {wDeprecated}
+
+proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode =
+  result = nil
+  let p = procAst[pragmasPos]
+  if p.kind == nkEmpty: return nil
+  for it in p:
+    if it.kind in nkPragmaCallKinds and it.len == 2 and it[0].kind == nkIdent and
+        it[0].ident.id == ord(name):
+      return it[1]
+
+proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords;
+            isStatement: bool = false)
+
+proc recordPragma(c: PContext; n: PNode; args: varargs[string]) =
+  var recorded = newNodeI(nkReplayAction, n.info)
+  for i in 0..args.high:
+    recorded.add newStrNode(args[i], n.info)
+  addPragmaComputation(c, recorded)
 
-proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords)
-# implementation
-
-proc invalidPragma*(n: PNode) =
-  localError(n.info, errInvalidPragmaX, renderTree(n, {renderNoComments}))
-
-proc pragmaAsm*(c: PContext, n: PNode): char =
-  result = '\0'
-  if n != nil:
-    for i in countup(0, sonsLen(n) - 1):
-      let it = n.sons[i]
-      if it.kind == nkExprColonExpr and it.sons[0].kind == nkIdent:
-        case whichKeyword(it.sons[0].ident)
-        of wSubsChar:
-          if it.sons[1].kind == nkCharLit: result = chr(int(it.sons[1].intVal))
-          else: invalidPragma(it)
-        else: invalidPragma(it)
-      else:
-        invalidPragma(it)
+const
+  errStringLiteralExpected = "string literal expected"
+  errIntLiteralExpected = "integer literal expected"
+
+proc invalidPragma*(c: PContext; n: PNode) =
+  localError(c.config, n.info, "invalid pragma: " & renderTree(n, {renderNoComments}))
+
+proc illegalCustomPragma*(c: PContext, n: PNode, s: PSym) =
+  var msg = "cannot attach a custom pragma to '" & s.name.s & "'"
+  if s != nil:
+    msg.add("; custom pragmas are not supported for ")
+    case s.kind
+    of skForVar: msg.add("`for` loop variables")
+    of skEnumField: msg.add("enum fields")
+    of skModule: msg.add("modules")
+    else: msg.add("symbol kind " & $s.kind)
+  localError(c.config, n.info, msg)
+
+proc pragmaProposition(c: PContext, n: PNode) =
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, "proposition expected")
+  else:
+    n[1] = c.semExpr(c, n[1])
 
-proc setExternName(s: PSym, extname: string, info: TLineInfo) =
+proc pragmaEnsures(c: PContext, n: PNode) =
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, "proposition expected")
+  else:
+    openScope(c)
+    let o = getCurrOwner(c)
+    if o.kind in routineKinds and o.typ != nil and o.typ.returnType != nil:
+      var s = newSym(skResult, getIdent(c.cache, "result"), c.idgen, o, n.info)
+      s.typ = o.typ.returnType
+      incl(s.flags, sfUsed)
+      addDecl(c, s)
+    n[1] = c.semExpr(c, n[1])
+    closeScope(c)
+
+proc setExternName(c: PContext; s: PSym, extname: string, info: TLineInfo) =
   # special cases to improve performance:
   if extname == "$1":
-    s.loc.r = rope(s.name.s)
+    s.loc.snippet = rope(s.name.s)
   elif '$' notin extname:
-    s.loc.r = rope(extname)
+    s.loc.snippet = rope(extname)
   else:
     try:
-      s.loc.r = rope(extname % s.name.s)
+      s.loc.snippet = rope(extname % s.name.s)
     except ValueError:
-      localError(info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)")
-  if gCmd == cmdPretty and '$' notin extname:
-    # note that '{.importc.}' is transformed into '{.importc: "$1".}'
-    s.loc.flags.incl(lfFullExternalName)
+      localError(c.config, info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)")
+  when hasFFI:
+    s.cname = $s.loc.snippet
+
 
-proc makeExternImport(s: PSym, extname: string, info: TLineInfo) =
-  setExternName(s, extname, info)
+proc makeExternImport(c: PContext; s: PSym, extname: string, info: TLineInfo) =
+  setExternName(c, s, extname, info)
   incl(s.flags, sfImportc)
   excl(s.flags, sfForward)
 
-proc makeExternExport(s: PSym, extname: string, info: TLineInfo) =
-  setExternName(s, extname, info)
+proc makeExternExport(c: PContext; s: PSym, extname: string, info: TLineInfo) =
+  setExternName(c, s, extname, info)
   incl(s.flags, sfExportc)
 
-proc processImportCompilerProc(s: PSym, extname: string, info: TLineInfo) =
-  setExternName(s, extname, info)
+proc processImportCompilerProc(c: PContext; s: PSym, extname: string, info: TLineInfo) =
+  setExternName(c, s, extname, info)
   incl(s.flags, sfImportc)
   excl(s.flags, sfForward)
   incl(s.loc.flags, lfImportCompilerProc)
 
-proc processImportCpp(s: PSym, extname: string, info: TLineInfo) =
-  setExternName(s, extname, info)
+proc processImportCpp(c: PContext; s: PSym, extname: string, info: TLineInfo) =
+  setExternName(c, s, extname, info)
   incl(s.flags, sfImportc)
   incl(s.flags, sfInfixCall)
   excl(s.flags, sfForward)
-  if gCmd == cmdCompileToC:
+  if c.config.backend == backendC:
     let m = s.getModule()
     incl(m.flags, sfCompileToCpp)
-  extccomp.gMixedMode = true
+  incl c.config.globalOptions, optMixedMode
 
-proc processImportObjC(s: PSym, extname: string, info: TLineInfo) =
-  setExternName(s, extname, info)
+proc processImportObjC(c: PContext; s: PSym, extname: string, info: TLineInfo) =
+  setExternName(c, s, extname, info)
   incl(s.flags, sfImportc)
   incl(s.flags, sfNamedParamCall)
   excl(s.flags, sfForward)
   let m = s.getModule()
-  incl(m.flags, sfCompileToObjC)
+  incl(m.flags, sfCompileToObjc)
 
-proc newEmptyStrNode(n: PNode): PNode {.noinline.} =
-  result = newNodeIT(nkStrLit, n.info, getSysType(tyString))
-  result.strVal = ""
+proc newEmptyStrNode(c: PContext; n: PNode, strVal: string = ""): PNode {.noinline.} =
+  result = newNodeIT(nkStrLit, n.info, getSysType(c.graph, n.info, tyString))
+  result.strVal = strVal
 
 proc getStrLitNode(c: PContext, n: PNode): PNode =
-  if n.kind != nkExprColonExpr:
-    localError(n.info, errStringLiteralExpected)
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errStringLiteralExpected)
     # error correction:
-    result = newEmptyStrNode(n)
+    result = newEmptyStrNode(c, n)
   else:
-    n.sons[1] = c.semConstExpr(c, n.sons[1])
-    case n.sons[1].kind
-    of nkStrLit, nkRStrLit, nkTripleStrLit: result = n.sons[1]
+    n[1] = c.semConstExpr(c, n[1])
+    case n[1].kind
+    of nkStrLit, nkRStrLit, nkTripleStrLit: result = n[1]
     else:
-      localError(n.info, errStringLiteralExpected)
+      localError(c.config, n.info, errStringLiteralExpected)
       # error correction:
-      result = newEmptyStrNode(n)
+      result = newEmptyStrNode(c, n)
 
 proc expectStrLit(c: PContext, n: PNode): string =
   result = getStrLitNode(c, n).strVal
 
 proc expectIntLit(c: PContext, n: PNode): int =
-  if n.kind != nkExprColonExpr:
-    localError(n.info, errIntLiteralExpected)
+  result = 0
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errIntLiteralExpected)
   else:
-    n.sons[1] = c.semConstExpr(c, n.sons[1])
-    case n.sons[1].kind
-    of nkIntLit..nkInt64Lit: result = int(n.sons[1].intVal)
-    else: localError(n.info, errIntLiteralExpected)
+    n[1] = c.semConstExpr(c, n[1])
+    case n[1].kind
+    of nkIntLit..nkInt64Lit: result = int(n[1].intVal)
+    else: localError(c.config, n.info, errIntLiteralExpected)
 
 proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string =
-  if n.kind == nkExprColonExpr: result = expectStrLit(c, n)
+  if n.kind in nkPragmaCallKinds: result = expectStrLit(c, n)
   else: result = defaultStr
 
+proc processVirtual(c: PContext, n: PNode, s: PSym, flag: TSymFlag) =
+  s.constraint = newEmptyStrNode(c, n, getOptionalStr(c, n, "$1"))
+  s.constraint.strVal = s.constraint.strVal % s.name.s
+  s.flags.incl {flag, sfInfixCall, sfExportc, sfMangleCpp}
+
+  s.typ.callConv = ccMember
+  incl c.config.globalOptions, optMixedMode
+
 proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
   sym.constraint = getStrLitNode(c, n)
+  sym.flags.incl sfCodegenDecl
 
 proc processMagic(c: PContext, n: PNode, s: PSym) =
   #if sfSystemModule notin c.module.flags:
   #  liMessage(n.info, errMagicOnlyInSystem)
-  if n.kind != nkExprColonExpr:
-    localError(n.info, errStringLiteralExpected)
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errStringLiteralExpected)
     return
   var v: string
-  if n.sons[1].kind == nkIdent: v = n.sons[1].ident.s
+  if n[1].kind == nkIdent: v = n[1].ident.s
   else: v = expectStrLit(c, n)
-  for m in countup(low(TMagic), high(TMagic)):
+  for m in TMagic:
     if substr($m, 1) == v:
       s.magic = m
       break
-  if s.magic == mNone: message(n.info, warnUnknownMagic, v)
+  if s.magic == mNone: message(c.config, n.info, warnUnknownMagic, v)
 
 proc wordToCallConv(sw: TSpecialWord): TCallingConvention =
   # this assumes that the order of special words and calling conventions is
   # the same
-  result = TCallingConvention(ord(ccDefault) + ord(sw) - ord(wNimcall))
+  TCallingConvention(ord(ccNimCall) + ord(sw) - ord(wNimcall))
 
 proc isTurnedOn(c: PContext, n: PNode): bool =
-  if n.kind == nkExprColonExpr:
-    let x = c.semConstBoolExpr(c, n.sons[1])
-    n.sons[1] = x
+  result = false
+  if n.kind in nkPragmaCallKinds and n.len == 2:
+    let x = c.semConstBoolExpr(c, n[1])
+    n[1] = x
     if x.kind == nkIntLit: return x.intVal != 0
-  localError(n.info, errOnOrOffExpected)
+  localError(c.config, n.info, "'on' or 'off' expected")
+
+proc onOff(c: PContext, n: PNode, op: TOptions, resOptions: var TOptions) =
+  if isTurnedOn(c, n): resOptions.incl op
+  else: resOptions.excl op
 
-proc onOff(c: PContext, n: PNode, op: TOptions) =
-  if isTurnedOn(c, n): gOptions = gOptions + op
-  else: gOptions = gOptions - op
+proc pragmaNoForward*(c: PContext, n: PNode; flag=sfNoForward) =
+  if isTurnedOn(c, n):
+    incl(c.module.flags, flag)
+    c.features.incl codeReordering
+  else:
+    excl(c.module.flags, flag)
+    # c.features.excl codeReordering
 
-proc pragmaDeadCodeElim(c: PContext, n: PNode) =
-  if isTurnedOn(c, n): incl(c.module.flags, sfDeadCodeElim)
-  else: excl(c.module.flags, sfDeadCodeElim)
+  # deprecated as of 0.18.1
+  message(c.config, n.info, warnDeprecated,
+          "use {.experimental: \"codeReordering\".} instead; " &
+          (if flag == sfNoForward: "{.noForward.}" else: "{.reorder.}") & " is deprecated")
 
-proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) =
-  if isTurnedOn(c, n): incl(c.module.flags, flag)
-  else: excl(c.module.flags, flag)
+proc pragmaAsm*(c: PContext, n: PNode): char =
+  ## Checks asm pragmas and get's the asm subschar (default: '`').
+  result = '\0'
+  if n != nil:
+    for i in 0..<n.len:
+      let it = n[i]
+      if it.kind in nkPragmaCallKinds and it.len == 2 and it[0].kind == nkIdent:
+        case whichKeyword(it[0].ident)
+        of wSubsChar:
+          if it[1].kind == nkCharLit: result = chr(int(it[1].intVal))
+          else: invalidPragma(c, it)
+        of wAsmSyntax:
+          let s = expectStrLit(c, it)
+          if s notin ["gcc", "vcc"]: invalidPragma(c, it)
+        else: invalidPragma(c, it)
+      else:
+        invalidPragma(c, it)
 
 proc processCallConv(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent):
-    var sw = whichKeyword(n.sons[1].ident)
+  if n.kind in nkPragmaCallKinds and n.len == 2 and n[1].kind == nkIdent:
+    let sw = whichKeyword(n[1].ident)
     case sw
     of FirstCallConv..LastCallConv:
       c.optionStack[^1].defaultCC = wordToCallConv(sw)
-    else: localError(n.info, errCallConvExpected)
+    else: localError(c.config, n.info, "calling convention expected")
   else:
-    localError(n.info, errCallConvExpected)
+    localError(c.config, n.info, "calling convention expected")
 
 proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib =
   for it in c.libs:
@@ -233,32 +330,32 @@ proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib =
   result.path = path
   c.libs.add result
   if path.kind in {nkStrLit..nkTripleStrLit}:
-    result.isOverriden = options.isDynlibOverride(path.strVal)
+    result.isOverridden = options.isDynlibOverride(c.config, path.strVal)
 
 proc expectDynlibNode(c: PContext, n: PNode): PNode =
-  if n.kind != nkExprColonExpr:
-    localError(n.info, errStringLiteralExpected)
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errStringLiteralExpected)
     # error correction:
-    result = newEmptyStrNode(n)
+    result = newEmptyStrNode(c, n)
   else:
     # For the OpenGL wrapper we support:
     # {.dynlib: myGetProcAddr(...).}
-    result = c.semExpr(c, n.sons[1])
+    result = c.semExpr(c, n[1])
     if result.kind == nkSym and result.sym.kind == skConst:
-      result = result.sym.ast # look it up
+      result = c.semConstExpr(c, result) # fold const
     if result.typ == nil or result.typ.kind notin {tyPointer, tyString, tyProc}:
-      localError(n.info, errStringLiteralExpected)
-      result = newEmptyStrNode(n)
+      localError(c.config, n.info, errStringLiteralExpected)
+      result = newEmptyStrNode(c, n)
 
 proc processDynLib(c: PContext, n: PNode, sym: PSym) =
   if (sym == nil) or (sym.kind == skModule):
     let lib = getLib(c, libDynamic, expectDynlibNode(c, n))
-    if not lib.isOverriden:
+    if not lib.isOverridden:
       c.optionStack[^1].dynlib = lib
   else:
-    if n.kind == nkExprColonExpr:
+    if n.kind in nkPragmaCallKinds:
       var lib = getLib(c, libDynamic, expectDynlibNode(c, n))
-      if not lib.isOverriden:
+      if not lib.isOverridden:
         addToLib(lib, sym)
         incl(sym.loc.flags, lfDynamicLib)
     else:
@@ -267,370 +364,423 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
     # a calling convention that doesn't introduce custom name mangling
     # cdecl is the default - the user can override this explicitly
     if sym.kind in routineKinds and sym.typ != nil and
-        sym.typ.callConv == ccDefault:
+       tfExplicitCallConv notin sym.typ.flags:
       sym.typ.callConv = ccCDecl
 
 proc processNote(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (sonsLen(n) == 2) and
-      (n.sons[0].kind == nkBracketExpr) and
-      (n.sons[0].sons.len == 2) and
-      (n.sons[0].sons[1].kind == nkIdent) and
-      (n.sons[0].sons[0].kind == nkIdent):
-      #and (n.sons[1].kind == nkIdent):
-    var nk: TNoteKind
-    case whichKeyword(n.sons[0].sons[0].ident)
-    of wHint:
-      var x = findStr(msgs.HintsToStr, n.sons[0].sons[1].ident.s)
-      if x >= 0: nk = TNoteKind(x + ord(hintMin))
-      else: invalidPragma(n); return
-    of wWarning:
-      var x = findStr(msgs.WarningsToStr, n.sons[0].sons[1].ident.s)
-      if x >= 0: nk = TNoteKind(x + ord(warnMin))
-      else: invalidPragma(n); return
+  template handleNote(enumVals, notes) =
+    let x = findStr(enumVals.a, enumVals.b, n[0][1].ident.s, errUnknown)
+    if x != errUnknown:
+      nk = TNoteKind(x)
+      let x = c.semConstBoolExpr(c, n[1])
+      n[1] = x
+      if x.kind == nkIntLit and x.intVal != 0: incl(notes, nk)
+      else: excl(notes, nk)
     else:
-      invalidPragma(n)
-      return
+      invalidPragma(c, n)
 
-    let x = c.semConstBoolExpr(c, n.sons[1])
-    n.sons[1] = x
-    if x.kind == nkIntLit and x.intVal != 0: incl(gNotes, nk)
-    else: excl(gNotes, nk)
+  if n.kind in nkPragmaCallKinds and n.len == 2 and
+      n[0].kind == nkBracketExpr and
+      n[0].len == 2 and
+      n[0][1].kind == nkIdent and n[0][0].kind == nkIdent:
+    var nk: TNoteKind
+    case whichKeyword(n[0][0].ident)
+    of wHint: handleNote(hintMin .. hintMax, c.config.notes)
+    of wWarning: handleNote(warnMin .. warnMax, c.config.notes)
+    of wWarningAsError: handleNote(warnMin .. warnMax, c.config.warningAsErrors)
+    of wHintAsError: handleNote(hintMin .. hintMax, c.config.warningAsErrors)
+    else: invalidPragma(c, n)
+  else: invalidPragma(c, n)
+
+proc pragmaToOptions*(w: TSpecialWord): TOptions {.inline.} =
+  case w
+  of wChecks: ChecksOptions
+  of wObjChecks: {optObjCheck}
+  of wFieldChecks: {optFieldCheck}
+  of wRangeChecks: {optRangeCheck}
+  of wBoundChecks: {optBoundsCheck}
+  of wOverflowChecks: {optOverflowCheck}
+  of wFloatChecks: {optNaNCheck, optInfCheck}
+  of wNanChecks: {optNaNCheck}
+  of wInfChecks: {optInfCheck}
+  of wStaticBoundchecks: {optStaticBoundsCheck}
+  of wStyleChecks: {optStyleCheck}
+  of wAssertions: {optAssert}
+  of wWarnings: {optWarns}
+  of wHints: {optHints}
+  of wLineDir: {optLineDir}
+  of wStackTrace: {optStackTrace}
+  of wLineTrace: {optLineTrace}
+  of wDebugger: {optNone}
+  of wProfiler: {optProfiler, optMemTracker}
+  of wMemTracker: {optMemTracker}
+  of wByRef: {optByRef}
+  of wImplicitStatic: {optImplicitStatic}
+  of wPatterns, wTrMacros: {optTrMacros}
+  of wSinkInference: {optSinkInference}
+  of wQuirky: {optQuirky}
+  else: {}
+
+proc processExperimental(c: PContext; n: PNode) =
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    c.features.incl oldExperimentalFeatures
   else:
-    invalidPragma(n)
+    n[1] = c.semConstExpr(c, n[1])
+    case n[1].kind
+    of nkStrLit, nkRStrLit, nkTripleStrLit:
+      try:
+        let feature = parseEnum[Feature](n[1].strVal)
+        c.features.incl feature
+        if feature == codeReordering:
+          if not isTopLevel(c):
+              localError(c.config, n.info,
+                         "Code reordering experimental pragma only valid at toplevel")
+          c.module.flags.incl sfReorder
+      except ValueError:
+        localError(c.config, n[1].info, "unknown experimental feature")
+    else:
+      localError(c.config, n.info, errStringLiteralExpected)
 
-proc processOption(c: PContext, n: PNode): bool =
-  if n.kind != nkExprColonExpr: result = true
-  elif n.sons[0].kind == nkBracketExpr: processNote(c, n)
-  elif n.sons[0].kind != nkIdent: result = true
+proc tryProcessOption(c: PContext, n: PNode, resOptions: var TOptions): bool =
+  result = true
+  if n.kind notin nkPragmaCallKinds or n.len != 2: result = false
+  elif n[0].kind == nkBracketExpr: processNote(c, n)
+  elif n[0].kind != nkIdent: result = false
   else:
-    var sw = whichKeyword(n.sons[0].ident)
-    case sw
-    of wChecks: onOff(c, n, ChecksOptions)
-    of wObjChecks: onOff(c, n, {optObjCheck})
-    of wFieldChecks: onOff(c, n, {optFieldCheck})
-    of wRangechecks: onOff(c, n, {optRangeCheck})
-    of wBoundchecks: onOff(c, n, {optBoundsCheck})
-    of wOverflowchecks: onOff(c, n, {optOverflowCheck})
-    of wNilchecks: onOff(c, n, {optNilCheck})
-    of wFloatchecks: onOff(c, n, {optNaNCheck, optInfCheck})
-    of wNanChecks: onOff(c, n, {optNaNCheck})
-    of wInfChecks: onOff(c, n, {optInfCheck})
-    of wAssertions: onOff(c, n, {optAssert})
-    of wWarnings: onOff(c, n, {optWarns})
-    of wHints: onOff(c, n, {optHints})
-    of wCallconv: processCallConv(c, n)
-    of wLinedir: onOff(c, n, {optLineDir})
-    of wStacktrace: onOff(c, n, {optStackTrace})
-    of wLinetrace: onOff(c, n, {optLineTrace})
-    of wDebugger: onOff(c, n, {optEndb})
-    of wProfiler: onOff(c, n, {optProfiler, optMemTracker})
-    of wMemTracker: onOff(c, n, {optMemTracker})
-    of wByRef: onOff(c, n, {optByRef})
-    of wDynlib: processDynLib(c, n, nil)
-    of wOptimization:
-      if n.sons[1].kind != nkIdent:
-        invalidPragma(n)
-      else:
-        case n.sons[1].ident.s.normalize
-        of "speed":
-          incl(gOptions, optOptimizeSpeed)
-          excl(gOptions, optOptimizeSize)
-        of "size":
-          excl(gOptions, optOptimizeSpeed)
-          incl(gOptions, optOptimizeSize)
-        of "none":
-          excl(gOptions, optOptimizeSpeed)
-          excl(gOptions, optOptimizeSize)
-        else: localError(n.info, errNoneSpeedOrSizeExpected)
-    of wImplicitStatic: onOff(c, n, {optImplicitStatic})
-    of wPatterns: onOff(c, n, {optPatterns})
-    else: result = true
+    let sw = whichKeyword(n[0].ident)
+    if sw == wExperimental:
+      processExperimental(c, n)
+      return true
+    let opts = pragmaToOptions(sw)
+    if opts != {}:
+      onOff(c, n, opts, resOptions)
+    else:
+      case sw
+      of wCallconv: processCallConv(c, n)
+      of wDynlib: processDynLib(c, n, nil)
+      of wOptimization:
+        if n[1].kind != nkIdent:
+          invalidPragma(c, n)
+        else:
+          case n[1].ident.s.normalize
+          of "speed":
+            incl(resOptions, optOptimizeSpeed)
+            excl(resOptions, optOptimizeSize)
+          of "size":
+            excl(resOptions, optOptimizeSpeed)
+            incl(resOptions, optOptimizeSize)
+          of "none":
+            excl(resOptions, optOptimizeSpeed)
+            excl(resOptions, optOptimizeSize)
+          else: localError(c.config, n.info, "'none', 'speed' or 'size' expected")
+      else: result = false
+
+proc processOption(c: PContext, n: PNode, resOptions: var TOptions) =
+  if not tryProcessOption(c, n, resOptions):
+    # calling conventions (boring...):
+    localError(c.config, n.info, "option expected")
+
+proc checkPushedPragma(c: PContext, n: PNode) =
+  let keyDeep = n.kind in nkPragmaCallKinds and n.len > 1
+  var key = if keyDeep: n[0] else: n
+  if key.kind in nkIdentKinds:
+    let ident = considerQuotedIdent(c, key)
+    var userPragma = strTableGet(c.userPragmas, ident)
+    if userPragma == nil:
+      let k = whichKeyword(ident)
+      # TODO: might as well make a list which is not accepted by `push`: emit, cast etc.
+      if k == wEmit:
+        localError(c.config, n.info, "an 'emit' pragma cannot be pushed")
 
 proc processPush(c: PContext, n: PNode, start: int) =
-  if n.sons[start-1].kind == nkExprColonExpr:
-    localError(n.info, errGenerated, "':' after 'push' not supported")
-  var x = newOptionEntry()
-  var y = c.optionStack[^1]
-  x.options = gOptions
-  x.defaultCC = y.defaultCC
-  x.dynlib = y.dynlib
-  x.notes = gNotes
-  c.optionStack.add(x)
-  for i in countup(start, sonsLen(n) - 1):
-    if processOption(c, n.sons[i]):
+  if n[start-1].kind in nkPragmaCallKinds:
+    localError(c.config, n.info, "'push' cannot have arguments")
+  var x = pushOptionEntry(c)
+  for i in start..<n.len:
+    if not tryProcessOption(c, n[i], c.config.options):
       # simply store it somewhere:
+      checkPushedPragma(c, n[i])
       if x.otherPragmas.isNil:
         x.otherPragmas = newNodeI(nkPragma, n.info)
-      x.otherPragmas.add n.sons[i]
-    #localError(n.info, errOptionExpected)
+      x.otherPragmas.add n[i]
+    #localError(c.config, n.info, errOptionExpected)
+
+  # If stacktrace is disabled globally we should not enable it
+  if optStackTrace notin c.optionStack[0].options:
+    c.config.options.excl(optStackTrace)
+  when defined(debugOptions):
+    echo c.config $ n.info, " PUSH config is now ", c.config.options
 
 proc processPop(c: PContext, n: PNode) =
   if c.optionStack.len <= 1:
-    localError(n.info, errAtPopWithoutPush)
+    localError(c.config, n.info, "{.pop.} without a corresponding {.push.}")
   else:
-    gOptions = c.optionStack[^1].options
-    gNotes = c.optionStack[^1].notes
-    c.optionStack.setLen(c.optionStack.len - 1)
-
-proc processDefine(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent):
-    defineSymbol(n.sons[1].ident.s)
-    message(n.info, warnDeprecated, "define")
+    popOptionEntry(c)
+  when defined(debugOptions):
+    echo c.config $ n.info, " POP config is now ", c.config.options
+
+proc processDefineConst(c: PContext, n: PNode, sym: PSym, kind: TMagic) =
+  sym.magic = kind
+  if n.kind in nkPragmaCallKinds and n.len == 2:
+    # could also use TLib
+    n[1] = getStrLitNode(c, n)
+
+proc processDefine(c: PContext, n: PNode, sym: PSym) =
+  if sym != nil and sym.kind == skConst:
+    processDefineConst(c, n, sym, mGenericDefine)
+  elif (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent):
+    defineSymbol(c.config.symbols, n[1].ident.s)
   else:
-    invalidPragma(n)
+    invalidPragma(c, n)
 
 proc processUndef(c: PContext, n: PNode) =
-  if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent):
-    undefSymbol(n.sons[1].ident.s)
-    message(n.info, warnDeprecated, "undef")
+  if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent):
+    undefSymbol(c.config.symbols, n[1].ident.s)
   else:
-    invalidPragma(n)
-
-type
-  TLinkFeature = enum
-    linkNormal, linkSys
+    invalidPragma(c, n)
 
-proc relativeFile(c: PContext; n: PNode; ext=""): string =
+proc relativeFile(c: PContext; n: PNode; ext=""): AbsoluteFile =
   var s = expectStrLit(c, n)
   if ext.len > 0 and splitFile(s).ext == "":
     s = addFileExt(s, ext)
-  result = parentDir(n.info.toFullPath) / s
+  result = AbsoluteFile parentDir(toFullPath(c.config, n.info)) / s
   if not fileExists(result):
-    if isAbsolute(s): result = s
+    if isAbsolute(s): result = AbsoluteFile s
     else:
-      result = findFile(s)
-      if result.len == 0: result = s
+      result = findFile(c.config, s)
+      if result.isEmpty: result = AbsoluteFile s
 
 proc processCompile(c: PContext, n: PNode) =
+  ## This pragma can take two forms. The first is a simple file input:
+  ##     {.compile: "file.c".}
+  ## The second is a tuple where the second arg is the output name strutils formatter:
+  ##     {.compile: ("file.c", "$1.o").}
+  proc docompile(c: PContext; it: PNode; src, dest: AbsoluteFile; customArgs: string) =
+    var cf = Cfile(nimname: splitFile(src).name,
+                   cname: src, obj: dest, flags: {CfileFlag.External},
+                   customArgs: customArgs)
+    if not fileExists(src):
+      localError(c.config, n.info, "cannot find: " & src.string)
+    else:
+      extccomp.addExternalFileToCompile(c.config, cf)
+      recordPragma(c, it, "compile", src.string, dest.string, customArgs)
 
   proc getStrLit(c: PContext, n: PNode; i: int): string =
-    n.sons[i] = c.semConstExpr(c, n.sons[i])
-    case n.sons[i].kind
+    n[i] = c.semConstExpr(c, n[i])
+    case n[i].kind
     of nkStrLit, nkRStrLit, nkTripleStrLit:
-      shallowCopy(result, n.sons[i].strVal)
+      when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
+        result = n[i].strVal
+      else:
+        shallowCopy(result, n[i].strVal)
     else:
-      localError(n.info, errStringLiteralExpected)
+      localError(c.config, n.info, errStringLiteralExpected)
       result = ""
 
-  let it = if n.kind == nkExprColonExpr: n.sons[1] else: n
-  if it.kind == nkPar and it.len == 2:
+  let it = if n.kind in nkPragmaCallKinds and n.len == 2: n[1] else: n
+  if it.kind in {nkPar, nkTupleConstr} and it.len == 2:
     let s = getStrLit(c, it, 0)
     let dest = getStrLit(c, it, 1)
-    var found = parentDir(n.info.toFullPath) / s
+    var found = parentDir(toFullPath(c.config, n.info)) / s
     for f in os.walkFiles(found):
-      let nameOnly = extractFilename(f)
-      var cf = Cfile(cname: f,
-          obj: completeCFilePath(dest % nameOnly),
-          flags: {CfileFlag.External})
-      extccomp.addExternalFileToCompile(cf)
+      let obj = completeCfilePath(c.config, AbsoluteFile(dest % extractFilename(f)))
+      docompile(c, it, AbsoluteFile f, obj, "")
   else:
-    let s = expectStrLit(c, n)
-    var found = parentDir(n.info.toFullPath) / s
+    var s = ""
+    var customArgs = ""
+    if n.kind in nkCallKinds:
+      s = getStrLit(c, n, 1)
+      if n.len <= 3:
+        customArgs = getStrLit(c, n, 2)
+      else:
+        localError(c.config, n.info, "'.compile' pragma takes up 2 arguments")
+    else:
+      s = expectStrLit(c, n)
+
+    var found = AbsoluteFile(parentDir(toFullPath(c.config, n.info)) / s)
     if not fileExists(found):
-      if isAbsolute(s): found = s
+      if isAbsolute(s): found = AbsoluteFile s
       else:
-        found = findFile(s)
-        if found.len == 0: found = s
-    extccomp.addExternalFileToCompile(found)
-
-proc processCommonLink(c: PContext, n: PNode, feature: TLinkFeature) =
-  let found = relativeFile(c, n, CC[cCompiler].objExt)
-  case feature
-  of linkNormal: extccomp.addExternalFileToLink(found)
-  of linkSys:
-    extccomp.addExternalFileToLink(libpath / completeCFilePath(found, false))
-  else: internalError(n.info, "processCommonLink")
-
-proc pragmaBreakpoint(c: PContext, n: PNode) =
-  discard getOptionalStr(c, n, "")
-
-proc pragmaWatchpoint(c: PContext, n: PNode) =
-  if n.kind == nkExprColonExpr:
-    n.sons[1] = c.semExpr(c, n.sons[1])
-  else:
-    invalidPragma(n)
+        found = findFile(c.config, s)
+        if found.isEmpty: found = AbsoluteFile s
+    let mangled = completeCfilePath(c.config, mangleModuleName(c.config, found).AbsoluteFile)
+    let obj = toObjFile(c.config, mangled)
+    docompile(c, it, found, obj, customArgs)
+
+proc processLink(c: PContext, n: PNode) =
+  let found = relativeFile(c, n, CC[c.config.cCompiler].objExt)
+  extccomp.addExternalFileToLink(c.config, found)
+  recordPragma(c, n, "link", found.string)
 
 proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
-  case n.sons[1].kind
+  case n[1].kind
   of nkStrLit, nkRStrLit, nkTripleStrLit:
-    result = newNode(if n.kind == nkAsmStmt: nkAsmStmt else: nkArgList, n.info)
-    var str = n.sons[1].strVal
+    result = newNodeI(if n.kind == nkAsmStmt: nkAsmStmt else: nkArgList, n.info)
+    if n.kind == nkAsmStmt: result.add n[0] # save asm pragmas for NIR
+    var str = n[1].strVal
     if str == "":
-      localError(n.info, errEmptyAsm)
+      localError(con.config, n.info, "empty 'asm' statement")
       return
     # now parse the string literal and substitute symbols:
     var a = 0
     while true:
       var b = strutils.find(str, marker, a)
       var sub = if b < 0: substr(str, a) else: substr(str, a, b - 1)
-      if sub != "": addSon(result, newStrNode(nkStrLit, sub))
+      if sub != "": result.add newStrNode(nkStrLit, sub)
       if b < 0: break
       var c = strutils.find(str, marker, b + 1)
       if c < 0: sub = substr(str, b + 1)
       else: sub = substr(str, b + 1, c - 1)
       if sub != "":
-        var e = searchInScopes(con, getIdent(sub))
+        var amb = false
+        var e = searchInScopes(con, getIdent(con.cache, sub), amb)
+        # XXX what to do here if 'amb' is true?
         if e != nil:
-          if e.kind == skStub: loadStub(e)
           incl(e.flags, sfUsed)
-          addSon(result, newSymNode(e))
+          result.add newSymNode(e)
         else:
-          addSon(result, newStrNode(nkStrLit, sub))
+          result.add newStrNode(nkStrLit, sub)
       else:
         # an empty '``' produces a single '`'
-        addSon(result, newStrNode(nkStrLit, $marker))
+        result.add newStrNode(nkStrLit, $marker)
       if c < 0: break
       a = c + 1
   else:
-    illFormedAstLocal(n)
-    result = newNode(nkAsmStmt, n.info)
+    illFormedAstLocal(n, con.config)
+    result = newNodeI(nkAsmStmt, n.info)
+    if n.kind == nkAsmStmt: result.add n[0]
 
 proc pragmaEmit(c: PContext, n: PNode) =
-  if n.kind != nkExprColonExpr:
-    localError(n.info, errStringLiteralExpected)
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errStringLiteralExpected)
   else:
     let n1 = n[1]
     if n1.kind == nkBracket:
       var b = newNodeI(nkBracket, n1.info, n1.len)
       for i in 0..<n1.len:
-        b.sons[i] = c.semExpr(c, n1[i])
-      n.sons[1] = b
+        b[i] = c.semExprWithType(c, n1[i], {efTypeAllowed})
+      n[1] = b
     else:
-      n.sons[1] = c.semConstExpr(c, n1)
-      case n.sons[1].kind
+      n[1] = c.semConstExpr(c, n1)
+      case n[1].kind
       of nkStrLit, nkRStrLit, nkTripleStrLit:
-        n.sons[1] = semAsmOrEmit(c, n, '`')
+        n[1] = semAsmOrEmit(c, n, '`')
       else:
-        localError(n.info, errStringLiteralExpected)
+        localError(c.config, n.info, errStringLiteralExpected)
 
-proc noVal(n: PNode) =
-  if n.kind == nkExprColonExpr: invalidPragma(n)
+proc noVal(c: PContext; n: PNode) =
+  if n.kind in nkPragmaCallKinds and n.len > 1: invalidPragma(c, n)
 
 proc pragmaUnroll(c: PContext, n: PNode) =
   if c.p.nestedLoopCounter <= 0:
-    invalidPragma(n)
-  elif n.kind == nkExprColonExpr:
+    invalidPragma(c, n)
+  elif n.kind in nkPragmaCallKinds and n.len == 2:
     var unrollFactor = expectIntLit(c, n)
     if unrollFactor <% 32:
-      n.sons[1] = newIntNode(nkIntLit, unrollFactor)
+      n[1] = newIntNode(nkIntLit, unrollFactor)
     else:
-      invalidPragma(n)
+      invalidPragma(c, n)
 
 proc pragmaLine(c: PContext, n: PNode) =
-  if n.kind == nkExprColonExpr:
-    n.sons[1] = c.semConstExpr(c, n.sons[1])
-    let a = n.sons[1]
-    if a.kind == nkPar:
-      var x = a.sons[0]
-      var y = a.sons[1]
-      if x.kind == nkExprColonExpr: x = x.sons[1]
-      if y.kind == nkExprColonExpr: y = y.sons[1]
+  if n.kind in nkPragmaCallKinds and n.len == 2:
+    n[1] = c.semConstExpr(c, n[1])
+    let a = n[1]
+    if a.kind in {nkPar, nkTupleConstr}:
+      # unpack the tuple
+      var x = a[0]
+      var y = a[1]
+      if x.kind == nkExprColonExpr: x = x[1]
+      if y.kind == nkExprColonExpr: y = y[1]
       if x.kind != nkStrLit:
-        localError(n.info, errStringLiteralExpected)
+        localError(c.config, n.info, errStringLiteralExpected)
       elif y.kind != nkIntLit:
-        localError(n.info, errIntLiteralExpected)
+        localError(c.config, n.info, errIntLiteralExpected)
       else:
-        # XXX this produces weird paths which are not properly resolved:
-        n.info.fileIndex = msgs.fileInfoIdx(x.strVal)
-        n.info.line = int16(y.intVal)
+        n.info.fileIndex = fileInfoIdx(c.config, AbsoluteFile(x.strVal))
+        n.info.line = uint16(y.intVal)
     else:
-      localError(n.info, errXExpected, "tuple")
+      localError(c.config, n.info, "tuple expected")
   else:
     # sensible default:
-    n.info = getInfoContext(-1)
+    n.info = getInfoContext(c.config, -1)
 
 proc processPragma(c: PContext, n: PNode, i: int) =
-  var it = n.sons[i]
-  if it.kind != nkExprColonExpr: invalidPragma(n)
-  elif it.sons[0].kind != nkIdent: invalidPragma(n)
-  elif it.sons[1].kind != nkIdent: invalidPragma(n)
-
-  var userPragma = newSym(skTemplate, it.sons[1].ident, nil, it.info)
-  var body = newNodeI(nkPragma, n.info)
-  for j in i+1 .. sonsLen(n)-1: addSon(body, n.sons[j])
-  userPragma.ast = body
+  ## Create and add a new custom pragma `{.pragma: name.}` node to the module's context.
+  let it = n[i]
+  if it.kind notin nkPragmaCallKinds and it.safeLen == 2:
+    invalidPragma(c, n)
+    return
+  elif it.safeLen != 2 or it[0].kind != nkIdent or it[1].kind != nkIdent:
+    invalidPragma(c, n)
+    return
+
+  var userPragma = newSym(skTemplate, it[1].ident, c.idgen, c.module, it.info, c.config.options)
+  styleCheckDef(c, userPragma)
+  userPragma.ast = newTreeI(nkPragma, n.info, n.sons[i+1..^1])
   strTableAdd(c.userPragmas, userPragma)
 
 proc pragmaRaisesOrTags(c: PContext, n: PNode) =
   proc processExc(c: PContext, x: PNode) =
-    var t = skipTypes(c.semTypeNode(c, x, nil), skipPtrs)
-    if t.kind != tyObject:
-      localError(x.info, errGenerated, "invalid type for raises/tags list")
-    x.typ = t
+    if c.hasUnresolvedArgs(c, x):
+      x.typ = makeTypeFromExpr(c, x)
+    else:
+      var t = skipTypes(c.semTypeNode(c, x, nil), skipPtrs)
+      if t.kind notin {tyObject, tyOr}:
+        localError(c.config, x.info, errGenerated, "invalid type for raises/tags list")
+      x.typ = t
 
-  if n.kind == nkExprColonExpr:
-    let it = n.sons[1]
+  if n.kind in nkPragmaCallKinds and n.len == 2:
+    let it = n[1]
     if it.kind notin {nkCurly, nkBracket}:
       processExc(c, it)
     else:
       for e in items(it): processExc(c, e)
   else:
-    invalidPragma(n)
+    invalidPragma(c, n)
 
 proc pragmaLockStmt(c: PContext; it: PNode) =
-  if it.kind != nkExprColonExpr:
-    invalidPragma(it)
+  if it.kind notin nkPragmaCallKinds or it.len != 2:
+    invalidPragma(c, it)
   else:
     let n = it[1]
     if n.kind != nkBracket:
-      localError(n.info, errGenerated, "locks pragma takes a list of expressions")
-    else:
-      for i in 0 .. <n.len:
-        n.sons[i] = c.semExpr(c, n.sons[i])
-
-proc pragmaLocks(c: PContext, it: PNode): TLockLevel =
-  if it.kind != nkExprColonExpr:
-    invalidPragma(it)
-  else:
-    case it[1].kind
-    of nkStrLit, nkRStrLit, nkTripleStrLit:
-      if it[1].strVal == "unknown":
-        result = UnknownLockLevel
-      else:
-        localError(it[1].info, "invalid string literal for locks pragma (only allowed string is \"unknown\")")
+      localError(c.config, n.info, errGenerated, "locks pragma takes a list of expressions")
     else:
-      let x = expectIntLit(c, it)
-      if x < 0 or x > MaxLockLevel:
-        localError(it[1].info, "integer must be within 0.." & $MaxLockLevel)
-      else:
-        result = TLockLevel(x)
+      for i in 0..<n.len:
+        n[i] = c.semExpr(c, n[i])
 
-proc typeBorrow(sym: PSym, n: PNode) =
-  if n.kind == nkExprColonExpr:
-    let it = n.sons[1]
+proc typeBorrow(c: PContext; sym: PSym, n: PNode) =
+  if n.kind in nkPragmaCallKinds and n.len == 2:
+    let it = n[1]
     if it.kind != nkAccQuoted:
-      localError(n.info, "a type can only borrow `.` for now")
+      localError(c.config, n.info, "a type can only borrow `.` for now")
   incl(sym.typ.flags, tfBorrowDot)
 
-proc markCompilerProc(s: PSym) =
-  # minor hack ahead: FlowVar is the only generic .compilerProc type which
+proc markCompilerProc(c: PContext; s: PSym) =
+  # minor hack ahead: FlowVar is the only generic .compilerproc type which
   # should not have an external name set:
   if s.kind != skType or s.name.s != "FlowVar":
-    makeExternExport(s, "$1", s.info)
+    makeExternExport(c, s, "$1", s.info)
   incl(s.flags, sfCompilerProc)
   incl(s.flags, sfUsed)
-  registerCompilerProc(s)
-
-proc deprecatedStmt(c: PContext; pragma: PNode) =
-  let pragma = pragma[1]
+  registerCompilerProc(c.graph, s)
+  if c.config.symbolFiles != disabledSf:
+    addCompilerProc(c.encoder, c.packedRepr, s)
+
+proc deprecatedStmt(c: PContext; outerPragma: PNode) =
+  let pragma = outerPragma[1]
+  if pragma.kind in {nkStrLit..nkTripleStrLit}:
+    incl(c.module.flags, sfDeprecated)
+    c.module.constraint = getStrLitNode(c, outerPragma)
+    return
   if pragma.kind != nkBracket:
-    localError(pragma.info, "list of key:value pairs expected"); return
-  for n in pragma:
-    if n.kind in {nkExprColonExpr, nkExprEqExpr}:
-      let dest = qualifiedLookUp(c, n[1], {checkUndeclared})
-      assert dest != nil
-      let src = considerQuotedIdent(n[0])
-      let alias = newSym(skAlias, src, dest, n[0].info)
-      incl(alias.flags, sfExported)
-      if sfCompilerProc in dest.flags: markCompilerProc(alias)
-      addInterfaceDecl(c, alias)
-      n.sons[1] = newSymNode(dest)
-    else:
-      localError(n.info, "key:value pair expected")
+    localError(c.config, pragma.info, "list of key:value pairs expected"); return
+  message(c.config, pragma.info, warnDeprecated,
+    "deprecated statement is now a no-op, use regular deprecated pragma")
 
 proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym =
-  if it.kind != nkExprColonExpr:
-    invalidPragma(it); return
+  if it.kind notin nkPragmaCallKinds or it.len != 2:
+    invalidPragma(c, it); return
   let n = it[1]
   if n.kind == nkSym:
     result = n.sym
@@ -642,106 +792,222 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym =
       # We return a dummy symbol; later passes over the type will repair it.
       # Generic instantiation needs to know about this too. But we're lazy
       # and perform the lookup on demand instead.
-      result = newSym(skUnknown, considerQuotedIdent(n), nil, n.info)
+      result = newSym(skUnknown, considerQuotedIdent(c, n), c.idgen, nil, n.info,
+        c.config.options)
   else:
     result = qualifiedLookUp(c, n, {checkUndeclared})
 
-proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
-                  validPragmas: TSpecialWords): bool =
-  var it = n.sons[i]
-  var key = if it.kind == nkExprColonExpr: it.sons[0] else: it
+proc semCustomPragma(c: PContext, n: PNode, sym: PSym): PNode =
+  var callNode: PNode
+
+  case n.kind
+  of nkIdentKinds:
+    # pragma -> pragma()
+    callNode = newTree(nkCall, n)
+  of nkExprColonExpr:
+    # pragma: arg -> pragma(arg)
+    callNode = newTree(nkCall, n[0], n[1])
+  of nkPragmaCallKinds - {nkExprColonExpr}:
+    callNode = n
+  else:
+    invalidPragma(c, n)
+    return n
+
+  trySuggestPragmas(c, callNode[0])
+
+  let r = c.semOverloadedCall(c, callNode, n, {skTemplate}, {efNoUndeclared})
+  if r.isNil or sfCustomPragma notin r[0].sym.flags:
+    invalidPragma(c, n)
+    return n
+
+  # we have a valid custom pragma
+  if sym != nil and sym.kind in {skEnumField, skForVar, skModule}:
+    illegalCustomPragma(c, n, sym)
+    return n
+
+  result = r
+  # Transform the nkCall node back to its original form if possible
+  if n.kind == nkIdent and r.len == 1:
+    # pragma() -> pragma
+    result = result[0]
+  elif n.kind == nkExprColonExpr and r.len == 2:
+    # pragma(arg) -> pragma: arg
+    result.transitionSonsKind(n.kind)
+
+proc processEffectsOf(c: PContext, n: PNode; owner: PSym) =
+  proc processParam(c: PContext; n: PNode) =
+    let r = c.semExpr(c, n)
+    if r.kind == nkSym and r.sym.kind == skParam:
+      if r.sym.owner == owner:
+        incl r.sym.flags, sfEffectsDelayed
+      else:
+        localError(c.config, n.info, errGenerated, "parameter cannot be declared as .effectsOf")
+    else:
+      localError(c.config, n.info, errGenerated, "parameter name expected")
+
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errGenerated, "parameter name expected")
+  else:
+    let it = n[1]
+    if it.kind in {nkCurly, nkBracket}:
+      for x in items(it): processParam(c, x)
+    else:
+      processParam(c, it)
+
+proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
+                  validPragmas: TSpecialWords,
+                  comesFromPush, isStatement: bool): bool =
+  result = false
+  var it = n[i]
+  let keyDeep = it.kind in nkPragmaCallKinds and it.len > 1
+  var key = if keyDeep: it[0] else: it
   if key.kind == nkBracketExpr:
     processNote(c, it)
     return
-  let ident = considerQuotedIdent(key)
+  elif key.kind == nkCast:
+    if comesFromPush:
+      localError(c.config, n.info, "a 'cast' pragma cannot be pushed")
+    elif not isStatement:
+      localError(c.config, n.info, "'cast' pragma only allowed in a statement context")
+    case whichPragma(key[1])
+    of wRaises, wTags, wForbids: pragmaRaisesOrTags(c, key[1])
+    else: discard
+    return
+  elif key.kind notin nkIdentKinds:
+    n[i] = semCustomPragma(c, it, sym)
+    return
+  let ident = considerQuotedIdent(c, key)
   var userPragma = strTableGet(c.userPragmas, ident)
   if userPragma != nil:
+    styleCheckUse(c, key.info, userPragma)
+
+    # number of pragmas increase/decrease with user pragma expansion
     inc c.instCounter
+    defer: dec c.instCounter
     if c.instCounter > 100:
-      globalError(it.info, errRecursiveDependencyX, userPragma.name.s)
-    pragma(c, sym, userPragma.ast, validPragmas)
-    # ensure the pragma is also remember for generic instantiations in other
-    # modules:
-    n.sons[i] = userPragma.ast
-    dec c.instCounter
+      globalError(c.config, it.info, "recursive dependency: " & userPragma.name.s)
+
+    if keyDeep:
+      localError(c.config, it.info, "user pragma cannot have arguments")
+
+    pragma(c, sym, userPragma.ast, validPragmas, isStatement)
+    n.sons[i..i] = userPragma.ast.sons # expand user pragma with its content
+    i.inc(userPragma.ast.len - 1) # inc by -1 is ok, user pragmas was empty
   else:
-    var k = whichKeyword(ident)
+    let k = whichKeyword(ident)
     if k in validPragmas:
+      checkPragmaUse(c, key.info, k, ident.s, (if sym != nil: sym else: c.module))
       case k
-      of wExportc:
-        makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info)
+      of wExportc, wExportCpp:
+        makeExternExport(c, sym, getOptionalStr(c, it, "$1"), it.info)
+        if k == wExportCpp:
+          if c.config.backend != backendCpp:
+            localError(c.config, it.info, "exportcpp requires `cpp` backend, got: " & $c.config.backend)
+          else:
+            incl(sym.flags, sfMangleCpp)
         incl(sym.flags, sfUsed) # avoid wrong hints
       of wImportc:
         let name = getOptionalStr(c, it, "$1")
-        cppDefine(c.graph.config, name)
-        makeExternImport(sym, name, it.info)
+        cppDefine(c.config, name)
+        recordPragma(c, it, "cppdefine", name)
+        makeExternImport(c, sym, name, it.info)
       of wImportCompilerProc:
         let name = getOptionalStr(c, it, "$1")
-        cppDefine(c.graph.config, name)
-        processImportCompilerProc(sym, name, it.info)
-      of wExtern: setExternName(sym, expectStrLit(c, it), it.info)
-      of wImmediate:
-        if sym.kind in {skTemplate, skMacro}:
-          incl(sym.flags, sfImmediate)
-          incl(sym.flags, sfAllUntyped)
-          message(n.info, warnDeprecated, "use 'untyped' parameters instead; immediate")
-        else: invalidPragma(it)
+        cppDefine(c.config, name)
+        recordPragma(c, it, "cppdefine", name)
+        processImportCompilerProc(c, sym, name, it.info)
+      of wExtern: setExternName(c, sym, expectStrLit(c, it), it.info)
       of wDirty:
         if sym.kind == skTemplate: incl(sym.flags, sfDirty)
-        else: invalidPragma(it)
+        else: invalidPragma(c, it)
+      of wRedefine:
+        if sym.kind == skTemplate: incl(sym.flags, sfTemplateRedefinition)
+        else: invalidPragma(c, it)
+      of wCallsite:
+        if sym.kind == skTemplate: incl(sym.flags, sfCallsite)
+        else: invalidPragma(c, it)
       of wImportCpp:
-        processImportCpp(sym, getOptionalStr(c, it, "$1"), it.info)
+        processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
+      of wCppNonPod:
+        incl(sym.flags, sfCppNonPod)
+      of wImportJs:
+        if c.config.backend != backendJs:
+          localError(c.config, it.info, "`importjs` pragma requires the JavaScript target")
+        let name = getOptionalStr(c, it, "$1")
+        incl(sym.flags, sfImportc)
+        incl(sym.flags, sfInfixCall)
+        if sym.kind in skProcKinds and {'(', '#', '@'} notin name:
+          localError(c.config, n.info, "`importjs` for routines requires a pattern")
+        setExternName(c, sym, name, it.info)
       of wImportObjC:
-        processImportObjC(sym, getOptionalStr(c, it, "$1"), it.info)
-      of wAlign:
-        if sym.typ == nil: invalidPragma(it)
-        var align = expectIntLit(c, it)
-        if (not isPowerOfTwo(align) and align != 0) or align >% high(int16):
-          localError(it.info, errPowerOfTwoExpected)
-        else:
-          sym.typ.align = align.int16
+        processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info)
       of wSize:
-        if sym.typ == nil: invalidPragma(it)
+        if sym.typ == nil: invalidPragma(c, it)
         var size = expectIntLit(c, it)
-        if not isPowerOfTwo(size) or size <= 0 or size > 8:
-          localError(it.info, errPowerOfTwoExpected)
-        else:
+        case size
+        of 1, 2, 4:
           sym.typ.size = size
+          sym.typ.align = int16 size
+        of 8:
+          sym.typ.size = 8
+          sym.typ.align = floatInt64Align(c.config)
+        else:
+          localError(c.config, it.info, "size may only be 1, 2, 4 or 8")
+      of wAlign:
+        let alignment = expectIntLit(c, it)
+        if isPowerOfTwo(alignment) and alignment > 0:
+          sym.alignment = max(sym.alignment, alignment)
+        else:
+          localError(c.config, it.info, "power of two expected")
       of wNodecl:
-        noVal(it)
+        noVal(c, it)
         incl(sym.loc.flags, lfNoDecl)
       of wPure, wAsmNoStackFrame:
-        noVal(it)
+        noVal(c, it)
         if sym != nil:
-          if k == wPure and sym.kind in routineKinds: invalidPragma(it)
+          if k == wPure and sym.kind in routineKinds: invalidPragma(c, it)
           else: incl(sym.flags, sfPure)
       of wVolatile:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfVolatile)
+      of wCursor:
+        noVal(c, it)
+        incl(sym.flags, sfCursor)
       of wRegister:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfRegister)
+      of wNoalias:
+        noVal(c, it)
+        incl(sym.flags, sfNoalias)
+      of wEffectsOf:
+        processEffectsOf(c, it, sym)
       of wThreadVar:
-        noVal(it)
-        incl(sym.flags, sfThread)
-      of wDeadCodeElim: pragmaDeadCodeElim(c, it)
+        noVal(c, it)
+        incl(sym.flags, {sfThread, sfGlobal})
+      of wDeadCodeElimUnused:
+        warningDeprecated(c.config, n.info, "'{.deadcodeelim: on.}' is deprecated, now a noop")  # deprecated, dead code elim always on
       of wNoForward: pragmaNoForward(c, it)
-      of wReorder: pragmaNoForward(c, it, sfReorder)
+      of wReorder: pragmaNoForward(c, it, flag = sfReorder)
       of wMagic: processMagic(c, it, sym)
       of wCompileTime:
-        noVal(it)
-        incl(sym.flags, sfCompileTime)
-        incl(sym.loc.flags, lfNoDecl)
+        noVal(c, it)
+        if comesFromPush:
+          if sym.kind in {skProc, skFunc}:
+            incl(sym.flags, sfCompileTime)
+        else:
+          incl(sym.flags, sfCompileTime)
+        #incl(sym.loc.flags, lfNoDecl)
       of wGlobal:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfGlobal)
         incl(sym.flags, sfPure)
-      of wMerge:
-        # only supported for backwards compat, doesn't do anything anymore
-        noVal(it)
       of wConstructor:
-        noVal(it)
         incl(sym.flags, sfConstructor)
+        if sfImportc notin sym.flags:
+          sym.constraint = newEmptyStrNode(c, it, getOptionalStr(c, it, ""))
+          sym.constraint.strVal = sym.constraint.strVal
+          sym.flags.incl {sfExportc, sfMangleCpp}
+          sym.typ.callConv = ccNoConvention
       of wHeader:
         var lib = getLib(c, libHeader, getStrLitNode(c, it))
         addToLib(lib, sym)
@@ -749,273 +1015,397 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         incl(sym.loc.flags, lfHeader)
         incl(sym.loc.flags, lfNoDecl)
         # implies nodecl, because otherwise header would not make sense
-        if sym.loc.r == nil: sym.loc.r = rope(sym.name.s)
-      of wDestructor:
-        sym.flags.incl sfOverriden
-        if sym.name.s.normalize != "destroy":
-          localError(n.info, errGenerated, "destructor has to be named 'destroy'")
-      of wOverride:
-        sym.flags.incl sfOverriden
-      of wNosideeffect:
-        noVal(it)
-        incl(sym.flags, sfNoSideEffect)
-        if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect)
-      of wSideeffect:
-        noVal(it)
+        if sym.loc.snippet == "": sym.loc.snippet = rope(sym.name.s)
+      of wNoSideEffect:
+        noVal(c, it)
+        if sym != nil:
+          incl(sym.flags, sfNoSideEffect)
+          if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect)
+      of wSideEffect:
+        noVal(c, it)
         incl(sym.flags, sfSideEffect)
       of wNoreturn:
-        noVal(it)
-        incl(sym.flags, sfNoReturn)
+        noVal(c, it)
+        # Disable the 'noreturn' annotation when in the "Quirky Exceptions" mode!
+        if c.config.exc != excQuirky:
+          incl(sym.flags, sfNoReturn)
+        if sym.typ.returnType != nil:
+          localError(c.config, sym.ast[paramsPos][0].info,
+            ".noreturn with return type not allowed")
+      of wNoDestroy:
+        noVal(c, it)
+        incl(sym.flags, sfGeneratedOp)
+      of wNosinks:
+        noVal(c, it)
+        incl(sym.flags, sfWasForwarded)
       of wDynlib:
         processDynLib(c, it, sym)
-      of wCompilerproc:
-        noVal(it)           # compilerproc may not get a string!
+      of wCompilerProc, wCore:
+        noVal(c, it)           # compilerproc may not get a string!
         cppDefine(c.graph.config, sym.name.s)
-        if sfFromGeneric notin sym.flags: markCompilerProc(sym)
+        recordPragma(c, it, "cppdefine", sym.name.s)
+        if sfFromGeneric notin sym.flags: markCompilerProc(c, sym)
+      of wNonReloadable:
+        sym.flags.incl sfNonReloadable
       of wProcVar:
-        noVal(it)
-        incl(sym.flags, sfProcvar)
+        # old procvar annotation, no longer needed
+        noVal(c, it)
       of wExplain:
         sym.flags.incl sfExplain
       of wDeprecated:
-        if it.kind == nkExprColonExpr: deprecatedStmt(c, it)
-        elif sym != nil: incl(sym.flags, sfDeprecated)
+        if sym != nil and sym.kind in routineKinds + {skType, skVar, skLet, skConst}:
+          if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it)
+          incl(sym.flags, sfDeprecated)
+        elif sym != nil and sym.kind != skModule:
+          # We don't support the extra annotation field
+          if it.kind in nkPragmaCallKinds:
+            localError(c.config, it.info, "annotation to deprecated not supported here")
+          incl(sym.flags, sfDeprecated)
+        # At this point we're quite sure this is a statement and applies to the
+        # whole module
+        elif it.kind in nkPragmaCallKinds: deprecatedStmt(c, it)
         else: incl(c.module.flags, sfDeprecated)
       of wVarargs:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfVarargs)
       of wBorrow:
         if sym.kind == skType:
-          typeBorrow(sym, it)
+          typeBorrow(c, sym, it)
         else:
-          noVal(it)
+          noVal(c, it)
           incl(sym.flags, sfBorrow)
       of wFinal:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfFinal)
       of wInheritable:
-        noVal(it)
-        if sym.typ == nil or tfFinal in sym.typ.flags: invalidPragma(it)
+        noVal(c, it)
+        if sym.typ == nil or tfFinal in sym.typ.flags: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfInheritable)
+      of wPackage:
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
+        else: incl(sym.flags, sfForward)
       of wAcyclic:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfAcyclic)
       of wShallow:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfShallow)
       of wThread:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfThread)
-        incl(sym.flags, sfProcvar)
         if sym.typ != nil:
           incl(sym.typ.flags, tfThread)
-          if sym.typ.callConv == ccClosure: sym.typ.callConv = ccDefault
+          if sym.typ.callConv == ccClosure: sym.typ.callConv = ccNimCall
+      of wSendable:
+        noVal(c, it)
+        if sym != nil and sym.typ != nil:
+          incl(sym.typ.flags, tfSendable)
+        else:
+          invalidPragma(c, it)
       of wGcSafe:
-        noVal(it)
+        noVal(c, it)
         if sym != nil:
           if sym.kind != skType: incl(sym.flags, sfThread)
           if sym.typ != nil: incl(sym.typ.flags, tfGcSafe)
-          else: invalidPragma(it)
+          else: invalidPragma(c, it)
         else:
           discard "no checking if used as a code block"
       of wPacked:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfPacked)
-      of wHint: message(it.info, hintUser, expectStrLit(c, it))
-      of wWarning: message(it.info, warnUser, expectStrLit(c, it))
+      of wHint:
+        let s = expectStrLit(c, it)
+        recordPragma(c, it, "hint", s)
+        message(c.config, it.info, hintUser, s)
+      of wWarning:
+        let s = expectStrLit(c, it)
+        recordPragma(c, it, "warning", s)
+        message(c.config, it.info, warnUser, s)
       of wError:
-        if sym != nil and sym.isRoutine:
+        if sym != nil and (sym.isRoutine or sym.kind == skType) and not isStatement:
           # This is subtle but correct: the error *statement* is only
-          # allowed for top level statements. Seems to be easier than
-          # distinguishing properly between
+          # allowed when 'wUsed' is not in validPragmas. Here this is the easiest way to
+          # distinguish properly between
           # ``proc p() {.error}`` and ``proc p() = {.error: "msg".}``
-          noVal(it)
+          if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it)
           incl(sym.flags, sfError)
+          excl(sym.flags, sfForward)
         else:
-          localError(it.info, errUser, expectStrLit(c, it))
-      of wFatal: fatal(it.info, errUser, expectStrLit(c, it))
-      of wDefine: processDefine(c, it)
+          let s = expectStrLit(c, it)
+          recordPragma(c, it, "error", s)
+          localError(c.config, it.info, errUser, s)
+      of wFatal: fatal(c.config, it.info, expectStrLit(c, it))
+      of wDefine: processDefine(c, it, sym)
       of wUndef: processUndef(c, it)
-      of wCompile: processCompile(c, it)
-      of wLink: processCommonLink(c, it, linkNormal)
-      of wLinksys: processCommonLink(c, it, linkSys)
-      of wPassl: extccomp.addLinkOption(expectStrLit(c, it))
-      of wPassc: extccomp.addCompileOption(expectStrLit(c, it))
-      of wBreakpoint: pragmaBreakpoint(c, it)
-      of wWatchPoint: pragmaWatchpoint(c, it)
+      of wCompile:
+        let m = sym.getModule()
+        incl(m.flags, sfUsed)
+        processCompile(c, it)
+      of wLink: processLink(c, it)
+      of wPassl:
+        let m = sym.getModule()
+        incl(m.flags, sfUsed)
+        let s = expectStrLit(c, it)
+        extccomp.addLinkOption(c.config, s)
+        recordPragma(c, it, "passl", s)
+      of wPassc:
+        let m = sym.getModule()
+        incl(m.flags, sfUsed)
+        let s = expectStrLit(c, it)
+        extccomp.addCompileOption(c.config, s)
+        recordPragma(c, it, "passc", s)
+      of wLocalPassc:
+        assert sym != nil and sym.kind == skModule
+        let s = expectStrLit(c, it)
+        appendToModule(sym, n)
+        extccomp.addLocalCompileOption(c.config, s, toFullPathConsiderDirty(c.config, sym.info.fileIndex))
+        recordPragma(c, it, "localpassl", s)
       of wPush:
         processPush(c, n, i + 1)
         result = true
-      of wPop: processPop(c, it)
-      of wPragma:
-        processPragma(c, n, i)
+      of wPop:
+        processPop(c, it)
         result = true
+      of wPragma:
+        if not sym.isNil and sym.kind == skTemplate:
+          sym.flags.incl sfCustomPragma
+        else:
+          processPragma(c, n, i)
+          result = true
       of wDiscardable:
-        noVal(it)
+        noVal(c, it)
         if sym != nil: incl(sym.flags, sfDiscardable)
       of wNoInit:
-        noVal(it)
+        noVal(c, it)
         if sym != nil: incl(sym.flags, sfNoInit)
       of wCodegenDecl: processCodegenDecl(c, it, sym)
-      of wChecks, wObjChecks, wFieldChecks, wRangechecks, wBoundchecks,
-         wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
-         wLinedir, wStacktrace, wLinetrace, wOptimization,
-         wCallconv,
-         wDebugger, wProfiler, wFloatchecks, wNanChecks, wInfChecks,
-         wPatterns:
-        if processOption(c, it):
-          # calling conventions (boring...):
-          localError(it.info, errOptionExpected)
+      of wChecks, wObjChecks, wFieldChecks, wRangeChecks, wBoundChecks,
+         wOverflowChecks, wNilChecks, wAssertions, wWarnings, wHints,
+         wLineDir, wOptimization, wStaticBoundchecks, wStyleChecks,
+         wCallconv, wDebugger, wProfiler,
+         wFloatChecks, wNanChecks, wInfChecks, wPatterns, wTrMacros:
+        processOption(c, it, c.config.options)
+      of wStackTrace, wLineTrace:
+        if sym.kind in {skProc, skMethod, skConverter}:
+          processOption(c, it, sym.options)
+        else:
+          processOption(c, it, c.config.options)
       of FirstCallConv..LastCallConv:
         assert(sym != nil)
-        if sym.typ == nil: invalidPragma(it)
-        else: sym.typ.callConv = wordToCallConv(k)
+        if sym.typ == nil: invalidPragma(c, it)
+        else:
+          sym.typ.callConv = wordToCallConv(k)
+          sym.typ.flags.incl tfExplicitCallConv
       of wEmit: pragmaEmit(c, it)
       of wUnroll: pragmaUnroll(c, it)
-      of wLinearScanEnd, wComputedGoto: noVal(it)
+      of wLinearScanEnd, wComputedGoto: noVal(c, it)
       of wEffects:
         # is later processed in effect analysis:
-        noVal(it)
+        noVal(c, it)
       of wIncompleteStruct:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfIncompleteStruct)
+      of wCompleteStruct:
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
+        else: incl(sym.typ.flags, tfCompleteStruct)
       of wUnchecked:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
-        else: incl(sym.typ.flags, tfUncheckedArray)
+        noVal(c, it)
+        if sym.typ == nil or sym.typ.kind notin {tyArray, tyUncheckedArray}:
+          invalidPragma(c, it)
+        else:
+          sym.typ.kind = tyUncheckedArray
       of wUnion:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
-        else: incl(sym.typ.flags, tfUnion)
+        if c.config.backend == backendJs:
+          localError(c.config, it.info, "`{.union.}` is not implemented for js backend.")
+        else:
+          noVal(c, it)
+          if sym.typ == nil: invalidPragma(c, it)
+          else: incl(sym.typ.flags, tfUnion)
       of wRequiresInit:
-        noVal(it)
-        if sym.typ == nil: invalidPragma(it)
-        else: incl(sym.typ.flags, tfNeedsInit)
+        noVal(c, it)
+        if sym.kind == skField:
+          sym.flags.incl sfRequiresInit
+        elif sym.typ != nil:
+          incl(sym.typ.flags, tfNeedsFullInit)
+        else:
+          invalidPragma(c, it)
       of wByRef:
-        noVal(it)
-        if sym == nil or sym.typ == nil:
-          if processOption(c, it): localError(it.info, errOptionExpected)
+        noVal(c, it)
+        if sym != nil and sym.kind == skParam:
+          sym.options.incl optByRef
+        elif sym == nil or sym.typ == nil:
+          processOption(c, it, c.config.options)
         else:
           incl(sym.typ.flags, tfByRef)
       of wByCopy:
-        noVal(it)
-        if sym.kind != skType or sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.kind == skParam:
+          incl(sym.flags, sfByCopy)
+        elif sym.kind != skType or sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfByCopy)
       of wPartial:
-        noVal(it)
-        if sym.kind != skType or sym.typ == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym.kind != skType or sym.typ == nil: invalidPragma(c, it)
         else:
           incl(sym.typ.flags, tfPartial)
-          # .partial types can only work with dead code elimination
-          # to prevent the codegen from doing anything before we compiled
-          # the whole program:
-          incl gGlobalOptions, optDeadCodeElim
       of wInject, wGensym:
         # We check for errors, but do nothing with these pragmas otherwise
         # as they are handled directly in 'evalTemplate'.
-        noVal(it)
-        if sym == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym == nil: invalidPragma(c, it)
       of wLine: pragmaLine(c, it)
-      of wRaises, wTags: pragmaRaisesOrTags(c, it)
+      of wRaises, wTags, wForbids: pragmaRaisesOrTags(c, it)
       of wLocks:
         if sym == nil: pragmaLockStmt(c, it)
-        elif sym.typ == nil: invalidPragma(it)
-        else: sym.typ.lockLevel = pragmaLocks(c, it)
+        elif sym.typ == nil: invalidPragma(c, it)
+        else: warningDeprecated(c.config, n.info, "'Lock levels' are deprecated, now a noop")
       of wBitsize:
-        if sym == nil or sym.kind != skField or it.kind != nkExprColonExpr:
-          invalidPragma(it)
+        if sym == nil or sym.kind != skField:
+          invalidPragma(c, it)
         else:
           sym.bitsize = expectIntLit(c, it)
+          if sym.bitsize <= 0:
+            localError(c.config, it.info, "bitsize needs to be positive")
       of wGuard:
         if sym == nil or sym.kind notin {skVar, skLet, skField}:
-          invalidPragma(it)
+          invalidPragma(c, it)
         else:
           sym.guard = pragmaGuard(c, it, sym.kind)
       of wGoto:
         if sym == nil or sym.kind notin {skVar, skLet}:
-          invalidPragma(it)
+          invalidPragma(c, it)
         else:
           sym.flags.incl sfGoto
       of wExportNims:
-        if sym == nil: invalidPragma(it)
-        else: magicsys.registerNimScriptSymbol(sym)
-      of wInjectStmt:
-        if it.kind != nkExprColonExpr:
-          localError(it.info, errExprExpected)
-        else:
-          it.sons[1] = c.semExpr(c, it.sons[1])
+        if sym == nil: invalidPragma(c, it)
+        else: magicsys.registerNimScriptSymbol(c.graph, sym)
       of wExperimental:
-        noVal(it)
-        if isTopLevel(c):
-          c.module.flags.incl sfExperimental
-        else:
-          localError(it.info, "'experimental' pragma only valid as toplevel statement")
-      of wThis:
-        if it.kind == nkExprColonExpr:
-          c.selfName = considerQuotedIdent(it[1])
-        else:
-          c.selfName = getIdent("self")
+        if not isTopLevel(c):
+          localError(c.config, n.info, "'experimental' pragma only valid as toplevel statement or in a 'push' environment")
+        processExperimental(c, it)
+      of wDoctype:
+        if not isTopLevel(c):
+          localError(c.config, n.info, "\"doctype\" pragma only valid as top-level statement")
       of wNoRewrite:
-        noVal(it)
+        noVal(c, it)
       of wBase:
-        noVal(it)
+        noVal(c, it)
         sym.flags.incl sfBase
       of wIntDefine:
-        sym.magic = mIntDefine
+        processDefineConst(c, n, sym, mIntDefine)
       of wStrDefine:
-        sym.magic = mStrDefine
+        processDefineConst(c, n, sym, mStrDefine)
+      of wBoolDefine:
+        processDefineConst(c, n, sym, mBoolDefine)
       of wUsed:
-        noVal(it)
-        if sym == nil: invalidPragma(it)
+        noVal(c, it)
+        if sym == nil: invalidPragma(c, it)
         else: sym.flags.incl sfUsed
-      else: invalidPragma(it)
-    else: invalidPragma(it)
-
-proc implicitPragmas*(c: PContext, sym: PSym, n: PNode,
+      of wLiftLocals:
+        sym.flags.incl(sfForceLift)
+      of wRequires, wInvariant, wAssume, wAssert:
+        pragmaProposition(c, it)
+      of wEnsures:
+        pragmaEnsures(c, it)
+      of wEnforceNoRaises, wQuirky:
+        sym.flags.incl sfNeverRaises
+      of wSystemRaisesDefect:
+        sym.flags.incl sfSystemRaisesDefect
+      of wVirtual:
+        processVirtual(c, it, sym, sfVirtual)
+      of wMember:
+        processVirtual(c, it, sym, sfMember)
+
+      else: invalidPragma(c, it)
+    elif comesFromPush and whichKeyword(ident) != wInvalid:
+      discard "ignore the .push pragma; it doesn't apply"
+    else:
+      # semCustomPragma gives appropriate error for invalid pragmas
+      n[i] = semCustomPragma(c, it, sym)
+
+proc overwriteLineInfo(n: PNode; info: TLineInfo) =
+  n.info = info
+  for i in 0..<n.safeLen:
+    overwriteLineInfo(n[i], info)
+
+proc mergePragmas(n, pragmas: PNode) =
+  var pragmas = copyTree(pragmas)
+  overwriteLineInfo pragmas, n.info
+  if n[pragmasPos].kind == nkEmpty:
+    n[pragmasPos] = pragmas
+  else:
+    for p in pragmas: n[pragmasPos].add p
+
+proc mergeValidPragmas(n, pragmas: PNode, validPragmas: TSpecialWords) =
+  if n[pragmasPos].kind == nkEmpty:
+    n[pragmasPos] = newNodeI(nkPragma, n.info)
+  for p in pragmas:
+    let prag = whichPragma(p)
+    if prag in validPragmas:
+      let copy = copyTree(p)
+      overwriteLineInfo copy, n.info
+      n[pragmasPos].add copy
+
+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:
-        pushInfoContext(n.info)
-        for i in countup(0, sonsLen(o) - 1):
-          if singlePragma(c, sym, o, i, validPragmas):
-            internalError(n.info, "implicitPragmas")
-        popInfoContext()
+      if not o.isNil and sfFromGeneric notin sym.flags: # bug #23019
+        pushInfoContext(c.config, info)
+        var i = 0
+        while i < o.len:
+          if singlePragma(c, sym, o, i, validPragmas, true, false):
+            internalError(c.config, info, "implicitPragmas")
+          inc i
+        popInfoContext(c.config)
+        if sym.kind in routineKinds and sym.ast != nil:
+          mergeValidPragmas(sym.ast, o, validPragmas)
 
     if lfExportLib in sym.loc.flags and sfExportc notin sym.flags:
-      localError(n.info, errDynlibRequiresExportc)
+      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:
       incl(sym.loc.flags, lfDynamicLib)
       addToLib(lib, sym)
-      if sym.loc.r == nil: sym.loc.r = rope(sym.name.s)
+      if sym.loc.snippet == "": sym.loc.snippet = rope(sym.name.s)
 
 proc hasPragma*(n: PNode, pragma: TSpecialWord): bool =
-  if n == nil or n.sons == nil:
-    return false
+  if n == nil: return false
 
-  for p in n.sons:
-    var key = if p.kind == nkExprColonExpr: p[0] else: p
+  for p in n:
+    var key = if p.kind in nkPragmaCallKinds and p.len > 1: p[0] else: p
     if key.kind == nkIdent and whichKeyword(key.ident) == pragma:
       return true
 
   return false
 
-proc pragmaRec(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) =
+proc pragmaRec(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords;
+               isStatement: bool) =
+  if n == nil: return
+  var i = 0
+  while i < n.len:
+    if singlePragma(c, sym, n, i, validPragmas, false, isStatement): break
+    inc i
+
+proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords;
+            isStatement: bool) =
   if n == nil: return
-  for i in countup(0, sonsLen(n) - 1):
-    if n.sons[i].kind == nkPragma: pragmaRec(c, sym, n.sons[i], validPragmas)
-    elif singlePragma(c, sym, n, i, validPragmas): break
+  pragmaRec(c, sym, n, validPragmas, isStatement)
+  # XXX: in the case of a callable def, this should use its info
+  implicitPragmas(c, sym, n.info, validPragmas)
 
-proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) =
+proc pragmaCallable*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords,
+                    isStatement: bool = false) =
   if n == nil: return
-  pragmaRec(c, sym, n, validPragmas)
-  implicitPragmas(c, sym, n, validPragmas)
+  if n[pragmasPos].kind != nkEmpty:
+    pragmaRec(c, sym, n[pragmasPos], validPragmas, isStatement)