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.nim1411
1 files changed, 1411 insertions, 0 deletions
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
new file mode 100644
index 000000000..9a298cd90
--- /dev/null
+++ b/compiler/pragmas.nim
@@ -0,0 +1,1411 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This module implements semantic checking for pragmas
+
+import
+  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
+  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* = {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,
+    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,
+    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, 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)
+
+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 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.snippet = rope(s.name.s)
+  elif '$' notin extname:
+    s.loc.snippet = rope(extname)
+  else:
+    try:
+      s.loc.snippet = rope(extname % s.name.s)
+    except ValueError:
+      localError(c.config, info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)")
+  when hasFFI:
+    s.cname = $s.loc.snippet
+
+
+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(c: PContext; s: PSym, extname: string, info: TLineInfo) =
+  setExternName(c, s, extname, info)
+  incl(s.flags, sfExportc)
+
+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(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 c.config.backend == backendC:
+    let m = s.getModule()
+    incl(m.flags, sfCompileToCpp)
+  incl c.config.globalOptions, optMixedMode
+
+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)
+
+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 notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errStringLiteralExpected)
+    # error correction:
+    result = newEmptyStrNode(c, n)
+  else:
+    n[1] = c.semConstExpr(c, n[1])
+    case n[1].kind
+    of nkStrLit, nkRStrLit, nkTripleStrLit: result = n[1]
+    else:
+      localError(c.config, n.info, errStringLiteralExpected)
+      # error correction:
+      result = newEmptyStrNode(c, n)
+
+proc expectStrLit(c: PContext, n: PNode): string =
+  result = getStrLitNode(c, n).strVal
+
+proc expectIntLit(c: PContext, n: PNode): int =
+  result = 0
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errIntLiteralExpected)
+  else:
+    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 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 notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errStringLiteralExpected)
+    return
+  var v: string
+  if n[1].kind == nkIdent: v = n[1].ident.s
+  else: v = expectStrLit(c, n)
+  for m in TMagic:
+    if substr($m, 1) == v:
+      s.magic = m
+      break
+  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
+  TCallingConvention(ord(ccNimCall) + ord(sw) - ord(wNimcall))
+
+proc isTurnedOn(c: PContext, n: PNode): bool =
+  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(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 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
+
+  # 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 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 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(c.config, n.info, "calling convention expected")
+  else:
+    localError(c.config, n.info, "calling convention expected")
+
+proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib =
+  for it in c.libs:
+    if it.kind == kind and trees.exprStructuralEquivalent(it.path, path):
+      return it
+
+  result = newLib(kind)
+  result.path = path
+  c.libs.add result
+  if path.kind in {nkStrLit..nkTripleStrLit}:
+    result.isOverridden = options.isDynlibOverride(c.config, path.strVal)
+
+proc expectDynlibNode(c: PContext, n: PNode): PNode =
+  if n.kind notin nkPragmaCallKinds or n.len != 2:
+    localError(c.config, n.info, errStringLiteralExpected)
+    # error correction:
+    result = newEmptyStrNode(c, n)
+  else:
+    # For the OpenGL wrapper we support:
+    # {.dynlib: myGetProcAddr(...).}
+    result = c.semExpr(c, n[1])
+    if result.kind == nkSym and result.sym.kind == skConst:
+      result = c.semConstExpr(c, result) # fold const
+    if result.typ == nil or result.typ.kind notin {tyPointer, tyString, tyProc}:
+      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.isOverridden:
+      c.optionStack[^1].dynlib = lib
+  else:
+    if n.kind in nkPragmaCallKinds:
+      var lib = getLib(c, libDynamic, expectDynlibNode(c, n))
+      if not lib.isOverridden:
+        addToLib(lib, sym)
+        incl(sym.loc.flags, lfDynamicLib)
+    else:
+      incl(sym.loc.flags, lfExportLib)
+    # since we'll be loading the dynlib symbols dynamically, we must use
+    # 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
+       tfExplicitCallConv notin sym.typ.flags:
+      sym.typ.callConv = ccCDecl
+
+proc processNote(c: PContext, n: PNode) =
+  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(c, n)
+
+  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:
+    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 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:
+    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[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[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(c.config, n.info, "{.pop.} without a corresponding {.push.}")
+  else:
+    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(c, n)
+
+proc processUndef(c: PContext, n: PNode) =
+  if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent):
+    undefSymbol(c.config.symbols, n[1].ident.s)
+  else:
+    invalidPragma(c, n)
+
+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 = AbsoluteFile parentDir(toFullPath(c.config, n.info)) / s
+  if not fileExists(result):
+    if isAbsolute(s): result = AbsoluteFile s
+    else:
+      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[i] = c.semConstExpr(c, n[i])
+    case n[i].kind
+    of nkStrLit, nkRStrLit, nkTripleStrLit:
+      when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
+        result = n[i].strVal
+      else:
+        shallowCopy(result, n[i].strVal)
+    else:
+      localError(c.config, n.info, errStringLiteralExpected)
+      result = ""
+
+  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(toFullPath(c.config, n.info)) / s
+    for f in os.walkFiles(found):
+      let obj = completeCfilePath(c.config, AbsoluteFile(dest % extractFilename(f)))
+      docompile(c, it, AbsoluteFile f, obj, "")
+  else:
+    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 = AbsoluteFile s
+      else:
+        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[1].kind
+  of nkStrLit, nkRStrLit, nkTripleStrLit:
+    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(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 != "": 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 amb = false
+        var e = searchInScopes(con, getIdent(con.cache, sub), amb)
+        # XXX what to do here if 'amb' is true?
+        if e != nil:
+          incl(e.flags, sfUsed)
+          result.add newSymNode(e)
+        else:
+          result.add newStrNode(nkStrLit, sub)
+      else:
+        # an empty '``' produces a single '`'
+        result.add newStrNode(nkStrLit, $marker)
+      if c < 0: break
+      a = c + 1
+  else:
+    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 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[i] = c.semExprWithType(c, n1[i], {efTypeAllowed})
+      n[1] = b
+    else:
+      n[1] = c.semConstExpr(c, n1)
+      case n[1].kind
+      of nkStrLit, nkRStrLit, nkTripleStrLit:
+        n[1] = semAsmOrEmit(c, n, '`')
+      else:
+        localError(c.config, n.info, errStringLiteralExpected)
+
+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(c, n)
+  elif n.kind in nkPragmaCallKinds and n.len == 2:
+    var unrollFactor = expectIntLit(c, n)
+    if unrollFactor <% 32:
+      n[1] = newIntNode(nkIntLit, unrollFactor)
+    else:
+      invalidPragma(c, n)
+
+proc pragmaLine(c: PContext, n: PNode) =
+  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(c.config, n.info, errStringLiteralExpected)
+      elif y.kind != nkIntLit:
+        localError(c.config, n.info, errIntLiteralExpected)
+      else:
+        n.info.fileIndex = fileInfoIdx(c.config, AbsoluteFile(x.strVal))
+        n.info.line = uint16(y.intVal)
+    else:
+      localError(c.config, n.info, "tuple expected")
+  else:
+    # sensible default:
+    n.info = getInfoContext(c.config, -1)
+
+proc processPragma(c: PContext, n: PNode, i: int) =
+  ## 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) =
+    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 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(c, n)
+
+proc pragmaLockStmt(c: PContext; it: PNode) =
+  if it.kind notin nkPragmaCallKinds or it.len != 2:
+    invalidPragma(c, it)
+  else:
+    let n = it[1]
+    if n.kind != nkBracket:
+      localError(c.config, n.info, errGenerated, "locks pragma takes a list of expressions")
+    else:
+      for i in 0..<n.len:
+        n[i] = c.semExpr(c, n[i])
+
+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(c.config, n.info, "a type can only borrow `.` for now")
+  incl(sym.typ.flags, tfBorrowDot)
+
+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(c, s, "$1", s.info)
+  incl(s.flags, sfCompilerProc)
+  incl(s.flags, sfUsed)
+  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(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 notin nkPragmaCallKinds or it.len != 2:
+    invalidPragma(c, it); return
+  let n = it[1]
+  if n.kind == nkSym:
+    result = n.sym
+  elif kind == skField:
+    # First check if the guard is a global variable:
+    result = qualifiedLookUp(c, n, {})
+    if result.isNil or result.kind notin {skLet, skVar} or
+        sfGlobal notin result.flags:
+      # 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(c, n), c.idgen, nil, n.info,
+        c.config.options)
+  else:
+    result = qualifiedLookUp(c, n, {checkUndeclared})
+
+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
+  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(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:
+    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, 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.config, name)
+        recordPragma(c, it, "cppdefine", name)
+        makeExternImport(c, sym, name, it.info)
+      of wImportCompilerProc:
+        let name = getOptionalStr(c, it, "$1")
+        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(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(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(c, sym, getOptionalStr(c, it, "$1"), it.info)
+      of wSize:
+        if sym.typ == nil: invalidPragma(c, it)
+        var size = expectIntLit(c, it)
+        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(c, it)
+        incl(sym.loc.flags, lfNoDecl)
+      of wPure, wAsmNoStackFrame:
+        noVal(c, it)
+        if sym != nil:
+          if k == wPure and sym.kind in routineKinds: invalidPragma(c, it)
+          else: incl(sym.flags, sfPure)
+      of wVolatile:
+        noVal(c, it)
+        incl(sym.flags, sfVolatile)
+      of wCursor:
+        noVal(c, it)
+        incl(sym.flags, sfCursor)
+      of wRegister:
+        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(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, flag = sfReorder)
+      of wMagic: processMagic(c, it, sym)
+      of wCompileTime:
+        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(c, it)
+        incl(sym.flags, sfGlobal)
+        incl(sym.flags, sfPure)
+      of wConstructor:
+        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)
+        incl(sym.flags, sfImportc)
+        incl(sym.loc.flags, lfHeader)
+        incl(sym.loc.flags, lfNoDecl)
+        # implies nodecl, because otherwise header would not make sense
+        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(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, wCore:
+        noVal(c, it)           # compilerproc may not get a string!
+        cppDefine(c.graph.config, sym.name.s)
+        recordPragma(c, it, "cppdefine", sym.name.s)
+        if sfFromGeneric notin sym.flags: markCompilerProc(c, sym)
+      of wNonReloadable:
+        sym.flags.incl sfNonReloadable
+      of wProcVar:
+        # old procvar annotation, no longer needed
+        noVal(c, it)
+      of wExplain:
+        sym.flags.incl sfExplain
+      of wDeprecated:
+        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(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
+        else: incl(sym.typ.flags, tfVarargs)
+      of wBorrow:
+        if sym.kind == skType:
+          typeBorrow(c, sym, it)
+        else:
+          noVal(c, it)
+          incl(sym.flags, sfBorrow)
+      of wFinal:
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
+        else: incl(sym.typ.flags, tfFinal)
+      of wInheritable:
+        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(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
+        else: incl(sym.typ.flags, tfAcyclic)
+      of wShallow:
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
+        else: incl(sym.typ.flags, tfShallow)
+      of wThread:
+        noVal(c, it)
+        incl(sym.flags, sfThread)
+        if sym.typ != nil:
+          incl(sym.typ.flags, tfThread)
+          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(c, it)
+        if sym != nil:
+          if sym.kind != skType: incl(sym.flags, sfThread)
+          if sym.typ != nil: incl(sym.typ.flags, tfGcSafe)
+          else: invalidPragma(c, it)
+        else:
+          discard "no checking if used as a code block"
+      of wPacked:
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
+        else: incl(sym.typ.flags, tfPacked)
+      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 or sym.kind == skType) and not isStatement:
+          # This is subtle but correct: the error *statement* is only
+          # 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".}``
+          if it.kind in nkPragmaCallKinds: discard getStrLitNode(c, it)
+          incl(sym.flags, sfError)
+          excl(sym.flags, sfForward)
+        else:
+          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:
+        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)
+        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(c, it)
+        if sym != nil: incl(sym.flags, sfDiscardable)
+      of wNoInit:
+        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, 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(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(c, it)
+      of wEffects:
+        # is later processed in effect analysis:
+        noVal(c, it)
+      of wIncompleteStruct:
+        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(c, it)
+        if sym.typ == nil or sym.typ.kind notin {tyArray, tyUncheckedArray}:
+          invalidPragma(c, it)
+        else:
+          sym.typ.kind = tyUncheckedArray
+      of wUnion:
+        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(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(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(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(c, it)
+        if sym.kind != skType or sym.typ == nil: invalidPragma(c, it)
+        else:
+          incl(sym.typ.flags, tfPartial)
+      of wInject, wGensym:
+        # We check for errors, but do nothing with these pragmas otherwise
+        # as they are handled directly in 'evalTemplate'.
+        noVal(c, it)
+        if sym == nil: invalidPragma(c, it)
+      of wLine: pragmaLine(c, it)
+      of wRaises, wTags, wForbids: pragmaRaisesOrTags(c, it)
+      of wLocks:
+        if sym == nil: pragmaLockStmt(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:
+          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(c, it)
+        else:
+          sym.guard = pragmaGuard(c, it, sym.kind)
+      of wGoto:
+        if sym == nil or sym.kind notin {skVar, skLet}:
+          invalidPragma(c, it)
+        else:
+          sym.flags.incl sfGoto
+      of wExportNims:
+        if sym == nil: invalidPragma(c, it)
+        else: magicsys.registerNimScriptSymbol(c.graph, sym)
+      of wExperimental:
+        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(c, it)
+      of wBase:
+        noVal(c, it)
+        sym.flags.incl sfBase
+      of wIntDefine:
+        processDefineConst(c, n, sym, mIntDefine)
+      of wStrDefine:
+        processDefineConst(c, n, sym, mStrDefine)
+      of wBoolDefine:
+        processDefineConst(c, n, sym, mBoolDefine)
+      of wUsed:
+        noVal(c, it)
+        if sym == nil: invalidPragma(c, it)
+        else: sym.flags.incl sfUsed
+      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 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(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.snippet == "": sym.loc.snippet = rope(sym.name.s)
+
+proc hasPragma*(n: PNode, pragma: TSpecialWord): bool =
+  if n == nil: return false
+
+  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;
+               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
+  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 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)