summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/aliases.nim16
-rw-r--r--compiler/ast.nim511
-rw-r--r--compiler/astalgo.nim356
-rw-r--r--compiler/bitsets.nim26
-rw-r--r--compiler/btrees.nim233
-rw-r--r--compiler/canonicalizer.nim10
-rw-r--r--compiler/ccgcalls.nim133
-rw-r--r--compiler/ccgexprs.nim1563
-rw-r--r--compiler/ccgliterals.nim107
-rw-r--r--compiler/ccgmerge.nim76
-rw-r--r--compiler/ccgstmts.nim640
-rw-r--r--compiler/ccgthreadvars.nim39
-rw-r--r--compiler/ccgtrav.nim91
-rw-r--r--compiler/ccgtypes.nim538
-rw-r--r--compiler/ccgutils.nim136
-rw-r--r--compiler/cgen.nim984
-rw-r--r--compiler/cgendata.nim60
-rw-r--r--compiler/cgmeth.nim80
-rw-r--r--compiler/closureiters.nim1332
-rw-r--r--compiler/cmdlinehelper.nim92
-rw-r--r--compiler/commands.nim975
-rw-r--r--compiler/condsyms.nim104
-rw-r--r--compiler/configuration.nim6
-rw-r--r--compiler/depends.nim44
-rw-r--r--compiler/destroyer.nim666
-rw-r--r--compiler/dfa.nim481
-rw-r--r--compiler/docgen.nim812
-rw-r--r--compiler/docgen2.nim37
-rw-r--r--compiler/evalffi.nim48
-rw-r--r--compiler/evaltempl.nim96
-rw-r--r--compiler/extccomp.nim782
-rw-r--r--compiler/filter_tmpl.nim76
-rw-r--r--compiler/filters.nim53
-rw-r--r--compiler/forloops.nim4
-rw-r--r--compiler/gorgeimpl.nim58
-rw-r--r--compiler/guards.nim276
-rw-r--r--compiler/hlo.nim28
-rw-r--r--compiler/idents.nim67
-rw-r--r--compiler/idgen.nim14
-rw-r--r--compiler/importer.nim185
-rw-r--r--compiler/incremental.nim197
-rw-r--r--compiler/installer.ini29
-rw-r--r--compiler/jsgen.nim1677
-rw-r--r--compiler/jstypes.nim63
-rw-r--r--compiler/lambdalifting.nim407
-rw-r--r--compiler/layouter.nim305
-rw-r--r--compiler/lexer.nim605
-rw-r--r--compiler/liftlocals.nim71
-rw-r--r--compiler/lineinfos.nim264
-rw-r--r--compiler/linter.nim180
-rw-r--r--compiler/llstream.nim13
-rw-r--r--compiler/lookups.nim242
-rw-r--r--compiler/lowerings.nim358
-rw-r--r--compiler/macrocacheimpl.nim79
-rw-r--r--compiler/magicsys.nim182
-rw-r--r--compiler/main.nim444
-rw-r--r--compiler/modulegraphs.nim172
-rw-r--r--compiler/modulepaths.nim168
-rw-r--r--compiler/modules.nim278
-rw-r--r--compiler/msgs.nim1071
-rw-r--r--compiler/ndi.nim16
-rw-r--r--compiler/nim.cfg5
-rw-r--r--compiler/nim.nim152
-rw-r--r--compiler/nimblecmd.nim186
-rw-r--r--compiler/nimconf.nim171
-rw-r--r--compiler/nimeval.nim137
-rw-r--r--compiler/nimfix/nimfix.nim18
-rw-r--r--compiler/nimfix/pretty.nim154
-rw-r--r--compiler/nimfix/prettybase.nim79
-rw-r--r--compiler/nimlexbase.nim5
-rw-r--r--compiler/nimsets.nim103
-rw-r--r--compiler/nversion.nim5
-rw-r--r--compiler/options.nim752
-rw-r--r--compiler/packagehandling.nim28
-rw-r--r--compiler/parampatterns.nim108
-rw-r--r--compiler/parser.nim845
-rw-r--r--compiler/passaux.nim22
-rw-r--r--compiler/passes.nim212
-rw-r--r--compiler/pathutils.nim245
-rw-r--r--compiler/patterns.nim37
-rw-r--r--compiler/pbraces.nim1780
-rw-r--r--compiler/platform.nim83
-rw-r--r--compiler/plugins/active.nim13
-rw-r--r--compiler/plugins/itersgen.nim29
-rw-r--r--compiler/plugins/locals.nim (renamed from compiler/plugins/locals/locals.nim)10
-rw-r--r--compiler/pluginsupport.nim31
-rw-r--r--compiler/pragmas.nim951
-rw-r--r--compiler/prefixmatches.nim6
-rw-r--r--compiler/procfind.nim23
-rw-r--r--compiler/renderer.nim687
-rw-r--r--compiler/reorder.nim444
-rw-r--r--compiler/rod.nim31
-rw-r--r--compiler/rodimpl.nim946
-rw-r--r--compiler/rodread.nim1236
-rw-r--r--compiler/rodutils.nim63
-rw-r--r--compiler/rodwrite.nim651
-rw-r--r--compiler/ropes.nim82
-rw-r--r--compiler/scriptconfig.nim102
-rw-r--r--compiler/sem.nim374
-rw-r--r--compiler/semasgn.nim219
-rw-r--r--compiler/semcall.nim389
-rw-r--r--compiler/semdata.nim166
-rw-r--r--compiler/semdestruct.nim245
-rw-r--r--compiler/semexprs.nim1504
-rw-r--r--compiler/semfields.nim42
-rw-r--r--compiler/semfold.nim800
-rw-r--r--compiler/semgnrc.nim199
-rw-r--r--compiler/seminst.nim213
-rw-r--r--compiler/semmacrosanity.nim44
-rw-r--r--compiler/semmagic.nim342
-rw-r--r--compiler/semobjconstr.nim308
-rw-r--r--compiler/semparallel.nim163
-rw-r--r--compiler/sempass2.nim444
-rw-r--r--compiler/semstmts.nim1567
-rw-r--r--compiler/semtempl.nim187
-rw-r--r--compiler/semtypes.nim924
-rw-r--r--compiler/semtypinst.nim286
-rw-r--r--compiler/service.nim87
-rw-r--r--compiler/sighashes.nim124
-rw-r--r--compiler/sigmatch.nim1219
-rw-r--r--compiler/sizealignoffsetimpl.nim417
-rw-r--r--compiler/suggest.nim255
-rw-r--r--compiler/syntaxes.nim127
-rw-r--r--compiler/tccgen.nim22
-rw-r--r--compiler/testability.nim5
-rw-r--r--compiler/transf.nim412
-rw-r--r--compiler/trees.nim20
-rw-r--r--compiler/treetab.nim3
-rw-r--r--compiler/types.nim616
-rw-r--r--compiler/typesrenderer.nim42
-rw-r--r--compiler/vm.nim816
-rw-r--r--compiler/vmdef.nim45
-rw-r--r--compiler/vmdeps.nim201
-rw-r--r--compiler/vmgen.nim701
-rw-r--r--compiler/vmhooks.nim4
-rw-r--r--compiler/vmmarshal.nim84
-rw-r--r--compiler/vmops.nim76
-rw-r--r--compiler/wordrecg.nim46
-rw-r--r--compiler/writetracking.nim19
139 files changed, 25296 insertions, 17549 deletions
diff --git a/compiler/aliases.nim b/compiler/aliases.nim
index 0c836bb24..f79210dd7 100644
--- a/compiler/aliases.nim
+++ b/compiler/aliases.nim
@@ -34,10 +34,10 @@ proc isPartOfAux(n: PNode, b: PType, marker: var IntSet): TAnalysisResult =
       of nkOfBranch, nkElse:
         result = isPartOfAux(lastSon(n.sons[i]), b, marker)
         if result == arYes: return
-      else: internalError("isPartOfAux(record case branch)")
+      else: discard "isPartOfAux(record case branch)"
   of nkSym:
     result = isPartOfAux(n.sym.typ, b, marker)
-  else: internalError(n.info, "isPartOfAux()")
+  else: discard
 
 proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult =
   result = arNo
@@ -49,7 +49,7 @@ proc isPartOfAux(a, b: PType, marker: var IntSet): TAnalysisResult =
     if a.sons[0] != nil:
       result = isPartOfAux(a.sons[0].skipTypes(skipPtrs), b, marker)
     if result == arNo: result = isPartOfAux(a.n, b, marker)
-  of tyGenericInst, tyDistinct, tyAlias:
+  of tyGenericInst, tyDistinct, tyAlias, tySink:
     result = isPartOfAux(lastSon(a), b, marker)
   of tyArray, tySet, tyTuple:
     for i in countup(0, sonsLen(a) - 1):
@@ -95,7 +95,7 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
   if a.kind == b.kind:
     case a.kind
     of nkSym:
-      const varKinds = {skVar, skTemp, skProc}
+      const varKinds = {skVar, skTemp, skProc, skFunc}
       # same symbol: aliasing:
       if a.sym.id == b.sym.id: result = arYes
       elif a.sym.kind in varKinds or b.sym.kind in varKinds:
@@ -179,5 +179,11 @@ proc isPartOf*(a, b: PNode): TAnalysisResult =
           result = isPartOf(a[0], b)
           if result == arNo: result = arMaybe
       else: discard
+    of nkObjConstr:
+      result = arNo
+      for i in 1..<b.len:
+        let res = isPartOf(a, b[i][1])
+        if res != arNo:
+          result = res
+          if res == arYes: break
     else: discard
-
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 26305cf3b..5f5f296cb 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -10,7 +10,7 @@
 # abstract syntax tree + symbol table
 
 import
-  msgs, hashes, nversion, options, strutils, securehash, ropes, idents,
+  lineinfos, hashes, nversion, options, strutils, std / sha1, ropes, idents,
   intsets, idgen
 
 type
@@ -62,8 +62,8 @@ type
     nkTripleStrLit,       # a triple string literal """
     nkNilLit,             # the nil literal
                           # end of atoms
-    nkMetaNode_Obsolete,  # difficult to explain; represents itself
-                          # (used for macros)
+    nkComesFrom,          # "comes from" template/macro information for
+                          # better stack trace generation
     nkDotCall,            # used to temporarily flag a nkCall node;
                           # this is used
                           # for transforming ``s.len`` to ``len(s)``
@@ -221,10 +221,13 @@ type
     nkGotoState,          # used for the state machine (for iterators)
     nkState,              # give a label to a code section (for iterators)
     nkBreakState,         # special break statement for easier code generation
+    nkFuncDef,            # a func
+    nkTupleConstr         # a tuple constructor
+
   TNodeKinds* = set[TNodeKind]
 
 type
-  TSymFlag* = enum    # already 32 flags!
+  TSymFlag* = enum    # already 33 flags!
     sfUsed,           # read access of sym (for warnings) or simply used
     sfExported,       # symbol is exported from module
     sfFromGeneric,    # symbol is instantiation of a generic; this is needed
@@ -252,13 +255,15 @@ type
     sfProcvar,        # proc can be passed to a proc var
     sfDiscriminant,   # field is a discriminant in a record/object
     sfDeprecated,     # symbol is deprecated
+    sfExplain,        # provide more diagnostics when this symbol is used
     sfError,          # usage of symbol should trigger a compile-time error
     sfShadowed,       # a symbol that was shadowed in some inner scope
     sfThread,         # proc will run as a thread
                       # variable is a thread variable
     sfCompileTime,    # proc can be evaluated at compile time
     sfConstructor,    # proc is a C++ constructor
-    sfDeadCodeElim,   # dead code elimination for the module is turned on
+    sfDispatcher,     # copied method symbol is the dispatcher
+                      # deprecated and unused, except for the con
     sfBorrow,         # proc is borrowed
     sfInfixCall,      # symbol needs infix call syntax in target language;
                       # for interfacing with C++, JS
@@ -271,10 +276,9 @@ type
   TSymFlags* = set[TSymFlag]
 
 const
-  sfDispatcher* = sfDeadCodeElim # copied method symbol is the dispatcher
   sfNoInit* = sfMainModule       # don't generate code to init the variable
 
-  sfImmediate* = sfDeadCodeElim
+  sfImmediate* = sfDispatcher
     # macro or template is immediately expanded
     # without considering any possible overloads
   sfAllUntyped* = sfVolatile # macro or template is immediately expanded \
@@ -289,8 +293,14 @@ const
     # the compiler will avoid printing such names
     # in user messages.
 
+  sfHoisted* = sfForward
+    # an expression was hoised to an anonymous variable.
+    # the flag is applied to the var/let symbol
+
   sfNoForward* = sfRegister
     # forward declarations are not required (per module)
+  sfReorder* = sfForward
+    # reordering pass is enabled
 
   sfCompileToCpp* = sfInfixCall       # compile the module as C++ code
   sfCompileToObjc* = sfNamedParamCall # compile the module as Objective-C code
@@ -300,6 +310,7 @@ const
   sfEscapes* = sfProcvar              # param escapes
   sfBase* = sfDiscriminant
   sfIsSelf* = sfOverriden             # param is 'self'
+  sfCustomPragma* = sfRegister        # symbol is custom pragma template
 
 const
   # getting ready for the future expr/stmt merge
@@ -311,7 +322,8 @@ const
   usesEffects* = 1      # read effects at position 1
   writeEffects* = 2     # write effects at position 2
   tagEffects* = 3       # user defined tag ('gc', 'time' etc.)
-  effectListLen* = 4    # list of effects list
+  pragmasEffects* = 4    # not an effect, but a slot for pragmas in proc type
+  effectListLen* = 5    # list of effects list
 
 type
   TTypeKind* = enum  # order is important!
@@ -349,58 +361,62 @@ type
     tyInt, tyInt8, tyInt16, tyInt32, tyInt64, # signed integers
     tyFloat, tyFloat32, tyFloat64, tyFloat128,
     tyUInt, tyUInt8, tyUInt16, tyUInt32, tyUInt64,
-    tyUnused0, tyUnused1, tyUnused2,
+    tyOptAsRef, tySink, tyLent,
     tyVarargs,
-    tyUnused,
+    tyUncheckedArray
+      # An array with boundaries [0,+∞]
+
     tyProxy # used as errornous type (for idetools)
 
-    tyBuiltInTypeClass #\
+    tyBuiltInTypeClass
       # Type such as the catch-all object, tuple, seq, etc
 
-    tyUserTypeClass #\
+    tyUserTypeClass
       # the body of a user-defined type class
 
-    tyUserTypeClassInst #\
+    tyUserTypeClassInst
       # Instance of a parametric user-defined type class.
       # Structured similarly to tyGenericInst.
       # tyGenericInst represents concrete types, while
       # this is still a "generic param" that will bind types
       # and resolves them during sigmatch and instantiation.
 
-    tyCompositeTypeClass #\
+    tyCompositeTypeClass
       # Type such as seq[Number]
       # The notes for tyUserTypeClassInst apply here as well
       # sons[0]: the original expression used by the user.
       # sons[1]: fully expanded and instantiated meta type
       # (potentially following aliases)
 
-    tyAnd, tyOr, tyNot #\
+    tyInferred
+      # In the initial state `base` stores a type class constraining
+      # the types that can be inferred. After a candidate type is
+      # selected, it's stored in `lastSon`. Between `base` and `lastSon`
+      # there may be 0, 2 or more types that were also considered as
+      # possible candidates in the inference process (i.e. lastSon will
+      # be updated to store a type best conforming to all candidates)
+
+    tyAnd, tyOr, tyNot
       # boolean type classes such as `string|int`,`not seq`,
       # `Sortable and Enumable`, etc
 
-    tyAnything #\
+    tyAnything
       # a type class matching any type
 
-    tyStatic #\
+    tyStatic
       # a value known at compile type (the underlying type is .base)
 
-    tyFromExpr #\
+    tyFromExpr
       # This is a type representing an expression that depends
       # on generic parameters (the expression is stored in t.n)
       # It will be converted to a real type only during generic
       # instantiation and prior to this it has the potential to
       # be any type.
 
-    tyFieldAccessor #\
-      # Expressions such as Type.field (valid in contexts such
-      # as the `is` operator and magics like `high` and `low`).
-      # Could be lifted to a single argument proc returning the
-      # field value.
-      # sons[0]: type of containing object or tuple
-      # sons[1]: field type
-      # .n: nkDotExpr storing the field name
+    tyOpt
+      # Builtin optional type
 
-    tyVoid #\
+    tyVoid
       # now different from tyEmpty, hurray!
 
 static:
@@ -420,6 +436,7 @@ const
                     tyAnd, tyOr, tyNot, tyAnything}
 
   tyMetaTypes* = {tyGenericParam, tyTypeDesc, tyExpr} + tyTypeClasses
+  tyUserTypeClasses* = {tyUserTypeClass, tyUserTypeClassInst}
 
 type
   TTypeKinds* = set[TTypeKind]
@@ -442,10 +459,16 @@ type
     nfExprCall  # this is an attempt to call a regular expression
     nfIsRef     # this node is a 'ref' node; used for the VM
     nfPreventCg # this node should be ignored by the codegen
+    nfBlockArg  # this a stmtlist appearing in a call (e.g. a do block)
+    nfFromTemplate # a top-level node returned from a template
+    nfDefaultParam # an automatically inserter default parameter
+    nfDefaultRefsParam # a default param value references another parameter
+                       # the flag is applied to proc default values and to calls
 
   TNodeFlags* = set[TNodeFlag]
-  TTypeFlag* = enum   # keep below 32 for efficiency reasons (now: 30)
+  TTypeFlag* = enum   # keep below 32 for efficiency reasons (now: beyond that)
     tfVarargs,        # procedure has C styled varargs
+                      # tyArray type represeting a varargs list
     tfNoSideEffect,   # procedure type does not allow side effects
     tfFinal,          # is the object final?
     tfInheritable,    # is the object inheritable?
@@ -463,6 +486,8 @@ type
                       # can be attached to generic procs with free standing
                       # type parameters: e.g. proc foo[T]()
                       # depends on unresolved static params.
+    tfResolved        # marks a user type class, after it has been bound to a
+                      # concrete type (lastSon becomes the concrete type)
     tfRetType,        # marks return types in proc (used to detect type classes
                       # used as return types for return type inference)
     tfCapturesEnv,    # whether proc really captures some environment
@@ -482,6 +507,10 @@ type
     tfHasStatic
     tfGenericTypeParam
     tfImplicitTypeParam
+    tfInferrableStatic
+    tfConceptMatchedTypeSym
+    tfExplicit        # for typedescs, marks types explicitly prefixed with the
+                      # `type` operator (e.g. type int)
     tfWildcard        # consider a proc like foo[T, I](x: Type[T, I])
                       # T and I here can bind to both typedesc and static types
                       # before this is determined, we'll consider them to be a
@@ -491,6 +520,9 @@ type
     tfTriggersCompileTime # uses the NimNode type which make the proc
                           # implicitly '.compiletime'
     tfRefsAnonObj     # used for 'ref object' and 'ptr object'
+    tfCovariant       # covariant generic param mimicing a ptr type
+    tfWeakCovariant   # covariant generic param mimicing a seq/array type
+    tfContravariant   # contravariant generic param
 
   TTypeFlags* = set[TTypeFlag]
 
@@ -511,6 +543,7 @@ type
     skConst,              # a constant
     skResult,             # special 'result' variable
     skProc,               # a proc
+    skFunc,               # a func
     skMethod,             # a method
     skIterator,           # an iterator
     skConverter,          # a type converter
@@ -529,16 +562,12 @@ type
   TSymKinds* = set[TSymKind]
 
 const
-  routineKinds* = {skProc, skMethod, skIterator,
+  routineKinds* = {skProc, skFunc, skMethod, skIterator,
                    skConverter, skMacro, skTemplate}
   tfIncompleteStruct* = tfVarargs
-  tfUncheckedArray* = tfVarargs
   tfUnion* = tfNoSideEffect
   tfGcSafe* = tfThread
   tfObjHasKids* = tfEnumHasHoles
-  tfOldSchoolExprStmt* = tfVarargs # for now used to distinguish \
-    # 'varargs[expr]' from 'varargs[untyped]'. Eventually 'expr' will be
-    # deprecated and this mess can be cleaned up.
   tfReturnsNew* = tfInheritable
   skError* = skUnknown
 
@@ -549,8 +578,9 @@ type
   TMagic* = enum # symbols that require compiler magic:
     mNone,
     mDefined, mDefinedInScope, mCompiles, mArrGet, mArrPut, mAsgn,
-    mLow, mHigh, mSizeOf, mTypeTrait, mIs, mOf, mAddr, mTypeOf, mRoof, mPlugin,
-    mEcho, mShallowCopy, mSlurp, mStaticExec,
+    mLow, mHigh, mSizeOf, mAlignOf, mOffsetOf, mTypeTrait,
+    mIs, mOf, mAddr, mType, mTypeOf,
+    mRoof, mPlugin, mEcho, mShallowCopy, mSlurp, mStaticExec, mStatic,
     mParseExprToAst, mParseStmtToAst, mExpandToAst, mQuoteAst,
     mUnaryLt, mInc, mDec, mOrd,
     mNew, mNewFinalize, mNewSeq, mNewSeqOfCap,
@@ -561,7 +591,7 @@ type
     mAddI, mSubI, mMulI, mDivI, mModI,
     mSucc, mPred,
     mAddF64, mSubF64, mMulF64, mDivF64,
-    mShrI, mShlI, mBitandI, mBitorI, mBitxorI,
+    mShrI, mShlI, mAshrI, mBitandI, mBitorI, mBitxorI,
     mMinI, mMaxI,
     mMinF64, mMaxF64,
     mAddU, mSubU, mMulU, mDivU, mModU,
@@ -597,8 +627,9 @@ type
     mIsPartOf, mAstToStr, mParallel,
     mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast,
     mNewString, mNewStringOfCap, mParseBiggestFloat,
+    mMove, mWasMoved, mDestroy,
     mReset,
-    mArray, mOpenArray, mRange, mSet, mSeq, mVarargs,
+    mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs,
     mRef, mPtr, mVar, mDistinct, mVoid, mTuple,
     mOrdinal,
     mInt, mInt8, mInt16, mInt32, mInt64,
@@ -608,25 +639,32 @@ type
     mPointer, mEmptySet, mIntSetBaseType, mNil, mExpr, mStmt, mTypeDesc,
     mVoidType, mPNimrodNode, mShared, mGuarded, mLock, mSpawn, mDeepCopy,
     mIsMainModule, mCompileDate, mCompileTime, mProcCall,
-    mCpuEndian, mHostOS, mHostCPU, mAppType,
+    mCpuEndian, mHostOS, mHostCPU, mBuildOS, mBuildCPU, mAppType,
     mNaN, mInf, mNegInf,
     mCompileOption, mCompileOptionArg,
-    mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind,
+    mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel,
+    mNKind, mNSymKind,
+
+    mNccValue, mNccInc, mNcsAdd, mNcsIncl, mNcsLen, mNcsAt,
+    mNctPut, mNctLen, mNctGet, mNctHasNext, mNctNext,
+
     mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal,
     mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNLineInfo,
-    mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr,
+    mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent,
     mNBindSym, mLocals, mNCallSite,
-    mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl,
+    mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym,
     mNHint, mNWarning, mNError,
-    mInstantiationInfo, mGetTypeInfo, mNGenSym,
-    mNimvm, mIntDefine, mStrDefine
+    mInstantiationInfo, mGetTypeInfo,
+    mNimvm, mIntDefine, mStrDefine, mRunnableExamples,
+    mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf,
+    mSymIsInstantiationOf
 
 # things that we can evaluate safely at compile time, even if not asked for it:
 const
   ctfeWhitelist* = {mNone, mUnaryLt, mSucc,
     mPred, mInc, mDec, mOrd, mLengthOpenArray,
     mLengthStr, mLengthArray, mLengthSeq, mXLenStr, mXLenSeq,
-    mArrGet, mArrPut, mAsgn,
+    mArrGet, mArrPut, mAsgn, mDestroy,
     mIncl, mExcl, mCard, mChr,
     mAddI, mSubI, mMulI, mDivI, mModI,
     mAddF64, mSubF64, mMulF64, mDivF64,
@@ -658,11 +696,6 @@ const
     mConStrStr, mAppendStrCh, mAppendStrStr, mAppendSeqElem,
     mInRange, mInSet, mRepr,
     mCopyStr, mCopyStrLast}
-  # magics that require special semantic checking and
-  # thus cannot be overloaded (also documented in the spec!):
-  SpecialSemMagics* = {
-    mDefined, mDefinedInScope, mCompiles, mLow, mHigh, mSizeOf, mIs, mOf,
-    mShallowCopy, mExpandToAst, mParallel, mSpawn, mAstToStr}
 
 type
   PNode* = ref TNode
@@ -690,10 +723,9 @@ type
       sons*: TNodeSeq
     comment*: string
 
-  TSymSeq* = seq[PSym]
   TStrTable* = object         # a table[PIdent] of PSym
     counter*: int
-    data*: TSymSeq
+    data*: seq[PSym]
 
   # -------------- backend information -------------------------------
   TLocKind* = enum
@@ -710,7 +742,7 @@ type
     locOther                  # location is something other
   TLocFlag* = enum
     lfIndirect,               # backend introduced a pointer
-    lfFullExternalName, # only used when 'gCmd == cmdPretty': Indicates
+    lfFullExternalName, # only used when 'conf.cmd == cmdPretty': Indicates
       # that the symbol has been imported via 'importc: "fullname"' and
       # no format string.
     lfNoDeepCopy,             # no need for a deep copy
@@ -729,9 +761,9 @@ type
   TLocFlags* = set[TLocFlag]
   TLoc* = object
     k*: TLocKind              # kind of location
-    s*: TStorageLoc
+    storage*: TStorageLoc
     flags*: TLocFlags         # location's flags
-    t*: PType                 # type of location
+    lode*: PNode              # Node where the location came from; can be faked
     r*: Rope                  # rope value of location (code generators)
 
   # ---------------- end of backend information ------------------------------
@@ -772,7 +804,7 @@ type
     of routineKinds:
       procInstCache*: seq[PInstantiation]
       gcUnsafetyReason*: PSym  # for better error messages wrt gcsafe
-      #scope*: PScope          # the scope where the proc was defined
+      transformedBody*: PNode  # cached body after transf pass
     of skModule, skPackage:
       # modules keep track of the generic symbols they use from other modules.
       # this is because in incremental compilation, when a module is about to
@@ -822,6 +854,8 @@ type
     constraint*: PNode        # additional constraints like 'lit|result'; also
                               # misused for the codegenDecl pragma in the hope
                               # it won't cause problems
+                              # for skModule the string literal to output for
+                              # deprecated modules.
     when defined(nimsuggest):
       allUsages*: seq[TLineInfo]
 
@@ -851,7 +885,8 @@ type
                               # mean that there is no destructor.
                               # see instantiateDestructor in semdestruct.nim
     deepCopy*: PSym           # overriden 'deepCopy' operation
-    assignment*: PSym         # overriden '=' operator
+    assignment*: PSym         # overriden '=' operation
+    sink*: PSym               # overriden '=sink' operation
     methods*: seq[(int,PSym)] # attached methods
     size*: BiggestInt         # the size of the type in bytes
                               # -1 means that the size is unkwown
@@ -860,6 +895,8 @@ type
     loc*: TLoc
     typeInst*: PType          # for generic instantiations the tyGenericInst that led to this
                               # type.
+    uniqueId*: int            # due to a design mistake, we need to keep the real ID here as it
+                              # required by the --incremental:on mode.
 
   TPair* = object
     key*, val*: RootRef
@@ -908,20 +945,20 @@ type
 # the poor naming choices in the standard library.
 
 const
-  OverloadableSyms* = {skProc, skMethod, skIterator,
+  OverloadableSyms* = {skProc, skFunc, skMethod, skIterator,
     skConverter, skModule, skTemplate, skMacro}
 
   GenericTypes*: TTypeKinds = {tyGenericInvocation, tyGenericBody,
     tyGenericParam}
 
   StructuralEquivTypes*: TTypeKinds = {tyNil, tyTuple, tyArray,
-    tySet, tyRange, tyPtr, tyRef, tyVar, tySequence, tyProc, tyOpenArray,
+    tySet, tyRange, tyPtr, tyRef, tyVar, tyLent, tySequence, tyProc, tyOpenArray,
     tyVarargs}
 
   ConcreteTypes*: TTypeKinds = { # types of the expr that may occur in::
                                  # var x = expr
     tyBool, tyChar, tyEnum, tyArray, tyObject,
-    tySet, tyTuple, tyRange, tyPtr, tyRef, tyVar, tySequence, tyProc,
+    tySet, tyTuple, tyRange, tyPtr, tyRef, tyVar, tyLent, tySequence, tyProc,
     tyPointer,
     tyOpenArray, tyString, tyCString, tyInt..tyInt64, tyFloat..tyFloat128,
     tyUInt..tyUInt64}
@@ -929,14 +966,15 @@ const
     tyFloat..tyFloat128, tyUInt..tyUInt64}
   ConstantDataTypes*: TTypeKinds = {tyArray, tySet,
                                     tyTuple, tySequence}
-  NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr, tySequence,
-    tyProc, tyString, tyError}
-  ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType,
+  NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr,
+    tyProc, tyError}
+  ExportableSymKinds* = {skVar, skConst, skProc, skFunc, skMethod, skType,
     skIterator,
     skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias}
   PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16,
                                       nfDotSetter, nfDotField,
-                                      nfIsRef, nfPreventCg, nfLL}
+                                      nfIsRef, nfPreventCg, nfLL,
+                                      nfFromTemplate, nfDefaultRefsParam}
   namePos* = 0
   patternPos* = 1    # empty except for term rewriting macros
   genericParamsPos* = 2
@@ -952,21 +990,23 @@ const
   nkIdentKinds* = {nkIdent, nkSym, nkAccQuoted, nkOpenSymChoice,
                    nkClosedSymChoice}
 
+  nkPragmaCallKinds* = {nkExprColonExpr, nkCall, nkCallStrLit}
   nkLiterals* = {nkCharLit..nkTripleStrLit}
+  nkFloatLiterals* = {nkFloatLit..nkFloat128Lit}
   nkLambdaKinds* = {nkLambda, nkDo}
-  declarativeDefs* = {nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef}
+  declarativeDefs* = {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef}
   procDefs* = nkLambdaKinds + declarativeDefs
 
   nkSymChoices* = {nkClosedSymChoice, nkOpenSymChoice}
   nkStrKinds* = {nkStrLit..nkTripleStrLit}
 
   skLocalVars* = {skVar, skLet, skForVar, skParam, skResult}
-  skProcKinds* = {skProc, skTemplate, skMacro, skIterator,
+  skProcKinds* = {skProc, skFunc, skTemplate, skMacro, skIterator,
                   skMethod, skConverter}
 
 var ggDebug* {.deprecated.}: bool ## convenience switch for trying out things
-var
-  gMainPackageId*: int
+#var
+#  gMainPackageId*: int
 
 proc isCallExpr*(n: PNode): bool =
   result = n.kind in nkCallKinds
@@ -974,29 +1014,36 @@ proc isCallExpr*(n: PNode): bool =
 proc discardSons*(father: PNode)
 
 proc len*(n: PNode): int {.inline.} =
-  if isNil(n.sons): result = 0
-  else: result = len(n.sons)
+  when defined(nimNoNilSeqs):
+    result = len(n.sons)
+  else:
+    if isNil(n.sons): result = 0
+    else: result = len(n.sons)
 
 proc safeLen*(n: PNode): int {.inline.} =
   ## works even for leaves.
-  if n.kind in {nkNone..nkNilLit} or isNil(n.sons): result = 0
-  else: result = len(n.sons)
+  if n.kind in {nkNone..nkNilLit}: result = 0
+  else: result = len(n)
+
+proc safeArrLen*(n: PNode): int {.inline.} =
+  ## works for array-like objects (strings passed as openArray in VM).
+  if n.kind in {nkStrLit..nkTripleStrLit}:result = len(n.strVal)
+  elif n.kind in {nkNone..nkFloat128Lit}: result = 0
+  else: result = len(n)
 
 proc add*(father, son: PNode) =
   assert son != nil
-  if isNil(father.sons): father.sons = @[]
+  when not defined(nimNoNilSeqs):
+    if isNil(father.sons): father.sons = @[]
   add(father.sons, son)
 
-proc `[]`*(n: PNode, i: int): PNode {.inline.} =
-  result = n.sons[i]
+type Indexable = PNode | PType
 
-template `-|`*(b, s: untyped): untyped =
-  (if b >= 0: b else: s.len + b)
+template `[]`*(n: Indexable, i: int): Indexable = n.sons[i]
+template `[]=`*(n: Indexable, i: int; x: Indexable) = n.sons[i] = x
 
-# son access operators with support for negative indices
-template `{}`*(n: PNode, i: int): untyped = n[i -| n]
-template `{}=`*(n: PNode, i: int, s: PNode) =
-  n.sons[i -| n] = s
+template `[]`*(n: Indexable, i: BackwardsIndex): Indexable = n[n.len - i.int]
+template `[]=`*(n: Indexable, i: BackwardsIndex; x: Indexable) = n[n.len - i.int] = x
 
 when defined(useNodeIds):
   const nodeIdToDebug* = -1 # 299750 # 300761 #300863 # 300879
@@ -1006,9 +1053,9 @@ proc newNode*(kind: TNodeKind): PNode =
   new(result)
   result.kind = kind
   #result.info = UnknownLineInfo() inlined:
-  result.info.fileIndex = int32(- 1)
-  result.info.col = int16(- 1)
-  result.info.line = int16(- 1)
+  result.info.fileIndex = InvalidFileIdx
+  result.info.col = int16(-1)
+  result.info.line = uint16(0)
   when defined(useNodeIds):
     result.id = gNodeId
     if result.id == nodeIdToDebug:
@@ -1018,50 +1065,36 @@ proc newNode*(kind: TNodeKind): PNode =
 
 proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode =
   result = newNode(kind)
+  if children.len > 0:
+    result.info = children[0].info
   result.sons = @children
 
-proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
-  result = newNode(kind)
-  result.intVal = intVal
-
-proc newIntTypeNode*(kind: TNodeKind, intVal: BiggestInt, typ: PType): PNode =
-  result = newIntNode(kind, intVal)
-  result.typ = typ
-
-proc newFloatNode*(kind: TNodeKind, floatVal: BiggestFloat): PNode =
-  result = newNode(kind)
-  result.floatVal = floatVal
-
-proc newStrNode*(kind: TNodeKind, strVal: string): PNode =
-  result = newNode(kind)
-  result.strVal = strVal
+template previouslyInferred*(t: PType): PType =
+  if t.sons.len > 1: t.lastSon else: nil
 
 proc newSym*(symKind: TSymKind, name: PIdent, owner: PSym,
-             info: TLineInfo): PSym =
+             info: TLineInfo; options: TOptions = {}): PSym =
   # generates a symbol and initializes the hash field too
   new(result)
   result.name = name
   result.kind = symKind
   result.flags = {}
   result.info = info
-  result.options = gOptions
+  result.options = options
   result.owner = owner
-  result.offset = - 1
+  result.offset = -1
   result.id = getID()
   when debugIds:
     registerId(result)
-  #if result.id == 93289:
-  #  writeStacktrace()
-  #  MessageOut(name.s & " has id: " & toString(result.id))
-
-var emptyNode* = newNode(nkEmpty)
-# There is a single empty node that is shared! Do not overwrite it!
 
 proc isMetaType*(t: PType): bool =
   return t.kind in tyMetaTypes or
          (t.kind == tyStatic and t.n == nil) or
          tfHasMeta in t.flags
 
+proc isUnresolvedStatic*(t: PType): bool =
+  return t.kind == tyStatic and t.n == nil
+
 proc linkTo*(t: PType, s: PSym): PType {.discardable.} =
   t.sym = s
   s.typ = t
@@ -1072,13 +1105,13 @@ proc linkTo*(s: PSym, t: PType): PSym {.discardable.} =
   s.typ = t
   result = s
 
-template fileIdx*(c: PSym): int32 =
+template fileIdx*(c: PSym): FileIndex =
   # XXX: this should be used only on module symbols
-  c.position.int32
+  c.position.FileIndex
 
 template filename*(c: PSym): string =
   # XXX: this should be used only on module symbols
-  c.position.int32.toFilename
+  c.position.FileIndex.toFilename
 
 proc appendToModule*(m: PSym, n: PNode) =
   ## The compiler will use this internally to add nodes that will be
@@ -1096,24 +1129,24 @@ const                         # for all kind of hash tables:
 
 proc copyStrTable*(dest: var TStrTable, src: TStrTable) =
   dest.counter = src.counter
-  if isNil(src.data): return
   setLen(dest.data, len(src.data))
   for i in countup(0, high(src.data)): dest.data[i] = src.data[i]
 
 proc copyIdTable*(dest: var TIdTable, src: TIdTable) =
   dest.counter = src.counter
-  if isNil(src.data): return
   newSeq(dest.data, len(src.data))
   for i in countup(0, high(src.data)): dest.data[i] = src.data[i]
 
 proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) =
   dest.counter = src.counter
-  if isNil(src.data): return
   setLen(dest.data, len(src.data))
   for i in countup(0, high(src.data)): dest.data[i] = src.data[i]
 
 proc discardSons*(father: PNode) =
-  father.sons = nil
+  when defined(nimNoNilSeqs):
+    father.sons = @[]
+  else:
+    father.sons = nil
 
 proc withInfo*(n: PNode, info: TLineInfo): PNode =
   n.info = info
@@ -1180,18 +1213,36 @@ proc newNodeIT*(kind: TNodeKind, info: TLineInfo, typ: PType): PNode =
   result.info = info
   result.typ = typ
 
+proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
+  result = newNode(kind)
+  result.intVal = intVal
+
+proc newIntTypeNode*(kind: TNodeKind, intVal: BiggestInt, typ: PType): PNode =
+  result = newIntNode(kind, intVal)
+  result.typ = typ
+
+proc newFloatNode*(kind: TNodeKind, floatVal: BiggestFloat): PNode =
+  result = newNode(kind)
+  result.floatVal = floatVal
+
+proc newStrNode*(kind: TNodeKind, strVal: string): PNode =
+  result = newNode(kind)
+  result.strVal = strVal
+
+proc newStrNode*(strVal: string; info: TLineInfo): PNode =
+  result = newNodeI(nkStrLit, info)
+  result.strVal = strVal
+
 proc addSon*(father, son: PNode) =
   assert son != nil
-  if isNil(father.sons): father.sons = @[]
+  when not defined(nimNoNilSeqs):
+    if isNil(father.sons): father.sons = @[]
   add(father.sons, son)
 
-var emptyParams = newNode(nkFormalParams)
-emptyParams.addSon(emptyNode)
-
 proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
-                 params = emptyParams,
+                 params,
                  name, pattern, genericParams,
-                 pragmas, exceptions = ast.emptyNode): PNode =
+                 pragmas, exceptions: PNode): PNode =
   result = newNodeI(kind, info)
   result.sons = @[name, pattern, genericParams, params,
                   pragmas, exceptions, body]
@@ -1206,40 +1257,49 @@ proc `$`*(x: TLockLevel): string =
   elif x.ord == UnknownLockLevel.ord: result = "<unknown>"
   else: result = $int16(x)
 
+proc `$`*(s: PSym): string =
+  result = s.name.s & "@" & $s.id
+
 proc newType*(kind: TTypeKind, owner: PSym): PType =
   new(result)
   result.kind = kind
   result.owner = owner
-  result.size = - 1
-  result.align = 2            # default alignment
+  result.size = -1
+  result.align = -1            # default alignment
   result.id = getID()
+  result.uniqueId = result.id
   result.lockLevel = UnspecifiedLockLevel
   when debugIds:
     registerId(result)
   when false:
-    if result.id == 205734:
+    if result.id == 76426:
       echo "KNID ", kind
       writeStackTrace()
 
 proc mergeLoc(a: var TLoc, b: TLoc) =
   if a.k == low(a.k): a.k = b.k
-  if a.s == low(a.s): a.s = b.s
+  if a.storage == low(a.storage): a.storage = b.storage
   a.flags = a.flags + b.flags
-  if a.t == nil: a.t = b.t
+  if a.lode == nil: a.lode = b.lode
   if a.r == nil: a.r = b.r
-  #if a.a == 0: a.a = b.a
 
 proc newSons*(father: PNode, length: int) =
-  if isNil(father.sons):
-    newSeq(father.sons, length)
-  else:
+  when defined(nimNoNilSeqs):
     setLen(father.sons, length)
+  else:
+    if isNil(father.sons):
+      newSeq(father.sons, length)
+    else:
+      setLen(father.sons, length)
 
 proc newSons*(father: PType, length: int) =
-  if isNil(father.sons):
-    newSeq(father.sons, length)
-  else:
+  when defined(nimNoNilSeqs):
     setLen(father.sons, length)
+  else:
+    if isNil(father.sons):
+      newSeq(father.sons, length)
+    else:
+      setLen(father.sons, length)
 
 proc sonsLen*(n: PType): int = n.sons.len
 proc len*(n: PType): int = n.sons.len
@@ -1256,6 +1316,7 @@ proc assignType*(dest, src: PType) =
   dest.align = src.align
   dest.destructor = src.destructor
   dest.deepCopy = src.deepCopy
+  dest.sink = src.sink
   dest.assignment = src.assignment
   dest.lockLevel = src.lockLevel
   # this fixes 'type TLock = TSysLock':
@@ -1278,15 +1339,14 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType =
     when debugIds: registerId(result)
   result.sym = t.sym          # backend-info should not be copied
 
-proc copySym*(s: PSym, keepId: bool = false): PSym =
-  result = newSym(s.kind, s.name, s.owner, s.info)
+proc exactReplica*(t: PType): PType = copyType(t, t.owner, true)
+
+proc copySym*(s: PSym): PSym =
+  result = newSym(s.kind, s.name, s.owner, s.info, s.options)
   #result.ast = nil            # BUGFIX; was: s.ast which made problems
   result.typ = s.typ
-  if keepId:
-    result.id = s.id
-  else:
-    result.id = getID()
-    when debugIds: registerId(result)
+  result.id = getID()
+  when debugIds: registerId(result)
   result.flags = s.flags
   result.magic = s.magic
   if s.kind == skModule:
@@ -1298,8 +1358,9 @@ proc copySym*(s: PSym, keepId: bool = false): PSym =
   if result.kind in {skVar, skLet, skField}:
     result.guard = s.guard
 
-proc createModuleAlias*(s: PSym, newIdent: PIdent, info: TLineInfo): PSym =
-  result = newSym(s.kind, newIdent, s.owner, info)
+proc createModuleAlias*(s: PSym, newIdent: PIdent, info: TLineInfo;
+                        options: TOptions): PSym =
+  result = newSym(s.kind, newIdent, s.owner, info, options)
   # keep ID!
   result.ast = s.ast
   result.id = s.id
@@ -1310,7 +1371,7 @@ proc createModuleAlias*(s: PSym, newIdent: PIdent, info: TLineInfo): PSym =
   result.loc = s.loc
   result.annex = s.annex
   # XXX once usedGenerics is used, ensure module aliases keep working!
-  assert s.usedGenerics == nil
+  assert s.usedGenerics.len == 0
 
 proc initStrTable*(x: var TStrTable) =
   x.counter = 0
@@ -1323,6 +1384,9 @@ proc initIdTable*(x: var TIdTable) =
   x.counter = 0
   newSeq(x.data, StartSize)
 
+proc newIdTable*: TIdTable =
+  initIdTable(result)
+
 proc resetIdTable*(x: var TIdTable) =
   x.counter = 0
   # clear and set to old initial size:
@@ -1349,6 +1413,14 @@ proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
   result = t
   while result.kind in kinds: result = lastSon(result)
 
+proc skipTypes*(t: PType, kinds: TTypeKinds; maxIters: int): PType =
+  result = t
+  var i = maxIters
+  while result.kind in kinds:
+    result = lastSon(result)
+    dec i
+    if i == 0: return nil
+
 proc skipTypesOrNil*(t: PType, kinds: TTypeKinds): PType =
   ## same as skipTypes but handles 'nil'
   result = t
@@ -1361,8 +1433,8 @@ proc isGCedMem*(t: PType): bool {.inline.} =
            t.kind == tyProc and t.callConv == ccClosure
 
 proc propagateToOwner*(owner, elem: PType) =
-  const HaveTheirOwnEmpty = {tySequence, tySet, tyPtr, tyRef, tyProc}
-  owner.flags = owner.flags + (elem.flags * {tfHasMeta})
+  const HaveTheirOwnEmpty = {tySequence, tyOpt, tySet, tyPtr, tyRef, tyProc}
+  owner.flags = owner.flags + (elem.flags * {tfHasMeta, tfTriggersCompileTime})
   if tfNotNil in elem.flags:
     if owner.kind in {tyGenericInst, tyGenericBody, tyGenericInvocation}:
       owner.flags.incl tfNotNil
@@ -1377,34 +1449,41 @@ proc propagateToOwner*(owner, elem: PType) =
     owner.flags.incl tfHasMeta
 
   if tfHasAsgn in elem.flags:
-    let o2 = elem.skipTypes({tyGenericInst, tyAlias})
+    let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
     if o2.kind in {tyTuple, tyObject, tyArray,
-                   tySequence, tySet, tyDistinct}:
+                   tySequence, tyOpt, tySet, tyDistinct}:
       o2.flags.incl tfHasAsgn
       owner.flags.incl tfHasAsgn
 
-  if tfTriggersCompileTime in elem.flags:
-    owner.flags.incl tfTriggersCompileTime
-
   if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
                        tyGenericInvocation, tyPtr}:
-    let elemB = elem.skipTypes({tyGenericInst, tyAlias})
+    let elemB = elem.skipTypes({tyGenericInst, tyAlias, tySink})
     if elemB.isGCedMem or tfHasGCedMem in elemB.flags:
       # for simplicity, we propagate this flag even to generics. We then
       # ensure this doesn't bite us in sempass2.
       owner.flags.incl tfHasGCedMem
 
 proc rawAddSon*(father, son: PType) =
-  if isNil(father.sons): father.sons = @[]
+  when not defined(nimNoNilSeqs):
+    if isNil(father.sons): father.sons = @[]
   add(father.sons, son)
   if not son.isNil: propagateToOwner(father, son)
 
+proc rawAddSonNoPropagationOfTypeFlags*(father, son: PType) =
+  when not defined(nimNoNilSeqs):
+    if isNil(father.sons): father.sons = @[]
+  add(father.sons, son)
+
 proc addSonNilAllowed*(father, son: PNode) =
-  if isNil(father.sons): father.sons = @[]
+  when not defined(nimNoNilSeqs):
+    if isNil(father.sons): father.sons = @[]
   add(father.sons, son)
 
 proc delSon*(father: PNode, idx: int) =
-  if isNil(father.sons): return
+  when defined(nimNoNilSeqs):
+    if father.len == 0: return
+  else:
+    if isNil(father.sons): return
   var length = sonsLen(father)
   for i in countup(idx, length - 2): father.sons[i] = father.sons[i + 1]
   setLen(father.sons, length - 1)
@@ -1423,7 +1502,7 @@ proc copyNode*(src: PNode): PNode =
       echo "COMES FROM ", src.id
   case src.kind
   of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
-  of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal
+  of nkFloatLiterals: result.floatVal = src.floatVal
   of nkSym: result.sym = src.sym
   of nkIdent: result.ident = src.ident
   of nkStrLit..nkTripleStrLit: result.strVal = src.strVal
@@ -1442,7 +1521,7 @@ proc shallowCopy*(src: PNode): PNode =
       echo "COMES FROM ", src.id
   case src.kind
   of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
-  of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal
+  of nkFloatLiterals: result.floatVal = src.floatVal
   of nkSym: result.sym = src.sym
   of nkIdent: result.ident = src.ident
   of nkStrLit..nkTripleStrLit: result.strVal = src.strVal
@@ -1462,7 +1541,7 @@ proc copyTree*(src: PNode): PNode =
       echo "COMES FROM ", src.id
   case src.kind
   of nkCharLit..nkUInt64Lit: result.intVal = src.intVal
-  of nkFloatLit..nkFloat128Lit: result.floatVal = src.floatVal
+  of nkFloatLiterals: result.floatVal = src.floatVal
   of nkSym: result.sym = src.sym
   of nkIdent: result.ident = src.ident
   of nkStrLit..nkTripleStrLit: result.strVal = src.strVal
@@ -1506,33 +1585,44 @@ proc getInt*(a: PNode): BiggestInt =
   case a.kind
   of nkCharLit..nkUInt64Lit: result = a.intVal
   else:
-    internalError(a.info, "getInt")
-    result = 0
+    raiseRecoverableError("cannot extract number from invalid AST node")
+    #internalError(a.info, "getInt")
+    #doAssert false, "getInt"
+    #result = 0
 
 proc getFloat*(a: PNode): BiggestFloat =
   case a.kind
-  of nkFloatLit..nkFloat128Lit: result = a.floatVal
+  of nkFloatLiterals: result = a.floatVal
   else:
-    internalError(a.info, "getFloat")
-    result = 0.0
+    raiseRecoverableError("cannot extract number from invalid AST node")
+    #doAssert false, "getFloat"
+    #internalError(a.info, "getFloat")
+    #result = 0.0
 
 proc getStr*(a: PNode): string =
   case a.kind
   of nkStrLit..nkTripleStrLit: result = a.strVal
   of nkNilLit:
     # let's hope this fixes more problems than it creates:
-    result = nil
+    when defined(nimNoNilSeqs):
+      result = ""
+    else:
+      result = nil
   else:
-    internalError(a.info, "getStr")
-    result = ""
+    raiseRecoverableError("cannot extract string from invalid AST node")
+    #doAssert false, "getStr"
+    #internalError(a.info, "getStr")
+    #result = ""
 
 proc getStrOrChar*(a: PNode): string =
   case a.kind
   of nkStrLit..nkTripleStrLit: result = a.strVal
   of nkCharLit..nkUInt64Lit: result = $chr(int(a.intVal))
   else:
-    internalError(a.info, "getStrOrChar")
-    result = ""
+    raiseRecoverableError("cannot extract string from invalid AST node")
+    #doAssert false, "getStrOrChar"
+    #internalError(a.info, "getStrOrChar")
+    #result = ""
 
 proc isGenericRoutine*(s: PSym): bool =
   case s.kind
@@ -1557,14 +1647,32 @@ proc originatingModule*(s: PSym): PSym =
 proc isRoutine*(s: PSym): bool {.inline.} =
   result = s.kind in skProcKinds
 
+proc isCompileTimeProc*(s: PSym): bool {.inline.} =
+  result = s.kind == skMacro or
+           s.kind == skProc and sfCompileTime in s.flags
+
+proc isRunnableExamples*(n: PNode): bool =
+  # Templates and generics don't perform symbol lookups.
+  result = n.kind == nkSym and n.sym.magic == mRunnableExamples or
+    n.kind == nkIdent and n.ident.s == "runnableExamples"
+
+proc requiredParams*(s: PSym): int =
+  # Returns the number of required params (without default values)
+  # XXX: Perhaps we can store this in the `offset` field of the
+  # symbol instead?
+  for i in 1 ..< s.typ.len:
+    if s.typ.n[i].sym.ast != nil:
+      return i - 1
+  return s.typ.len - 1
+
 proc hasPattern*(s: PSym): bool {.inline.} =
   result = isRoutine(s) and s.ast.sons[patternPos].kind != nkEmpty
 
 iterator items*(n: PNode): PNode =
-  for i in 0.. <n.len: yield n.sons[i]
+  for i in 0..<n.safeLen: yield n.sons[i]
 
 iterator pairs*(n: PNode): tuple[i: int, n: PNode] =
-  for i in 0.. <n.len: yield (i, n.sons[i])
+  for i in 0..<n.safeLen: yield (i, n.sons[i])
 
 proc isAtom*(n: PNode): bool {.inline.} =
   result = n.kind >= nkNone and n.kind <= nkNilLit
@@ -1588,11 +1696,18 @@ proc skipStmtList*(n: PNode): PNode =
   else:
     result = n
 
+proc toVar*(typ: PType): PType =
+  ## If ``typ`` is not a tyVar then it is converted into a `var <typ>` and
+  ## returned. Otherwise ``typ`` is simply returned as-is.
+  result = typ
+  if typ.kind != tyVar:
+    result = newType(tyVar, typ.owner)
+    rawAddSon(result, typ)
+
 proc toRef*(typ: PType): PType =
   ## If ``typ`` is a tyObject then it is converted into a `ref <typ>` and
   ## returned. Otherwise ``typ`` is simply returned as-is.
-  result = typ
-  if typ.kind == tyObject:
+  if typ.skipTypes({tyAlias, tyGenericInst}).kind == tyObject:
     result = newType(tyRef, typ.owner)
     rawAddSon(result, typ)
 
@@ -1600,9 +1715,47 @@ proc toObject*(typ: PType): PType =
   ## If ``typ`` is a tyRef then its immediate son is returned (which in many
   ## cases should be a ``tyObject``).
   ## Otherwise ``typ`` is simply returned as-is.
-  result = typ
-  if result.kind == tyRef:
-    result = result.lastSon
+  let t = typ.skipTypes({tyAlias, tyGenericInst})
+  if t.kind == tyRef: t.lastSon
+  else: typ
+
+proc isException*(t: PType): bool =
+  # check if `y` is object type and it inherits from Exception
+  assert(t != nil)
+
+  if t.kind notin {tyObject, tyGenericInst}:
+    return false
+
+  var base = t
+  while base != nil and base.kind in {tyRef, tyObject, tyGenericInst}:
+    if base.sym != nil and base.sym.magic == mException:
+      return true
+    base = base.lastSon
+  return false
+
+proc isImportedException*(t: PType; conf: ConfigRef): bool =
+  assert t != nil
+
+  if optNoCppExceptions in conf.globalOptions:
+    return false
+
+  let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst})
+
+  if base.sym != nil and {sfCompileToCpp, sfImportc} * base.sym.flags != {}:
+    result = true
+
+proc isInfixAs*(n: PNode): bool =
+  return n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "as"
+
+proc findUnresolvedStatic*(n: PNode): PNode =
+  if n.kind == nkSym and n.typ.kind == tyStatic and n.typ.n == nil:
+    return n
+
+  for son in n:
+    let n = son.findUnresolvedStatic
+    if n != nil: return n
+
+  return nil
 
 when false:
   proc containsNil*(n: PNode): bool =
@@ -1610,3 +1763,15 @@ when false:
     if n.isNil: return true
     for i in 0 ..< n.safeLen:
       if n[i].containsNil: return true
+
+template hasDestructor*(t: PType): bool = tfHasAsgn in t.flags
+template incompleteType*(t: PType): bool =
+  t.sym != nil and {sfForward, sfNoForward} * t.sym.flags == {sfForward}
+
+template typeCompleted*(s: PSym) =
+  incl s.flags, sfNoForward
+
+template getBody*(s: PSym): PNode = s.ast[bodyPos]
+
+template detailedInfo*(sym: PSym): string =
+  sym.name.s
diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim
index 161e4d637..60eecbdc5 100644
--- a/compiler/astalgo.nim
+++ b/compiler/astalgo.nim
@@ -12,77 +12,58 @@
 # the data structures here are used in various places of the compiler.
 
 import
-  ast, hashes, intsets, strutils, options, msgs, ropes, idents, rodutils
+  ast, hashes, intsets, strutils, options, lineinfos, ropes, idents, rodutils,
+  msgs
 
 proc hashNode*(p: RootRef): Hash
-proc treeToYaml*(n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope
+proc treeToYaml*(conf: ConfigRef; n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope
   # Convert a tree into its YAML representation; this is used by the
   # YAML code generator and it is invaluable for debugging purposes.
   # If maxRecDepht <> -1 then it won't print the whole graph.
-proc typeToYaml*(n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope
-proc symToYaml*(n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope
-proc lineInfoToStr*(info: TLineInfo): Rope
-
-# ----------------------- node sets: ---------------------------------------
-proc objectSetContains*(t: TObjectSet, obj: RootRef): bool
-  # returns true whether n is in t
-proc objectSetIncl*(t: var TObjectSet, obj: RootRef)
-  # include an element n in the table t
-proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool
-  # more are not needed ...
-
-# ----------------------- str table -----------------------------------------
-proc strTableContains*(t: TStrTable, n: PSym): bool
-proc strTableAdd*(t: var TStrTable, n: PSym)
-proc strTableGet*(t: TStrTable, name: PIdent): PSym
-
-type
-  TTabIter*{.final.} = object # consider all fields here private
-    h*: Hash                  # current hash
-
-proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym
-proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym
-  # usage:
-  # var
-  #   i: TTabIter
-  #   s: PSym
-  # s = InitTabIter(i, table)
-  # while s != nil:
-  #   ...
-  #   s = NextIter(i, table)
-  #
-
-type
-  TIdentIter*{.final.} = object # iterator over all syms with same identifier
-    h*: Hash                    # current hash
-    name*: PIdent
-
-
-proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym
-proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym
-
-# these are for debugging only: They are not really deprecated, but I want
-# the warning so that release versions do not contain debugging statements:
-proc debug*(n: PSym) {.deprecated.}
-proc debug*(n: PType) {.deprecated.}
-proc debug*(n: PNode) {.deprecated.}
-
-template mdbg*: bool {.dirty.} =
-  when compiles(c.module):
-    c.module.fileIdx == gProjectMainIdx
-  elif compiles(m.c.module):
-    m.c.module.fileIdx == gProjectMainIdx
-  elif compiles(cl.c.module):
-    cl.c.module.fileIdx == gProjectMainIdx
-  elif compiles(p):
-    when compiles(p.lex):
-      p.lex.fileIdx == gProjectMainIdx
+proc typeToYaml*(conf: ConfigRef; n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope
+proc symToYaml*(conf: ConfigRef; n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope
+proc lineInfoToStr*(conf: ConfigRef; info: TLineInfo): Rope
+
+when declared(echo):
+  # these are for debugging only: They are not really deprecated, but I want
+  # the warning so that release versions do not contain debugging statements:
+  proc debug*(n: PSym; conf: ConfigRef = nil) {.exportc: "debugSym", deprecated.}
+  proc debug*(n: PType; conf: ConfigRef = nil) {.exportc: "debugType", deprecated.}
+  proc debug*(n: PNode; conf: ConfigRef = nil) {.exportc: "debugNode", deprecated.}
+
+  template debug*(x: PSym|PType|PNode) {.deprecated.} =
+    when compiles(c.config):
+      debug(c.config, x)
+    elif compiles(c.graph.config):
+      debug(c.graph.config, x)
     else:
-      p.module.module.fileIdx == gProjectMainIdx
-  elif compiles(L.fileIdx):
-    L.fileIdx == gProjectMainIdx
-  else:
-    false
+      error()
+
+  template debug*(x: auto) {.deprecated.} =
+    echo x
+
+  template mdbg*: bool {.deprecated.} =
+    when compiles(c.graph):
+      c.module.fileIdx == c.graph.config.projectMainIdx
+    elif compiles(c.module):
+      c.module.fileIdx == c.config.projectMainIdx
+    elif compiles(c.c.module):
+      c.c.module.fileIdx == c.c.config.projectMainIdx
+    elif compiles(m.c.module):
+      m.c.module.fileIdx == m.c.config.projectMainIdx
+    elif compiles(cl.c.module):
+      cl.c.module.fileIdx == cl.c.config.projectMainIdx
+    elif compiles(p):
+      when compiles(p.lex):
+        p.lex.fileIdx == p.lex.config.projectMainIdx
+      else:
+        p.module.module.fileIdx == p.config.projectMainIdx
+    elif compiles(m.module.fileIdx):
+      m.module.fileIdx == m.config.projectMainIdx
+    elif compiles(L.fileIdx):
+      L.fileIdx == L.config.projectMainIdx
+    else:
+      error()
 
 # --------------------------- ident tables ----------------------------------
 proc idTableGet*(t: TIdTable, key: PIdObj): RootRef
@@ -98,7 +79,6 @@ proc idNodeTablePut*(t: var TIdNodeTable, key: PIdObj, val: PNode)
 
 proc getSymFromList*(list: PNode, ident: PIdent, start: int = 0): PSym
 proc lookupInRecord*(n: PNode, field: PIdent): PSym
-proc getModule*(s: PSym): PSym
 proc mustRehash*(length, counter: int): bool
 proc nextTry*(h, maxHash: Hash): Hash {.inline.}
 
@@ -176,7 +156,7 @@ proc lookupInRecord(n: PNode, field: PIdent): PSym =
       result = lookupInRecord(n.sons[i], field)
       if result != nil: return
   of nkRecCase:
-    if (n.sons[0].kind != nkSym): internalError(n.info, "lookupInRecord")
+    if (n.sons[0].kind != nkSym): return nil
     result = lookupInRecord(n.sons[0], field)
     if result != nil: return
     for i in countup(1, sonsLen(n) - 1):
@@ -184,12 +164,12 @@ proc lookupInRecord(n: PNode, field: PIdent): PSym =
       of nkOfBranch, nkElse:
         result = lookupInRecord(lastSon(n.sons[i]), field)
         if result != nil: return
-      else: internalError(n.info, "lookupInRecord(record case branch)")
+      else: return nil
   of nkSym:
     if n.sym.name.id == field.id: result = n.sym
-  else: internalError(n.info, "lookupInRecord()")
+  else: return nil
 
-proc getModule(s: PSym): PSym =
+proc getModule*(s: PSym): PSym =
   result = s
   assert((result.kind == skModule) or (result.owner != result))
   while result != nil and result.kind != skModule: result = result.owner
@@ -199,7 +179,7 @@ proc getSymFromList(list: PNode, ident: PIdent, start: int = 0): PSym =
     if list.sons[i].kind == nkSym:
       result = list.sons[i].sym
       if result.name.id == ident.id: return
-    else: internalError(list.info, "getSymFromList")
+    else: return nil
   result = nil
 
 proc hashNode(p: RootRef): Hash =
@@ -215,7 +195,7 @@ proc rspaces(x: int): Rope =
 
 proc toYamlChar(c: char): string =
   case c
-  of '\0'..'\x1F', '\x80'..'\xFF': result = "\\u" & strutils.toHex(ord(c), 4)
+  of '\0'..'\x1F', '\x7F'..'\xFF': result = "\\u" & strutils.toHex(ord(c), 4)
   of '\'', '\"', '\\': result = '\\' & c
   else: result = $c
 
@@ -226,7 +206,7 @@ proc makeYamlString*(s: string): Rope =
   const MaxLineLength = 64
   result = nil
   var res = "\""
-  for i in countup(0, if s.isNil: -1 else: (len(s)-1)):
+  for i in 0 ..< s.len:
     if (i + 1) mod MaxLineLength == 0:
       add(res, '\"')
       add(res, "\n")
@@ -246,16 +226,16 @@ proc flagsToStr[T](flags: set[T]): Rope =
       add(result, makeYamlString($x))
     result = "[" & result & "]"
 
-proc lineInfoToStr(info: TLineInfo): Rope =
-  result = "[$1, $2, $3]" % [makeYamlString(toFilename(info)),
+proc lineInfoToStr(conf: ConfigRef; info: TLineInfo): Rope =
+  result = "[$1, $2, $3]" % [makeYamlString(toFilename(conf, info)),
                              rope(toLinenumber(info)),
                              rope(toColumn(info))]
 
-proc treeToYamlAux(n: PNode, marker: var IntSet,
+proc treeToYamlAux(conf: ConfigRef; n: PNode, marker: var IntSet,
                    indent, maxRecDepth: int): Rope
-proc symToYamlAux(n: PSym, marker: var IntSet,
+proc symToYamlAux(conf: ConfigRef; n: PSym, marker: var IntSet,
                   indent, maxRecDepth: int): Rope
-proc typeToYamlAux(n: PType, marker: var IntSet,
+proc typeToYamlAux(conf: ConfigRef; n: PType, marker: var IntSet,
                    indent, maxRecDepth: int): Rope
 
 proc ropeConstr(indent: int, c: openArray[Rope]): Rope =
@@ -269,28 +249,33 @@ proc ropeConstr(indent: int, c: openArray[Rope]): Rope =
     inc(i, 2)
   addf(result, "$N$1}", [rspaces(indent)])
 
-proc symToYamlAux(n: PSym, marker: var IntSet, indent: int,
+proc symToYamlAux(conf: ConfigRef; n: PSym, marker: var IntSet, indent: int,
                   maxRecDepth: int): Rope =
   if n == nil:
     result = rope("null")
   elif containsOrIncl(marker, n.id):
-    result = "\"$1 @$2\"" % [rope(n.name.s), rope(
-        strutils.toHex(cast[ByteAddress](n), sizeof(n) * 2))]
+    result = "\"$1\"" % [rope(n.name.s)]
   else:
-    var ast = treeToYamlAux(n.ast, marker, indent + 2, maxRecDepth - 1)
+    var ast = treeToYamlAux(conf, n.ast, marker, indent + 2, maxRecDepth - 1)
     result = ropeConstr(indent, [rope("kind"),
                                  makeYamlString($n.kind),
                                  rope("name"), makeYamlString(n.name.s),
-                                 rope("typ"), typeToYamlAux(n.typ, marker,
-                                   indent + 2, maxRecDepth - 1),
-                                 rope("info"), lineInfoToStr(n.info),
+                                 #rope("typ"), typeToYamlAux(conf, n.typ, marker,
+                                 #  indent + 2, maxRecDepth - 1),
+                                 rope("info"), lineInfoToStr(conf, n.info),
                                  rope("flags"), flagsToStr(n.flags),
                                  rope("magic"), makeYamlString($n.magic),
                                  rope("ast"), ast, rope("options"),
                                  flagsToStr(n.options), rope("position"),
-                                 rope(n.position)])
-
-proc typeToYamlAux(n: PType, marker: var IntSet, indent: int,
+                                 rope(n.position),
+                                 rope("k"), makeYamlString($n.loc.k),
+                                 rope("storage"), makeYamlString($n.loc.storage),
+                                 rope("flags"), makeYamlString($n.loc.flags),
+                                 rope("r"), n.loc.r,
+                                 rope("lode"), treeToYamlAux(conf, n.loc.lode, marker, indent + 2, maxRecDepth - 1)
+    ])
+
+proc typeToYamlAux(conf: ConfigRef; n: PType, marker: var IntSet, indent: int,
                    maxRecDepth: int): Rope =
   if n == nil:
     result = rope("null")
@@ -302,15 +287,15 @@ proc typeToYamlAux(n: PType, marker: var IntSet, indent: int,
       result = rope("[")
       for i in countup(0, sonsLen(n) - 1):
         if i > 0: add(result, ",")
-        addf(result, "$N$1$2", [rspaces(indent + 4), typeToYamlAux(n.sons[i],
+        addf(result, "$N$1$2", [rspaces(indent + 4), typeToYamlAux(conf, n.sons[i],
             marker, indent + 4, maxRecDepth - 1)])
       addf(result, "$N$1]", [rspaces(indent + 2)])
     else:
       result = rope("null")
     result = ropeConstr(indent, [rope("kind"),
                                  makeYamlString($n.kind),
-                                 rope("sym"), symToYamlAux(n.sym, marker,
-        indent + 2, maxRecDepth - 1), rope("n"), treeToYamlAux(n.n, marker,
+                                 rope("sym"), symToYamlAux(conf, n.sym, marker,
+        indent + 2, maxRecDepth - 1), rope("n"), treeToYamlAux(conf, n.n, marker,
         indent + 2, maxRecDepth - 1), rope("flags"), flagsToStr(n.flags),
                                  rope("callconv"),
                                  makeYamlString(CallingConvToStr[n.callConv]),
@@ -318,7 +303,7 @@ proc typeToYamlAux(n: PType, marker: var IntSet, indent: int,
                                  rope("align"), rope(n.align),
                                  rope("sons"), result])
 
-proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int,
+proc treeToYamlAux(conf: ConfigRef; n: PNode, marker: var IntSet, indent: int,
                    maxRecDepth: int): Rope =
   if n == nil:
     result = rope("null")
@@ -326,7 +311,7 @@ proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int,
     var istr = rspaces(indent + 2)
     result = "{$N$1\"kind\": $2" % [istr, makeYamlString($n.kind)]
     if maxRecDepth != 0:
-      addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(n.info)])
+      addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(conf, n.info)])
       case n.kind
       of nkCharLit..nkInt64Lit:
         addf(result, ",$N$1\"intVal\": $2", [istr, rope(n.intVal)])
@@ -334,13 +319,10 @@ proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int,
         addf(result, ",$N$1\"floatVal\": $2",
             [istr, rope(n.floatVal.toStrMaxPrecision)])
       of nkStrLit..nkTripleStrLit:
-        if n.strVal.isNil:
-          addf(result, ",$N$1\"strVal\": null", [istr])
-        else:
-          addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)])
+        addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)])
       of nkSym:
         addf(result, ",$N$1\"sym\": $2",
-             [istr, symToYamlAux(n.sym, marker, indent + 2, maxRecDepth)])
+             [istr, symToYamlAux(conf, n.sym, marker, indent + 2, maxRecDepth)])
       of nkIdent:
         if n.ident != nil:
           addf(result, ",$N$1\"ident\": $2", [istr, makeYamlString(n.ident.s)])
@@ -351,27 +333,27 @@ proc treeToYamlAux(n: PNode, marker: var IntSet, indent: int,
           addf(result, ",$N$1\"sons\": [", [istr])
           for i in countup(0, sonsLen(n) - 1):
             if i > 0: add(result, ",")
-            addf(result, "$N$1$2", [rspaces(indent + 4), treeToYamlAux(n.sons[i],
+            addf(result, "$N$1$2", [rspaces(indent + 4), treeToYamlAux(conf, n.sons[i],
                 marker, indent + 4, maxRecDepth - 1)])
           addf(result, "$N$1]", [istr])
       addf(result, ",$N$1\"typ\": $2",
-           [istr, typeToYamlAux(n.typ, marker, indent + 2, maxRecDepth)])
+           [istr, typeToYamlAux(conf, n.typ, marker, indent + 2, maxRecDepth)])
     addf(result, "$N$1}", [rspaces(indent)])
 
-proc treeToYaml(n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope =
+proc treeToYaml(conf: ConfigRef; n: PNode, indent: int = 0, maxRecDepth: int = - 1): Rope =
   var marker = initIntSet()
-  result = treeToYamlAux(n, marker, indent, maxRecDepth)
+  result = treeToYamlAux(conf, n, marker, indent, maxRecDepth)
 
-proc typeToYaml(n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope =
+proc typeToYaml(conf: ConfigRef; n: PType, indent: int = 0, maxRecDepth: int = - 1): Rope =
   var marker = initIntSet()
-  result = typeToYamlAux(n, marker, indent, maxRecDepth)
+  result = typeToYamlAux(conf, n, marker, indent, maxRecDepth)
 
-proc symToYaml(n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope =
+proc symToYaml(conf: ConfigRef; n: PSym, indent: int = 0, maxRecDepth: int = - 1): Rope =
   var marker = initIntSet()
-  result = symToYamlAux(n, marker, indent, maxRecDepth)
+  result = symToYamlAux(conf, n, marker, indent, maxRecDepth)
 
-proc debugTree*(n: PNode, indent: int, maxRecDepth: int; renderType=false): Rope
-proc debugType(n: PType, maxRecDepth=100): Rope =
+proc debugTree*(conf: ConfigRef; n: PNode, indent: int, maxRecDepth: int; renderType=false): Rope
+proc debugType(conf: ConfigRef; n: PType, maxRecDepth=100): Rope =
   if n == nil:
     result = rope("null")
   else:
@@ -381,7 +363,7 @@ proc debugType(n: PType, maxRecDepth=100): Rope =
       add(result, n.sym.name.s)
     if n.kind in IntegralTypes and n.n != nil:
       add(result, ", node: ")
-      add(result, debugTree(n.n, 2, maxRecDepth-1, renderType=true))
+      add(result, debugTree(conf, n.n, 2, maxRecDepth-1, renderType=true))
     if (n.kind != tyString) and (sonsLen(n) > 0) and maxRecDepth != 0:
       add(result, "(")
       for i in countup(0, sonsLen(n) - 1):
@@ -389,13 +371,13 @@ proc debugType(n: PType, maxRecDepth=100): Rope =
         if n.sons[i] == nil:
           add(result, "null")
         else:
-          add(result, debugType(n.sons[i], maxRecDepth-1))
+          add(result, debugType(conf, n.sons[i], maxRecDepth-1))
       if n.kind == tyObject and n.n != nil:
         add(result, ", node: ")
-        add(result, debugTree(n.n, 2, maxRecDepth-1, renderType=true))
+        add(result, debugTree(conf, n.n, 2, maxRecDepth-1, renderType=true))
       add(result, ")")
 
-proc debugTree(n: PNode, indent: int, maxRecDepth: int;
+proc debugTree(conf: ConfigRef; n: PNode, indent: int, maxRecDepth: int;
                renderType=false): Rope =
   if n == nil:
     result = rope("null")
@@ -403,7 +385,9 @@ proc debugTree(n: PNode, indent: int, maxRecDepth: int;
     var istr = rspaces(indent + 2)
     result = "{$N$1\"kind\": $2" %
              [istr, makeYamlString($n.kind)]
-    addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(n.info)])
+    when defined(useNodeIds):
+      addf(result, ",$N$1\"id\": $2", [istr, rope(n.id)])
+    addf(result, ",$N$1\"info\": $2", [istr, lineInfoToStr(conf, n.info)])
     if maxRecDepth != 0:
       addf(result, ",$N$1\"flags\": $2", [istr, rope($n.flags)])
       case n.kind
@@ -413,48 +397,54 @@ proc debugTree(n: PNode, indent: int, maxRecDepth: int;
         addf(result, ",$N$1\"floatVal\": $2",
             [istr, rope(n.floatVal.toStrMaxPrecision)])
       of nkStrLit..nkTripleStrLit:
-        if n.strVal.isNil:
-          addf(result, ",$N$1\"strVal\": null", [istr])
-        else:
-          addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)])
+        addf(result, ",$N$1\"strVal\": $2", [istr, makeYamlString(n.strVal)])
       of nkSym:
-        addf(result, ",$N$1\"sym\": $2_$3",
-            [istr, rope(n.sym.name.s), rope(n.sym.id)])
-        #     [istr, symToYaml(n.sym, indent, maxRecDepth),
-        #     rope(n.sym.id)])
+        let s = n.sym
+        addf(result, ",$N$1\"sym\": $2_$3 k: $4 storage: $5 flags: $6 r: $7",
+             [istr, rope(s.name.s), rope(s.id),
+                                 rope($s.loc.k),
+                                 rope($s.loc.storage),
+                                 rope($s.loc.flags),
+                                 s.loc.r
+             ])
+#             [istr, symToYaml(conf, n.sym, indent, maxRecDepth),
+#             rope(n.sym.id)])
         if renderType and n.sym.typ != nil:
-          addf(result, ",$N$1\"typ\": $2", [istr, debugType(n.sym.typ, 2)])
+          addf(result, ",$N$1\"typ\": $2", [istr, debugType(conf, n.sym.typ, 2)])
       of nkIdent:
         if n.ident != nil:
           addf(result, ",$N$1\"ident\": $2", [istr, makeYamlString(n.ident.s)])
         else:
           addf(result, ",$N$1\"ident\": null", [istr])
       else:
+        if renderType and n.typ != nil:
+          addf(result, ",$N$1\"typ\": $2", [istr, debugType(conf, n.typ, 2)])
         if sonsLen(n) > 0:
           addf(result, ",$N$1\"sons\": [", [istr])
           for i in countup(0, sonsLen(n) - 1):
             if i > 0: add(result, ",")
-            addf(result, "$N$1$2", [rspaces(indent + 4), debugTree(n.sons[i],
+            addf(result, "$N$1$2", [rspaces(indent + 4), debugTree(conf, n.sons[i],
                 indent + 4, maxRecDepth - 1, renderType)])
           addf(result, "$N$1]", [istr])
     addf(result, "$N$1}", [rspaces(indent)])
 
-proc debug(n: PSym) =
-  if n == nil:
-    echo("null")
-  elif n.kind == skUnknown:
-    echo("skUnknown")
-  else:
-    #writeLine(stdout, $symToYaml(n, 0, 1))
-    echo("$1_$2: $3, $4, $5, $6" % [
-      n.name.s, $n.id, $flagsToStr(n.flags), $flagsToStr(n.loc.flags),
-      $lineInfoToStr(n.info), $n.kind])
+when declared(echo):
+  proc debug(n: PSym; conf: ConfigRef) =
+    if n == nil:
+      echo("null")
+    elif n.kind == skUnknown:
+      echo("skUnknown")
+    else:
+      #writeLine(stdout, $symToYaml(n, 0, 1))
+      echo("$1_$2: $3, $4, $5, $6" % [
+        n.name.s, $n.id, $flagsToStr(n.flags), $flagsToStr(n.loc.flags),
+        $lineInfoToStr(conf, n.info), $n.kind])
 
-proc debug(n: PType) =
-  echo($debugType(n))
+  proc debug(n: PType; conf: ConfigRef) =
+    echo($debugType(conf, n))
 
-proc debug(n: PNode) =
-  echo($debugTree(n, 0, 100))
+  proc debug(n: PNode; conf: ConfigRef) =
+    echo($debugTree(conf, n, 0, 100))
 
 proc nextTry(h, maxHash: Hash): Hash =
   result = ((5 * h) + 1) and maxHash
@@ -462,7 +452,7 @@ proc nextTry(h, maxHash: Hash): Hash =
   # generates each int in range(maxHash) exactly once (see any text on
   # random-number generation for proof).
 
-proc objectSetContains(t: TObjectSet, obj: RootRef): bool =
+proc objectSetContains*(t: TObjectSet, obj: RootRef): bool =
   # returns true whether n is in t
   var h: Hash = hashNode(obj) and high(t.data) # start with real hash value
   while t.data[h] != nil:
@@ -486,12 +476,12 @@ proc objectSetEnlarge(t: var TObjectSet) =
     if t.data[i] != nil: objectSetRawInsert(n, t.data[i])
   swap(t.data, n)
 
-proc objectSetIncl(t: var TObjectSet, obj: RootRef) =
+proc objectSetIncl*(t: var TObjectSet, obj: RootRef) =
   if mustRehash(len(t.data), t.counter): objectSetEnlarge(t)
   objectSetRawInsert(t.data, obj)
   inc(t.counter)
 
-proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool =
+proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool =
   # returns true if obj is already in the string table:
   var h: Hash = hashNode(obj) and high(t.data)
   while true:
@@ -509,7 +499,7 @@ proc objectSetContainsOrIncl(t: var TObjectSet, obj: RootRef): bool =
   inc(t.counter)
   result = false
 
-proc strTableContains(t: TStrTable, n: PSym): bool =
+proc strTableContains*(t: TStrTable, n: PSym): bool =
   var h: Hash = n.name.h and high(t.data) # start with real hash value
   while t.data[h] != nil:
     if (t.data[h] == n):
@@ -517,7 +507,7 @@ proc strTableContains(t: TStrTable, n: PSym): bool =
     h = nextTry(h, high(t.data))
   result = false
 
-proc strTableRawInsert(data: var TSymSeq, n: PSym) =
+proc strTableRawInsert(data: var seq[PSym], n: PSym) =
   var h: Hash = n.name.h and high(data)
   if sfImmediate notin n.flags:
     # fast path:
@@ -545,7 +535,7 @@ proc strTableRawInsert(data: var TSymSeq, n: PSym) =
     data[h] = n
     if favPos >= 0: swap data[h], data[favPos]
 
-proc symTabReplaceRaw(data: var TSymSeq, prevSym: PSym, newSym: PSym) =
+proc symTabReplaceRaw(data: var seq[PSym], prevSym: PSym, newSym: PSym) =
   assert prevSym.name.h == newSym.name.h
   var h: Hash = prevSym.name.h and high(data)
   while data[h] != nil:
@@ -559,24 +549,19 @@ proc symTabReplace*(t: var TStrTable, prevSym: PSym, newSym: PSym) =
   symTabReplaceRaw(t.data, prevSym, newSym)
 
 proc strTableEnlarge(t: var TStrTable) =
-  var n: TSymSeq
+  var n: seq[PSym]
   newSeq(n, len(t.data) * GrowthFactor)
   for i in countup(0, high(t.data)):
     if t.data[i] != nil: strTableRawInsert(n, t.data[i])
   swap(t.data, n)
 
-proc strTableAdd(t: var TStrTable, n: PSym) =
+proc strTableAdd*(t: var TStrTable, n: PSym) =
   if mustRehash(len(t.data), t.counter): strTableEnlarge(t)
   strTableRawInsert(t.data, n)
   inc(t.counter)
 
-proc reallySameIdent(a, b: string): bool {.inline.} =
-  when defined(nimfix):
-    result = a[0] == b[0]
-  else:
-    result = true
-
-proc strTableIncl*(t: var TStrTable, n: PSym; onConflictKeepOld=false): bool {.discardable.} =
+proc strTableInclReportConflict*(t: var TStrTable, n: PSym;
+                                 onConflictKeepOld = false): PSym =
   # returns true if n is already in the string table:
   # It is essential that `n` is written nevertheless!
   # This way the newest redefinition is picked by the semantic analyses!
@@ -590,14 +575,14 @@ proc strTableIncl*(t: var TStrTable, n: PSym; onConflictKeepOld=false): bool {.d
     # and overloading: (var x=@[]; x).mapIt(it).
     # So it is possible the very same sym is added multiple
     # times to the symbol table which we allow here with the 'it == n' check.
-    if it.name.id == n.name.id and reallySameIdent(it.name.s, n.name.s):
-      if it == n: return false
+    if it.name.id == n.name.id:
+      if it == n: return nil
       replaceSlot = h
     h = nextTry(h, high(t.data))
   if replaceSlot >= 0:
     if not onConflictKeepOld:
       t.data[replaceSlot] = n # overwrite it with newer definition!
-    return true             # found it
+    return t.data[replaceSlot] # found it
   elif mustRehash(len(t.data), t.counter):
     strTableEnlarge(t)
     strTableRawInsert(t.data, n)
@@ -605,9 +590,13 @@ proc strTableIncl*(t: var TStrTable, n: PSym; onConflictKeepOld=false): bool {.d
     assert(t.data[h] == nil)
     t.data[h] = n
   inc(t.counter)
-  result = false
+  result = nil
 
-proc strTableGet(t: TStrTable, name: PIdent): PSym =
+proc strTableIncl*(t: var TStrTable, n: PSym;
+                   onConflictKeepOld = false): bool {.discardable.} =
+  result = strTableInclReportConflict(t, n, onConflictKeepOld) != nil
+
+proc strTableGet*(t: TStrTable, name: PIdent): PSym =
   var h: Hash = name.h and high(t.data)
   while true:
     result = t.data[h]
@@ -615,13 +604,13 @@ proc strTableGet(t: TStrTable, name: PIdent): PSym =
     if result.name.id == name.id: break
     h = nextTry(h, high(t.data))
 
-proc initIdentIter(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym =
-  ti.h = s.h
-  ti.name = s
-  if tab.counter == 0: result = nil
-  else: result = nextIdentIter(ti, tab)
 
-proc nextIdentIter(ti: var TIdentIter, tab: TStrTable): PSym =
+type
+  TIdentIter* = object # iterator over all syms with same identifier
+    h*: Hash           # current hash
+    name*: PIdent
+
+proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
   var h = ti.h and high(tab.data)
   var start = h
   result = tab.data[h]
@@ -634,6 +623,12 @@ proc nextIdentIter(ti: var TIdentIter, tab: TStrTable): PSym =
     result = tab.data[h]
   ti.h = nextTry(h, high(tab.data))
 
+proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym =
+  ti.h = s.h
+  ti.name = s
+  if tab.counter == 0: result = nil
+  else: result = nextIdentIter(ti, tab)
+
 proc nextIdentExcluding*(ti: var TIdentIter, tab: TStrTable,
                          excluding: IntSet): PSym =
   var h: Hash = ti.h and high(tab.data)
@@ -657,20 +652,33 @@ proc firstIdentExcluding*(ti: var TIdentIter, tab: TStrTable, s: PIdent,
   if tab.counter == 0: result = nil
   else: result = nextIdentExcluding(ti, tab, excluding)
 
-proc initTabIter(ti: var TTabIter, tab: TStrTable): PSym =
-  ti.h = 0                    # we start by zero ...
-  if tab.counter == 0:
-    result = nil              # FIX 1: removed endless loop
-  else:
-    result = nextIter(ti, tab)
+type
+  TTabIter* = object
+    h: Hash
 
-proc nextIter(ti: var TTabIter, tab: TStrTable): PSym =
+proc nextIter*(ti: var TTabIter, tab: TStrTable): PSym =
+  # usage:
+  # var
+  #   i: TTabIter
+  #   s: PSym
+  # s = InitTabIter(i, table)
+  # while s != nil:
+  #   ...
+  #   s = NextIter(i, table)
+  #
   result = nil
   while (ti.h <= high(tab.data)):
     result = tab.data[ti.h]
     inc(ti.h)                 # ... and increment by one always
     if result != nil: break
 
+proc initTabIter*(ti: var TTabIter, tab: TStrTable): PSym =
+  ti.h = 0
+  if tab.counter == 0:
+    result = nil
+  else:
+    result = nextIter(ti, tab)
+
 iterator items*(tab: TStrTable): PSym =
   var it: TTabIter
   var s = initTabIter(it, tab)
@@ -761,10 +769,6 @@ proc idNodeTableGet(t: TIdNodeTable, key: PIdObj): PNode =
   if index >= 0: result = t.data[index].val
   else: result = nil
 
-proc idNodeTableGetLazy*(t: TIdNodeTable, key: PIdObj): PNode =
-  if not isNil(t.data):
-    result = idNodeTableGet(t, key)
-
 proc idNodeTableRawInsert(data: var TIdNodePairSeq, key: PIdObj, val: PNode) =
   var h: Hash
   h = key.id and high(data)
@@ -791,10 +795,6 @@ proc idNodeTablePut(t: var TIdNodeTable, key: PIdObj, val: PNode) =
     idNodeTableRawInsert(t.data, key, val)
     inc(t.counter)
 
-proc idNodeTablePutLazy*(t: var TIdNodeTable, key: PIdObj, val: PNode) =
-  if isNil(t.data): initIdNodeTable(t)
-  idNodeTablePut(t, key, val)
-
 iterator pairs*(t: TIdNodeTable): tuple[key: PIdObj, val: PNode] =
   for i in 0 .. high(t.data):
     if not isNil(t.data[i].key): yield (t.data[i].key, t.data[i].val)
diff --git a/compiler/bitsets.nim b/compiler/bitsets.nim
index 5454ef5e7..e38732877 100644
--- a/compiler/bitsets.nim
+++ b/compiler/bitsets.nim
@@ -28,6 +28,7 @@ proc bitSetExcl*(x: var TBitSet, elem: BiggestInt)
 proc bitSetIn*(x: TBitSet, e: BiggestInt): bool
 proc bitSetEquals*(x, y: TBitSet): bool
 proc bitSetContains*(x, y: TBitSet): bool
+proc bitSetCard*(x: TBitSet): BiggestInt
 # implementation
 
 proc bitSetIn(x: TBitSet, e: BiggestInt): bool =
@@ -69,3 +70,28 @@ proc bitSetContains(x, y: TBitSet): bool =
     if (x[i] and not y[i]) != int8(0):
       return false
   result = true
+
+# Number of set bits for all values of int8
+const populationCount: array[low(int8)..high(int8), int8] = block:
+    var arr: array[low(int8)..high(int8), int8]
+
+    proc countSetBits(x: int8): int8 =
+      return
+        ( x and 0b00000001'i8) +
+        ((x and 0b00000010'i8) shr 1) +
+        ((x and 0b00000100'i8) shr 2) +
+        ((x and 0b00001000'i8) shr 3) +
+        ((x and 0b00010000'i8) shr 4) +
+        ((x and 0b00100000'i8) shr 5) +
+        ((x and 0b01000000'i8) shr 6) +
+        ((x and 0b10000000'i8) shr 7)
+        
+
+    for it in low(int8)..high(int8):
+      arr[it] = countSetBits(it)
+
+    arr
+
+proc bitSetCard(x: TBitSet): BiggestInt =
+  for it in x:
+    result.inc populationCount[it]
diff --git a/compiler/btrees.nim b/compiler/btrees.nim
new file mode 100644
index 000000000..6cd6e51f4
--- /dev/null
+++ b/compiler/btrees.nim
@@ -0,0 +1,233 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## BTree implementation with few features, but good enough for the
+## Nim compiler's needs.
+
+const
+  M = 512    # max children per B-tree node = M-1
+             # (must be even and greater than 2)
+  Mhalf = M div 2
+
+type
+  Node[Key, Val] = ref object
+    entries: int
+    keys: array[M, Key]
+    case isInternal: bool
+    of false:
+      vals: array[M, Val]
+    of true:
+      links: array[M, Node[Key, Val]]
+  BTree*[Key, Val] = object
+    root: Node[Key, Val]
+    entries: int      ## number of key-value pairs
+
+proc initBTree*[Key, Val](): BTree[Key, Val] =
+  BTree[Key, Val](root: Node[Key, Val](entries: 0, isInternal: false))
+
+template less(a, b): bool = cmp(a, b) < 0
+template eq(a, b): bool = cmp(a, b) == 0
+
+proc getOrDefault*[Key, Val](b: BTree[Key, Val], key: Key): Val =
+  var x = b.root
+  while x.isInternal:
+    for j in 0 ..< x.entries:
+      if j+1 == x.entries or less(key, x.keys[j+1]):
+        x = x.links[j]
+        break
+  assert(not x.isInternal)
+  for j in 0 ..< x.entries:
+    if eq(key, x.keys[j]): return x.vals[j]
+
+proc contains*[Key, Val](b: BTree[Key, Val], key: Key): bool =
+  var x = b.root
+  while x.isInternal:
+    for j in 0 ..< x.entries:
+      if j+1 == x.entries or less(key, x.keys[j+1]):
+        x = x.links[j]
+        break
+  assert(not x.isInternal)
+  for j in 0 ..< x.entries:
+    if eq(key, x.keys[j]): return true
+  return false
+
+proc copyHalf[Key, Val](h, result: Node[Key, Val]) =
+  for j in 0 ..< Mhalf:
+    result.keys[j] = h.keys[Mhalf + j]
+  if h.isInternal:
+    for j in 0 ..< Mhalf:
+      result.links[j] = h.links[Mhalf + j]
+  else:
+    for j in 0 ..< Mhalf:
+      shallowCopy(result.vals[j], h.vals[Mhalf + j])
+
+proc split[Key, Val](h: Node[Key, Val]): Node[Key, Val] =
+  ## split node in half
+  result = Node[Key, Val](entries: Mhalf, isInternal: h.isInternal)
+  h.entries = Mhalf
+  copyHalf(h, result)
+
+proc insert[Key, Val](h: Node[Key, Val], key: Key, val: Val): Node[Key, Val] =
+  #var t = Entry(key: key, val: val, next: nil)
+  var newKey = key
+  var j = 0
+  if not h.isInternal:
+    while j < h.entries:
+      if less(key, h.keys[j]): break
+      inc j
+    for i in countdown(h.entries, j+1):
+      shallowCopy(h.vals[i], h.vals[i-1])
+    h.vals[j] = val
+  else:
+    var newLink: Node[Key, Val] = nil
+    while j < h.entries:
+      if j+1 == h.entries or less(key, h.keys[j+1]):
+        let u = insert(h.links[j], key, val)
+        inc j
+        if u == nil: return nil
+        newKey = u.keys[0]
+        newLink = u
+        break
+      inc j
+    for i in countdown(h.entries, j+1):
+      h.links[i] = h.links[i-1]
+    h.links[j] = newLink
+
+  for i in countdown(h.entries, j+1):
+    h.keys[i] = h.keys[i-1]
+  h.keys[j] = newKey
+  inc h.entries
+  return if h.entries < M: nil else: split(h)
+
+proc add*[Key, Val](b: var BTree[Key, Val]; key: Key; val: Val) =
+  let u = insert(b.root, key, val)
+  inc b.entries
+  if u == nil: return
+
+  # need to split root
+  let t = Node[Key, Val](entries: 2, isInternal: true)
+  t.keys[0] = b.root.keys[0]
+  t.links[0] = b.root
+  t.keys[1] = u.keys[0]
+  t.links[1] = u
+  b.root = t
+
+proc toString[Key, Val](h: Node[Key, Val], indent: string; result: var string) =
+  if not h.isInternal:
+    for j in 0..<h.entries:
+      result.add(indent)
+      result.add($h.keys[j] & " " & $h.vals[j] & "\n")
+  else:
+    for j in 0..<h.entries:
+      if j > 0: result.add(indent & "(" & $h.keys[j] & ")\n")
+      toString(h.links[j], indent & "   ", result)
+
+proc `$`[Key, Val](b: BTree[Key, Val]): string =
+  result = ""
+  toString(b.root, "", result)
+
+proc hasNext*[Key, Val](b: BTree[Key, Val]; index: int): bool =
+  result = index < b.entries
+
+proc countSubTree[Key, Val](it: Node[Key, Val]): int =
+  if it.isInternal:
+    result = 0
+    for k in 0..<it.entries:
+      inc result, countSubTree(it.links[k])
+  else:
+    result = it.entries
+
+proc next*[Key, Val](b: BTree[Key, Val]; index: int): (Key, Val, int) =
+  var it = b.root
+  var i = index
+  # navigate to the right leaf:
+  while it.isInternal:
+    var sum = 0
+    for k in 0..<it.entries:
+      let c = countSubTree(it.links[k])
+      inc sum, c
+      if sum > i:
+        it = it.links[k]
+        dec i, (sum - c)
+        break
+  result = (it.keys[i], it.vals[i], index+1)
+
+iterator pairs*[Key, Val](b: BTree[Key, Val]): (Key, Val) =
+  var i = 0
+  while hasNext(b, i):
+    let (k, v, i2) = next(b, i)
+    i = i2
+    yield (k, v)
+
+proc len*[Key, Val](b: BTree[Key, Val]): int {.inline.} = b.entries
+
+when isMainModule:
+
+  import random, tables
+
+  proc main =
+    var st = initBTree[string, string]()
+    st.add("www.cs.princeton.edu", "abc")
+    st.add("www.princeton.edu",    "128.112.128.15")
+    st.add("www.yale.edu",         "130.132.143.21")
+    st.add("www.simpsons.com",     "209.052.165.60")
+    st.add("www.apple.com",        "17.112.152.32")
+    st.add("www.amazon.com",       "207.171.182.16")
+    st.add("www.ebay.com",         "66.135.192.87")
+    st.add("www.cnn.com",          "64.236.16.20")
+    st.add("www.google.com",       "216.239.41.99")
+    st.add("www.nytimes.com",      "199.239.136.200")
+    st.add("www.microsoft.com",    "207.126.99.140")
+    st.add("www.dell.com",         "143.166.224.230")
+    st.add("www.slashdot.org",     "66.35.250.151")
+    st.add("www.espn.com",         "199.181.135.201")
+    st.add("www.weather.com",      "63.111.66.11")
+    st.add("www.yahoo.com",        "216.109.118.65")
+
+    assert st.getOrDefault("www.cs.princeton.edu") == "abc"
+    assert st.getOrDefault("www.harvardsucks.com") == nil
+
+    assert st.getOrDefault("www.simpsons.com") == "209.052.165.60"
+    assert st.getOrDefault("www.apple.com") == "17.112.152.32"
+    assert st.getOrDefault("www.ebay.com") == "66.135.192.87"
+    assert st.getOrDefault("www.dell.com") == "143.166.224.230"
+    assert(st.entries == 16)
+
+    for k, v in st:
+      echo k, ": ", v
+
+    when false:
+      var b2 = initBTree[string, string]()
+      const iters = 10_000
+      for i in 1..iters:
+        b2.add($i, $(iters - i))
+      for i in 1..iters:
+        let x = b2.getOrDefault($i)
+        if x != $(iters - i):
+          echo "got ", x, ", but expected ", iters - i
+      echo b2.entries
+
+    when true:
+      var b2 = initBTree[int, string]()
+      var t2 = initTable[int, string]()
+      const iters = 100_000
+      for i in 1..iters:
+        let x = rand(high(int))
+        if not t2.hasKey(x):
+          doAssert b2.getOrDefault(x).len == 0, " what, tree has this element " & $x
+          t2[x] = $x
+          b2.add(x, $x)
+
+      doAssert b2.entries == t2.len
+      echo "unique entries ", b2.entries
+      for k, v in t2:
+        doAssert $k == v
+        doAssert b2.getOrDefault(k) == $k
+
+  main()
diff --git a/compiler/canonicalizer.nim b/compiler/canonicalizer.nim
index d17d928c8..2b6096298 100644
--- a/compiler/canonicalizer.nim
+++ b/compiler/canonicalizer.nim
@@ -102,7 +102,7 @@ proc hashTree(c: var MD5Context, n: PNode) =
   of nkStrLit..nkTripleStrLit:
     c &= n.strVal
   else:
-    for i in 0.. <n.len: hashTree(c, n.sons[i])
+    for i in 0..<n.len: hashTree(c, n.sons[i])
 
 proc hashType(c: var MD5Context, t: PType) =
   # modelled after 'typeToString'
@@ -130,7 +130,7 @@ proc hashType(c: var MD5Context, t: PType) =
     c.hashSym body.sym
     for i in countup(1, sonsLen(t) - 2):
       c.hashType t.sons[i]
-  of tyFromExpr, tyFieldAccessor:
+  of tyFromExpr:
     c.hashTree(t.n)
   of tyArray:
     c.hashTree(t.sons[0].n)
@@ -151,13 +151,13 @@ proc hashType(c: var MD5Context, t: PType) =
     c.hashType(t.sons[0])
   of tyProc:
     c &= (if tfIterator in t.flags: "iterator " else: "proc ")
-    for i in 0.. <t.len: c.hashType(t.sons[i])
+    for i in 0..<t.len: c.hashType(t.sons[i])
     md5Update(c, cast[cstring](addr(t.callConv)), 1)
 
     if tfNoSideEffect in t.flags: c &= ".noSideEffect"
     if tfThread in t.flags: c &= ".thread"
   else:
-    for i in 0.. <t.len: c.hashType(t.sons[i])
+    for i in 0..<t.len: c.hashType(t.sons[i])
   if tfNotNil in t.flags: c &= "not nil"
 
 proc canonConst(n: PNode): TUid =
@@ -300,7 +300,7 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) =
   if t.size != - 1:
     add(result, '/')
     encodeVBiggestInt(t.size, result)
-  if t.align != 2:
+  if t.align != - 1:
     add(result, '=')
     encodeVInt(t.align, result)
   encodeLoc(w, t.loc, result)
diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim
index 7493a50ca..d177e1f88 100644
--- a/compiler/ccgcalls.nim
+++ b/compiler/ccgcalls.nim
@@ -11,7 +11,7 @@
 
 proc leftAppearsOnRightSide(le, ri: PNode): bool =
   if le != nil:
-    for i in 1 .. <ri.len:
+    for i in 1 ..< ri.len:
       let r = ri[i]
       if isPartOf(le, r) != arNo: return true
 
@@ -24,7 +24,7 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
   # getUniqueType() is too expensive here:
   var typ = skipTypes(ri.sons[0].typ, abstractInst)
   if typ.sons[0] != nil:
-    if isInvalidReturnType(typ.sons[0]):
+    if isInvalidReturnType(p.config, typ.sons[0]):
       if params != nil: pl.add(~", ")
       # beware of 'result = p(result)'. We may need to allocate a temporary:
       if d.k in {locTemp, locNone} or not leftAppearsOnRightSide(le, ri):
@@ -32,14 +32,14 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
         if d.k == locNone: getTemp(p, typ.sons[0], d, needsInit=true)
         elif d.k notin {locTemp} and not hasNoInit(ri):
           # reset before pass as 'result' var:
-          resetLoc(p, d)
-        add(pl, addrLoc(d))
+          discard "resetLoc(p, d)"
+        add(pl, addrLoc(p.config, d))
         add(pl, ~");$n")
         line(p, cpsStmts, pl)
       else:
         var tmp: TLoc
         getTemp(p, typ.sons[0], tmp, needsInit=true)
-        add(pl, addrLoc(tmp))
+        add(pl, addrLoc(p.config, tmp))
         add(pl, ~");$n")
         line(p, cpsStmts, pl)
         genAssignment(p, d, tmp, {}) # no need for deep copying
@@ -56,32 +56,14 @@ proc fixupCall(p: BProc, le, ri: PNode, d: var TLoc,
         if d.k == locNone: getTemp(p, typ.sons[0], d)
         assert(d.t != nil)        # generate an assignment to d:
         var list: TLoc
-        initLoc(list, locCall, d.t, OnUnknown)
+        initLoc(list, locCall, d.lode, OnUnknown)
         list.r = pl
         genAssignment(p, d, list, {}) # no need for deep copying
   else:
     add(pl, ~");$n")
     line(p, cpsStmts, pl)
 
-proc isInCurrentFrame(p: BProc, n: PNode): bool =
-  # checks if `n` is an expression that refers to the current frame;
-  # this does not work reliably because of forwarding + inlining can break it
-  case n.kind
-  of nkSym:
-    if n.sym.kind in {skVar, skResult, skTemp, skLet} and p.prc != nil:
-      result = p.prc.id == n.sym.owner.id
-  of nkDotExpr, nkBracketExpr:
-    if skipTypes(n.sons[0].typ, abstractInst).kind notin {tyVar,tyPtr,tyRef}:
-      result = isInCurrentFrame(p, n.sons[0])
-  of nkHiddenStdConv, nkHiddenSubConv, nkConv:
-    result = isInCurrentFrame(p, n.sons[1])
-  of nkHiddenDeref, nkDerefExpr:
-    # what about: var x = addr(y); callAsOpenArray(x[])?
-    # *shrug* ``addr`` is unsafe anyway.
-    result = false
-  of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
-    result = isInCurrentFrame(p, n.sons[0])
-  else: discard
+proc genBoundsCheck(p: BProc; arr, a, b: TLoc)
 
 proc openArrayLoc(p: BProc, n: PNode): Rope =
   var a: TLoc
@@ -93,18 +75,27 @@ proc openArrayLoc(p: BProc, n: PNode): Rope =
     initLocExpr(p, q[1], a)
     initLocExpr(p, q[2], b)
     initLocExpr(p, q[3], c)
-    let fmt =
-      case skipTypes(a.t, abstractVar+{tyPtr}).kind
-      of tyOpenArray, tyVarargs, tyArray:
-        "($1)+($2), ($3)-($2)+1"
-      of tyString, tySequence:
-        if skipTypes(n.typ, abstractInst).kind == tyVar and
-            not compileToCpp(p.module):
-          "(*$1)->data+($2), ($3)-($2)+1"
-        else:
-          "$1->data+($2), ($3)-($2)+1"
-      else: (internalError("openArrayLoc: " & typeToString(a.t)); "")
-    result = fmt % [rdLoc(a), rdLoc(b), rdLoc(c)]
+    # but first produce the required index checks:
+    if optBoundsCheck in p.options:
+      genBoundsCheck(p, a, b, c)
+    let ty = skipTypes(a.t, abstractVar+{tyPtr})
+    case ty.kind
+    of tyArray:
+      let first = firstOrd(p.config, ty)
+      if first == 0:
+        result = "($1)+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c)]
+      else:
+        result = "($1)+(($2)-($4)), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), intLiteral(first)]
+    of tyOpenArray, tyVarargs, tyUncheckedArray:
+      result = "($1)+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c)]
+    of tyString, tySequence:
+      if skipTypes(n.typ, abstractInst).kind == tyVar and
+          not compileToCpp(p.module):
+        result = "(*$1)$4+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), dataField(p)]
+      else:
+        result = "$1$4+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), dataField(p)]
+    else:
+      internalError(p.config, "openArrayLoc: " & typeToString(a.t))
   else:
     initLocExpr(p, n, a)
     case skipTypes(a.t, abstractVar).kind
@@ -113,25 +104,29 @@ proc openArrayLoc(p: BProc, n: PNode): Rope =
     of tyString, tySequence:
       if skipTypes(n.typ, abstractInst).kind == tyVar and
             not compileToCpp(p.module):
-        result = "(*$1)->data, (*$1)->$2" % [a.rdLoc, lenField(p)]
+        var t: TLoc
+        t.r = "(*$1)" % [a.rdLoc]
+        result = "(*$1)$3, $2" % [a.rdLoc, lenExpr(p, t), dataField(p)]
       else:
-        result = "$1->data, $1->$2" % [a.rdLoc, lenField(p)]
+        result = "$1$3, $2" % [a.rdLoc, lenExpr(p, a), dataField(p)]
     of tyArray:
-      result = "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))]
+      result = "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, a.t))]
     of tyPtr, tyRef:
       case lastSon(a.t).kind
       of tyString, tySequence:
-        result = "(*$1)->data, (*$1)->$2" % [a.rdLoc, lenField(p)]
+        var t: TLoc
+        t.r = "(*$1)" % [a.rdLoc]
+        result = "(*$1)$3, $2" % [a.rdLoc, lenExpr(p, t), dataField(p)]
       of tyArray:
-        result = "$1, $2" % [rdLoc(a), rope(lengthOrd(lastSon(a.t)))]
+        result = "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, lastSon(a.t)))]
       else:
-        internalError("openArrayLoc: " & typeToString(a.t))
-    else: internalError("openArrayLoc: " & typeToString(a.t))
+        internalError(p.config, "openArrayLoc: " & typeToString(a.t))
+    else: internalError(p.config, "openArrayLoc: " & typeToString(a.t))
 
 proc genArgStringToCString(p: BProc, n: PNode): Rope {.inline.} =
   var a: TLoc
   initLocExpr(p, n.sons[0], a)
-  result = "$1->data" % [a.rdLoc]
+  result = ropecg(p.module, "#nimToCStringConv($1)", [a.rdLoc])
 
 proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope =
   var a: TLoc
@@ -140,9 +135,9 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope =
   elif skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs}:
     var n = if n.kind != nkHiddenAddr: n else: n.sons[0]
     result = openArrayLoc(p, n)
-  elif ccgIntroducedPtr(param):
+  elif ccgIntroducedPtr(p.config, param):
     initLocExpr(p, n, a)
-    result = addrLoc(a)
+    result = addrLoc(p.config, a)
   elif p.module.compileToCpp and param.typ.kind == tyVar and
       n.kind == nkHiddenAddr:
     initLocExprSingleUse(p, n.sons[0], a)
@@ -152,7 +147,7 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope =
     if callee.kind == nkSym and
         {sfImportC, sfInfixCall, sfCompilerProc} * callee.sym.flags == {sfImportC} and
         {lfHeader, lfNoDecl} * callee.sym.loc.flags != {}:
-      result = addrLoc(a)
+      result = addrLoc(p.config, a)
     else:
       result = rdLoc(a)
   else:
@@ -219,7 +214,7 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
   let rawProc = getRawProcType(p, typ)
   let callPattern = if tfIterator in typ.flags: PatIter else: PatProc
   if typ.sons[0] != nil:
-    if isInvalidReturnType(typ.sons[0]):
+    if isInvalidReturnType(p.config, typ.sons[0]):
       if sonsLen(ri) > 1: add(pl, ~", ")
       # beware of 'result = p(result)'. We may need to allocate a temporary:
       if d.k in {locTemp, locNone} or not leftAppearsOnRightSide(le, ri):
@@ -228,20 +223,20 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) =
           getTemp(p, typ.sons[0], d, needsInit=true)
         elif d.k notin {locTemp} and not hasNoInit(ri):
           # reset before pass as 'result' var:
-          resetLoc(p, d)
-        add(pl, addrLoc(d))
+          discard "resetLoc(p, d)"
+        add(pl, addrLoc(p.config, d))
         genCallPattern()
       else:
         var tmp: TLoc
         getTemp(p, typ.sons[0], tmp, needsInit=true)
-        add(pl, addrLoc(tmp))
+        add(pl, addrLoc(p.config, tmp))
         genCallPattern()
         genAssignment(p, d, tmp, {}) # no need for deep copying
     else:
       if d.k == locNone: getTemp(p, typ.sons[0], d)
       assert(d.t != nil)        # generate an assignment to d:
       var list: TLoc
-      initLoc(list, locCall, d.t, OnUnknown)
+      initLoc(list, locCall, d.lode, OnUnknown)
       list.r = callPattern % [op.r, pl, pl.addComma, rawProc]
       genAssignment(p, d, list, {}) # no need for deep copying
   else:
@@ -261,7 +256,7 @@ proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope =
       result = genArgNoParam(p, ri.sons[i]) #, typ.n.sons[i].sym)
   else:
     if tfVarargs notin typ.flags:
-      localError(ri.info, "wrong argument count")
+      localError(p.config, ri.info, "wrong argument count")
       result = nil
     else:
       result = genArgNoParam(p, ri.sons[i])
@@ -325,13 +320,13 @@ proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType): Rope =
   # for better or worse c2nim translates the 'this' argument to a 'var T'.
   # However manual wrappers may also use 'ptr T'. In any case we support both
   # for convenience.
-  internalAssert i < sonsLen(typ)
+  internalAssert p.config, i < sonsLen(typ)
   assert(typ.n.sons[i].kind == nkSym)
   # if the parameter is lying (tyVar) and thus we required an additional deref,
   # skip the deref:
   var ri = ri[i]
   while ri.kind == nkObjDownConv: ri = ri[0]
-  let t = typ.sons[i].skipTypes({tyGenericInst, tyAlias})
+  let t = typ.sons[i].skipTypes({tyGenericInst, tyAlias, tySink})
   if t.kind == tyVar:
     let x = if ri.kind == nkHiddenAddr: ri[0] else: ri
     if x.typ.kind == tyPtr:
@@ -364,7 +359,7 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType): Rope =
     of '@':
       if j < ri.len:
         result.add genOtherArg(p, ri, j, typ)
-        for k in j+1 .. < ri.len:
+        for k in j+1 ..< ri.len:
           result.add(~", ")
           result.add genOtherArg(p, ri, k, typ)
       inc i
@@ -377,12 +372,12 @@ proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType): Rope =
           result.add(~"(")
           if 1 < ri.len:
             result.add genOtherArg(p, ri, 1, typ)
-          for k in j+1 .. < ri.len:
+          for k in j+1 ..< ri.len:
             result.add(~", ")
             result.add genOtherArg(p, ri, k, typ)
           result.add(~")")
         else:
-          localError(ri.info, "call expression expected for C++ pattern")
+          localError(p.config, ri.info, "call expression expected for C++ pattern")
         inc i
       elif pat[i+1] == '.':
         result.add genThisArg(p, ri, j, typ)
@@ -420,7 +415,7 @@ proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
   assert(sonsLen(typ) == sonsLen(typ.n))
   # don't call '$' here for efficiency:
   let pat = ri.sons[0].sym.loc.r.data
-  internalAssert pat != nil
+  internalAssert p.config, pat.len > 0
   if pat.contains({'#', '(', '@', '\''}):
     var pl = genPatternCall(p, ri, pat, typ)
     # simpler version of 'fixupCall' that works with the pl+params combination:
@@ -437,7 +432,7 @@ proc genInfixCall(p: BProc, le, ri: PNode, d: var TLoc) =
         if d.k == locNone: getTemp(p, typ.sons[0], d)
         assert(d.t != nil)        # generate an assignment to d:
         var list: TLoc
-        initLoc(list, locCall, d.t, OnUnknown)
+        initLoc(list, locCall, d.lode, OnUnknown)
         list.r = pl
         genAssignment(p, d, list, {}) # no need for deep copying
     else:
@@ -469,7 +464,7 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
 
   # don't call '$' here for efficiency:
   let pat = ri.sons[0].sym.loc.r.data
-  internalAssert pat != nil
+  internalAssert p.config, pat.len > 0
   var start = 3
   if ' ' in pat:
     start = 1
@@ -489,7 +484,7 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
   for i in countup(start, length-1):
     assert(sonsLen(typ) == sonsLen(typ.n))
     if i >= sonsLen(typ):
-      internalError(ri.info, "varargs for objective C method?")
+      internalError(p.config, ri.info, "varargs for objective C method?")
     assert(typ.n.sons[i].kind == nkSym)
     var param = typ.n.sons[i].sym
     add(pl, ~" ")
@@ -497,20 +492,20 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
     add(pl, ~": ")
     add(pl, genArg(p, ri.sons[i], param, ri))
   if typ.sons[0] != nil:
-    if isInvalidReturnType(typ.sons[0]):
+    if isInvalidReturnType(p.config, typ.sons[0]):
       if sonsLen(ri) > 1: add(pl, ~" ")
       # beware of 'result = p(result)'. We always allocate a temporary:
       if d.k in {locTemp, locNone}:
         # We already got a temp. Great, special case it:
         if d.k == locNone: getTemp(p, typ.sons[0], d, needsInit=true)
         add(pl, ~"Result: ")
-        add(pl, addrLoc(d))
+        add(pl, addrLoc(p.config, d))
         add(pl, ~"];$n")
         line(p, cpsStmts, pl)
       else:
         var tmp: TLoc
         getTemp(p, typ.sons[0], tmp, needsInit=true)
-        add(pl, addrLoc(tmp))
+        add(pl, addrLoc(p.config, tmp))
         add(pl, ~"];$n")
         line(p, cpsStmts, pl)
         genAssignment(p, d, tmp, {}) # no need for deep copying
@@ -519,7 +514,7 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
       if d.k == locNone: getTemp(p, typ.sons[0], d)
       assert(d.t != nil)        # generate an assignment to d:
       var list: TLoc
-      initLoc(list, locCall, nil, OnUnknown)
+      initLoc(list, locCall, ri, OnUnknown)
       list.r = pl
       genAssignment(p, d, list, {}) # no need for deep copying
   else:
@@ -527,7 +522,7 @@ proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
     line(p, cpsStmts, pl)
 
 proc genCall(p: BProc, e: PNode, d: var TLoc) =
-  if e.sons[0].typ.skipTypes({tyGenericInst, tyAlias}).callConv == ccClosure:
+  if e.sons[0].typ.skipTypes({tyGenericInst, tyAlias, tySink}).callConv == ccClosure:
     genClosureCall(p, nil, e, d)
   elif e.sons[0].kind == nkSym and sfInfixCall in e.sons[0].sym.flags:
     genInfixCall(p, nil, e, d)
@@ -538,7 +533,7 @@ proc genCall(p: BProc, e: PNode, d: var TLoc) =
   postStmtActions(p)
 
 proc genAsgnCall(p: BProc, le, ri: PNode, d: var TLoc) =
-  if ri.sons[0].typ.skipTypes({tyGenericInst, tyAlias}).callConv == ccClosure:
+  if ri.sons[0].typ.skipTypes({tyGenericInst, tyAlias, tySink}).callConv == ccClosure:
     genClosureCall(p, le, ri, d)
   elif ri.sons[0].kind == nkSym and sfInfixCall in ri.sons[0].sym.flags:
     genInfixCall(p, le, ri, d)
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 309fb1f20..d00371dd8 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -13,7 +13,7 @@
 
 proc int64Literal(i: BiggestInt): Rope =
   if i > low(int64):
-    result = rfmt(nil, "IL64($1)", rope(i))
+    result = "IL64($1)" % [rope(i)]
   else:
     result = ~"(IL64(-9223372036854775807) - IL64(1))"
 
@@ -26,21 +26,23 @@ proc intLiteral(i: BiggestInt): Rope =
     # Nim has the same bug for the same reasons :-)
     result = ~"(-2147483647 -1)"
   elif i > low(int64):
-    result = rfmt(nil, "IL64($1)", rope(i))
+    result = "IL64($1)" % [rope(i)]
   else:
     result = ~"(IL64(-9223372036854775807) - IL64(1))"
 
-proc getStrLit(m: BModule, s: string): Rope =
-  discard cgsym(m, "TGenericSeq")
-  result = getTempName(m)
-  addf(m.s[cfsData], "STRING_LITERAL($1, $2, $3);$n",
-       [result, makeCString(s), rope(len(s))])
-
 proc genLiteral(p: BProc, n: PNode, ty: PType): Rope =
-  if ty == nil: internalError(n.info, "genLiteral: ty is nil")
   case n.kind
   of nkCharLit..nkUInt64Lit:
-    case skipTypes(ty, abstractVarRange).kind
+    var k: TTypeKind
+    if ty != nil:
+      k = skipTypes(ty, abstractVarRange).kind
+    else:
+      case n.kind
+      of nkCharLit: k = tyChar
+      of nkUInt64Lit: k = tyUInt64
+      of nkInt64Lit: k = tyInt64
+      else: k = tyNil # don't go into the case variant that uses 'ty'
+    case k
     of tyChar, tyNil:
       result = intLiteral(n.intVal)
     of tyBool:
@@ -52,8 +54,8 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope =
       result = "(($1) $2)" % [getTypeDesc(p.module,
           ty), intLiteral(n.intVal)]
   of nkNilLit:
-    let t = skipTypes(ty, abstractVarRange)
-    if t.kind == tyProc and t.callConv == ccClosure:
+    let k = if ty == nil: tyPointer else: skipTypes(ty, abstractVarRange).kind
+    if k == tyProc and skipTypes(ty, abstractVarRange).callConv == ccClosure:
       let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
       result = p.module.tmpBase & rope(id)
       if id == p.module.labels:
@@ -65,23 +67,27 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope =
     else:
       result = rope("NIM_NIL")
   of nkStrLit..nkTripleStrLit:
-    if n.strVal.isNil:
-      result = ropecg(p.module, "((#NimStringDesc*) NIM_NIL)", [])
-    elif skipTypes(ty, abstractVarRange).kind == tyString:
-      let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
-      if id == p.module.labels:
-        # string literal not found in the cache:
-        result = ropecg(p.module, "((#NimStringDesc*) &$1)",
-                        [getStrLit(p.module, n.strVal)])
+    let k = if ty == nil: tyString
+            else: skipTypes(ty, abstractVarRange + {tyStatic, tyUserTypeClass, tyUserTypeClassInst}).kind
+    case k
+    of tyNil:
+      result = genNilStringLiteral(p.module, n.info)
+    of tyString:
+      # with the new semantics for 'nil' strings, we can map "" to nil and
+      # save tons of allocations:
+      if n.strVal.len == 0 and optNilSeqs notin p.options and
+          p.config.selectedGc != gcDestructors:
+        result = genNilStringLiteral(p.module, n.info)
       else:
-        result = ropecg(p.module, "((#NimStringDesc*) &$1$2)",
-                        [p.module.tmpBase, rope(id)])
+        result = genStringLiteral(p.module, n)
     else:
       result = makeCString(n.strVal)
-  of nkFloatLit..nkFloat64Lit:
+  of nkFloatLit, nkFloat64Lit:
     result = rope(n.floatVal.toStrMaxPrecision)
+  of nkFloat32Lit:
+    result = rope(n.floatVal.toStrMaxPrecision("f"))
   else:
-    internalError(n.info, "genLiteral(" & $n.kind & ')')
+    internalError(p.config, n.info, "genLiteral(" & $n.kind & ')')
     result = nil
 
 proc genLiteral(p: BProc, n: PNode): Rope =
@@ -119,8 +125,8 @@ proc genRawSetData(cs: TBitSet, size: int): Rope =
 
 proc genSetNode(p: BProc, n: PNode): Rope =
   var cs: TBitSet
-  var size = int(getSize(n.typ))
-  toBitSet(n, cs)
+  var size = int(getSize(p.config, n.typ))
+  toBitSet(p.config, n, cs)
   if size > 8:
     let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
     result = p.module.tmpBase & rope(id)
@@ -147,43 +153,40 @@ proc getStorageLoc(n: PNode): TStorageLoc =
     else: result = OnUnknown
   of nkDerefExpr, nkHiddenDeref:
     case n.sons[0].typ.kind
-    of tyVar: result = OnUnknown
+    of tyVar, tyLent: result = OnUnknown
     of tyPtr: result = OnStack
     of tyRef: result = OnHeap
-    else: internalError(n.info, "getStorageLoc")
+    else: doAssert(false, "getStorageLoc")
   of nkBracketExpr, nkDotExpr, nkObjDownConv, nkObjUpConv:
     result = getStorageLoc(n.sons[0])
   else: result = OnUnknown
 
-proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
-  if dest.s == OnStack or not usesNativeGC():
+proc canMove(p: BProc, n: PNode): bool =
+  # for now we're conservative here:
+  if n.kind == nkBracket:
+    # This needs to be kept consistent with 'const' seq code
+    # generation!
+    if not isDeepConstExpr(n) or n.len == 0:
+      if skipTypes(n.typ, abstractVarRange).kind == tySequence:
+        return true
+  elif optNilSeqs notin p.options and
+    n.kind in nkStrKinds and n.strVal.len == 0:
+    # Empty strings are codegen'd as NIM_NIL so it's just a pointer copy
+    return true
+  result = n.kind in nkCallKinds
+  #if result:
+  #  echo n.info, " optimized ", n
+  #  result = false
+
+proc genRefAssign(p: BProc, dest, src: TLoc) =
+  if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
     linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
-  elif dest.s == OnHeap:
-    # location is on heap
-    # now the writer barrier is inlined for performance:
-    #
-    #    if afSrcIsNotNil in flags:
-    #      UseMagic(p.module, 'nimGCref')
-    #      lineF(p, cpsStmts, 'nimGCref($1);$n', [rdLoc(src)])
-    #    elif afSrcIsNil notin flags:
-    #      UseMagic(p.module, 'nimGCref')
-    #      lineF(p, cpsStmts, 'if ($1) nimGCref($1);$n', [rdLoc(src)])
-    #    if afDestIsNotNil in flags:
-    #      UseMagic(p.module, 'nimGCunref')
-    #      lineF(p, cpsStmts, 'nimGCunref($1);$n', [rdLoc(dest)])
-    #    elif afDestIsNil notin flags:
-    #      UseMagic(p.module, 'nimGCunref')
-    #      lineF(p, cpsStmts, 'if ($1) nimGCunref($1);$n', [rdLoc(dest)])
-    #    lineF(p, cpsStmts, '$1 = $2;$n', [rdLoc(dest), rdLoc(src)])
-    if canFormAcycle(dest.t):
-      linefmt(p, cpsStmts, "#asgnRef((void**) $1, $2);$n",
-              addrLoc(dest), rdLoc(src))
-    else:
-      linefmt(p, cpsStmts, "#asgnRefNoCycle((void**) $1, $2);$n",
-              addrLoc(dest), rdLoc(src))
+  elif dest.storage == OnHeap:
+    linefmt(p, cpsStmts, "#asgnRef((void**) $1, $2);$n",
+            addrLoc(p.config, dest), rdLoc(src))
   else:
     linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n",
-            addrLoc(dest), rdLoc(src))
+            addrLoc(p.config, dest), rdLoc(src))
 
 proc asgnComplexity(n: PNode): int =
   if n != nil:
@@ -200,20 +203,20 @@ proc asgnComplexity(n: PNode): int =
 proc optAsgnLoc(a: TLoc, t: PType, field: Rope): TLoc =
   assert field != nil
   result.k = locField
-  result.s = a.s
-  result.t = t
+  result.storage = a.storage
+  result.lode = lodeTyp t
   result.r = rdLoc(a) & "." & field
 
 proc genOptAsgnTuple(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
   let newflags =
-    if src.s == OnStatic:
+    if src.storage == OnStatic:
       flags + {needToCopy}
     elif tfShallow in dest.t.flags:
       flags - {needToCopy}
     else:
       flags
   let t = skipTypes(dest.t, abstractInst).getUniqueType()
-  for i in 0 .. <t.len:
+  for i in 0 ..< t.len:
     let t = t.sons[i]
     let field = "Field$1" % [i.rope]
     genAssignment(p, optAsgnLoc(dest, t, field),
@@ -223,7 +226,7 @@ proc genOptAsgnObject(p: BProc, dest, src: TLoc, flags: TAssignmentFlags,
                       t: PNode, typ: PType) =
   if t == nil: return
   let newflags =
-    if src.s == OnStatic:
+    if src.storage == OnStatic:
       flags + {needToCopy}
     elif tfShallow in dest.t.flags:
       flags - {needToCopy}
@@ -246,19 +249,22 @@ proc genGenericAsgn(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
   # tfShallow flag for the built-in string type too! So we check only
   # here for this flag, where it is reasonably safe to do so
   # (for objects, etc.):
-  if needToCopy notin flags or
+  if p.config.selectedGC == gcDestructors:
+    linefmt(p, cpsStmts,
+        "$1 = $2;$n",
+        rdLoc(dest), rdLoc(src))
+  elif needToCopy notin flags or
       tfShallow in skipTypes(dest.t, abstractVarRange).flags:
-    if dest.s == OnStack or not usesNativeGC():
-      useStringh(p.module)
+    if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
       linefmt(p, cpsStmts,
-           "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
-           addrLoc(dest), addrLoc(src), rdLoc(dest))
+           "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
+           addrLoc(p.config, dest), addrLoc(p.config, src), rdLoc(dest))
     else:
       linefmt(p, cpsStmts, "#genericShallowAssign((void*)$1, (void*)$2, $3);$n",
-              addrLoc(dest), addrLoc(src), genTypeInfo(p.module, dest.t))
+              addrLoc(p.config, dest), addrLoc(p.config, src), genTypeInfo(p.module, dest.t, dest.lode.info))
   else:
     linefmt(p, cpsStmts, "#genericAssign((void*)$1, (void*)$2, $3);$n",
-            addrLoc(dest), addrLoc(src), genTypeInfo(p.module, dest.t))
+            addrLoc(p.config, dest), addrLoc(p.config, src), genTypeInfo(p.module, dest.t, dest.lode.info))
 
 proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
   # This function replaces all other methods for generating
@@ -267,23 +273,28 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
     # little HACK to support the new 'var T' as return type:
     linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
     return
-  let ty = skipTypes(dest.t, abstractRange)
+  let ty = skipTypes(dest.t, abstractRange + tyUserTypeClasses + {tyStatic})
   case ty.kind
   of tyRef:
-    genRefAssign(p, dest, src, flags)
+    genRefAssign(p, dest, src)
   of tySequence:
-    if needToCopy notin flags and src.s != OnStatic:
-      genRefAssign(p, dest, src, flags)
+    if p.config.selectedGC == gcDestructors:
+      genGenericAsgn(p, dest, src, flags)
+    elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode):
+      genRefAssign(p, dest, src)
     else:
       linefmt(p, cpsStmts, "#genericSeqAssign($1, $2, $3);$n",
-              addrLoc(dest), rdLoc(src), genTypeInfo(p.module, dest.t))
+              addrLoc(p.config, dest), rdLoc(src),
+              genTypeInfo(p.module, dest.t, dest.lode.info))
   of tyString:
-    if needToCopy notin flags and src.s != OnStatic:
-      genRefAssign(p, dest, src, flags)
+    if p.config.selectedGC == gcDestructors:
+      genGenericAsgn(p, dest, src, flags)
+    elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode):
+      genRefAssign(p, dest, src)
     else:
-      if dest.s == OnStack or not usesNativeGC():
+      if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config):
         linefmt(p, cpsStmts, "$1 = #copyString($2);$n", dest.rdLoc, src.rdLoc)
-      elif dest.s == OnHeap:
+      elif dest.storage == OnHeap:
         # we use a temporary to care for the dreaded self assignment:
         var tmp: TLoc
         getTemp(p, ty, tmp)
@@ -292,18 +303,18 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
         linefmt(p, cpsStmts, "if ($1) #nimGCunrefNoCycle($1);$n", tmp.rdLoc)
       else:
         linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, #copyString($2));$n",
-               addrLoc(dest), rdLoc(src))
+               addrLoc(p.config, dest), rdLoc(src))
   of tyProc:
-    if needsComplexAssignment(dest.t):
+    if containsGarbageCollectedRef(dest.t):
       # optimize closure assignment:
       let a = optAsgnLoc(dest, dest.t, "ClE_0".rope)
       let b = optAsgnLoc(src, dest.t, "ClE_0".rope)
-      genRefAssign(p, a, b, flags)
+      genRefAssign(p, a, b)
       linefmt(p, cpsStmts, "$1.ClP_0 = $2.ClP_0;$n", rdLoc(dest), rdLoc(src))
     else:
       linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
   of tyTuple:
-    if needsComplexAssignment(dest.t):
+    if containsGarbageCollectedRef(dest.t):
       if dest.t.len <= 4: genOptAsgnTuple(p, dest, src, flags)
       else: genGenericAsgn(p, dest, src, flags)
     else:
@@ -314,53 +325,53 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) =
       linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
     elif not isObjLackingTypeField(ty):
       genGenericAsgn(p, dest, src, flags)
-    elif needsComplexAssignment(ty):
+    elif containsGarbageCollectedRef(ty):
       if ty.sons[0].isNil and asgnComplexity(ty.n) <= 4:
         discard getTypeDesc(p.module, ty)
-        internalAssert ty.n != nil
+        internalAssert p.config, ty.n != nil
         genOptAsgnObject(p, dest, src, flags, ty.n, ty)
       else:
         genGenericAsgn(p, dest, src, flags)
     else:
       linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
   of tyArray:
-    if needsComplexAssignment(dest.t):
+    if containsGarbageCollectedRef(dest.t):
       genGenericAsgn(p, dest, src, flags)
     else:
-      useStringh(p.module)
       linefmt(p, cpsStmts,
-           "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
+           "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
            rdLoc(dest), rdLoc(src), getTypeDesc(p.module, dest.t))
   of tyOpenArray, tyVarargs:
     # open arrays are always on the stack - really? What if a sequence is
     # passed to an open array?
-    if needsComplexAssignment(dest.t):
+    if containsGarbageCollectedRef(dest.t):
       linefmt(p, cpsStmts,     # XXX: is this correct for arrays?
            "#genericAssignOpenArray((void*)$1, (void*)$2, $1Len_0, $3);$n",
-           addrLoc(dest), addrLoc(src), genTypeInfo(p.module, dest.t))
+           addrLoc(p.config, dest), addrLoc(p.config, src),
+           genTypeInfo(p.module, dest.t, dest.lode.info))
     else:
-      useStringh(p.module)
       linefmt(p, cpsStmts,
-           "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n",
+           # bug #4799, keep the nimCopyMem for a while
+           #"#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n",
+           "$1 = $2;$n",
            rdLoc(dest), rdLoc(src))
   of tySet:
-    if mapType(ty) == ctArray:
-      useStringh(p.module)
-      linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n",
-              rdLoc(dest), rdLoc(src), rope(getSize(dest.t)))
+    if mapType(p.config, ty) == ctArray:
+      linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n",
+              rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t)))
     else:
       linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
   of tyPtr, tyPointer, tyChar, tyBool, tyEnum, tyCString,
-     tyInt..tyUInt64, tyRange, tyVar:
+     tyInt..tyUInt64, tyRange, tyVar, tyLent:
     linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
-  else: internalError("genAssignment: " & $ty.kind)
+  else: internalError(p.config, "genAssignment: " & $ty.kind)
 
-  if optMemTracker in p.options and dest.s in {OnHeap, OnUnknown}:
+  if optMemTracker in p.options and dest.storage in {OnHeap, OnUnknown}:
     #writeStackTrace()
     #echo p.currLineInfo, " requesting"
     linefmt(p, cpsStmts, "#memTrackerWrite((void*)$1, $2, $3, $4);$n",
-            addrLoc(dest), rope getSize(dest.t),
-            makeCString(p.currLineInfo.toFullPath),
+            addrLoc(p.config, dest), rope getSize(p.config, dest.t),
+            makeCString(toFullPath(p.config, p.currLineInfo)),
             rope p.currLineInfo.safeLineNm)
 
 proc genDeepCopy(p: BProc; dest, src: TLoc) =
@@ -369,34 +380,36 @@ proc genDeepCopy(p: BProc; dest, src: TLoc) =
       var tmp: TLoc
       getTemp(p, a.t, tmp)
       genAssignment(p, tmp, a, {})
-      addrLoc(tmp)
+      addrLoc(p.config, tmp)
     else:
-      addrLoc(a)
+      addrLoc(p.config, a)
 
-  var ty = skipTypes(dest.t, abstractVarRange)
+  var ty = skipTypes(dest.t, abstractVarRange + {tyStatic})
   case ty.kind
   of tyPtr, tyRef, tyProc, tyTuple, tyObject, tyArray:
     # XXX optimize this
     linefmt(p, cpsStmts, "#genericDeepCopy((void*)$1, (void*)$2, $3);$n",
-            addrLoc(dest), addrLocOrTemp(src), genTypeInfo(p.module, dest.t))
+            addrLoc(p.config, dest), addrLocOrTemp(src),
+            genTypeInfo(p.module, dest.t, dest.lode.info))
   of tySequence, tyString:
     linefmt(p, cpsStmts, "#genericSeqDeepCopy($1, $2, $3);$n",
-            addrLoc(dest), rdLoc(src), genTypeInfo(p.module, dest.t))
+            addrLoc(p.config, dest), rdLoc(src),
+            genTypeInfo(p.module, dest.t, dest.lode.info))
   of tyOpenArray, tyVarargs:
     linefmt(p, cpsStmts,
          "#genericDeepCopyOpenArray((void*)$1, (void*)$2, $1Len_0, $3);$n",
-         addrLoc(dest), addrLocOrTemp(src), genTypeInfo(p.module, dest.t))
+         addrLoc(p.config, dest), addrLocOrTemp(src),
+         genTypeInfo(p.module, dest.t, dest.lode.info))
   of tySet:
-    if mapType(ty) == ctArray:
-      useStringh(p.module)
-      linefmt(p, cpsStmts, "memcpy((void*)$1, (NIM_CONST void*)$2, $3);$n",
-              rdLoc(dest), rdLoc(src), rope(getSize(dest.t)))
+    if mapType(p.config, ty) == ctArray:
+      linefmt(p, cpsStmts, "#nimCopyMem((void*)$1, (NIM_CONST void*)$2, $3);$n",
+              rdLoc(dest), rdLoc(src), rope(getSize(p.config, dest.t)))
     else:
       linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
   of tyPointer, tyChar, tyBool, tyEnum, tyCString,
-     tyInt..tyUInt64, tyRange, tyVar:
+     tyInt..tyUInt64, tyRange, tyVar, tyLent:
     linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src))
-  else: internalError("genDeepCopy: " & $ty.kind)
+  else: internalError(p.config, "genDeepCopy: " & $ty.kind)
 
 proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) =
   if d.k != locNone:
@@ -405,11 +418,11 @@ proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) =
   else:
     d = s # ``d`` is free, so fill it with ``s``
 
-proc putDataIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope) =
+proc putDataIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope) =
   var a: TLoc
   if d.k != locNone:
     # need to generate an assignment here
-    initLoc(a, locData, t, OnStatic)
+    initLoc(a, locData, n, OnStatic)
     a.r = r
     if lfNoDeepCopy in d.flags: genAssignment(p, d, a, {})
     else: genAssignment(p, d, a, {needToCopy})
@@ -417,14 +430,14 @@ proc putDataIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope) =
     # we cannot call initLoc() here as that would overwrite
     # the flags field!
     d.k = locData
-    d.t = t
+    d.lode = n
     d.r = r
 
-proc putIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope; s=OnUnknown) =
+proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) =
   var a: TLoc
   if d.k != locNone:
     # need to generate an assignment here
-    initLoc(a, locExpr, t, s)
+    initLoc(a, locExpr, n, s)
     a.r = r
     if lfNoDeepCopy in d.flags: genAssignment(p, d, a, {})
     else: genAssignment(p, d, a, {needToCopy})
@@ -432,19 +445,26 @@ proc putIntoDest(p: BProc, d: var TLoc, t: PType, r: Rope; s=OnUnknown) =
     # we cannot call initLoc() here as that would overwrite
     # the flags field!
     d.k = locExpr
-    d.t = t
+    d.lode = n
     d.r = r
 
 proc binaryStmt(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   var a, b: TLoc
-  if d.k != locNone: internalError(e.info, "binaryStmt")
+  if d.k != locNone: internalError(p.config, e.info, "binaryStmt")
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
   lineCg(p, cpsStmts, frmt, rdLoc(a), rdLoc(b))
 
+proc binaryStmtAddr(p: BProc, e: PNode, d: var TLoc, frmt: string) =
+  var a, b: TLoc
+  if d.k != locNone: internalError(p.config, e.info, "binaryStmtAddr")
+  initLocExpr(p, e.sons[1], a)
+  initLocExpr(p, e.sons[2], b)
+  lineCg(p, cpsStmts, frmt, addrLoc(p.config, a), rdLoc(b))
+
 proc unaryStmt(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   var a: TLoc
-  if d.k != locNone: internalError(e.info, "unaryStmt")
+  if d.k != locNone: internalError(p.config, e.info, "unaryStmt")
   initLocExpr(p, e.sons[1], a)
   lineCg(p, cpsStmts, frmt, [rdLoc(a)])
 
@@ -454,7 +474,7 @@ proc binaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   assert(e.sons[2].typ != nil)
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
-  putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [rdLoc(a), rdLoc(b)]))
+  putIntoDest(p, d, e, ropecg(p.module, frmt, [rdLoc(a), rdLoc(b)]))
 
 proc binaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   var a, b: TLoc
@@ -462,29 +482,29 @@ proc binaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   assert(e.sons[2].typ != nil)
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
-  putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [a.rdCharLoc, b.rdCharLoc]))
+  putIntoDest(p, d, e, ropecg(p.module, frmt, [a.rdCharLoc, b.rdCharLoc]))
 
 proc unaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   var a: TLoc
   initLocExpr(p, e.sons[1], a)
-  putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [rdLoc(a)]))
+  putIntoDest(p, d, e, ropecg(p.module, frmt, [rdLoc(a)]))
 
 proc unaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   var a: TLoc
   initLocExpr(p, e.sons[1], a)
-  putIntoDest(p, d, e.typ, ropecg(p.module, frmt, [rdCharLoc(a)]))
+  putIntoDest(p, d, e, ropecg(p.module, frmt, [rdCharLoc(a)]))
 
 proc binaryArithOverflowRaw(p: BProc, t: PType, a, b: TLoc;
                             frmt: string): Rope =
-  var size = getSize(t)
-  let storage = if size < platform.intSize: rope("NI")
+  var size = getSize(p.config, t)
+  let storage = if size < p.config.target.intSize: rope("NI")
                 else: getTypeDesc(p.module, t)
   result = getTempName(p.module)
   linefmt(p, cpsLocals, "$1 $2;$n", storage, result)
   lineCg(p, cpsStmts, frmt, result, rdCharLoc(a), rdCharLoc(b))
-  if size < platform.intSize or t.kind in {tyRange, tyEnum}:
+  if size < p.config.target.intSize or t.kind in {tyRange, tyEnum}:
     linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3) #raiseOverflow();$n",
-            result, intLiteral(firstOrd(t)), intLiteral(lastOrd(t)))
+            result, intLiteral(firstOrd(p.config, t)), intLiteral(lastOrd(p.config, t)))
 
 proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
   const
@@ -512,11 +532,11 @@ proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
   let t = e.typ.skipTypes(abstractRange)
   if optOverflowCheck notin p.options:
     let res = opr[m] % [getTypeDesc(p.module, e.typ), rdLoc(a), rdLoc(b)]
-    putIntoDest(p, d, e.typ, res)
+    putIntoDest(p, d, e, res)
   else:
     let res = binaryArithOverflowRaw(p, t, a, b,
                                    if t.kind == tyInt64: prc64[m] else: prc[m])
-    putIntoDest(p, d, e.typ, "($#)($#)" % [getTypeDesc(p.module, e.typ), res])
+    putIntoDest(p, d, e, "($#)($#)" % [getTypeDesc(p.module, e.typ), res])
 
 proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
   const
@@ -532,8 +552,8 @@ proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
   t = skipTypes(e.typ, abstractRange)
   if optOverflowCheck in p.options:
     linefmt(p, cpsStmts, "if ($1 == $2) #raiseOverflow();$n",
-            rdLoc(a), intLiteral(firstOrd(t)))
-  putIntoDest(p, d, e.typ, opr[m] % [rdLoc(a), rope(getSize(t) * 8)])
+            rdLoc(a), intLiteral(firstOrd(p.config, t)))
+  putIntoDest(p, d, e, opr[m] % [rdLoc(a), rope(getSize(p.config, t) * 8)])
 
 proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   const
@@ -542,9 +562,9 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
       "(($4)($1) - ($4)($2))", # SubF64
       "(($4)($1) * ($4)($2))", # MulF64
       "(($4)($1) / ($4)($2))", # DivF64
-
-      "($4)((NU$3)($1) >> (NU$3)($2))", # ShrI
+      "($4)((NU$5)($1) >> (NU$3)($2))", # ShrI
       "($4)((NU$3)($1) << (NU$3)($2))", # ShlI
+      "($4)((NI$3)($1) >> (NU$3)($2))", # AshrI
       "($4)($1 & $2)",      # BitandI
       "($4)($1 | $2)",      # BitorI
       "($4)($1 ^ $2)",      # BitxorI
@@ -583,16 +603,17 @@ proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
       "($1 != $2)"]           # Xor
   var
     a, b: TLoc
-    s: BiggestInt
+    s, k: BiggestInt
   assert(e.sons[1].typ != nil)
   assert(e.sons[2].typ != nil)
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
   # BUGFIX: cannot use result-type here, as it may be a boolean
-  s = max(getSize(a.t), getSize(b.t)) * 8
-  putIntoDest(p, d, e.typ,
+  s = max(getSize(p.config, a.t), getSize(p.config, b.t)) * 8
+  k = getSize(p.config, a.t) * 8
+  putIntoDest(p, d, e,
               binArithTab[op] % [rdLoc(a), rdLoc(b), rope(s),
-                                      getSimpleTypeDesc(p.module, e.typ)])
+                                      getSimpleTypeDesc(p.module, e.typ), rope(k)])
 
 proc genEqProc(p: BProc, e: PNode, d: var TLoc) =
   var a, b: TLoc
@@ -601,10 +622,10 @@ proc genEqProc(p: BProc, e: PNode, d: var TLoc) =
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
   if a.t.skipTypes(abstractInst).callConv == ccClosure:
-    putIntoDest(p, d, e.typ,
+    putIntoDest(p, d, e,
       "($1.ClP_0 == $2.ClP_0 && $1.ClE_0 == $2.ClE_0)" % [rdLoc(a), rdLoc(b)])
   else:
-    putIntoDest(p, d, e.typ, "($1 == $2)" % [rdLoc(a), rdLoc(b)])
+    putIntoDest(p, d, e, "($1 == $2)" % [rdLoc(a), rdLoc(b)])
 
 proc genIsNil(p: BProc, e: PNode, d: var TLoc) =
   let t = skipTypes(e.sons[1].typ, abstractRange)
@@ -641,8 +662,8 @@ proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   assert(e.sons[1].typ != nil)
   initLocExpr(p, e.sons[1], a)
   t = skipTypes(e.typ, abstractRange)
-  putIntoDest(p, d, e.typ,
-              unArithTab[op] % [rdLoc(a), rope(getSize(t) * 8),
+  putIntoDest(p, d, e,
+              unArithTab[op] % [rdLoc(a), rope(getSize(p.config, t) * 8),
                 getSimpleTypeDesc(p.module, e.typ)])
 
 proc isCppRef(p: BProc; typ: PType): bool {.inline.} =
@@ -651,7 +672,7 @@ proc isCppRef(p: BProc; typ: PType): bool {.inline.} =
       tfVarIsPtr notin skipTypes(typ, abstractInst).flags
 
 proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) =
-  let mt = mapType(e.sons[0].typ)
+  let mt = mapType(p.config, e.sons[0].typ)
   if mt in {ctArray, ctPtrToArray} and not enforceDeref:
     # XXX the amount of hacks for C's arrays is incredible, maybe we should
     # simply wrap them in a struct? --> Losing auto vectorization then?
@@ -659,10 +680,13 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) =
     #  message(e.info, warnUser, "CAME HERE " & renderTree(e))
     expr(p, e.sons[0], d)
     if e.sons[0].typ.skipTypes(abstractInst).kind == tyRef:
-      d.s = OnHeap
+      d.storage = OnHeap
   else:
     var a: TLoc
-    let typ = skipTypes(e.sons[0].typ, abstractInst)
+    var typ = e.sons[0].typ
+    if typ.kind in {tyUserTypeClass, tyUserTypeClassInst} and typ.isResolvedUserTypeClass:
+      typ = typ.lastSon
+    typ = typ.skipTypes(abstractInst)
     if typ.kind == tyVar and tfVarIsPtr notin typ.flags and p.module.compileToCpp and e.sons[0].kind == nkHiddenAddr:
       initLocExprSingleUse(p, e[0][0], d)
       return
@@ -670,25 +694,25 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) =
       initLocExprSingleUse(p, e.sons[0], a)
     if d.k == locNone:
       # dest = *a;  <-- We do not know that 'dest' is on the heap!
-      # It is completely wrong to set 'd.s' here, unless it's not yet
+      # It is completely wrong to set 'd.storage' here, unless it's not yet
       # been assigned to.
       case typ.kind
       of tyRef:
-        d.s = OnHeap
-      of tyVar:
-        d.s = OnUnknown
+        d.storage = OnHeap
+      of tyVar, tyLent:
+        d.storage = OnUnknown
         if tfVarIsPtr notin typ.flags and p.module.compileToCpp and
             e.kind == nkHiddenDeref:
-          putIntoDest(p, d, e.typ, rdLoc(a), a.s)
+          putIntoDest(p, d, e, rdLoc(a), a.storage)
           return
       of tyPtr:
-        d.s = OnUnknown         # BUGFIX!
+        d.storage = OnUnknown         # BUGFIX!
       else:
-        internalError(e.info, "genDeref " & $typ.kind)
+        internalError(p.config, e.info, "genDeref " & $typ.kind)
     elif p.module.compileToCpp:
       if typ.kind == tyVar and tfVarIsPtr notin typ.flags and
            e.kind == nkHiddenDeref:
-        putIntoDest(p, d, e.typ, rdLoc(a), a.s)
+        putIntoDest(p, d, e, rdLoc(a), a.storage)
         return
     if enforceDeref and mt == ctPtrToArray:
       # we lie about the type for better C interop: 'ptr array[3,T]' is
@@ -696,30 +720,30 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false) =
       # See tmissingderef. So we get rid of the deref instead. The codegen
       # ends up using 'memcpy' for the array assignment,
       # so the '&' and '*' cancel out:
-      putIntoDest(p, d, a.t.sons[0], rdLoc(a), a.s)
+      putIntoDest(p, d, lodeTyp(a.t.sons[0]), rdLoc(a), a.storage)
     else:
-      putIntoDest(p, d, e.typ, "(*$1)" % [rdLoc(a)], a.s)
+      putIntoDest(p, d, e, "(*$1)" % [rdLoc(a)], a.storage)
 
 proc genAddr(p: BProc, e: PNode, d: var TLoc) =
   # careful  'addr(myptrToArray)' needs to get the ampersand:
   if e.sons[0].typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
     var a: TLoc
     initLocExpr(p, e.sons[0], a)
-    putIntoDest(p, d, e.typ, "&" & a.r, a.s)
+    putIntoDest(p, d, e, "&" & a.r, a.storage)
     #Message(e.info, warnUser, "HERE NEW &")
-  elif mapType(e.sons[0].typ) == ctArray or isCppRef(p, e.sons[0].typ):
+  elif mapType(p.config, e.sons[0].typ) == ctArray or isCppRef(p, e.typ):
     expr(p, e.sons[0], d)
   else:
     var a: TLoc
     initLocExpr(p, e.sons[0], a)
-    putIntoDest(p, d, e.typ, addrLoc(a), a.s)
+    putIntoDest(p, d, e, addrLoc(p.config, a), a.storage)
 
 template inheritLocation(d: var TLoc, a: TLoc) =
-  if d.k == locNone: d.s = a.s
+  if d.k == locNone: d.storage = a.storage
 
 proc genRecordFieldAux(p: BProc, e: PNode, d, a: var TLoc) =
   initLocExpr(p, e.sons[0], a)
-  if e.sons[1].kind != nkSym: internalError(e.info, "genRecordFieldAux")
+  if e.sons[1].kind != nkSym: internalError(p.config, e.info, "genRecordFieldAux")
   d.inheritLocation(a)
   discard getTypeDesc(p.module, a.t) # fill the record's fields.loc
 
@@ -735,45 +759,47 @@ proc genTupleElem(p: BProc, e: PNode, d: var TLoc) =
   var r = rdLoc(a)
   case e.sons[1].kind
   of nkIntLit..nkUInt64Lit: i = int(e.sons[1].intVal)
-  else: internalError(e.info, "genTupleElem")
+  else: internalError(p.config, e.info, "genTupleElem")
   addf(r, ".Field$1", [rope(i)])
-  putIntoDest(p, d, tupType.sons[i], r, a.s)
+  putIntoDest(p, d, e, r, a.storage)
 
-proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope): PSym =
+proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope;
+                      resTyp: ptr PType = nil): PSym =
   var ty = ty
   assert r != nil
   while ty != nil:
     ty = ty.skipTypes(skipPtrs)
     assert(ty.kind in {tyTuple, tyObject})
     result = lookupInRecord(ty.n, field.name)
-    if result != nil: break
+    if result != nil:
+      if resTyp != nil: resTyp[] = ty
+      break
     if not p.module.compileToCpp: add(r, ".Sup")
     ty = ty.sons[0]
-  if result == nil: internalError(field.info, "genCheckedRecordField")
+  if result == nil: internalError(p.config, field.info, "genCheckedRecordField")
 
 proc genRecordField(p: BProc, e: PNode, d: var TLoc) =
   var a: TLoc
   genRecordFieldAux(p, e, d, a)
   var r = rdLoc(a)
   var f = e.sons[1].sym
-  let ty = skipTypes(a.t, abstractInst)
+  let ty = skipTypes(a.t, abstractInst + tyUserTypeClasses)
   if ty.kind == tyTuple:
     # we found a unique tuple type which lacks field information
     # so we use Field$i
     addf(r, ".Field$1", [rope(f.position)])
-    putIntoDest(p, d, f.typ, r, a.s)
+    putIntoDest(p, d, e, r, a.storage)
   else:
-    let field = lookupFieldAgain(p, ty, f, r)
-    if field.loc.r == nil: fillObjectFields(p.module, ty)
-    if field.loc.r == nil: internalError(e.info, "genRecordField 3 " & typeToString(ty))
+    var rtyp: PType
+    let field = lookupFieldAgain(p, ty, f, r, addr rtyp)
+    if field.loc.r == nil and rtyp != nil: fillObjectFields(p.module, rtyp)
+    if field.loc.r == nil: internalError(p.config, e.info, "genRecordField 3 " & typeToString(ty))
     addf(r, ".$1", [field.loc.r])
-    putIntoDest(p, d, field.typ, r, a.s)
-  #d.s = a.s
+    putIntoDest(p, d, e, r, a.storage)
 
 proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc)
 
-proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym;
-                   origTy: PType) =
+proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym) =
   var test, u, v: TLoc
   for i in countup(1, sonsLen(e) - 1):
     var it = e.sons[i]
@@ -783,91 +809,117 @@ proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym;
     if op.magic == mNot: it = it.sons[1]
     let disc = it.sons[2].skipConv
     assert(disc.kind == nkSym)
-    initLoc(test, locNone, it.typ, OnStack)
+    initLoc(test, locNone, it, OnStack)
     initLocExpr(p, it.sons[1], u)
-    var o = obj
-    let d = lookupFieldAgain(p, origTy, disc.sym, o)
-    initLoc(v, locExpr, d.typ, OnUnknown)
-    v.r = o
+    initLoc(v, locExpr, disc, OnUnknown)
+    v.r = obj
     v.r.add(".")
-    v.r.add(d.loc.r)
+    v.r.add(disc.sym.loc.r)
     genInExprAux(p, it, u, v, test)
     let id = nodeTableTestOrSet(p.module.dataCache,
                                newStrNode(nkStrLit, field.name.s), p.module.labels)
-    let strLit = if id == p.module.labels: getStrLit(p.module, field.name.s)
+    let strLit = if id == p.module.labels: genStringLiteralDataOnly(p.module, field.name.s, e.info)
                  else: p.module.tmpBase & rope(id)
     if op.magic == mNot:
       linefmt(p, cpsStmts,
-              "if ($1) #raiseFieldError(((#NimStringDesc*) &$2));$n",
-              rdLoc(test), strLit)
+              "if ($1) #raiseFieldError($2);$n",
+              rdLoc(test), genStringLiteralFromData(p.module, strLit, e.info))
     else:
       linefmt(p, cpsStmts,
-              "if (!($1)) #raiseFieldError(((#NimStringDesc*) &$2));$n",
-              rdLoc(test), strLit)
+              "if (!($1)) #raiseFieldError($2);$n",
+              rdLoc(test), genStringLiteralFromData(p.module, strLit, e.info))
 
 proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) =
   if optFieldCheck in p.options:
     var a: TLoc
     genRecordFieldAux(p, e.sons[0], d, a)
-    let ty = skipTypes(a.t, abstractInst)
+    let ty = skipTypes(a.t, abstractInst + tyUserTypeClasses)
     var r = rdLoc(a)
     let f = e.sons[0].sons[1].sym
     let field = lookupFieldAgain(p, ty, f, r)
     if field.loc.r == nil: fillObjectFields(p.module, ty)
     if field.loc.r == nil:
-      internalError(e.info, "genCheckedRecordField") # generate the checks:
-    genFieldCheck(p, e, r, field, ty)
-    add(r, rfmt(nil, ".$1", field.loc.r))
-    putIntoDest(p, d, field.typ, r, a.s)
+      internalError(p.config, e.info, "genCheckedRecordField") # generate the checks:
+    genFieldCheck(p, e, r, field)
+    add(r, ropecg(p.module, ".$1", field.loc.r))
+    putIntoDest(p, d, e.sons[0], r, a.storage)
   else:
     genRecordField(p, e.sons[0], d)
 
-proc genArrayElem(p: BProc, x, y: PNode, d: var TLoc) =
+proc genUncheckedArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) =
+  var a, b: TLoc
+  initLocExpr(p, x, a)
+  initLocExpr(p, y, b)
+  d.inheritLocation(a)
+  putIntoDest(p, d, n, ropecg(p.module, "$1[$2]", rdLoc(a), rdCharLoc(b)),
+              a.storage)
+
+proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) =
   var a, b: TLoc
   initLocExpr(p, x, a)
   initLocExpr(p, y, b)
-  var ty = skipTypes(skipTypes(a.t, abstractVarRange), abstractPtrs)
-  var first = intLiteral(firstOrd(ty))
+  var ty = skipTypes(a.t, abstractVarRange + abstractPtrs + tyUserTypeClasses)
+  var first = intLiteral(firstOrd(p.config, ty))
   # emit range check:
-  if optBoundsCheck in p.options and tfUncheckedArray notin ty.flags:
+  if optBoundsCheck in p.options and ty.kind != tyUncheckedArray:
     if not isConstExpr(y):
       # semantic pass has already checked for const index expressions
-      if firstOrd(ty) == 0:
-        if (firstOrd(b.t) < firstOrd(ty)) or (lastOrd(b.t) > lastOrd(ty)):
-          linefmt(p, cpsStmts, "if ((NU)($1) > (NU)($2)) #raiseIndexError();$n",
-                  rdCharLoc(b), intLiteral(lastOrd(ty)))
+      if firstOrd(p.config, ty) == 0:
+        if (firstOrd(p.config, b.t) < firstOrd(p.config, ty)) or (lastOrd(p.config, b.t) > lastOrd(p.config, ty)):
+          linefmt(p, cpsStmts, "if ((NU)($1) > (NU)($2)) #raiseIndexError2($1, $2);$n",
+                  rdCharLoc(b), intLiteral(lastOrd(p.config, ty)))
       else:
-        linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3) #raiseIndexError();$n",
-                rdCharLoc(b), first, intLiteral(lastOrd(ty)))
+        linefmt(p, cpsStmts, "if ($1 < $2 || $1 > $3) #raiseIndexError3($1, $2, $3);$n",
+                rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty)))
     else:
       let idx = getOrdValue(y)
-      if idx < firstOrd(ty) or idx > lastOrd(ty):
-        localError(x.info, errIndexOutOfBounds)
+      if idx < firstOrd(p.config, ty) or idx > lastOrd(p.config, ty):
+        localError(p.config, x.info, formatErrorIndexBound(idx, firstOrd(p.config, ty), lastOrd(p.config, ty)))
   d.inheritLocation(a)
-  putIntoDest(p, d, elemType(skipTypes(ty, abstractVar)),
-              rfmt(nil, "$1[($2)- $3]", rdLoc(a), rdCharLoc(b), first), a.s)
+  putIntoDest(p, d, n,
+              ropecg(p.module, "$1[($2)- $3]", rdLoc(a), rdCharLoc(b), first), a.storage)
 
-proc genCStringElem(p: BProc, x, y: PNode, d: var TLoc) =
+proc genCStringElem(p: BProc, n, x, y: PNode, d: var TLoc) =
   var a, b: TLoc
   initLocExpr(p, x, a)
   initLocExpr(p, y, b)
-  var ty = skipTypes(a.t, abstractVarRange)
-  if d.k == locNone: d.s = a.s
-  putIntoDest(p, d, elemType(skipTypes(ty, abstractVar)),
-              rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.s)
+  inheritLocation(d, a)
+  putIntoDest(p, d, n,
+              ropecg(p.module, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage)
+
+proc genBoundsCheck(p: BProc; arr, a, b: TLoc) =
+  let ty = skipTypes(arr.t, abstractVarRange)
+  case ty.kind
+  of tyOpenArray, tyVarargs:
+    linefmt(p, cpsStmts,
+      "if ($2-$1 != -1 && " &
+      "((NU)($1) >= (NU)($3Len_0) || (NU)($2) >= (NU)($3Len_0))) #raiseIndexError();$n",
+      rdLoc(a), rdLoc(b), rdLoc(arr))
+  of tyArray:
+    let first = intLiteral(firstOrd(p.config, ty))
+    linefmt(p, cpsStmts,
+      "if ($2-$1 != -1 && " &
+      "($2-$1 < -1 || $1 < $3 || $1 > $4 || $2 < $3 || $2 > $4)) #raiseIndexError();$n",
+      rdCharLoc(a), rdCharLoc(b), first, intLiteral(lastOrd(p.config, ty)))
+  of tySequence, tyString:
+    linefmt(p, cpsStmts,
+      "if ($2-$1 != -1 && " &
+      "((NU)($1) >= (NU)$3 || (NU)($2) >= (NU)$3)) #raiseIndexError();$n",
+      rdLoc(a), rdLoc(b), lenExpr(p, arr))
+  else: discard
 
-proc genOpenArrayElem(p: BProc, x, y: PNode, d: var TLoc) =
+proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) =
   var a, b: TLoc
   initLocExpr(p, x, a)
   initLocExpr(p, y, b) # emit range check:
   if optBoundsCheck in p.options:
-    linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2Len_0)) #raiseIndexError();$n",
+    linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2Len_0)) #raiseIndexError2($1,$2Len_0-1);$n",
             rdLoc(b), rdLoc(a)) # BUGFIX: ``>=`` and not ``>``!
-  if d.k == locNone: d.s = a.s
-  putIntoDest(p, d, elemType(skipTypes(a.t, abstractVar)),
-              rfmt(nil, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.s)
+  inheritLocation(d, a)
+  putIntoDest(p, d, n,
+              ropecg(p.module, "$1[$2]", rdLoc(a), rdCharLoc(b)), a.storage)
 
-proc genSeqElem(p: BProc, x, y: PNode, d: var TLoc) =
+proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) =
   var a, b: TLoc
   initLocExpr(p, x, a)
   initLocExpr(p, y, b)
@@ -875,30 +927,31 @@ proc genSeqElem(p: BProc, x, y: PNode, d: var TLoc) =
   if ty.kind in {tyRef, tyPtr}:
     ty = skipTypes(ty.lastSon, abstractVarRange) # emit range check:
   if optBoundsCheck in p.options:
-    if ty.kind == tyString:
+    if ty.kind == tyString and (not defined(nimNoZeroTerminator) or optLaxStrings in p.options):
       linefmt(p, cpsStmts,
-           "if ((NU)($1) > (NU)($2->$3)) #raiseIndexError();$n",
-           rdLoc(b), rdLoc(a), lenField(p))
+              "if ((NU)($1) > (NU)$2) #raiseIndexError2($1,$2);$n",
+              rdLoc(b), lenExpr(p, a))
     else:
       linefmt(p, cpsStmts,
-           "if ((NU)($1) >= (NU)($2->$3)) #raiseIndexError();$n",
-           rdLoc(b), rdLoc(a), lenField(p))
-  if d.k == locNone: d.s = OnHeap
+              "if ((NU)($1) >= (NU)$2) #raiseIndexError2($1,$2-1);$n",
+              rdLoc(b), lenExpr(p, a))
+  if d.k == locNone: d.storage = OnHeap
   if skipTypes(a.t, abstractVar).kind in {tyRef, tyPtr}:
-    a.r = rfmt(nil, "(*$1)", a.r)
-  putIntoDest(p, d, elemType(skipTypes(a.t, abstractVar)),
-              rfmt(nil, "$1->data[$2]", rdLoc(a), rdCharLoc(b)), a.s)
+    a.r = ropecg(p.module, "(*$1)", a.r)
+  putIntoDest(p, d, n,
+              ropecg(p.module, "$1$3[$2]", rdLoc(a), rdCharLoc(b), dataField(p)), a.storage)
 
 proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) =
-  var ty = skipTypes(n.sons[0].typ, abstractVarRange)
+  var ty = skipTypes(n.sons[0].typ, abstractVarRange + tyUserTypeClasses)
   if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange)
   case ty.kind
-  of tyArray: genArrayElem(p, n.sons[0], n.sons[1], d)
-  of tyOpenArray, tyVarargs: genOpenArrayElem(p, n.sons[0], n.sons[1], d)
-  of tySequence, tyString: genSeqElem(p, n.sons[0], n.sons[1], d)
-  of tyCString: genCStringElem(p, n.sons[0], n.sons[1], d)
+  of tyUncheckedArray: genUncheckedArrayElem(p, n, n.sons[0], n.sons[1], d)
+  of tyArray: genArrayElem(p, n, n.sons[0], n.sons[1], d)
+  of tyOpenArray, tyVarargs: genOpenArrayElem(p, n, n.sons[0], n.sons[1], d)
+  of tySequence, tyString: genSeqElem(p, n, n.sons[0], n.sons[1], d)
+  of tyCString: genCStringElem(p, n, n.sons[0], n.sons[1], d)
   of tyTuple: genTupleElem(p, n, d)
-  else: internalError(n.info, "expr(nkBracketExpr, " & $ty.kind & ')')
+  else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')')
 
 proc genAndOr(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
   # how to generate code?
@@ -943,22 +996,41 @@ proc genAndOr(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
 proc genEcho(p: BProc, n: PNode) =
   # this unusal way of implementing it ensures that e.g. ``echo("hallo", 45)``
   # is threadsafe.
-  internalAssert n.kind == nkBracket
-  p.module.includeHeader("<stdio.h>")
-  var args: Rope = nil
-  var a: TLoc
-  for i in countup(0, n.len-1):
-    if n.sons[i].skipConv.kind == nkNilLit:
-      add(args, ", \"nil\"")
+  internalAssert p.config, n.kind == nkBracket
+  if p.config.target.targetOS == osGenode:
+    # echo directly to the Genode LOG session
+    var args: Rope = nil
+    var a: TLoc
+    for it in n.sons:
+      if it.skipConv.kind == nkNilLit:
+        add(args, ", \"\"")
+      else:
+        initLocExpr(p, it, a)
+        add(args, ropecg(p.module, ", Genode::Cstring($1->data, $1->len)", [rdLoc(a)]))
+    p.module.includeHeader("<base/log.h>")
+    p.module.includeHeader("<util/string.h>")
+    linefmt(p, cpsStmts, """Genode::log(""$1);$n""", args)
+  else:
+    if n.len == 0:
+      linefmt(p, cpsStmts, "#echoBinSafe(NIM_NIL, $1);$n", n.len.rope)
     else:
-      initLocExpr(p, n.sons[i], a)
-      addf(args, ", $1? ($1)->data:\"nil\"", [rdLoc(a)])
-  linefmt(p, cpsStmts, "printf($1$2);$n",
-          makeCString(repeat("%s", n.len) & tnl), args)
-  linefmt(p, cpsStmts, "fflush(stdout);$n")
+      var a: TLoc
+      initLocExpr(p, n, a)
+      linefmt(p, cpsStmts, "#echoBinSafe($1, $2);$n", a.rdLoc, n.len.rope)
+    when false:
+      p.module.includeHeader("<stdio.h>")
+      linefmt(p, cpsStmts, "printf($1$2);$n",
+              makeCString(repeat("%s", n.len) & "\L"), args)
+      linefmt(p, cpsStmts, "fflush(stdout);$n")
 
-proc gcUsage(n: PNode) =
-  if gSelectedGC == gcNone: message(n.info, warnGcMem, n.renderTree)
+proc gcUsage(conf: ConfigRef; n: PNode) =
+  if conf.selectedGC == gcNone: message(conf, n.info, warnGcMem, n.renderTree)
+
+proc strLoc(p: BProc; d: TLoc): Rope =
+  if p.config.selectedGc == gcDestructors:
+    result = addrLoc(p.config, d)
+  else:
+    result = rdLoc(d)
 
 proc genStrConcat(p: BProc, e: PNode, d: var TLoc) =
   #   <Nim code>
@@ -987,20 +1059,21 @@ proc genStrConcat(p: BProc, e: PNode, d: var TLoc) =
     initLocExpr(p, e.sons[i + 1], a)
     if skipTypes(e.sons[i + 1].typ, abstractVarRange).kind == tyChar:
       inc(L)
-      add(appends, rfmt(p.module, "#appendChar($1, $2);$n", tmp.r, rdLoc(a)))
+      add(appends, ropecg(p.module, "#appendChar($1, $2);$n", strLoc(p, tmp), rdLoc(a)))
     else:
       if e.sons[i + 1].kind in {nkStrLit..nkTripleStrLit}:
         inc(L, len(e.sons[i + 1].strVal))
       else:
-        addf(lens, "$1->$2 + ", [rdLoc(a), lenField(p)])
-      add(appends, rfmt(p.module, "#appendString($1, $2);$n", tmp.r, rdLoc(a)))
+        add(lens, lenExpr(p, a))
+        add(lens, " + ")
+      add(appends, ropecg(p.module, "#appendString($1, $2);$n", strLoc(p, tmp), rdLoc(a)))
   linefmt(p, cpsStmts, "$1 = #rawNewString($2$3);$n", tmp.r, lens, rope(L))
   add(p.s(cpsStmts), appends)
   if d.k == locNone:
     d = tmp
   else:
     genAssignment(p, d, tmp, {}) # no need for deep copying
-  gcUsage(e)
+  gcUsage(p.config, e)
 
 proc genStrAppend(p: BProc, e: PNode, d: var TLoc) =
   #  <Nim code>
@@ -1015,7 +1088,7 @@ proc genStrAppend(p: BProc, e: PNode, d: var TLoc) =
   #    appendChar(s, 'z');
   #  }
   var
-    a, dest: TLoc
+    a, dest, call: TLoc
     appends, lens: Rope
   assert(d.k == locNone)
   var L = 0
@@ -1025,72 +1098,101 @@ proc genStrAppend(p: BProc, e: PNode, d: var TLoc) =
     initLocExpr(p, e.sons[i + 2], a)
     if skipTypes(e.sons[i + 2].typ, abstractVarRange).kind == tyChar:
       inc(L)
-      add(appends, rfmt(p.module, "#appendChar($1, $2);$n",
-                        rdLoc(dest), rdLoc(a)))
+      add(appends, ropecg(p.module, "#appendChar($1, $2);$n",
+                        strLoc(p, dest), rdLoc(a)))
     else:
       if e.sons[i + 2].kind in {nkStrLit..nkTripleStrLit}:
         inc(L, len(e.sons[i + 2].strVal))
       else:
-        addf(lens, "$1->$2 + ", [rdLoc(a), lenField(p)])
-      add(appends, rfmt(p.module, "#appendString($1, $2);$n",
-                        rdLoc(dest), rdLoc(a)))
-  linefmt(p, cpsStmts, "$1 = #resizeString($1, $2$3);$n",
-          rdLoc(dest), lens, rope(L))
+        add(lens, lenExpr(p, a))
+        add(lens, " + ")
+      add(appends, ropecg(p.module, "#appendString($1, $2);$n",
+                        strLoc(p, dest), rdLoc(a)))
+  if p.config.selectedGC == gcDestructors:
+    linefmt(p, cpsStmts, "#prepareAdd($1, $2$3);$n",
+            addrLoc(p.config, dest), lens, rope(L))
+  else:
+    initLoc(call, locCall, e, OnHeap)
+    call.r = ropecg(p.module, "#resizeString($1, $2$3)", [rdLoc(dest), lens, rope(L)])
+    genAssignment(p, dest, call, {})
+    gcUsage(p.config, e)
   add(p.s(cpsStmts), appends)
-  gcUsage(e)
 
 proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) =
   # seq &= x  -->
   #    seq = (typeof seq) incrSeq(&seq->Sup, sizeof(x));
   #    seq->data[seq->len-1] = x;
   let seqAppendPattern = if not p.module.compileToCpp:
-                           "$1 = ($2) #incrSeqV2(&($1)->Sup, sizeof($3));$n"
+                           "($2) #incrSeqV3(&($1)->Sup, $3)"
                          else:
-                           "$1 = ($2) #incrSeqV2($1, sizeof($3));$n"
-  var a, b, dest: TLoc
+                           "($2) #incrSeqV3($1, $3)"
+  var a, b, dest, tmpL, call: TLoc
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
-  let bt = skipTypes(e.sons[2].typ, {tyVar})
-  lineCg(p, cpsStmts, seqAppendPattern, [
-      rdLoc(a),
-      getTypeDesc(p.module, e.sons[1].typ),
-      getTypeDesc(p.module, bt)])
+  let seqType = skipTypes(e.sons[1].typ, {tyVar})
+  initLoc(call, locCall, e, OnHeap)
+  call.r = ropecg(p.module, seqAppendPattern, [rdLoc(a),
+    getTypeDesc(p.module, e.sons[1].typ),
+    genTypeInfo(p.module, seqType, e.info)])
+  # emit the write barrier if required, but we can always move here, so
+  # use 'genRefAssign' for the seq.
+  genRefAssign(p, a, call)
   #if bt != b.t:
   #  echo "YES ", e.info, " new: ", typeToString(bt), " old: ", typeToString(b.t)
-  initLoc(dest, locExpr, bt, OnHeap)
-  dest.r = rfmt(nil, "$1->data[$1->$2]", rdLoc(a), lenField(p))
-  genAssignment(p, dest, b, {needToCopy, afDestIsNil})
-  lineCg(p, cpsStmts, "++$1->$2;$n", rdLoc(a), lenField(p))
-  gcUsage(e)
+  initLoc(dest, locExpr, e.sons[2], OnHeap)
+  getIntTemp(p, tmpL)
+  lineCg(p, cpsStmts, "$1 = $2->$3++;$n", tmpL.r, rdLoc(a), lenField(p))
+  dest.r = ropecg(p.module, "$1$3[$2]", rdLoc(a), tmpL.r, dataField(p))
+  genAssignment(p, dest, b, {needToCopy})
+  gcUsage(p.config, e)
 
 proc genReset(p: BProc, n: PNode) =
   var a: TLoc
   initLocExpr(p, n.sons[1], a)
   linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n",
-          addrLoc(a), genTypeInfo(p.module, skipTypes(a.t, {tyVar})))
+          addrLoc(p.config, a),
+          genTypeInfo(p.module, skipTypes(a.t, {tyVar}), n.info))
 
 proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) =
   var sizeExpr = sizeExpr
   let typ = a.t
   var b: TLoc
-  initLoc(b, locExpr, a.t, OnHeap)
+  initLoc(b, locExpr, a.lode, OnHeap)
   let refType = typ.skipTypes(abstractInst)
   assert refType.kind == tyRef
   let bt = refType.lastSon
   if sizeExpr.isNil:
     sizeExpr = "sizeof($1)" %
         [getTypeDesc(p.module, bt)]
-  let args = [getTypeDesc(p.module, typ),
-              genTypeInfo(p.module, typ),
-              sizeExpr]
-  if a.s == OnHeap and usesNativeGC():
-    # use newObjRC1 as an optimization
+
+  let ti = genTypeInfo(p.module, typ, a.lode.info)
+  if bt.destructor != nil:
+    # the prototype of a destructor is ``=destroy(x: var T)`` and that of a
+    # finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
+    # convention at least:
+    if bt.destructor.typ == nil or bt.destructor.typ.callConv != ccDefault:
+      localError(p.module.config, a.lode.info,
+        "the destructor that is turned into a finalizer needs " &
+        "to have the 'nimcall' calling convention")
+    var f: TLoc
+    initLocExpr(p, newSymNode(bt.destructor), f)
+    addf(p.module.s[cfsTypeInit3], "$1->finalizer = (void*)$2;$n", [ti, rdLoc(f)])
+
+  let args = [getTypeDesc(p.module, typ), ti, sizeExpr]
+  if a.storage == OnHeap and usesWriteBarrier(p.config):
     if canFormAcycle(a.t):
-      linefmt(p, cpsStmts, "if ($1) #nimGCunref($1);$n", a.rdLoc)
+      linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", a.rdLoc)
+    else:
+      linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", a.rdLoc)
+    if p.config.selectedGC == gcGo:
+      # newObjRC1() would clash with unsureAsgnRef() - which is used by gcGo to
+      # implement the write barrier
+      b.r = ropecg(p.module, "($1) #newObj($2, $3)", args)
+      linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", addrLoc(p.config, a), b.rdLoc)
     else:
-      linefmt(p, cpsStmts, "if ($1) #nimGCunrefNoCycle($1);$n", a.rdLoc)
-    b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args)
-    linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc)
+      # use newObjRC1 as an optimization
+      b.r = ropecg(p.module, "($1) #newObjRC1($2, $3)", args)
+      linefmt(p, cpsStmts, "$1 = $2;$n", a.rdLoc, b.rdLoc)
   else:
     b.r = ropecg(p.module, "($1) #newObj($2, $3)", args)
     genAssignment(p, a, b, {})  # set the object type:
@@ -1106,41 +1208,58 @@ proc genNew(p: BProc, e: PNode) =
     rawGenNew(p, a, se.rdLoc)
   else:
     rawGenNew(p, a, nil)
-  gcUsage(e)
+  gcUsage(p.config, e)
 
-proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope) =
+proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) =
   let seqtype = skipTypes(dest.t, abstractVarRange)
   let args = [getTypeDesc(p.module, seqtype),
-              genTypeInfo(p.module, seqtype), length]
+              genTypeInfo(p.module, seqtype, dest.lode.info), length]
   var call: TLoc
-  initLoc(call, locExpr, dest.t, OnHeap)
-  if dest.s == OnHeap and usesNativeGC():
+  initLoc(call, locExpr, dest.lode, OnHeap)
+  if dest.storage == OnHeap and usesWriteBarrier(p.config):
     if canFormAcycle(dest.t):
-      linefmt(p, cpsStmts, "if ($1) #nimGCunref($1);$n", dest.rdLoc)
+      linefmt(p, cpsStmts, "if ($1) { #nimGCunrefRC1($1); $1 = NIM_NIL; }$n", dest.rdLoc)
     else:
-      linefmt(p, cpsStmts, "if ($1) #nimGCunrefNoCycle($1);$n", dest.rdLoc)
-    call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args)
-    linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc)
+      linefmt(p, cpsStmts, "if ($1) { #nimGCunrefNoCycle($1); $1 = NIM_NIL; }$n", dest.rdLoc)
+    if not lenIsZero:
+      if p.config.selectedGC == gcGo:
+        # we need the write barrier
+        call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args)
+        linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, $2);$n", addrLoc(p.config, dest), call.rdLoc)
+      else:
+        call.r = ropecg(p.module, "($1) #newSeqRC1($2, $3)", args)
+        linefmt(p, cpsStmts, "$1 = $2;$n", dest.rdLoc, call.rdLoc)
   else:
-    call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args)
+    if lenIsZero:
+      call.r = rope"NIM_NIL"
+    else:
+      call.r = ropecg(p.module, "($1) #newSeq($2, $3)", args)
     genAssignment(p, dest, call, {})
 
 proc genNewSeq(p: BProc, e: PNode) =
   var a, b: TLoc
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
-  genNewSeqAux(p, a, b.rdLoc)
-  gcUsage(e)
+  if p.config.selectedGC == gcDestructors:
+    let seqtype = skipTypes(e.sons[1].typ, abstractVarRange)
+    linefmt(p, cpsStmts, "$1.len = $2; $1.p = ($4*) #newSeqPayload($2, sizeof($3));$n",
+      a.rdLoc, b.rdLoc, getTypeDesc(p.module, seqtype.lastSon),
+      getSeqPayloadType(p.module, seqtype))
+  else:
+    let lenIsZero = optNilSeqs notin p.options and
+      e[2].kind == nkIntLit and e[2].intVal == 0
+    genNewSeqAux(p, a, b.rdLoc, lenIsZero)
+    gcUsage(p.config, e)
 
 proc genNewSeqOfCap(p: BProc; e: PNode; d: var TLoc) =
   let seqtype = skipTypes(e.typ, abstractVarRange)
   var a: TLoc
   initLocExpr(p, e.sons[1], a)
-  putIntoDest(p, d, e.typ, ropecg(p.module,
+  putIntoDest(p, d, e, ropecg(p.module,
               "($1)#nimNewSeqOfCap($2, $3)", [
               getTypeDesc(p.module, seqtype),
-              genTypeInfo(p.module, seqtype), a.rdLoc]))
-  gcUsage(e)
+              genTypeInfo(p.module, seqtype, e.info), a.rdLoc]))
+  gcUsage(p.config, e)
 
 proc genConstExpr(p: BProc, n: PNode): Rope
 proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool =
@@ -1148,7 +1267,7 @@ proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool =
     let t = n.typ
     discard getTypeDesc(p.module, t) # so that any fields are initialized
     let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels)
-    fillLoc(d, locData, t, p.module.tmpBase & rope(id), OnStatic)
+    fillLoc(d, locData, n, p.module.tmpBase & rope(id), OnStatic)
     if id == p.module.labels:
       # expression not found in the cache:
       inc(p.module.labels)
@@ -1160,75 +1279,136 @@ proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool =
 
 proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
   #echo rendertree e, " ", e.isDeepConstExpr
-  if handleConstExpr(p, e, d): return
-  var tmp: TLoc
+  # inheritance in C++ does not allow struct initialization so
+  # we skip this step here:
+  if not p.module.compileToCpp:
+    if handleConstExpr(p, e, d): return
   var t = e.typ.skipTypes(abstractInst)
-  getTemp(p, t, tmp)
   let isRef = t.kind == tyRef
-  var r = rdLoc(tmp)
-  if isRef:
-    rawGenNew(p, tmp, nil)
-    t = t.lastSon.skipTypes(abstractInst)
-    r = "(*$1)" % [r]
-    gcUsage(e)
+
+  # check if we need to construct the object in a temporary
+  var useTemp =
+        isRef or
+        (d.k notin {locTemp,locLocalVar,locGlobalVar,locParam,locField}) or
+        (isPartOf(d.lode, e) != arNo)
+
+  var tmp: TLoc
+  var r: Rope
+  if useTemp:
+    getTemp(p, t, tmp)
+    r = rdLoc(tmp)
+    if isRef:
+      rawGenNew(p, tmp, nil)
+      t = t.lastSon.skipTypes(abstractInst)
+      r = "(*$1)" % [r]
+      gcUsage(p.config, e)
+    else:
+      constructLoc(p, tmp)
   else:
-    constructLoc(p, tmp)
+    resetLoc(p, d)
+    r = rdLoc(d)
   discard getTypeDesc(p.module, t)
   let ty = getUniqueType(t)
-  for i in 1 .. <e.len:
+  for i in 1 ..< e.len:
     let it = e.sons[i]
     var tmp2: TLoc
     tmp2.r = r
     let field = lookupFieldAgain(p, ty, it.sons[0].sym, tmp2.r)
     if field.loc.r == nil: fillObjectFields(p.module, ty)
-    if field.loc.r == nil: internalError(e.info, "genObjConstr")
+    if field.loc.r == nil: internalError(p.config, e.info, "genObjConstr")
     if it.len == 3 and optFieldCheck in p.options:
-      genFieldCheck(p, it.sons[2], r, field, ty)
+      genFieldCheck(p, it.sons[2], r, field)
     add(tmp2.r, ".")
     add(tmp2.r, field.loc.r)
-    tmp2.k = locTemp
-    tmp2.t = field.loc.t
-    tmp2.s = if isRef: OnHeap else: OnStack
+    if useTemp:
+      tmp2.k = locTemp
+      tmp2.storage = if isRef: OnHeap else: OnStack
+    else:
+      tmp2.k = d.k
+      tmp2.storage = if isRef: OnHeap else: d.storage
+    tmp2.lode = it.sons[1]
     expr(p, it.sons[1], tmp2)
-
-  if d.k == locNone:
-    d = tmp
+  if useTemp:
+    if d.k == locNone:
+      d = tmp
+    else:
+      genAssignment(p, d, tmp, {})
+
+proc lhsDoesAlias(a, b: PNode): bool =
+  for y in b:
+    if isPartOf(a, y) != arNo: return true
+
+proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) =
+  var arr, tmp: TLoc
+  # bug #668
+  let doesAlias = lhsDoesAlias(d.lode, n)
+  let dest = if doesAlias: addr(tmp) else: addr(d)
+  if doesAlias:
+    getTemp(p, n.typ, tmp)
+  elif d.k == locNone:
+    getTemp(p, n.typ, d)
+
+  let l = intLiteral(sonsLen(n))
+  if p.config.selectedGC == gcDestructors:
+    let seqtype = n.typ
+    linefmt(p, cpsStmts, "$1.len = $2; $1.p = ($4*) #newSeqPayload($2, sizeof($3));$n",
+      rdLoc dest[], l, getTypeDesc(p.module, seqtype.lastSon),
+      getSeqPayloadType(p.module, seqtype))
   else:
-    genAssignment(p, d, tmp, {})
+    # generate call to newSeq before adding the elements per hand:
+    genNewSeqAux(p, dest[], l,
+      optNilSeqs notin p.options and n.len == 0)
+  for i in countup(0, sonsLen(n) - 1):
+    initLoc(arr, locExpr, n[i], OnHeap)
+    arr.r = ropecg(p.module, "$1$3[$2]", rdLoc(dest[]), intLiteral(i), dataField(p))
+    arr.storage = OnHeap            # we know that sequences are on the heap
+    expr(p, n[i], arr)
+  gcUsage(p.config, n)
+  if doesAlias:
+    if d.k == locNone:
+      d = tmp
+    else:
+      genAssignment(p, d, tmp, {})
 
-proc genSeqConstr(p: BProc, t: PNode, d: var TLoc) =
-  var arr: TLoc
-  if d.k == locNone:
-    getTemp(p, t.typ, d)
-  # generate call to newSeq before adding the elements per hand:
-  genNewSeqAux(p, d, intLiteral(sonsLen(t)))
-  for i in countup(0, sonsLen(t) - 1):
-    initLoc(arr, locExpr, elemType(skipTypes(t.typ, typedescInst)), OnHeap)
-    arr.r = rfmt(nil, "$1->data[$2]", rdLoc(d), intLiteral(i))
-    arr.s = OnHeap            # we know that sequences are on the heap
-    expr(p, t.sons[i], arr)
-  gcUsage(t)
-
-proc genArrToSeq(p: BProc, t: PNode, d: var TLoc) =
+proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) =
   var elem, a, arr: TLoc
-  if t.sons[1].kind == nkBracket:
-    t.sons[1].typ = t.typ
-    genSeqConstr(p, t.sons[1], d)
+  if n.sons[1].kind == nkBracket:
+    n.sons[1].typ = n.typ
+    genSeqConstr(p, n.sons[1], d)
     return
   if d.k == locNone:
-    getTemp(p, t.typ, d)
+    getTemp(p, n.typ, d)
   # generate call to newSeq before adding the elements per hand:
-  var L = int(lengthOrd(t.sons[1].typ))
-
-  genNewSeqAux(p, d, intLiteral(L))
-  initLocExpr(p, t.sons[1], a)
-  for i in countup(0, L - 1):
-    initLoc(elem, locExpr, elemType(skipTypes(t.typ, abstractInst)), OnHeap)
-    elem.r = rfmt(nil, "$1->data[$2]", rdLoc(d), intLiteral(i))
-    elem.s = OnHeap # we know that sequences are on the heap
-    initLoc(arr, locExpr, elemType(skipTypes(t.sons[1].typ, abstractInst)), a.s)
-    arr.r = rfmt(nil, "$1[$2]", rdLoc(a), intLiteral(i))
-    genAssignment(p, elem, arr, {afDestIsNil, needToCopy})
+  let L = int(lengthOrd(p.config, n.sons[1].typ))
+  if p.config.selectedGC == gcDestructors:
+    let seqtype = n.typ
+    linefmt(p, cpsStmts, "$1.len = $2; $1.p = ($4*) #newSeqPayload($2, sizeof($3));$n",
+      rdLoc d, rope L, getTypeDesc(p.module, seqtype.lastSon),
+      getSeqPayloadType(p.module, seqtype))
+  else:
+    genNewSeqAux(p, d, intLiteral(L), optNilSeqs notin p.options and L == 0)
+  initLocExpr(p, n.sons[1], a)
+  # bug #5007; do not produce excessive C source code:
+  if L < 10:
+    for i in countup(0, L - 1):
+      initLoc(elem, locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), OnHeap)
+      elem.r = ropecg(p.module, "$1$3[$2]", rdLoc(d), intLiteral(i), dataField(p))
+      elem.storage = OnHeap # we know that sequences are on the heap
+      initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.sons[1].typ, abstractInst)), a.storage)
+      arr.r = ropecg(p.module, "$1[$2]", rdLoc(a), intLiteral(i))
+      genAssignment(p, elem, arr, {needToCopy})
+  else:
+    var i: TLoc
+    getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i)
+    linefmt(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",  i.r, L.rope)
+    initLoc(elem, locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), OnHeap)
+    elem.r = ropecg(p.module, "$1$3[$2]", rdLoc(d), rdLoc(i), dataField(p))
+    elem.storage = OnHeap # we know that sequences are on the heap
+    initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.sons[1].typ, abstractInst)), a.storage)
+    arr.r = ropecg(p.module, "$1[$2]", rdLoc(a), rdLoc(i))
+    genAssignment(p, elem, arr, {needToCopy})
+    lineF(p, cpsStmts, "}$n", [])
+
 
 proc genNewFinalize(p: BProc, e: PNode) =
   var
@@ -1238,8 +1418,8 @@ proc genNewFinalize(p: BProc, e: PNode) =
   refType = skipTypes(e.sons[1].typ, abstractVarRange)
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], f)
-  initLoc(b, locExpr, a.t, OnHeap)
-  ti = genTypeInfo(p.module, refType)
+  initLoc(b, locExpr, a.lode, OnHeap)
+  ti = genTypeInfo(p.module, refType, e.info)
   addf(p.module.s[cfsTypeInit3], "$1->finalizer = (void*)$2;$n", [ti, rdLoc(f)])
   b.r = ropecg(p.module, "($1) #newObj($2, sizeof($3))", [
       getTypeDesc(p.module, refType),
@@ -1247,12 +1427,12 @@ proc genNewFinalize(p: BProc, e: PNode) =
   genAssignment(p, a, b, {})  # set the object type:
   bt = skipTypes(refType.lastSon, abstractRange)
   genObjectInit(p, cpsStmts, bt, a, false)
-  gcUsage(e)
+  gcUsage(p.config, e)
 
-proc genOfHelper(p: BProc; dest: PType; a: Rope): Rope =
+proc genOfHelper(p: BProc; dest: PType; a: Rope; info: TLineInfo): Rope =
   # unfortunately 'genTypeInfo' sets tfObjHasKids as a side effect, so we
   # have to call it here first:
-  let ti = genTypeInfo(p.module, dest)
+  let ti = genTypeInfo(p.module, dest, info)
   if tfFinal in dest.flags or (objHasKidsValid in p.module.flags and
                                tfObjHasKids notin dest.flags):
     result = "$1.m_type == $2" % [a, ti]
@@ -1261,11 +1441,11 @@ proc genOfHelper(p: BProc; dest: PType; a: Rope): Rope =
     inc p.module.labels
     let cache = "Nim_OfCheck_CACHE" & p.module.labels.rope
     addf(p.module.s[cfsVars], "static TNimType* $#[2];$n", [cache])
-    result = rfmt(p.module, "#isObjWithCache($#.m_type, $#, $#)", a, ti, cache)
+    result = ropecg(p.module, "#isObjWithCache($#.m_type, $#, $#)", a, ti, cache)
   when false:
     # former version:
-    result = rfmt(p.module, "#isObj($1.m_type, $2)",
-                  a, genTypeInfo(p.module, dest))
+    result = ropecg(p.module, "#isObj($1.m_type, $2)",
+                  a, genTypeInfo(p.module, dest, info))
 
 proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) =
   var a: TLoc
@@ -1274,23 +1454,24 @@ proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) =
   var r = rdLoc(a)
   var nilCheck: Rope = nil
   var t = skipTypes(a.t, abstractInst)
-  while t.kind in {tyVar, tyPtr, tyRef}:
-    if t.kind != tyVar: nilCheck = r
-    if t.kind != tyVar or not p.module.compileToCpp:
-      r = rfmt(nil, "(*$1)", r)
+  while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
+    if t.kind notin {tyVar, tyLent}: nilCheck = r
+    if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
+      r = ropecg(p.module, "(*$1)", r)
     t = skipTypes(t.lastSon, typedescInst)
+  discard getTypeDesc(p.module, t)
   if not p.module.compileToCpp:
     while t.kind == tyObject and t.sons[0] != nil:
       add(r, ~".Sup")
       t = skipTypes(t.sons[0], skipPtrs)
   if isObjLackingTypeField(t):
-    globalError(x.info, errGenerated,
+    globalError(p.config, x.info,
       "no 'of' operator available for pure objects")
   if nilCheck != nil:
-    r = rfmt(p.module, "(($1) && ($2))", nilCheck, genOfHelper(p, dest, r))
+    r = ropecg(p.module, "(($1) && ($2))", nilCheck, genOfHelper(p, dest, r, x.info))
   else:
-    r = rfmt(p.module, "($1)", genOfHelper(p, dest, r))
-  putIntoDest(p, d, getSysType(tyBool), r, a.s)
+    r = ropecg(p.module, "($1)", genOfHelper(p, dest, r, x.info))
+  putIntoDest(p, d, x, r, a.storage)
 
 proc genOf(p: BProc, n: PNode, d: var TLoc) =
   genOf(p, n.sons[1], n.sons[2].typ, d)
@@ -1301,52 +1482,53 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
   var t = skipTypes(e.sons[1].typ, abstractVarRange)
   case t.kind
   of tyInt..tyInt64, tyUInt..tyUInt64:
-    putIntoDest(p, d, e.typ,
-                ropecg(p.module, "#reprInt((NI64)$1)", [rdLoc(a)]), a.s)
+    putIntoDest(p, d, e,
+                ropecg(p.module, "#reprInt((NI64)$1)", [rdLoc(a)]), a.storage)
   of tyFloat..tyFloat128:
-    putIntoDest(p, d, e.typ, ropecg(p.module, "#reprFloat($1)", [rdLoc(a)]), a.s)
+    putIntoDest(p, d, e, ropecg(p.module, "#reprFloat($1)", [rdLoc(a)]), a.storage)
   of tyBool:
-    putIntoDest(p, d, e.typ, ropecg(p.module, "#reprBool($1)", [rdLoc(a)]), a.s)
+    putIntoDest(p, d, e, ropecg(p.module, "#reprBool($1)", [rdLoc(a)]), a.storage)
   of tyChar:
-    putIntoDest(p, d, e.typ, ropecg(p.module, "#reprChar($1)", [rdLoc(a)]), a.s)
+    putIntoDest(p, d, e, ropecg(p.module, "#reprChar($1)", [rdLoc(a)]), a.storage)
   of tyEnum, tyOrdinal:
-    putIntoDest(p, d, e.typ,
+    putIntoDest(p, d, e,
                 ropecg(p.module, "#reprEnum((NI)$1, $2)", [
-                rdLoc(a), genTypeInfo(p.module, t)]), a.s)
+                rdLoc(a), genTypeInfo(p.module, t, e.info)]), a.storage)
   of tyString:
-    putIntoDest(p, d, e.typ, ropecg(p.module, "#reprStr($1)", [rdLoc(a)]), a.s)
+    putIntoDest(p, d, e, ropecg(p.module, "#reprStr($1)", [rdLoc(a)]), a.storage)
   of tySet:
-    putIntoDest(p, d, e.typ, ropecg(p.module, "#reprSet($1, $2)", [
-                addrLoc(a), genTypeInfo(p.module, t)]), a.s)
+    putIntoDest(p, d, e, ropecg(p.module, "#reprSet($1, $2)", [
+                addrLoc(p.config, a), genTypeInfo(p.module, t, e.info)]), a.storage)
   of tyOpenArray, tyVarargs:
     var b: TLoc
     case a.t.kind
     of tyOpenArray, tyVarargs:
-      putIntoDest(p, b, e.typ, "$1, $1Len_0" % [rdLoc(a)], a.s)
+      putIntoDest(p, b, e, "$1, $1Len_0" % [rdLoc(a)], a.storage)
     of tyString, tySequence:
-      putIntoDest(p, b, e.typ,
-                  "$1->data, $1->$2" % [rdLoc(a), lenField(p)], a.s)
+      putIntoDest(p, b, e,
+                  "$1$3, $2" % [rdLoc(a), lenExpr(p, a), dataField(p)], a.storage)
     of tyArray:
-      putIntoDest(p, b, e.typ,
-                  "$1, $2" % [rdLoc(a), rope(lengthOrd(a.t))], a.s)
-    else: internalError(e.sons[0].info, "genRepr()")
-    putIntoDest(p, d, e.typ,
+      putIntoDest(p, b, e,
+                  "$1, $2" % [rdLoc(a), rope(lengthOrd(p.config, a.t))], a.storage)
+    else: internalError(p.config, e.sons[0].info, "genRepr()")
+    putIntoDest(p, d, e,
         ropecg(p.module, "#reprOpenArray($1, $2)", [rdLoc(b),
-        genTypeInfo(p.module, elemType(t))]), a.s)
+        genTypeInfo(p.module, elemType(t), e.info)]), a.storage)
   of tyCString, tyArray, tyRef, tyPtr, tyPointer, tyNil, tySequence:
-    putIntoDest(p, d, e.typ,
+    putIntoDest(p, d, e,
                 ropecg(p.module, "#reprAny($1, $2)", [
-                rdLoc(a), genTypeInfo(p.module, t)]), a.s)
+                rdLoc(a), genTypeInfo(p.module, t, e.info)]), a.storage)
   of tyEmpty, tyVoid:
-    localError(e.info, "'repr' doesn't support 'void' type")
+    localError(p.config, e.info, "'repr' doesn't support 'void' type")
   else:
-    putIntoDest(p, d, e.typ, ropecg(p.module, "#reprAny($1, $2)",
-                                   [addrLoc(a), genTypeInfo(p.module, t)]), a.s)
-  gcUsage(e)
+    putIntoDest(p, d, e, ropecg(p.module, "#reprAny($1, $2)",
+                              [addrLoc(p.config, a), genTypeInfo(p.module, t, e.info)]),
+                               a.storage)
+  gcUsage(p.config, e)
 
 proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) =
   let t = e.sons[1].typ
-  putIntoDest(p, d, e.typ, genTypeInfo(p.module, t))
+  putIntoDest(p, d, e, genTypeInfo(p.module, t, e.info))
 
 proc genDollar(p: BProc, n: PNode, d: var TLoc, frmt: string) =
   var a: TLoc
@@ -1354,35 +1536,65 @@ proc genDollar(p: BProc, n: PNode, d: var TLoc, frmt: string) =
   a.r = ropecg(p.module, frmt, [rdLoc(a)])
   if d.k == locNone: getTemp(p, n.typ, d)
   genAssignment(p, d, a, {})
-  gcUsage(n)
+  gcUsage(p.config, n)
 
 proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   var a = e.sons[1]
   if a.kind == nkHiddenAddr: a = a.sons[0]
-  let typ = skipTypes(a.typ, abstractVar)
+  var typ = skipTypes(a.typ, abstractVar + tyUserTypeClasses)
   case typ.kind
   of tyOpenArray, tyVarargs:
-    if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)")
-    else: unaryExpr(p, e, d, "$1Len_0")
-  of tyCString:
-    useStringh(p.module)
-    if op == mHigh: unaryExpr(p, e, d, "($1 ? (strlen($1)-1) : -1)")
-    else: unaryExpr(p, e, d, "($1 ? strlen($1) : 0)")
-  of tyString, tySequence:
-    if not p.module.compileToCpp:
-      if op == mHigh: unaryExpr(p, e, d, "($1 ? ($1->Sup.len-1) : -1)")
-      else: unaryExpr(p, e, d, "($1 ? $1->Sup.len : 0)")
+    # Bug #9279, len(toOpenArray()) has to work:
+    if a.kind in nkCallKinds and a[0].kind == nkSym and a[0].sym.magic == mSlice:
+      # magic: pass slice to openArray:
+      var b, c: TLoc
+      initLocExpr(p, a[2], b)
+      initLocExpr(p, a[3], c)
+      if op == mHigh:
+        putIntoDest(p, d, e, ropecg(p.module, "($2)-($1)", [rdLoc(b), rdLoc(c)]))
+      else:
+        putIntoDest(p, d, e, ropecg(p.module, "($2)-($1)+1", [rdLoc(b), rdLoc(c)]))
     else:
-      if op == mHigh: unaryExpr(p, e, d, "($1 ? ($1->len-1) : -1)")
-      else: unaryExpr(p, e, d, "($1 ? $1->len : 0)")
+      if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)")
+      else: unaryExpr(p, e, d, "$1Len_0")
+  of tyCString:
+    if op == mHigh: unaryExpr(p, e, d, "($1 ? (#nimCStrLen($1)-1) : -1)")
+    else: unaryExpr(p, e, d, "($1 ? #nimCStrLen($1) : 0)")
+  of tyString:
+    var a: TLoc
+    initLocExpr(p, e.sons[1], a)
+    var x = lenExpr(p, a)
+    if op == mHigh: x = "($1-1)" % [x]
+    putIntoDest(p, d, e, x)
+  of tySequence:
+    # we go through a temporary here because people write bullshit code.
+    var a, tmp: TLoc
+    initLocExpr(p, e[1], a)
+    getIntTemp(p, tmp)
+    var x = lenExpr(p, a)
+    if op == mHigh: x = "($1-1)" % [x]
+    lineCg(p, cpsStmts, "$1 = $2;$n", tmp.r, x)
+    putIntoDest(p, d, e, tmp.r)
   of tyArray:
     # YYY: length(sideeffect) is optimized away incorrectly?
-    if op == mHigh: putIntoDest(p, d, e.typ, rope(lastOrd(typ)))
-    else: putIntoDest(p, d, e.typ, rope(lengthOrd(typ)))
-  else: internalError(e.info, "genArrayLen()")
+    if op == mHigh: putIntoDest(p, d, e, rope(lastOrd(p.config, typ)))
+    else: putIntoDest(p, d, e, rope(lengthOrd(p.config, typ)))
+  else: internalError(p.config, e.info, "genArrayLen()")
+
+proc makePtrType(baseType: PType): PType =
+  result = newType(tyPtr, baseType.owner)
+  addSonSkipIntLit(result, baseType)
+
+proc makeAddr(n: PNode): PNode =
+  result = newTree(nkHiddenAddr, n)
+  result.typ = makePtrType(n.typ)
 
 proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) =
-  var a, b: TLoc
+  if p.config.selectedGc == gcDestructors:
+    e.sons[1] = makeAddr(e[1])
+    genCall(p, e, d)
+    return
+  var a, b, call: TLoc
   assert(d.k == locNone)
   var x = e.sons[1]
   if x.kind in {nkAddr, nkHiddenAddr}: x = x[0]
@@ -1390,18 +1602,31 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) =
   initLocExpr(p, e.sons[2], b)
   let t = skipTypes(e.sons[1].typ, {tyVar})
   let setLenPattern = if not p.module.compileToCpp:
-      "$1 = ($3) #setLengthSeq(&($1)->Sup, sizeof($4), $2);$n"
+      "($3) #setLengthSeqV2(&($1)->Sup, $4, $2)"
     else:
-      "$1 = ($3) #setLengthSeq($1, sizeof($4), $2);$n"
+      "($3) #setLengthSeqV2($1, $4, $2)"
 
-  lineCg(p, cpsStmts, setLenPattern, [
+  initLoc(call, locCall, e, OnHeap)
+  call.r = ropecg(p.module, setLenPattern, [
       rdLoc(a), rdLoc(b), getTypeDesc(p.module, t),
-      getTypeDesc(p.module, t.skipTypes(abstractInst).sons[0])])
-  gcUsage(e)
+      genTypeInfo(p.module, t.skipTypes(abstractInst), e.info)])
+  genAssignment(p, a, call, {})
+  gcUsage(p.config, e)
 
 proc genSetLengthStr(p: BProc, e: PNode, d: var TLoc) =
-  binaryStmt(p, e, d, "$1 = #setLengthStr($1, $2);$n")
-  gcUsage(e)
+  if p.config.selectedGc == gcDestructors:
+    binaryStmtAddr(p, e, d, "#setLengthStrV2($1, $2);$n")
+  else:
+    var a, b, call: TLoc
+    if d.k != locNone: internalError(p.config, e.info, "genSetLengthStr")
+    initLocExpr(p, e.sons[1], a)
+    initLocExpr(p, e.sons[2], b)
+
+    initLoc(call, locCall, e, OnHeap)
+    call.r = ropecg(p.module, "#setLengthStr($1, $2)", [
+        rdLoc(a), rdLoc(b)])
+    genAssignment(p, a, call, {})
+    gcUsage(p.config, e)
 
 proc genSwap(p: BProc, e: PNode, d: var TLoc) =
   # swap(a, b) -->
@@ -1416,19 +1641,20 @@ proc genSwap(p: BProc, e: PNode, d: var TLoc) =
   genAssignment(p, a, b, {})
   genAssignment(p, b, tmp, {})
 
-proc rdSetElemLoc(a: TLoc, setType: PType): Rope =
+proc rdSetElemLoc(conf: ConfigRef; a: TLoc, typ: PType): Rope =
   # read a location of an set element; it may need a subtraction operation
   # before the set operation
   result = rdCharLoc(a)
+  let setType = typ.skipTypes(abstractPtrs)
   assert(setType.kind == tySet)
-  if firstOrd(setType) != 0:
-    result = "($1- $2)" % [result, rope(firstOrd(setType))]
+  if firstOrd(conf, setType) != 0:
+    result = "($1- $2)" % [result, rope(firstOrd(conf, setType))]
 
-proc fewCmps(s: PNode): bool =
+proc fewCmps(conf: ConfigRef; s: PNode): bool =
   # this function estimates whether it is better to emit code
   # for constructing the set or generating a bunch of comparisons directly
-  if s.kind != nkCurly: internalError(s.info, "fewCmps")
-  if (getSize(s.typ) <= platform.intSize) and (nfAllConst in s.flags):
+  if s.kind != nkCurly: return false
+  if (getSize(conf, s.typ) <= conf.target.intSize) and (nfAllConst in s.flags):
     result = false            # it is better to emit the set generation code
   elif elemType(s.typ).kind in {tyInt, tyInt16..tyInt64}:
     result = true             # better not emit the set if int is basetype!
@@ -1436,10 +1662,10 @@ proc fewCmps(s: PNode): bool =
     result = sonsLen(s) <= 8  # 8 seems to be a good value
 
 proc binaryExprIn(p: BProc, e: PNode, a, b, d: var TLoc, frmt: string) =
-  putIntoDest(p, d, e.typ, frmt % [rdLoc(a), rdSetElemLoc(b, a.t)])
+  putIntoDest(p, d, e, frmt % [rdLoc(a), rdSetElemLoc(p.config, b, a.t)])
 
 proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) =
-  case int(getSize(skipTypes(e.sons[1].typ, abstractVar)))
+  case int(getSize(p.config, skipTypes(e.sons[1].typ, abstractVar)))
   of 1: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&7U)))!=0)")
   of 2: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&15U)))!=0)")
   of 4: binaryExprIn(p, e, a, b, d, "(($1 &(1U<<((NU)($2)&31U)))!=0)")
@@ -1451,11 +1677,11 @@ proc binaryStmtInExcl(p: BProc, e: PNode, d: var TLoc, frmt: string) =
   assert(d.k == locNone)
   initLocExpr(p, e.sons[1], a)
   initLocExpr(p, e.sons[2], b)
-  lineF(p, cpsStmts, frmt, [rdLoc(a), rdSetElemLoc(b, a.t)])
+  lineF(p, cpsStmts, frmt, [rdLoc(a), rdSetElemLoc(p.config, b, a.t)])
 
 proc genInOp(p: BProc, e: PNode, d: var TLoc) =
   var a, b, x, y: TLoc
-  if (e.sons[1].kind == nkCurly) and fewCmps(e.sons[1]):
+  if (e.sons[1].kind == nkCurly) and fewCmps(p.config, e.sons[1]):
     # a set constructor but not a constant set:
     # do not emit the set, but generate a bunch of comparisons; and if we do
     # so, we skip the unnecessary range check: This is a semantical extension
@@ -1465,21 +1691,26 @@ proc genInOp(p: BProc, e: PNode, d: var TLoc) =
              else:
                e.sons[2]
     initLocExpr(p, ea, a)
-    initLoc(b, locExpr, e.typ, OnUnknown)
-    b.r = rope("(")
+    initLoc(b, locExpr, e, OnUnknown)
     var length = sonsLen(e.sons[1])
-    for i in countup(0, length - 1):
-      if e.sons[1].sons[i].kind == nkRange:
-        initLocExpr(p, e.sons[1].sons[i].sons[0], x)
-        initLocExpr(p, e.sons[1].sons[i].sons[1], y)
-        addf(b.r, "$1 >= $2 && $1 <= $3",
-             [rdCharLoc(a), rdCharLoc(x), rdCharLoc(y)])
-      else:
-        initLocExpr(p, e.sons[1].sons[i], x)
-        addf(b.r, "$1 == $2", [rdCharLoc(a), rdCharLoc(x)])
-      if i < length - 1: add(b.r, " || ")
-    add(b.r, ")")
-    putIntoDest(p, d, e.typ, b.r)
+    if length > 0:
+      b.r = rope("(")
+      for i in countup(0, length - 1):
+        let it = e.sons[1].sons[i]
+        if it.kind == nkRange:
+          initLocExpr(p, it.sons[0], x)
+          initLocExpr(p, it.sons[1], y)
+          addf(b.r, "$1 >= $2 && $1 <= $3",
+               [rdCharLoc(a), rdCharLoc(x), rdCharLoc(y)])
+        else:
+          initLocExpr(p, it, x)
+          addf(b.r, "$1 == $2", [rdCharLoc(a), rdCharLoc(x)])
+        if i < length - 1: add(b.r, " || ")
+      add(b.r, ")")
+    else:
+      # handle the case of an empty set
+      b.r = rope("0")
+    putIntoDest(p, d, e, b.r)
   else:
     assert(e.sons[1].typ != nil)
     assert(e.sons[2].typ != nil)
@@ -1494,11 +1725,11 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
         "  $3 = (($4[$1] & ~ $5[$1]) == 0);$n" &
         "  if (!$3) break;}$n", "for ($1 = 0; $1 < $2; $1++) { $n" &
         "  $3 = (($4[$1] & ~ $5[$1]) == 0);$n" & "  if (!$3) break;}$n" &
-        "if ($3) $3 = (memcmp($4, $5, $2) != 0);$n",
+        "if ($3) $3 = (#nimCmpMem($4, $5, $2) != 0);$n",
       "&", "|", "& ~", "^"]
   var a, b, i: TLoc
   var setType = skipTypes(e.sons[1].typ, abstractVar)
-  var size = int(getSize(setType))
+  var size = int(getSize(p.config, setType))
   case size
   of 1, 2, 4, 8:
     case op
@@ -1522,35 +1753,34 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
     of mSymDiffSet: binaryExpr(p, e, d, "($1 ^ $2)")
     of mInSet:
       genInOp(p, e, d)
-    else: internalError(e.info, "genSetOp()")
+    else: internalError(p.config, e.info, "genSetOp()")
   else:
     case op
     of mIncl: binaryStmtInExcl(p, e, d, "$1[(NU)($2)>>3] |=(1U<<($2&7U));$n")
     of mExcl: binaryStmtInExcl(p, e, d, "$1[(NU)($2)>>3] &= ~(1U<<($2&7U));$n")
     of mCard: unaryExprChar(p, e, d, "#cardSet($1, " & $size & ')')
     of mLtSet, mLeSet:
-      getTemp(p, getSysType(tyInt), i) # our counter
+      getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) # our counter
       initLocExpr(p, e.sons[1], a)
       initLocExpr(p, e.sons[2], b)
-      if d.k == locNone: getTemp(p, getSysType(tyBool), d)
-      lineF(p, cpsStmts, lookupOpr[op],
+      if d.k == locNone: getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyBool), d)
+      linefmt(p, cpsStmts, lookupOpr[op],
            [rdLoc(i), rope(size), rdLoc(d), rdLoc(a), rdLoc(b)])
     of mEqSet:
-      useStringh(p.module)
-      binaryExprChar(p, e, d, "(memcmp($1, $2, " & $(size) & ")==0)")
+      binaryExprChar(p, e, d, "(#nimCmpMem($1, $2, " & $(size) & ")==0)")
     of mMulSet, mPlusSet, mMinusSet, mSymDiffSet:
       # we inline the simple for loop for better code generation:
-      getTemp(p, getSysType(tyInt), i) # our counter
+      getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), i) # our counter
       initLocExpr(p, e.sons[1], a)
       initLocExpr(p, e.sons[2], b)
-      if d.k == locNone: getTemp(p, a.t, d)
+      if d.k == locNone: getTemp(p, setType, d)
       lineF(p, cpsStmts,
            "for ($1 = 0; $1 < $2; $1++) $n" &
            "  $3[$1] = $4[$1] $6 $5[$1];$n", [
           rdLoc(i), rope(size), rdLoc(d), rdLoc(a), rdLoc(b),
           rope(lookupOpr[op])])
     of mInSet: genInOp(p, e, d)
-    else: internalError(e.info, "genSetOp")
+    else: internalError(p.config, e.info, "genSetOp")
 
 proc genOrd(p: BProc, e: PNode, d: var TLoc) =
   unaryExprChar(p, e, d, "$1")
@@ -1564,14 +1794,20 @@ proc genSomeCast(p: BProc, e: PNode, d: var TLoc) =
   initLocExpr(p, e.sons[1], a)
   let etyp = skipTypes(e.typ, abstractRange)
   if etyp.kind in ValueTypes and lfIndirect notin a.flags:
-    putIntoDest(p, d, e.typ, "(*($1*) ($2))" %
-        [getTypeDesc(p.module, e.typ), addrLoc(a)], a.s)
+    putIntoDest(p, d, e, "(*($1*) ($2))" %
+        [getTypeDesc(p.module, e.typ), addrLoc(p.config, a)], a.storage)
   elif etyp.kind == tyProc and etyp.callConv == ccClosure:
-    putIntoDest(p, d, e.typ, "(($1) ($2))" %
-        [getClosureType(p.module, etyp, clHalfWithEnv), rdCharLoc(a)], a.s)
+    putIntoDest(p, d, e, "(($1) ($2))" %
+        [getClosureType(p.module, etyp, clHalfWithEnv), rdCharLoc(a)], a.storage)
   else:
-    putIntoDest(p, d, e.typ, "(($1) ($2))" %
-        [getTypeDesc(p.module, e.typ), rdCharLoc(a)], a.s)
+    let srcTyp = skipTypes(e.sons[1].typ, abstractRange)
+    # C++ does not like direct casts from pointer to shorter integral types
+    if srcTyp.kind in {tyPtr, tyPointer} and etyp.kind in IntegralTypes:
+      putIntoDest(p, d, e, "(($1) (ptrdiff_t) ($2))" %
+          [getTypeDesc(p.module, e.typ), rdCharLoc(a)], a.storage)
+    else:
+      putIntoDest(p, d, e, "(($1) ($2))" %
+          [getTypeDesc(p.module, e.typ), rdCharLoc(a)], a.storage)
 
 proc genCast(p: BProc, e: PNode, d: var TLoc) =
   const ValueTypes = {tyFloat..tyFloat128, tyTuple, tyObject, tyArray}
@@ -1587,11 +1823,11 @@ proc genCast(p: BProc, e: PNode, d: var TLoc) =
     linefmt(p, cpsLocals, "union { $1 source; $2 dest; } LOC$3;$n",
       getTypeDesc(p.module, e.sons[1].typ), getTypeDesc(p.module, e.typ), lbl)
     tmp.k = locExpr
-    tmp.t = srct
-    tmp.s = OnStack
+    tmp.lode = lodeTyp srct
+    tmp.storage = OnStack
     tmp.flags = {}
     expr(p, e.sons[1], tmp)
-    putIntoDest(p, d, e.typ, "LOC$#.dest" % [lbl], tmp.s)
+    putIntoDest(p, d, e, "LOC$#.dest" % [lbl], tmp.storage)
   else:
     # I prefer the shorter cast version for pointer types -> generate less
     # C code; plus it's the right thing to do for closures:
@@ -1604,18 +1840,18 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc, magic: string) =
   if optRangeCheck notin p.options or dest.skipTypes({tyRange}).kind in
                                              {tyUInt..tyUInt64}:
     initLocExpr(p, n.sons[0], a)
-    putIntoDest(p, d, n.typ, "(($1) ($2))" %
-        [getTypeDesc(p.module, dest), rdCharLoc(a)], a.s)
+    putIntoDest(p, d, n, "(($1) ($2))" %
+        [getTypeDesc(p.module, dest), rdCharLoc(a)], a.storage)
   else:
     initLocExpr(p, n.sons[0], a)
-    putIntoDest(p, d, dest, ropecg(p.module, "(($1)#$5($2, $3, $4))", [
+    putIntoDest(p, d, lodeTyp dest, ropecg(p.module, "(($1)#$5($2, $3, $4))", [
         getTypeDesc(p.module, dest), rdCharLoc(a),
         genLiteral(p, n.sons[1], dest), genLiteral(p, n.sons[2], dest),
-        rope(magic)]), a.s)
+        rope(magic)]), a.storage)
 
 proc genConv(p: BProc, e: PNode, d: var TLoc) =
-  let destType = e.typ.skipTypes({tyVar, tyGenericInst, tyAlias})
-  if compareTypes(destType, e.sons[1].typ, dcEqIgnoreDistinct):
+  let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink})
+  if sameBackendType(destType, e.sons[1].typ):
     expr(p, e.sons[1], d)
   else:
     genSomeCast(p, e, d)
@@ -1623,29 +1859,31 @@ proc genConv(p: BProc, e: PNode, d: var TLoc) =
 proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) =
   var a: TLoc
   initLocExpr(p, n.sons[0], a)
-  putIntoDest(p, d, skipTypes(n.typ, abstractVar), "$1->data" % [rdLoc(a)], a.s)
+  putIntoDest(p, d, n,
+              ropecg(p.module, "#nimToCStringConv($1)", [rdLoc(a)]),
+#                "($1 ? $1->data : (NCSTRING)\"\")" % [a.rdLoc],
+              a.storage)
 
 proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) =
   var a: TLoc
   initLocExpr(p, n.sons[0], a)
-  putIntoDest(p, d, skipTypes(n.typ, abstractVar),
-              ropecg(p.module, "#cstrToNimstr($1)", [rdLoc(a)]), a.s)
-  gcUsage(n)
+  putIntoDest(p, d, n,
+              ropecg(p.module, "#cstrToNimstr($1)", [rdLoc(a)]),
+              a.storage)
+  gcUsage(p.config, n)
 
 proc genStrEquals(p: BProc, e: PNode, d: var TLoc) =
   var x: TLoc
   var a = e.sons[1]
   var b = e.sons[2]
-  if (a.kind == nkNilLit) or (b.kind == nkNilLit):
-    binaryExpr(p, e, d, "($1 == $2)")
-  elif (a.kind in {nkStrLit..nkTripleStrLit}) and (a.strVal == ""):
+  if a.kind in {nkStrLit..nkTripleStrLit} and a.strVal == "":
     initLocExpr(p, e.sons[2], x)
-    putIntoDest(p, d, e.typ,
-      rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p)))
-  elif (b.kind in {nkStrLit..nkTripleStrLit}) and (b.strVal == ""):
+    putIntoDest(p, d, e,
+      ropecg(p.module, "($1 == 0)", lenExpr(p, x)))
+  elif b.kind in {nkStrLit..nkTripleStrLit} and b.strVal == "":
     initLocExpr(p, e.sons[1], x)
-    putIntoDest(p, d, e.typ,
-      rfmt(nil, "(($1) && ($1)->$2 == 0)", rdLoc(x), lenField(p)))
+    putIntoDest(p, d, e,
+      ropecg(p.module, "($1 == 0)", lenExpr(p, x)))
   else:
     binaryExpr(p, e, d, "#eqStrings($1, $2)")
 
@@ -1657,9 +1895,9 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
     assert(e.sons[2].typ != nil)
     initLocExpr(p, e.sons[1], a)
     initLocExpr(p, e.sons[2], b)
-    putIntoDest(p, d, e.typ, rfmt(nil, "(($4)($2) $1 ($4)($3))",
-                                  rope(opr[m]), rdLoc(a), rdLoc(b),
-                                  getSimpleTypeDesc(p.module, e[1].typ)))
+    putIntoDest(p, d, e, ropecg(p.module, "(($4)($2) $1 ($4)($3))",
+                              rope(opr[m]), rdLoc(a), rdLoc(b),
+                              getSimpleTypeDesc(p.module, e[1].typ)))
     if optNaNCheck in p.options:
       linefmt(p, cpsStmts, "#nanCheck($1);$n", rdLoc(d))
     if optInfCheck in p.options:
@@ -1667,6 +1905,23 @@ proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) =
   else:
     binaryArith(p, e, d, m)
 
+proc skipAddr(n: PNode): PNode =
+  result = if n.kind in {nkAddr, nkHiddenAddr}: n[0] else: n
+
+proc genWasMoved(p: BProc; n: PNode) =
+  var a: TLoc
+  initLocExpr(p, n[1].skipAddr, a)
+  resetLoc(p, a)
+  #linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n",
+  #  addrLoc(p.config, a), getTypeDesc(p.module, a.t))
+
+proc genMove(p: BProc; n: PNode; d: var TLoc) =
+  if d.k == locNone: getTemp(p, n.typ, d)
+  var a: TLoc
+  initLocExpr(p, n[1].skipAddr, a)
+  genAssignment(p, d, a, {})
+  resetLoc(p, a)
+
 proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   case op
   of mOr, mAnd: genAndOr(p, e, d, op)
@@ -1688,7 +1943,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
                                                "$# = #subInt64($#, $#);$n"]
     const fun: array[mInc..mDec, string] = ["$# = #addInt($#, $#);$n",
                                              "$# = #subInt($#, $#);$n"]
-    let underlying = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tyVar, tyRange})
+    let underlying = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent, tyRange})
     if optOverflowCheck notin p.options or underlying.kind in {tyUInt..tyUInt64}:
       binaryStmt(p, e, d, opr[op])
     else:
@@ -1698,16 +1953,30 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
       initLocExpr(p, e.sons[1], a)
       initLocExpr(p, e.sons[2], b)
 
-      let ranged = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tyVar})
+      let ranged = skipTypes(e.sons[1].typ, {tyGenericInst, tyAlias, tySink, tyVar, tyLent})
       let res = binaryArithOverflowRaw(p, ranged, a, b,
         if underlying.kind == tyInt64: fun64[op] else: fun[op])
-      putIntoDest(p, a, ranged, "($#)($#)" % [
+      putIntoDest(p, a, e.sons[1], "($#)($#)" % [
         getTypeDesc(p.module, ranged), res])
 
   of mConStrStr: genStrConcat(p, e, d)
-  of mAppendStrCh: binaryStmt(p, e, d, "$1 = #addChar($1, $2);$n")
+  of mAppendStrCh:
+    if p.config.selectedGC == gcDestructors:
+      binaryStmtAddr(p, e, d, "#nimAddCharV1($1, $2);$n")
+    else:
+      var dest, b, call: TLoc
+      initLoc(call, locCall, e, OnHeap)
+      initLocExpr(p, e.sons[1], dest)
+      initLocExpr(p, e.sons[2], b)
+      call.r = ropecg(p.module, "#addChar($1, $2)", [rdLoc(dest), rdLoc(b)])
+      genAssignment(p, dest, call, {})
   of mAppendStrStr: genStrAppend(p, e, d)
-  of mAppendSeqElem: genSeqElemAppend(p, e, d)
+  of mAppendSeqElem:
+    if p.config.selectedGc == gcDestructors:
+      e.sons[1] = makeAddr(e[1])
+      genCall(p, e, d)
+    else:
+      genSeqElemAppend(p, e, d)
   of mEqStr: genStrEquals(p, e, d)
   of mLeStr: binaryExpr(p, e, d, "(#cmpStrings($1, $2) <= 0)")
   of mLtStr: binaryExpr(p, e, d, "(#cmpStrings($1, $2) < 0)")
@@ -1727,25 +1996,38 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   of mNewSeqOfCap: genNewSeqOfCap(p, e, d)
   of mSizeOf:
     let t = e.sons[1].typ.skipTypes({tyTypeDesc})
-    putIntoDest(p, d, e.typ, "((NI)sizeof($1))" % [getTypeDesc(p.module, t)])
+    putIntoDest(p, d, e, "((NI)sizeof($1))" % [getTypeDesc(p.module, t)])
   of mChr: genSomeCast(p, e, d)
   of mOrd: genOrd(p, e, d)
   of mLengthArray, mHigh, mLengthStr, mLengthSeq, mLengthOpenArray:
     genArrayLen(p, e, d, op)
-  of mXLenStr, mXLenSeq:
+  of mXLenStr:
     if not p.module.compileToCpp:
       unaryExpr(p, e, d, "($1->Sup.len)")
     else:
       unaryExpr(p, e, d, "$1->len")
-  of mGCref: unaryStmt(p, e, d, "#nimGCref($1);$n")
-  of mGCunref: unaryStmt(p, e, d, "#nimGCunref($1);$n")
+  of mXLenSeq:
+    # see 'taddhigh.nim' for why we need to use a temporary here:
+    var a, tmp: TLoc
+    initLocExpr(p, e[1], a)
+    getIntTemp(p, tmp)
+    var frmt: FormatStr
+    if not p.module.compileToCpp:
+      frmt = "$1 = $2->Sup.len;$n"
+    else:
+      frmt = "$1 = $2->len;$n"
+    lineCg(p, cpsStmts, frmt, tmp.r, rdLoc(a))
+    putIntoDest(p, d, e, tmp.r)
+  of mGCref: unaryStmt(p, e, d, "if ($1) { #nimGCref($1); }$n")
+  of mGCunref: unaryStmt(p, e, d, "if ($1) { #nimGCunref($1); }$n")
   of mSetLengthStr: genSetLengthStr(p, e, d)
   of mSetLengthSeq: genSetLengthSeq(p, e, d)
   of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet,
      mInSet:
     genSetOp(p, e, d, op)
-  of mNewString, mNewStringOfCap, mCopyStr, mCopyStrLast, mExit,
-      mParseBiggestFloat:
+  of mCopyStr, mCopyStrLast:
+    genCall(p, e, d)
+  of mNewString, mNewStringOfCap, mExit, mParseBiggestFloat:
     var opr = e.sons[0].sym
     if lfNoDecl notin opr.loc.flags:
       discard cgsym(p.module, $opr.loc.r)
@@ -1754,13 +2036,16 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   of mEcho: genEcho(p, e[1].skipConv)
   of mArrToSeq: genArrToSeq(p, e, d)
   of mNLen..mNError, mSlurp..mQuoteAst:
-    localError(e.info, errXMustBeCompileTime, e.sons[0].sym.name.s)
+    localError(p.config, e.info, strutils.`%`(errXMustBeCompileTime, e.sons[0].sym.name.s))
   of mSpawn:
-    let n = lowerings.wrapProcForSpawn(p.module.module, e, e.typ, nil, nil)
+    let n = lowerings.wrapProcForSpawn(p.module.g.graph, p.module.module, e, e.typ, nil, nil)
     expr(p, n, d)
   of mParallel:
-    let n = semparallel.liftParallel(p.module.module, e)
-    expr(p, n, d)
+    when defined(leanCompiler):
+      quit "compiler built without support for the 'parallel' statement"
+    else:
+      let n = semparallel.liftParallel(p.module.g.graph, p.module.module, e)
+      expr(p, n, d)
   of mDeepCopy:
     var a, b: TLoc
     let x = if e[1].kind in {nkAddr, nkHiddenAddr}: e[1][0] else: e[1]
@@ -1768,53 +2053,62 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
     initLocExpr(p, e.sons[2], b)
     genDeepCopy(p, a, b)
   of mDotDot, mEqCString: genCall(p, e, d)
-  else: internalError(e.info, "genMagicExpr: " & $op)
+  of mWasMoved: genWasMoved(p, e)
+  of mMove: genMove(p, e, d)
+  of mDestroy: discard "ignore calls to the default destructor"
+  of mSlice:
+    localError(p.config, e.info, "invalid context for 'toOpenArray'; " &
+      " 'toOpenArray' is only valid within a call expression")
+  else:
+    when defined(debugMagics):
+      echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", p.prc.ast[genericParamsPos].kind
+    internalError(p.config, e.info, "genMagicExpr: " & $op)
 
 proc genSetConstr(p: BProc, e: PNode, d: var TLoc) =
   # example: { a..b, c, d, e, f..g }
   # we have to emit an expression of the form:
-  # memset(tmp, 0, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c);
+  # nimZeroMem(tmp, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c);
   # incl(tmp, d); incl(tmp, e); inclRange(tmp, f, g);
   var
     a, b, idx: TLoc
   if nfAllConst in e.flags:
-    putIntoDest(p, d, e.typ, genSetNode(p, e))
+    putIntoDest(p, d, e, genSetNode(p, e))
   else:
     if d.k == locNone: getTemp(p, e.typ, d)
-    if getSize(e.typ) > 8:
+    if getSize(p.config, e.typ) > 8:
       # big set:
-      useStringh(p.module)
-      lineF(p, cpsStmts, "memset($1, 0, sizeof($1));$n", [rdLoc(d)])
-      for i in countup(0, sonsLen(e) - 1):
-        if e.sons[i].kind == nkRange:
-          getTemp(p, getSysType(tyInt), idx) # our counter
-          initLocExpr(p, e.sons[i].sons[0], a)
-          initLocExpr(p, e.sons[i].sons[1], b)
+      linefmt(p, cpsStmts, "#nimZeroMem($1, sizeof($2));$n",
+          [rdLoc(d), getTypeDesc(p.module, e.typ)])
+      for it in e.sons:
+        if it.kind == nkRange:
+          getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), idx) # our counter
+          initLocExpr(p, it.sons[0], a)
+          initLocExpr(p, it.sons[1], b)
           lineF(p, cpsStmts, "for ($1 = $3; $1 <= $4; $1++) $n" &
               "$2[(NU)($1)>>3] |=(1U<<((NU)($1)&7U));$n", [rdLoc(idx), rdLoc(d),
-              rdSetElemLoc(a, e.typ), rdSetElemLoc(b, e.typ)])
+              rdSetElemLoc(p.config, a, e.typ), rdSetElemLoc(p.config, b, e.typ)])
         else:
-          initLocExpr(p, e.sons[i], a)
+          initLocExpr(p, it, a)
           lineF(p, cpsStmts, "$1[(NU)($2)>>3] |=(1U<<((NU)($2)&7U));$n",
-               [rdLoc(d), rdSetElemLoc(a, e.typ)])
+               [rdLoc(d), rdSetElemLoc(p.config, a, e.typ)])
     else:
       # small set
-      var ts = "NU" & $(getSize(e.typ) * 8)
+      var ts = "NU" & $(getSize(p.config, e.typ) * 8)
       lineF(p, cpsStmts, "$1 = 0;$n", [rdLoc(d)])
-      for i in countup(0, sonsLen(e) - 1):
-        if e.sons[i].kind == nkRange:
-          getTemp(p, getSysType(tyInt), idx) # our counter
-          initLocExpr(p, e.sons[i].sons[0], a)
-          initLocExpr(p, e.sons[i].sons[1], b)
+      for it in e.sons:
+        if it.kind == nkRange:
+          getTemp(p, getSysType(p.module.g.graph, unknownLineInfo(), tyInt), idx) # our counter
+          initLocExpr(p, it.sons[0], a)
+          initLocExpr(p, it.sons[1], b)
           lineF(p, cpsStmts, "for ($1 = $3; $1 <= $4; $1++) $n" &
               "$2 |=((" & ts & ")(1)<<(($1)%(sizeof(" & ts & ")*8)));$n", [
-              rdLoc(idx), rdLoc(d), rdSetElemLoc(a, e.typ),
-              rdSetElemLoc(b, e.typ)])
+              rdLoc(idx), rdLoc(d), rdSetElemLoc(p.config, a, e.typ),
+              rdSetElemLoc(p.config, b, e.typ)])
         else:
-          initLocExpr(p, e.sons[i], a)
+          initLocExpr(p, it, a)
           lineF(p, cpsStmts,
                "$1 |=((" & ts & ")(1)<<(($2)%(sizeof(" & ts & ")*8)));$n",
-               [rdLoc(d), rdSetElemLoc(a, e.typ)])
+               [rdLoc(d), rdSetElemLoc(p.config, a, e.typ)])
 
 proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) =
   var rec: TLoc
@@ -1825,7 +2119,7 @@ proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) =
     for i in countup(0, sonsLen(n) - 1):
       var it = n.sons[i]
       if it.kind == nkExprColonExpr: it = it.sons[1]
-      initLoc(rec, locExpr, it.typ, d.s)
+      initLoc(rec, locExpr, it, d.storage)
       rec.r = "$1.Field$2" % [rdLoc(d), rope(i)]
       expr(p, it, rec)
 
@@ -1834,20 +2128,20 @@ proc isConstClosure(n: PNode): bool {.inline.} =
       n.sons[1].kind == nkNilLit
 
 proc genClosure(p: BProc, n: PNode, d: var TLoc) =
-  assert n.kind == nkClosure
+  assert n.kind in {nkPar, nkTupleConstr, nkClosure}
 
   if isConstClosure(n):
     inc(p.module.labels)
     var tmp = "CNSTCLOSURE" & rope(p.module.labels)
     addf(p.module.s[cfsData], "static NIM_CONST $1 $2 = $3;$n",
         [getTypeDesc(p.module, n.typ), tmp, genConstExpr(p, n)])
-    putIntoDest(p, d, n.typ, tmp, OnStatic)
+    putIntoDest(p, d, n, tmp, OnStatic)
   else:
     var tmp, a, b: TLoc
     initLocExpr(p, n.sons[0], a)
     initLocExpr(p, n.sons[1], b)
     if n.sons[0].skipConv.kind == nkClosure:
-      internalError(n.info, "closure to closure created")
+      internalError(p.config, n.info, "closure to closure created")
     # tasyncawait.nim breaks with this optimization:
     when false:
       if d.k != locNone:
@@ -1864,7 +2158,7 @@ proc genArrayConstr(p: BProc, n: PNode, d: var TLoc) =
   if not handleConstExpr(p, n, d):
     if d.k == locNone: getTemp(p, n.typ, d)
     for i in countup(0, sonsLen(n) - 1):
-      initLoc(arr, locExpr, elemType(skipTypes(n.typ, abstractInst)), d.s)
+      initLoc(arr, locExpr, lodeTyp elemType(skipTypes(n.typ, abstractInst)), d.storage)
       arr.r = "$1[$2]" % [rdLoc(d), intLiteral(i)]
       expr(p, n.sons[i], arr)
 
@@ -1873,10 +2167,35 @@ proc genComplexConst(p: BProc, sym: PSym, d: var TLoc) =
   assert((sym.loc.r != nil) and (sym.loc.t != nil))
   putLocIntoDest(p, d, sym.loc)
 
+template genStmtListExprImpl(exprOrStmt) {.dirty.} =
+  #let hasNimFrame = magicsys.getCompilerProc("nimFrame") != nil
+  let hasNimFrame = p.prc != nil and
+      sfSystemModule notin p.module.module.flags and
+      optStackTrace in p.prc.options
+  var frameName: Rope = nil
+  for i in 0 .. n.len - 2:
+    let it = n[i]
+    if it.kind == nkComesFrom:
+      if hasNimFrame and frameName == nil:
+        inc p.labels
+        frameName = "FR" & rope(p.labels) & "_"
+        let theMacro = it[0].sym
+        add p.s(cpsStmts), initFrameNoDebug(p, frameName,
+           makeCString theMacro.name.s,
+           quotedFilename(p.config, theMacro.info), it.info.line.int)
+    else:
+      genStmts(p, it)
+  if n.len > 0: exprOrStmt
+  if frameName != nil:
+    add p.s(cpsStmts), deinitFrameNoDebug(p, frameName)
+
 proc genStmtListExpr(p: BProc, n: PNode, d: var TLoc) =
-  var length = sonsLen(n)
-  for i in countup(0, length - 2): genStmts(p, n.sons[i])
-  if length > 0: expr(p, n.sons[length - 1], d)
+  genStmtListExprImpl:
+    expr(p, n[n.len - 1], d)
+
+proc genStmtList(p: BProc, n: PNode) =
+  genStmtListExprImpl:
+    genStmts(p, n[n.len - 1])
 
 proc upConv(p: BProc, n: PNode, d: var TLoc) =
   var a: TLoc
@@ -1886,30 +2205,32 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
     var r = rdLoc(a)
     var nilCheck: Rope = nil
     var t = skipTypes(a.t, abstractInst)
-    while t.kind in {tyVar, tyPtr, tyRef}:
-      if t.kind != tyVar: nilCheck = r
-      if t.kind != tyVar or not p.module.compileToCpp:
+    while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
+      if t.kind notin {tyVar, tyLent}: nilCheck = r
+      if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
         r = "(*$1)" % [r]
       t = skipTypes(t.lastSon, abstractInst)
+    discard getTypeDesc(p.module, t)
     if not p.module.compileToCpp:
       while t.kind == tyObject and t.sons[0] != nil:
         add(r, ".Sup")
         t = skipTypes(t.sons[0], skipPtrs)
     if nilCheck != nil:
       linefmt(p, cpsStmts, "if ($1) #chckObj($2.m_type, $3);$n",
-              nilCheck, r, genTypeInfo(p.module, dest))
+              nilCheck, r, genTypeInfo(p.module, dest, n.info))
     else:
       linefmt(p, cpsStmts, "#chckObj($1.m_type, $2);$n",
-              r, genTypeInfo(p.module, dest))
+              r, genTypeInfo(p.module, dest, n.info))
   if n.sons[0].typ.kind != tyObject:
-    putIntoDest(p, d, n.typ,
-                "(($1) ($2))" % [getTypeDesc(p.module, n.typ), rdLoc(a)], a.s)
+    putIntoDest(p, d, n,
+                "(($1) ($2))" % [getTypeDesc(p.module, n.typ), rdLoc(a)], a.storage)
   else:
-    putIntoDest(p, d, n.typ, "(*($1*) ($2))" %
-                             [getTypeDesc(p.module, dest), addrLoc(a)], a.s)
+    putIntoDest(p, d, n, "(*($1*) ($2))" %
+                        [getTypeDesc(p.module, dest), addrLoc(p.config, a)], a.storage)
 
 proc downConv(p: BProc, n: PNode, d: var TLoc) =
   if p.module.compileToCpp:
+    discard getTypeDesc(p.module, skipTypes(n[0].typ, abstractPtrs))
     expr(p, n.sons[0], d)     # downcast does C++ for us
   else:
     var dest = skipTypes(n.typ, abstractPtrs)
@@ -1918,10 +2239,11 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) =
     while arg.kind == nkObjDownConv: arg = arg.sons[0]
 
     var src = skipTypes(arg.typ, abstractPtrs)
+    discard getTypeDesc(p.module, src)
     var a: TLoc
     initLocExpr(p, arg, a)
     var r = rdLoc(a)
-    let isRef = skipTypes(arg.typ, abstractInst).kind in {tyRef, tyPtr, tyVar}
+    let isRef = skipTypes(arg.typ, abstractInst).kind in {tyRef, tyPtr, tyVar, tyLent}
     if isRef:
       add(r, "->Sup")
     else:
@@ -1933,14 +2255,14 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) =
       # (see bug #837). However sometimes using a temporary is not correct:
       # init(TFigure(my)) # where it is passed to a 'var TFigure'. We test
       # this by ensuring the destination is also a pointer:
-      if d.k == locNone and skipTypes(n.typ, abstractInst).kind in {tyRef, tyPtr, tyVar}:
+      if d.k == locNone and skipTypes(n.typ, abstractInst).kind in {tyRef, tyPtr, tyVar, tyLent}:
         getTemp(p, n.typ, d)
         linefmt(p, cpsStmts, "$1 = &$2;$n", rdLoc(d), r)
       else:
         r = "&" & r
-        putIntoDest(p, d, n.typ, r, a.s)
+        putIntoDest(p, d, n, r, a.storage)
     else:
-      putIntoDest(p, d, n.typ, r, a.s)
+      putIntoDest(p, d, n, r, a.storage)
 
 proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) =
   let t = n.typ
@@ -1955,13 +2277,13 @@ proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) =
          [getTypeDesc(p.module, t), tmp, genConstExpr(p, n)])
 
   if d.k == locNone:
-    fillLoc(d, locData, t, tmp, OnStatic)
+    fillLoc(d, locData, n, tmp, OnStatic)
   else:
-    putDataIntoDest(p, d, t, tmp)
+    putDataIntoDest(p, d, n, tmp)
     # This fixes bug #4551, but we really need better dataflow
     # analysis to make this 100% safe.
     if t.kind notin {tySequence, tyString}:
-      d.s = OnStatic
+      d.storage = OnStatic
 
 proc expr(p: BProc, n: PNode, d: var TLoc) =
   p.currLineInfo = n.info
@@ -1972,39 +2294,42 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
     of skMethod:
       if {sfDispatcher, sfForward} * sym.flags != {}:
         # we cannot produce code for the dispatcher yet:
-        fillProcLoc(p.module, sym)
+        fillProcLoc(p.module, n)
         genProcPrototype(p.module, sym)
       else:
         genProc(p.module, sym)
       putLocIntoDest(p, d, sym.loc)
-    of skProc, skConverter, skIterator:
+    of skProc, skConverter, skIterator, skFunc:
       #if sym.kind == skIterator:
       #  echo renderTree(sym.getBody, {renderIds})
       if sfCompileTime in sym.flags:
-        localError(n.info, "request to generate code for .compileTime proc: " &
+        localError(p.config, n.info, "request to generate code for .compileTime proc: " &
            sym.name.s)
       genProc(p.module, sym)
-      if sym.loc.r == nil or sym.loc.t == nil:
-        internalError(n.info, "expr: proc not init " & sym.name.s)
+      if sym.loc.r == nil or sym.loc.lode == nil:
+        internalError(p.config, n.info, "expr: proc not init " & sym.name.s)
       putLocIntoDest(p, d, sym.loc)
     of skConst:
       if isSimpleConst(sym.typ):
-        putIntoDest(p, d, n.typ, genLiteral(p, sym.ast, sym.typ), OnStatic)
+        putIntoDest(p, d, n, genLiteral(p, sym.ast, sym.typ), OnStatic)
       else:
         genComplexConst(p, sym, d)
     of skEnumField:
-      putIntoDest(p, d, n.typ, rope(sym.position))
+      # we never reach this case - as of the time of this comment,
+      # skEnumField is folded to an int in semfold.nim, but this code
+      # remains for robustness
+      putIntoDest(p, d, n, rope(sym.position))
     of skVar, skForVar, skResult, skLet:
       if {sfGlobal, sfThread} * sym.flags != {}:
-        genVarPrototype(p.module, sym)
+        genVarPrototype(p.module, n)
       if sym.loc.r == nil or sym.loc.t == nil:
         #echo "FAILED FOR PRCO ", p.prc.name.s
         #echo renderTree(p.prc.ast, {renderIds})
-        internalError n.info, "expr: var not init " & sym.name.s & "_" & $sym.id
+        internalError p.config, n.info, "expr: var not init " & sym.name.s & "_" & $sym.id
       if sfThread in sym.flags:
         accessThreadLocalVar(p, sym)
-        if emulatedThreadVars():
-          putIntoDest(p, d, sym.loc.t, "NimTV_->" & sym.loc.r)
+        if emulatedThreadVars(p.config):
+          putIntoDest(p, d, sym.loc.lode, "NimTV_->" & sym.loc.r)
         else:
           putLocIntoDest(p, d, sym.loc)
       else:
@@ -2013,24 +2338,24 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
       if sym.loc.r == nil or sym.loc.t == nil:
         #echo "FAILED FOR PRCO ", p.prc.name.s
         #echo renderTree(p.prc.ast, {renderIds})
-        internalError(n.info, "expr: temp not init " & sym.name.s & "_" & $sym.id)
+        internalError(p.config, n.info, "expr: temp not init " & sym.name.s & "_" & $sym.id)
       putLocIntoDest(p, d, sym.loc)
     of skParam:
       if sym.loc.r == nil or sym.loc.t == nil:
         # echo "FAILED FOR PRCO ", p.prc.name.s
         # debug p.prc.typ.n
         # echo renderTree(p.prc.ast, {renderIds})
-        internalError(n.info, "expr: param not init " & sym.name.s & "_" & $sym.id)
+        internalError(p.config, n.info, "expr: param not init " & sym.name.s & "_" & $sym.id)
       putLocIntoDest(p, d, sym.loc)
-    else: internalError(n.info, "expr(" & $sym.kind & "); unknown symbol")
+    else: internalError(p.config, n.info, "expr(" & $sym.kind & "); unknown symbol")
   of nkNilLit:
     if not isEmptyType(n.typ):
-      putIntoDest(p, d, n.typ, genLiteral(p, n))
+      putIntoDest(p, d, n, genLiteral(p, n))
   of nkStrLit..nkTripleStrLit:
-    putDataIntoDest(p, d, n.typ, genLiteral(p, n))
+    putDataIntoDest(p, d, n, genLiteral(p, n))
   of nkIntLit..nkUInt64Lit,
      nkFloatLit..nkFloat128Lit, nkCharLit:
-    putIntoDest(p, d, n.typ, genLiteral(p, n))
+    putIntoDest(p, d, n, genLiteral(p, n))
   of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkPostfix, nkCommand,
      nkCallStrLit:
     genLineDir(p, n)
@@ -2050,7 +2375,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
         genCall(p, n, d)
   of nkCurly:
     if isDeepConstExpr(n) and n.len != 0:
-      putIntoDest(p, d, n.typ, genSetNode(p, n))
+      putIntoDest(p, d, n, genSetNode(p, n))
     else:
       genSetConstr(p, n, d)
   of nkBracket:
@@ -2060,8 +2385,10 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
       genSeqConstr(p, n, d)
     else:
       genArrayConstr(p, n, d)
-  of nkPar:
-    if isDeepConstExpr(n) and n.len != 0:
+  of nkPar, nkTupleConstr:
+    if n.typ != nil and n.typ.kind == tyProc and n.len == 2:
+      genClosure(p, n, d)
+    elif isDeepConstExpr(n) and n.len != 0:
       exprComplexConst(p, n, d)
     else:
       genTupleConstr(p, n, d)
@@ -2075,8 +2402,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
   of nkCheckedFieldExpr: genCheckedRecordField(p, n, d)
   of nkBlockExpr, nkBlockStmt: genBlock(p, n, d)
   of nkStmtListExpr: genStmtListExpr(p, n, d)
-  of nkStmtList:
-    for i in countup(0, sonsLen(n) - 1): genStmts(p, n.sons[i])
+  of nkStmtList: genStmtList(p, n)
   of nkIfExpr, nkIfStmt: genIf(p, n, d)
   of nkWhen:
     # This should be a "when nimvm" node.
@@ -2091,16 +2417,16 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
   of nkLambdaKinds:
     var sym = n.sons[namePos].sym
     genProc(p.module, sym)
-    if sym.loc.r == nil or sym.loc.t == nil:
-      internalError(n.info, "expr: proc not init " & sym.name.s)
+    if sym.loc.r == nil or sym.loc.lode == nil:
+      internalError(p.config, n.info, "expr: proc not init " & sym.name.s)
     putLocIntoDest(p, d, sym.loc)
   of nkClosure: genClosure(p, n, d)
 
   of nkEmpty: discard
   of nkWhileStmt: genWhileStmt(p, n)
   of nkVarSection, nkLetSection: genVarStmt(p, n)
-  of nkConstSection: genConstStmt(p, n)
-  of nkForStmt: internalError(n.info, "for statement not eliminated")
+  of nkConstSection: discard  # consts generated lazily on use
+  of nkForStmt: internalError(p.config, n.info, "for statement not eliminated")
   of nkCaseStmt: genCase(p, n, d)
   of nkReturnStmt: genReturnStmt(p, n)
   of nkBreakStmt: genBreakStmt(p, n)
@@ -2113,13 +2439,15 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
       # See tests/run/tcnstseq3 for an example that would fail otherwise.
       genAsgn(p, n, fastAsgn=p.prc != nil)
   of nkDiscardStmt:
-    if n.sons[0].kind != nkEmpty:
+    let ex = n[0]
+    if ex.kind != nkEmpty:
       genLineDir(p, n)
       var a: TLoc
-      initLocExpr(p, n.sons[0], a)
+      initLocExprSingleUse(p, ex, a)
+      line(p, cpsStmts, "(void)(" & a.r & ");\L")
   of nkAsmStmt: genAsmStmt(p, n)
   of nkTryStmt:
-    if p.module.compileToCpp and optNoCppExceptions notin gGlobalOptions:
+    if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions:
       genTryCpp(p, n, d)
     else:
       genTry(p, n, d)
@@ -2130,20 +2458,18 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
     genTypeSection(p.module, n)
   of nkCommentStmt, nkIteratorDef, nkIncludeStmt,
      nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
-     nkFromStmt, nkTemplateDef, nkMacroDef:
+     nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt:
     discard
   of nkPragma: genPragma(p, n)
   of nkPragmaBlock: expr(p, n.lastSon, d)
-  of nkProcDef, nkMethodDef, nkConverterDef:
+  of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef:
     if n.sons[genericParamsPos].kind == nkEmpty:
       var prc = n.sons[namePos].sym
       # due to a bug/limitation in the lambda lifting, unused inner procs
       # are not transformed correctly. We work around this issue (#411) here
       # by ensuring it's no inner proc (owner is a module):
       if prc.skipGenericOwner.kind == skModule and sfCompileTime notin prc.flags:
-        if (optDeadCodeElim notin gGlobalOptions and
-            sfDeadCodeElim notin getModule(prc).flags) or
-            ({sfExportc, sfCompilerProc} * prc.flags == {sfExportc}) or
+        if ({sfExportc, sfCompilerProc} * prc.flags == {sfExportc}) or
             (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
             (prc.kind == skMethod):
           # we have not only the header:
@@ -2152,8 +2478,8 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
   of nkParForStmt: genParForStmt(p, n)
   of nkState: genState(p, n)
   of nkGotoState: genGotoState(p, n)
-  of nkBreakState: genBreakState(p, n)
-  else: internalError(n.info, "expr(" & $n.kind & "); unknown node kind")
+  of nkBreakState: genBreakState(p, n, d)
+  else: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind")
 
 proc genNamedConstExpr(p: BProc, n: PNode): Rope =
   if n.kind == nkExprColonExpr: result = genConstExpr(p, n.sons[1])
@@ -2165,7 +2491,7 @@ proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo): Rope =
   of tyBool: result = rope"NIM_FALSE"
   of tyEnum, tyChar, tyInt..tyInt64, tyUInt..tyUInt64: result = rope"0"
   of tyFloat..tyFloat128: result = rope"0.0"
-  of tyCString, tyString, tyVar, tyPointer, tyPtr, tySequence, tyExpr,
+  of tyCString, tyString, tyVar, tyLent, tyPointer, tyPtr, tySequence, tyExpr,
      tyStmt, tyTypeDesc, tyStatic, tyRef, tyNil:
     result = rope"NIM_NIL"
   of tyProc:
@@ -2175,30 +2501,38 @@ proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo): Rope =
       result = rope"{NIM_NIL, NIM_NIL}"
   of tyObject:
     if not isObjLackingTypeField(t) and not p.module.compileToCpp:
-      result = "{{$1}}" % [genTypeInfo(p.module, t)]
+      result = "{{$1}}" % [genTypeInfo(p.module, t, info)]
     else:
       result = rope"{}"
-  of tyArray, tyTuple: result = rope"{}"
+  of tyTuple:
+    result = rope"{"
+    for i in 0 ..< typ.len:
+      if i > 0: result.add ", "
+      result.add getDefaultValue(p, typ.sons[i], info)
+    result.add "}"
+  of tyArray: result = rope"{}"
   of tySet:
-    if mapType(t) == ctArray: result = rope"{}"
+    if mapType(p.config, t) == ctArray: result = rope"{}"
     else: result = rope"0"
   else:
-    globalError(info, "cannot create null element for: " & $t.kind)
+    globalError(p.config, info, "cannot create null element for: " & $t.kind)
 
-proc getNullValueAux(p: BProc; obj, cons: PNode, result: var Rope) =
+proc getNullValueAux(p: BProc; t: PType; obj, cons: PNode, result: var Rope; count: var int) =
   case obj.kind
   of nkRecList:
-    for i in countup(0, sonsLen(obj) - 1): getNullValueAux(p, obj.sons[i], cons, result)
+    for it in obj.sons:
+      getNullValueAux(p, t, it, cons, result, count)
   of nkRecCase:
-    getNullValueAux(p, obj.sons[0], cons, result)
+    getNullValueAux(p, t, obj.sons[0], cons, result, count)
     for i in countup(1, sonsLen(obj) - 1):
-      getNullValueAux(p, lastSon(obj.sons[i]), cons, result)
+      getNullValueAux(p, t, lastSon(obj.sons[i]), cons, result, count)
   of nkSym:
-    if not result.isNil: result.add ", "
+    if count > 0: result.add ", "
+    inc count
     let field = obj.sym
     for i in 1..<cons.len:
       if cons[i].kind == nkExprColonExpr:
-        if cons[i][0].sym == field:
+        if cons[i][0].sym.name.id == field.name.id:
           result.add genConstExpr(p, cons[i][1])
           return
       elif i == field.position:
@@ -2207,21 +2541,38 @@ proc getNullValueAux(p: BProc; obj, cons: PNode, result: var Rope) =
     # not found, produce default value:
     result.add getDefaultValue(p, field.typ, cons.info)
   else:
-    localError(cons.info, "cannot create null element for: " & $obj)
+    localError(p.config, cons.info, "cannot create null element for: " & $obj)
+
+proc getNullValueAuxT(p: BProc; orig, t: PType; obj, cons: PNode, result: var Rope; count: var int) =
+  var base = t.sons[0]
+  let oldRes = result
+  if not p.module.compileToCpp: result.add "{"
+  let oldcount = count
+  if base != nil:
+    base = skipTypes(base, skipPtrs)
+    getNullValueAuxT(p, orig, base, base.n, cons, result, count)
+  elif not isObjLackingTypeField(t) and not p.module.compileToCpp:
+    addf(result, "$1", [genTypeInfo(p.module, orig, obj.info)])
+    inc count
+  getNullValueAux(p, t, obj, cons, result, count)
+  # do not emit '{}' as that is not valid C:
+  if oldcount == count: result = oldres
+  elif not p.module.compileToCpp: result.add "}"
 
 proc genConstObjConstr(p: BProc; n: PNode): Rope =
-  var length = sonsLen(n)
   result = nil
   let t = n.typ.skipTypes(abstractInst)
-  if not isObjLackingTypeField(t) and not p.module.compileToCpp:
-    addf(result, "{$1}", [genTypeInfo(p.module, t)])
-  getNullValueAux(p, t.n, n, result)
-  result = "{$1}$n" % [result]
+  var count = 0
+  #if not isObjLackingTypeField(t) and not p.module.compileToCpp:
+  #  addf(result, "{$1}", [genTypeInfo(p.module, t)])
+  #  inc count
+  getNullValueAuxT(p, t, t, t.n, n, result, count)
+  if p.module.compileToCpp:
+    result = "{$1}$n" % [result]
 
 proc genConstSimpleList(p: BProc, n: PNode): Rope =
   var length = sonsLen(n)
   result = rope("{")
-  let t = n.typ.skipTypes(abstractInst)
   for i in countup(0, length - 2):
     addf(result, "$1,$n", [genNamedConstExpr(p, n.sons[i])])
   if length > 0:
@@ -2229,7 +2580,7 @@ proc genConstSimpleList(p: BProc, n: PNode): Rope =
   addf(result, "}$n", [])
 
 proc genConstSeq(p: BProc, n: PNode, t: PType): Rope =
-  var data = "{{$1, $1}" % [n.len.rope]
+  var data = "{{$1, $1 | NIM_STRLIT_FLAG}" % [n.len.rope]
   if n.len > 0:
     # array part needs extra curlies:
     data.add(", {")
@@ -2257,16 +2608,38 @@ proc genConstExpr(p: BProc, n: PNode): Rope =
     result = genConstExpr(p, n.sons[1])
   of nkCurly:
     var cs: TBitSet
-    toBitSet(n, cs)
-    result = genRawSetData(cs, int(getSize(n.typ)))
-  of nkBracket, nkPar, nkClosure:
+    toBitSet(p.config, n, cs)
+    result = genRawSetData(cs, int(getSize(p.config, n.typ)))
+  of nkBracket, nkPar, nkTupleConstr, nkClosure:
     var t = skipTypes(n.typ, abstractInst)
     if t.kind == tySequence:
       result = genConstSeq(p, n, n.typ)
+    elif t.kind == tyProc and t.callConv == ccClosure and n.len > 1 and
+         n.sons[1].kind == nkNilLit:
+      # Conversion: nimcall -> closure.
+      # this hack fixes issue that nkNilLit is expanded to {NIM_NIL,NIM_NIL}
+      # this behaviour is needed since closure_var = nil must be
+      # expanded to {NIM_NIL,NIM_NIL}
+      # in VM closures are initialized with nkPar(nkNilLit, nkNilLit)
+      # leading to duplicate code like this:
+      # "{NIM_NIL,NIM_NIL}, {NIM_NIL,NIM_NIL}"
+      if n[0].kind == nkNilLit:
+        result = ~"{NIM_NIL,NIM_NIL}"
+      else:
+        var d: TLoc
+        initLocExpr(p, n[0], d)
+        result = "{(($1) $2),NIM_NIL}" % [getClosureType(p.module, t, clHalfWithEnv), rdLoc(d)]
     else:
       result = genConstSimpleList(p, n)
   of nkObjConstr:
     result = genConstObjConstr(p, n)
+  of nkStrLit..nkTripleStrLit:
+    if p.config.selectedGc == gcDestructors:
+      result = genStringLiteralV2Const(p.module, n)
+    else:
+      var d: TLoc
+      initLocExpr(p, n, d)
+      result = rdLoc(d)
   else:
     var d: TLoc
     initLocExpr(p, n, d)
diff --git a/compiler/ccgliterals.nim b/compiler/ccgliterals.nim
new file mode 100644
index 000000000..904d01e81
--- /dev/null
+++ b/compiler/ccgliterals.nim
@@ -0,0 +1,107 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This include file contains the logic to produce constant string
+## and seq literals. The code here is responsible that
+## ``const x = ["a", "b"]`` works without hidden runtime creation code.
+## The price is that seqs and strings are not purely a library
+## implementation.
+
+template detectVersion(field, corename) =
+  if m.g.field == 0:
+    let core = getCompilerProc(m.g.graph, corename)
+    if core == nil or core.kind != skConst:
+      m.g.field = 1
+    else:
+      m.g.field = int ast.getInt(core.ast)
+  result = m.g.field
+
+proc detectStrVersion(m: BModule): int =
+  detectVersion(strVersion, "nimStrVersion")
+
+proc detectSeqVersion(m: BModule): int =
+  detectVersion(seqVersion, "nimSeqVersion")
+
+# ----- Version 1: GC'ed strings and seqs --------------------------------
+
+proc genStringLiteralDataOnlyV1(m: BModule, s: string): Rope =
+  discard cgsym(m, "TGenericSeq")
+  result = getTempName(m)
+  addf(m.s[cfsData], "STRING_LITERAL($1, $2, $3);$n",
+       [result, makeCString(s), rope(len(s))])
+
+proc genStringLiteralV1(m: BModule; n: PNode): Rope =
+  if s.isNil:
+    result = ropecg(m, "((#NimStringDesc*) NIM_NIL)", [])
+  else:
+    let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
+    if id == m.labels:
+      # string literal not found in the cache:
+      result = ropecg(m, "((#NimStringDesc*) &$1)",
+                      [genStringLiteralDataOnlyV1(m, n.strVal)])
+    else:
+      result = ropecg(m, "((#NimStringDesc*) &$1$2)",
+                      [m.tmpBase, rope(id)])
+
+# ------ Version 2: destructor based strings and seqs -----------------------
+
+proc genStringLiteralDataOnlyV2(m: BModule, s: string): Rope =
+  result = getTempName(m)
+  addf(m.s[cfsData], "static const struct {$n" &
+       "  NI cap; void* allocator; NIM_CHAR data[$2+1];$n" &
+       "} $1 = { $2, NIM_NIL, $3 };$n",
+       [result, rope(len(s)), makeCString(s)])
+
+proc genStringLiteralV2(m: BModule; n: PNode): Rope =
+  let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
+  if id == m.labels:
+    discard cgsym(m, "NimStrPayload")
+    discard cgsym(m, "NimStringV2")
+    # string literal not found in the cache:
+    let pureLit = genStringLiteralDataOnlyV2(m, n.strVal)
+    result = getTempName(m)
+    addf(m.s[cfsData], "static const NimStringV2 $1 = {$2, (NimStrPayload*)&$3};$n",
+          [result, rope(len(n.strVal)), pureLit])
+  else:
+    result = m.tmpBase & rope(id+1)
+
+proc genStringLiteralV2Const(m: BModule; n: PNode): Rope =
+  let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
+  var pureLit: Rope
+  if id == m.labels:
+    discard cgsym(m, "NimStrPayload")
+    discard cgsym(m, "NimStringV2")
+    # string literal not found in the cache:
+    pureLit = genStringLiteralDataOnlyV2(m, n.strVal)
+  else:
+    pureLit = m.tmpBase & rope(id)
+  result = "{$1, (NimStrPayload*)&$2}" % [rope(len(n.strVal)), pureLit]
+
+# ------ Version selector ---------------------------------------------------
+
+proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo): Rope =
+  case detectStrVersion(m)
+  of 0, 1: result = genStringLiteralDataOnlyV1(m, s)
+  of 2: result = genStringLiteralDataOnlyV2(m, s)
+  else:
+    localError(m.config, info, "cannot determine how to produce code for string literal")
+
+proc genStringLiteralFromData(m: BModule; data: Rope; info: TLineInfo): Rope =
+  result = ropecg(m, "((#NimStringDesc*) &$1)",
+                [data])
+
+proc genNilStringLiteral(m: BModule; info: TLineInfo): Rope =
+  result = ropecg(m, "((#NimStringDesc*) NIM_NIL)", [])
+
+proc genStringLiteral(m: BModule; n: PNode): Rope =
+  case detectStrVersion(m)
+  of 0, 1: result = genStringLiteralV1(m, n)
+  of 2: result = genStringLiteralV2(m, n)
+  else:
+    localError(m.config, n.info, "cannot determine how to produce code for string literal")
diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim
index 58a03ecd2..144a45816 100644
--- a/compiler/ccgmerge.nim
+++ b/compiler/ccgmerge.nim
@@ -12,7 +12,7 @@
 
 import
   ast, astalgo, ropes, options, strutils, nimlexbase, msgs, cgendata, rodutils,
-  intsets, platform, llstream, tables, sighashes
+  intsets, platform, llstream, tables, sighashes, pathutils
 
 # Careful! Section marks need to contain a tabulator so that they cannot
 # be part of C string literals.
@@ -45,36 +45,34 @@ const
   ]
   NimMergeEndMark = "/*\tNIM_merge_END:*/"
 
-proc genSectionStart*(fs: TCFileSection): Rope =
-  if compilationCachePresent:
-    result = rope(tnl)
-    add(result, "/*\t")
+proc genSectionStart*(fs: TCFileSection; conf: ConfigRef): Rope =
+  if compilationCachePresent(conf):
+    result = nil
+    add(result, "\n/*\t")
     add(result, CFileSectionNames[fs])
-    add(result, ":*/")
-    add(result, tnl)
+    add(result, ":*/\n")
 
-proc genSectionEnd*(fs: TCFileSection): Rope =
-  if compilationCachePresent:
-    result = rope(NimMergeEndMark & tnl)
+proc genSectionEnd*(fs: TCFileSection; conf: ConfigRef): Rope =
+  if compilationCachePresent(conf):
+    result = rope(NimMergeEndMark & "\n")
 
-proc genSectionStart*(ps: TCProcSection): Rope =
-  if compilationCachePresent:
-    result = rope(tnl)
-    add(result, "/*\t")
+proc genSectionStart*(ps: TCProcSection; conf: ConfigRef): Rope =
+  if compilationCachePresent(conf):
+    result = rope("")
+    add(result, "\n/*\t")
     add(result, CProcSectionNames[ps])
-    add(result, ":*/")
-    add(result, tnl)
+    add(result, ":*/\n")
 
-proc genSectionEnd*(ps: TCProcSection): Rope =
-  if compilationCachePresent:
-    result = rope(NimMergeEndMark & tnl)
+proc genSectionEnd*(ps: TCProcSection; conf: ConfigRef): Rope =
+  if compilationCachePresent(conf):
+    result = rope(NimMergeEndMark & "\n")
 
 proc writeTypeCache(a: TypeCache, s: var string) =
   var i = 0
   for id, value in pairs(a):
     if i == 10:
       i = 0
-      s.add(tnl)
+      s.add('\L')
     else:
       s.add(' ')
     encodeStr($id, s)
@@ -88,7 +86,7 @@ proc writeIntSet(a: IntSet, s: var string) =
   for x in items(a):
     if i == 10:
       i = 0
-      s.add(tnl)
+      s.add('\L')
     else:
       s.add(' ')
     encodeVInt(x, s)
@@ -96,9 +94,8 @@ proc writeIntSet(a: IntSet, s: var string) =
   s.add('}')
 
 proc genMergeInfo*(m: BModule): Rope =
-  if optSymbolFiles notin gGlobalOptions: return nil
-  var s = "/*\tNIM_merge_INFO:"
-  s.add(tnl)
+  if not compilationCachePresent(m.config): return nil
+  var s = "/*\tNIM_merge_INFO:\n"
   s.add("typeCache:{")
   writeTypeCache(m.typeCache, s)
   s.add("declared:{")
@@ -110,8 +107,7 @@ proc genMergeInfo*(m: BModule): Rope =
   encodeVInt(m.labels, s)
   s.add(" flags:")
   encodeVInt(cast[int](m.flags), s)
-  s.add(tnl)
-  s.add("*/")
+  s.add("\n*/")
   result = s.rope
 
 template `^`(pos: int): untyped = L.buf[pos]
@@ -155,13 +151,13 @@ proc readVerbatimSection(L: var TBaseLexer): Rope =
     of CR:
       pos = nimlexbase.handleCR(L, pos)
       buf = L.buf
-      r.add(tnl)
+      r.add('\L')
     of LF:
       pos = nimlexbase.handleLF(L, pos)
       buf = L.buf
-      r.add(tnl)
+      r.add('\L')
     of '\0':
-      internalError("ccgmerge: expected: " & NimMergeEndMark)
+      doAssert(false, "ccgmerge: expected: " & NimMergeEndMark)
       break
     else:
       if atEndMark(buf, pos):
@@ -179,7 +175,7 @@ proc readKey(L: var TBaseLexer, result: var string) =
   while buf[pos] in IdentChars:
     result.add(buf[pos])
     inc pos
-  if buf[pos] != ':': internalError("ccgmerge: ':' expected")
+  if buf[pos] != ':': doAssert(false, "ccgmerge: ':' expected")
   L.bufpos = pos + 1 # skip ':'
 
 proc newFakeType(id: int): PType =
@@ -187,12 +183,12 @@ proc newFakeType(id: int): PType =
   result.id = id
 
 proc readTypeCache(L: var TBaseLexer, result: var TypeCache) =
-  if ^L.bufpos != '{': internalError("ccgmerge: '{' expected")
+  if ^L.bufpos != '{': doAssert(false, "ccgmerge: '{' expected")
   inc L.bufpos
   while ^L.bufpos != '}':
     skipWhite(L)
     var key = decodeStr(L.buf, L.bufpos)
-    if ^L.bufpos != ':': internalError("ccgmerge: ':' expected")
+    if ^L.bufpos != ':': doAssert(false, "ccgmerge: ':' expected")
     inc L.bufpos
     var value = decodeStr(L.buf, L.bufpos)
     # XXX implement me
@@ -201,7 +197,7 @@ proc readTypeCache(L: var TBaseLexer, result: var TypeCache) =
   inc L.bufpos
 
 proc readIntSet(L: var TBaseLexer, result: var IntSet) =
-  if ^L.bufpos != '{': internalError("ccgmerge: '{' expected")
+  if ^L.bufpos != '{': doAssert(false, "ccgmerge: '{' expected")
   inc L.bufpos
   while ^L.bufpos != '}':
     skipWhite(L)
@@ -225,12 +221,12 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) =
     of "labels":    m.labels = decodeVInt(L.buf, L.bufpos)
     of "flags":
       m.flags = cast[set[CodegenFlag]](decodeVInt(L.buf, L.bufpos) != 0)
-    else: internalError("ccgmerge: unknown key: " & k)
+    else: doAssert(false, "ccgmerge: unknown key: " & k)
 
 when not defined(nimhygiene):
   {.pragma: inject.}
 
-template withCFile(cfilename: string, body: untyped) =
+template withCFile(cfilename: AbsoluteFile, body: untyped) =
   var s = llStreamOpen(cfilename, fmRead)
   if s == nil: return
   var L {.inject.}: TBaseLexer
@@ -242,7 +238,7 @@ template withCFile(cfilename: string, body: untyped) =
     body
   closeBaseLexer(L)
 
-proc readMergeInfo*(cfilename: string, m: BModule) =
+proc readMergeInfo*(cfilename: AbsoluteFile, m: BModule) =
   ## reads the merge meta information into `m`.
   withCFile(cfilename):
     readKey(L, k)
@@ -255,7 +251,7 @@ type
     f: TCFileSections
     p: TCProcSections
 
-proc readMergeSections(cfilename: string, m: var TMergeSections) =
+proc readMergeSections(cfilename: AbsoluteFile, m: var TMergeSections) =
   ## reads the merge sections into `m`.
   withCFile(cfilename):
     readKey(L, k)
@@ -275,9 +271,9 @@ proc readMergeSections(cfilename: string, m: var TMergeSections) =
         if sectionB >= 0 and sectionB <= high(TCProcSection).int:
           m.p[TCProcSection(sectionB)] = verbatim
         else:
-          internalError("ccgmerge: unknown section: " & k)
+          doAssert(false, "ccgmerge: unknown section: " & k)
     else:
-      internalError("ccgmerge: '*/' expected")
+      doAssert(false, "ccgmerge: '*/' expected")
 
 proc mergeRequired*(m: BModule): bool =
   for i in cfsHeaders..cfsProcs:
@@ -289,7 +285,7 @@ proc mergeRequired*(m: BModule): bool =
       #echo "not empty: ", i, " ", m.initProc.s[i]
       return true
 
-proc mergeFiles*(cfilename: string, m: BModule) =
+proc mergeFiles*(cfilename: AbsoluteFile, m: BModule) =
   ## merges the C file with the old version on hard disc.
   var old: TMergeSections
   readMergeSections(cfilename, old)
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index cc925b150..6c33b302d 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -16,55 +16,62 @@ const
     # above X strings a hash-switch for strings is generated
 
 proc registerGcRoot(p: BProc, v: PSym) =
-  if gSelectedGC in {gcMarkAndSweep, gcGenerational, gcV2, gcRefc} and
+  if p.config.selectedGC in {gcMarkAndSweep, gcDestructors, gcV2, gcRefc} and
       containsGarbageCollectedRef(v.loc.t):
     # we register a specialized marked proc here; this has the advantage
     # that it works out of the box for thread local storage then :-)
-    let prc = genTraverseProcForGlobal(p.module, v)
-    appcg(p.module, p.module.initProc.procSec(cpsInit),
-      "#nimRegisterGlobalMarker($1);$n", [prc])
+    let prc = genTraverseProcForGlobal(p.module, v, v.info)
+    if sfThread in v.flags:
+      appcg(p.module, p.module.initProc.procSec(cpsInit),
+        "#nimRegisterThreadLocalMarker($1);$n", [prc])
+    else:
+      appcg(p.module, p.module.initProc.procSec(cpsInit),
+        "#nimRegisterGlobalMarker($1);$n", [prc])
 
-proc isAssignedImmediately(n: PNode): bool {.inline.} =
+proc isAssignedImmediately(conf: ConfigRef; n: PNode): bool {.inline.} =
   if n.kind == nkEmpty: return false
-  if isInvalidReturnType(n.typ):
+  if isInvalidReturnType(conf, n.typ):
     # var v = f()
     # is transformed into: var v;  f(addr v)
     # where 'f' **does not** initialize the result!
     return false
   result = true
 
+proc inExceptBlockLen(p: BProc): int =
+  for x in p.nestedTryStmts:
+    if x.inExcept: result.inc
+
 proc genVarTuple(p: BProc, n: PNode) =
   var tup, field: TLoc
-  if n.kind != nkVarTuple: internalError(n.info, "genVarTuple")
+  if n.kind != nkVarTuple: internalError(p.config, n.info, "genVarTuple")
   var L = sonsLen(n)
 
   # if we have a something that's been captured, use the lowering instead:
-  var useLowering = false
   for i in countup(0, L-3):
     if n[i].kind != nkSym:
-      useLowering = true; break
-  if useLowering:
-    genStmts(p, lowerTupleUnpacking(n, p.prc))
-    return
+      genStmts(p, lowerTupleUnpacking(p.module.g.graph, n, p.prc))
+      return
+
   genLineDir(p, n)
   initLocExpr(p, n.sons[L-1], tup)
   var t = tup.t.skipTypes(abstractInst)
   for i in countup(0, L-3):
-    var v = n.sons[i].sym
+    let vn = n.sons[i]
+    let v = vn.sym
     if sfCompileTime in v.flags: continue
     if sfGlobal in v.flags:
-      assignGlobalVar(p, v)
+      assignGlobalVar(p, vn)
       genObjectInit(p, cpsInit, v.typ, v.loc, true)
       registerGcRoot(p, v)
     else:
-      assignLocalVar(p, v)
-      initLocalVar(p, v, immediateAsgn=isAssignedImmediately(n[L-1]))
-    initLoc(field, locExpr, t.sons[i], tup.s)
+      assignLocalVar(p, vn)
+      initLocalVar(p, v, immediateAsgn=isAssignedImmediately(p.config, n[L-1]))
+    initLoc(field, locExpr, vn, tup.storage)
     if t.kind == tyTuple:
       field.r = "$1.Field$2" % [rdLoc(tup), rope(i)]
     else:
-      if t.n.sons[i].kind != nkSym: internalError(n.info, "genVarTuple")
-      field.r = "$1.$2" % [rdLoc(tup), mangleRecFieldName(p.module, t.n.sons[i].sym, t)]
+      if t.n.sons[i].kind != nkSym: internalError(p.config, n.info, "genVarTuple")
+      field.r = "$1.$2" % [rdLoc(tup), mangleRecFieldName(p.module, t.n.sons[i].sym)]
     putLocIntoDest(p, v.loc, field)
 
 proc genDeref(p: BProc, e: PNode, d: var TLoc; enforceDeref=false)
@@ -93,7 +100,7 @@ proc startBlock(p: BProc, start: FormatStr = "{$n",
   setLen(p.blocks, result + 1)
   p.blocks[result].id = p.labels
   p.blocks[result].nestedTryStmts = p.nestedTryStmts.len.int16
-  p.blocks[result].nestedExceptStmts = p.inExceptBlock.int16
+  p.blocks[result].nestedExceptStmts = p.inExceptBlockLen.int16
 
 proc assignLabel(b: var TBlock): Rope {.inline.} =
   b.label = "LA" & b.id.rope
@@ -118,7 +125,7 @@ proc endBlock(p: BProc, blockEnd: Rope) =
 proc endBlock(p: BProc) =
   let topBlock = p.blocks.len - 1
   var blockEnd = if p.blocks[topBlock].label != nil:
-      rfmt(nil, "} $1: ;$n", p.blocks[topBlock].label)
+      ropecg(p.module, "} $1: ;$n", p.blocks[topBlock].label)
     else:
       ~"}$n"
   let frameLen = p.blocks[topBlock].frameLen
@@ -142,9 +149,46 @@ template preserveBreakIdx(body: untyped): untyped =
   p.breakIdx = oldBreakIdx
 
 proc genState(p: BProc, n: PNode) =
-  internalAssert n.len == 1 and n.sons[0].kind == nkIntLit
-  let idx = n.sons[0].intVal
-  linefmt(p, cpsStmts, "STATE$1: ;$n", idx.rope)
+  internalAssert p.config, n.len == 1
+  let n0 = n[0]
+  if n0.kind == nkIntLit:
+    let idx = n.sons[0].intVal
+    linefmt(p, cpsStmts, "STATE$1: ;$n", idx.rope)
+  elif n0.kind == nkStrLit:
+    linefmt(p, cpsStmts, "$1: ;$n", n0.strVal.rope)
+
+proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
+  # Called by return and break stmts.
+  # Deals with issues faced when jumping out of try/except/finally stmts,
+
+  var stack = newSeq[tuple[n: PNode, inExcept: bool]](0)
+
+  for i in countup(1, howManyTrys):
+    let tryStmt = p.nestedTryStmts.pop
+    if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions:
+      # Pop safe points generated by try
+      if not tryStmt.inExcept:
+        linefmt(p, cpsStmts, "#popSafePoint();$n")
+
+    # Pop this try-stmt of the list of nested trys
+    # so we don't infinite recurse on it in the next step.
+    stack.add(tryStmt)
+
+    # Find finally-stmt for this try-stmt
+    # and generate a copy of its sons
+    var finallyStmt = lastSon(tryStmt.n)
+    if finallyStmt.kind == nkFinally:
+      genStmts(p, finallyStmt.sons[0])
+
+  # push old elements again:
+  for i in countdown(howManyTrys-1, 0):
+    p.nestedTryStmts.add(stack[i])
+
+  if not p.module.compileToCpp or optNoCppExceptions in p.config.globalOptions:
+    # Pop exceptions that was handled by the
+    # except-blocks we are in
+    for i in countdown(howManyExcepts-1, 0):
+      linefmt(p, cpsStmts, "#popCurrentException();$n")
 
 proc genGotoState(p: BProc, n: PNode) =
   # we resist the temptation to translate it into duff's device as it later
@@ -156,36 +200,45 @@ proc genGotoState(p: BProc, n: PNode) =
   initLocExpr(p, n.sons[0], a)
   lineF(p, cpsStmts, "switch ($1) {$n", [rdLoc(a)])
   p.beforeRetNeeded = true
-  lineF(p, cpsStmts, "case -1: goto BeforeRet_;$n", [])
-  for i in 0 .. lastOrd(n.sons[0].typ):
-    lineF(p, cpsStmts, "case $1: goto STATE$1;$n", [rope(i)])
+  lineF(p, cpsStmts, "case -1:$n", [])
+  blockLeaveActions(p,
+    howManyTrys    = p.nestedTryStmts.len,
+    howManyExcepts = p.inExceptBlockLen)
+  lineF(p, cpsStmts, " goto BeforeRet_;$n", [])
+  var statesCounter = lastOrd(p.config, n.sons[0].typ)
+  if n.len >= 2 and n[1].kind == nkIntLit:
+    statesCounter = n[1].intVal
+  let prefix = if n.len == 3 and n[2].kind == nkStrLit: n[2].strVal.rope
+               else: rope"STATE"
+  for i in 0i64 .. statesCounter:
+    lineF(p, cpsStmts, "case $2: goto $1$2;$n", [prefix, rope(i)])
   lineF(p, cpsStmts, "}$n", [])
 
-proc genBreakState(p: BProc, n: PNode) =
+proc genBreakState(p: BProc, n: PNode, d: var TLoc) =
   var a: TLoc
+  initLoc(d, locExpr, n, OnUnknown)
+
   if n.sons[0].kind == nkClosure:
-    # XXX this produces quite inefficient code!
     initLocExpr(p, n.sons[0].sons[1], a)
-    lineF(p, cpsStmts, "if (((NI*) $1)[0] < 0) break;$n", [rdLoc(a)])
+    d.r = "(((NI*) $1)[1] < 0)" % [rdLoc(a)]
   else:
     initLocExpr(p, n.sons[0], a)
-    # the environment is guaranteed to contain the 'state' field at offset 0:
-    lineF(p, cpsStmts, "if ((((NI*) $1.ClE_0)[0]) < 0) break;$n", [rdLoc(a)])
-  #  lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)])
-
-proc genVarPrototypeAux(m: BModule, sym: PSym)
+    # the environment is guaranteed to contain the 'state' field at offset 1:
+    d.r = "((((NI*) $1.ClE_0)[1]) < 0)" % [rdLoc(a)]
 
 proc genGotoVar(p: BProc; value: PNode) =
   if value.kind notin {nkCharLit..nkUInt64Lit}:
-    localError(value.info, "'goto' target must be a literal value")
+    localError(p.config, value.info, "'goto' target must be a literal value")
   else:
     lineF(p, cpsStmts, "goto NIMSTATE_$#;$n", [value.intVal.rope])
 
 proc genSingleVar(p: BProc, a: PNode) =
-  var v = a.sons[0].sym
-  if {sfCompileTime, sfGoto} * v.flags != {}:
+  let vn = a.sons[0]
+  let v = vn.sym
+  if sfCompileTime in v.flags: return
+  if sfGoto in v.flags:
     # translate 'var state {.goto.} = X' into 'goto LX':
-    if sfGoto in v.flags: genGotoVar(p, a.sons[2])
+    genGotoVar(p, a.sons[2])
     return
   var targetProc = p
   if sfGlobal in v.flags:
@@ -196,45 +249,56 @@ proc genSingleVar(p: BProc, a: PNode) =
     if sfPure in v.flags:
       # v.owner.kind != skModule:
       targetProc = p.module.preInitProc
-    assignGlobalVar(targetProc, v)
+    assignGlobalVar(targetProc, vn)
     # XXX: be careful here.
     # Global variables should not be zeromem-ed within loops
     # (see bug #20).
     # That's why we are doing the construction inside the preInitProc.
     # genObjectInit relies on the C runtime's guarantees that
     # global variables will be initialized to zero.
-    genObjectInit(p.module.preInitProc, cpsInit, v.typ, v.loc, true)
+    var loc = v.loc
+
+    # When the native TLS is unavailable, a global thread-local variable needs
+    # one more layer of indirection in order to access the TLS block.
+    # Only do this for complex types that may need a call to `objectInit`
+    if sfThread in v.flags and emulatedThreadVars(p.config) and
+      isComplexValueType(v.typ):
+      initLocExprSingleUse(p.module.preInitProc, vn, loc)
+    genObjectInit(p.module.preInitProc, cpsInit, v.typ, loc, true)
     # Alternative construction using default constructor (which may zeromem):
     # if sfImportc notin v.flags: constructLoc(p.module.preInitProc, v.loc)
     if sfExportc in v.flags and p.module.g.generatedHeader != nil:
-      genVarPrototypeAux(p.module.g.generatedHeader, v)
+      genVarPrototype(p.module.g.generatedHeader, vn)
     registerGcRoot(p, v)
   else:
     let value = a.sons[2]
-    let imm = isAssignedImmediately(value)
+    let imm = isAssignedImmediately(p.config, value)
     if imm and p.module.compileToCpp and p.splitDecls == 0 and
         not containsHiddenPointer(v.typ):
       # C++ really doesn't like things like 'Foo f; f = x' as that invokes a
       # parameterless constructor followed by an assignment operator. So we
-      # generate better code here:
+      # generate better code here: 'Foo f = x;'
       genLineDir(p, a)
-      let decl = localVarDecl(p, v)
+      let decl = localVarDecl(p, vn)
       var tmp: TLoc
       if value.kind in nkCallKinds and value[0].kind == nkSym and
            sfConstructor in value[0].sym.flags:
         var params: Rope
         let typ = skipTypes(value.sons[0].typ, abstractInst)
         assert(typ.kind == tyProc)
-        for i in 1.. <value.len:
+        for i in 1..<value.len:
           if params != nil: params.add(~", ")
           assert(sonsLen(typ) == sonsLen(typ.n))
           add(params, genOtherArg(p, value, i, typ))
-        lineF(p, cpsStmts, "$#($#);$n", [decl, params])
+        if params == nil:
+          lineF(p, cpsStmts, "$#;$n", [decl])
+        else:
+          lineF(p, cpsStmts, "$#($#);$n", [decl, params])
       else:
         initLocExprSingleUse(p, value, tmp)
         lineF(p, cpsStmts, "$# = $#;$n", [decl, tmp.rdLoc])
       return
-    assignLocalVar(p, v)
+    assignLocalVar(p, vn)
     initLocalVar(p, v, imm)
 
   if a.sons[2].kind != nkEmpty:
@@ -243,35 +307,25 @@ proc genSingleVar(p: BProc, a: PNode) =
 
 proc genClosureVar(p: BProc, a: PNode) =
   var immediateAsgn = a.sons[2].kind != nkEmpty
+  var v: TLoc
+  initLocExpr(p, a.sons[0], v)
+  genLineDir(p, a)
   if immediateAsgn:
-    var v: TLoc
-    initLocExpr(p, a.sons[0], v)
-    genLineDir(p, a)
     loadInto(p, a.sons[0], a.sons[2], v)
+  else:
+    constructLoc(p, v)
 
 proc genVarStmt(p: BProc, n: PNode) =
-  for i in countup(0, sonsLen(n) - 1):
-    var a = n.sons[i]
-    if a.kind == nkCommentStmt: continue
-    if a.kind == nkIdentDefs:
+  for it in n.sons:
+    if it.kind == nkCommentStmt: continue
+    if it.kind == nkIdentDefs:
       # can be a lifted var nowadays ...
-      if a.sons[0].kind == nkSym:
-        genSingleVar(p, a)
+      if it.sons[0].kind == nkSym:
+        genSingleVar(p, it)
       else:
-        genClosureVar(p, a)
+        genClosureVar(p, it)
     else:
-      genVarTuple(p, a)
-
-proc genConstStmt(p: BProc, t: PNode) =
-  for i in countup(0, sonsLen(t) - 1):
-    var it = t.sons[i]
-    if it.kind == nkCommentStmt: continue
-    if it.kind != nkConstDef: internalError(t.info, "genConstStmt")
-    var c = it.sons[0].sym
-    if c.typ.containsCompileTimeOnly: continue
-    elif c.typ.kind in ConstantDataTypes and lfNoDecl notin c.loc.flags and
-        c.ast.len != 0:
-      if not emitLazily(c): requestConstImpl(p, c)
+      genVarTuple(p, it)
 
 proc genIf(p: BProc, n: PNode, d: var TLoc) =
   #
@@ -292,18 +346,16 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) =
     getTemp(p, n.typ, d)
   genLineDir(p, n)
   let lend = getLabel(p)
-  for i in countup(0, sonsLen(n) - 1):
+  for it in n.sons:
     # bug #4230: avoid false sharing between branches:
     if d.k == locTemp and isEmptyType(n.typ): d.k = locNone
-    let it = n.sons[i]
     if it.len == 2:
-      when newScopeForIf: startBlock(p)
+      startBlock(p)
       initLocExprSingleUse(p, it.sons[0], a)
       lelse = getLabel(p)
       inc(p.labels)
       lineF(p, cpsStmts, "if (!$1) goto $2;$n",
             [rdLoc(a), lelse])
-      when not newScopeForIf: startBlock(p)
       if p.module.compileToCpp:
         # avoid "jump to label crosses initialization" error:
         add(p.s(cpsStmts), "{")
@@ -319,47 +371,9 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) =
       startBlock(p)
       expr(p, it.sons[0], d)
       endBlock(p)
-    else: internalError(n.info, "genIf()")
+    else: internalError(p.config, n.info, "genIf()")
   if sonsLen(n) > 1: fixLabel(p, lend)
 
-
-proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) =
-  # Called by return and break stmts.
-  # Deals with issues faced when jumping out of try/except/finally stmts,
-
-  var stack: seq[PNode]
-  newSeq(stack, 0)
-
-  var alreadyPoppedCnt = p.inExceptBlock
-  for i in countup(1, howManyTrys):
-    if not p.module.compileToCpp:
-      # Pop safe points generated by try
-      if alreadyPoppedCnt > 0:
-        dec alreadyPoppedCnt
-      else:
-        linefmt(p, cpsStmts, "#popSafePoint();$n")
-
-    # Pop this try-stmt of the list of nested trys
-    # so we don't infinite recurse on it in the next step.
-    var tryStmt = p.nestedTryStmts.pop
-    stack.add(tryStmt)
-
-    # Find finally-stmt for this try-stmt
-    # and generate a copy of its sons
-    var finallyStmt = lastSon(tryStmt)
-    if finallyStmt.kind == nkFinally:
-      genStmts(p, finallyStmt.sons[0])
-
-  # push old elements again:
-  for i in countdown(howManyTrys-1, 0):
-    p.nestedTryStmts.add(stack[i])
-
-  if not p.module.compileToCpp:
-    # Pop exceptions that was handled by the
-    # except-blocks we are in
-    for i in countdown(howManyExcepts-1, 0):
-      linefmt(p, cpsStmts, "#popCurrentException();$n")
-
 proc genReturnStmt(p: BProc, t: PNode) =
   if nfPreventCg in t.flags: return
   p.beforeRetNeeded = true
@@ -367,7 +381,7 @@ proc genReturnStmt(p: BProc, t: PNode) =
   if (t.sons[0].kind != nkEmpty): genStmts(p, t.sons[0])
   blockLeaveActions(p,
     howManyTrys    = p.nestedTryStmts.len,
-    howManyExcepts = p.inExceptBlock)
+    howManyExcepts = p.inExceptBlockLen)
   if (p.finallySafePoints.len > 0):
     # If we're in a finally block, and we came here by exception
     # consume it before we return.
@@ -376,60 +390,63 @@ proc genReturnStmt(p: BProc, t: PNode) =
   lineF(p, cpsStmts, "goto BeforeRet_;$n", [])
 
 proc genGotoForCase(p: BProc; caseStmt: PNode) =
-  for i in 1 .. <caseStmt.len:
+  for i in 1 ..< caseStmt.len:
     startBlock(p)
     let it = caseStmt.sons[i]
     for j in 0 .. it.len-2:
       if it.sons[j].kind == nkRange:
-        localError(it.info, "range notation not available for computed goto")
+        localError(p.config, it.info, "range notation not available for computed goto")
         return
       let val = getOrdValue(it.sons[j])
       lineF(p, cpsStmts, "NIMSTATE_$#:$n", [val.rope])
     genStmts(p, it.lastSon)
     endBlock(p)
 
+
+iterator fieldValuePairs(n: PNode): tuple[memberSym, valueSym: PNode] =
+  assert(n.kind in {nkLetSection, nkVarSection})
+  for identDefs in n:
+    if identDefs.kind == nkIdentDefs:
+      let valueSym = identDefs[^1]
+      for i in 0 ..< identDefs.len-2:
+        let memberSym = identDefs[i]
+        yield((memberSym: memberSym, valueSym: valueSym))
+
 proc genComputedGoto(p: BProc; n: PNode) =
   # first pass: Generate array of computed labels:
   var casePos = -1
   var arraySize: int
-  for i in 0 .. <n.len:
+  for i in 0 ..< n.len:
     let it = n.sons[i]
     if it.kind == nkCaseStmt:
       if lastSon(it).kind != nkOfBranch:
-        localError(it.info,
+        localError(p.config, it.info,
             "case statement must be exhaustive for computed goto"); return
       casePos = i
-      let aSize = lengthOrd(it.sons[0].typ)
+      if enumHasHoles(it.sons[0].typ):
+        localError(p.config, it.info,
+            "case statement cannot work on enums with holes for computed goto"); return
+      let aSize = lengthOrd(p.config, it.sons[0].typ)
       if aSize > 10_000:
-        localError(it.info,
+        localError(p.config, it.info,
             "case statement has too many cases for computed goto"); return
       arraySize = aSize.int
-      if firstOrd(it.sons[0].typ) != 0:
-        localError(it.info,
+      if firstOrd(p.config, it.sons[0].typ) != 0:
+        localError(p.config, it.info,
             "case statement has to start at 0 for computed goto"); return
   if casePos < 0:
-    localError(n.info, "no case statement found for computed goto"); return
+    localError(p.config, n.info, "no case statement found for computed goto"); return
   var id = p.labels+1
   inc p.labels, arraySize+1
   let tmp = "TMP$1_" % [id.rope]
   var gotoArray = "static void* $#[$#] = {" % [tmp, arraySize.rope]
   for i in 1..arraySize-1:
-    gotoArray.addf("&&TMP$#_, ", [(id+i).rope])
-  gotoArray.addf("&&TMP$#_};$n", [(id+arraySize).rope])
+    gotoArray.addf("&&TMP$#_, ", [rope(id+i)])
+  gotoArray.addf("&&TMP$#_};$n", [rope(id+arraySize)])
   line(p, cpsLocals, gotoArray)
 
-  let topBlock = p.blocks.len-1
-  let oldBody = p.blocks[topBlock].sections[cpsStmts]
-  p.blocks[topBlock].sections[cpsStmts] = nil
-
-  for j in casePos+1 .. <n.len: genStmts(p, n.sons[j])
-  let tailB = p.blocks[topBlock].sections[cpsStmts]
-
-  p.blocks[topBlock].sections[cpsStmts] = nil
-  for j in 0 .. casePos-1: genStmts(p, n.sons[j])
-  let tailA = p.blocks[topBlock].sections[cpsStmts]
-
-  p.blocks[topBlock].sections[cpsStmts] = oldBody & tailA
+  for j in 0 ..< casePos:
+    genStmts(p, n.sons[j])
 
   let caseStmt = n.sons[casePos]
   var a: TLoc
@@ -437,26 +454,47 @@ proc genComputedGoto(p: BProc; n: PNode) =
   # first goto:
   lineF(p, cpsStmts, "goto *$#[$#];$n", [tmp, a.rdLoc])
 
-  for i in 1 .. <caseStmt.len:
+  for i in 1 ..< caseStmt.len:
     startBlock(p)
     let it = caseStmt.sons[i]
     for j in 0 .. it.len-2:
       if it.sons[j].kind == nkRange:
-        localError(it.info, "range notation not available for computed goto")
+        localError(p.config, it.info, "range notation not available for computed goto")
         return
+
       let val = getOrdValue(it.sons[j])
       lineF(p, cpsStmts, "TMP$#_:$n", [intLiteral(val+id+1)])
+
     genStmts(p, it.lastSon)
-    #for j in casePos+1 .. <n.len: genStmts(p, n.sons[j]) # tailB
-    #for j in 0 .. casePos-1: genStmts(p, n.sons[j])  # tailA
-    add(p.s(cpsStmts), tailB)
-    add(p.s(cpsStmts), tailA)
+
+    for j in casePos+1 ..< n.sons.len:
+      genStmts(p, n.sons[j])
+
+    for j in 0 ..< casePos:
+      # prevent new local declarations
+      # compile declarations as assignments
+      let it = n.sons[j]
+      if it.kind in {nkLetSection, nkVarSection}:
+        let asgn = copyNode(it)
+        asgn.kind = nkAsgn
+        asgn.sons.setLen 2
+        for sym, value in it.fieldValuePairs:
+          if value.kind != nkEmpty:
+            asgn.sons[0] = sym
+            asgn.sons[1] = value
+            genStmts(p, asgn)
+      else:
+        genStmts(p, it)
 
     var a: TLoc
     initLocExpr(p, caseStmt.sons[0], a)
     lineF(p, cpsStmts, "goto *$#[$#];$n", [tmp, a.rdLoc])
     endBlock(p)
 
+  for j in casePos+1 ..< n.sons.len:
+    genStmts(p, n.sons[j])
+
+
 proc genWhileStmt(p: BProc, t: PNode) =
   # we don't generate labels here as for example GCC would produce
   # significantly worse code
@@ -467,26 +505,26 @@ proc genWhileStmt(p: BProc, t: PNode) =
   genLineDir(p, t)
 
   preserveBreakIdx:
-    p.breakIdx = startBlock(p, "while (1) {$n")
-    p.blocks[p.breakIdx].isLoop = true
-    initLocExpr(p, t.sons[0], a)
-    if (t.sons[0].kind != nkIntLit) or (t.sons[0].intVal == 0):
-      let label = assignLabel(p.blocks[p.breakIdx])
-      lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), label])
     var loopBody = t.sons[1]
     if loopBody.stmtsContainPragma(wComputedGoto) and
-        hasComputedGoto in CC[cCompiler].props:
-      # for closure support weird loop bodies are generated:
+       hasComputedGoto in CC[p.config.cCompiler].props:
+         # for closure support weird loop bodies are generated:
       if loopBody.len == 2 and loopBody.sons[0].kind == nkEmpty:
         loopBody = loopBody.sons[1]
       genComputedGoto(p, loopBody)
     else:
+      p.breakIdx = startBlock(p, "while (1) {$n")
+      p.blocks[p.breakIdx].isLoop = true
+      initLocExpr(p, t.sons[0], a)
+      if (t.sons[0].kind != nkIntLit) or (t.sons[0].intVal == 0):
+        let label = assignLabel(p.blocks[p.breakIdx])
+        lineF(p, cpsStmts, "if (!$1) goto $2;$n", [rdLoc(a), label])
       genStmts(p, loopBody)
 
-    if optProfiler in p.options:
-      # invoke at loop body exit:
-      linefmt(p, cpsStmts, "#nimProfile();$n")
-    endBlock(p)
+      if optProfiler in p.options:
+        # invoke at loop body exit:
+        linefmt(p, cpsStmts, "#nimProfile();$n")
+      endBlock(p)
 
   dec(p.withinLoop)
 
@@ -514,14 +552,15 @@ proc genParForStmt(p: BProc, t: PNode) =
   preserveBreakIdx:
     let forLoopVar = t.sons[0].sym
     var rangeA, rangeB: TLoc
-    assignLocalVar(p, forLoopVar)
+    assignLocalVar(p, t.sons[0])
     #initLoc(forLoopVar.loc, locLocalVar, forLoopVar.typ, onStack)
     #discard mangleName(forLoopVar)
     let call = t.sons[1]
     initLocExpr(p, call.sons[1], rangeA)
     initLocExpr(p, call.sons[2], rangeB)
 
-    lineF(p, cpsStmts, "#pragma omp parallel for $4$n" &
+    # $n at the beginning because of #9710
+    lineF(p, cpsStmts, "$n#pragma omp $4$n" &
                         "for ($1 = $2; $1 <= $3; ++$1)",
                         [forLoopVar.loc.rdLoc,
                         rangeA.rdLoc, rangeB.rdLoc,
@@ -546,35 +585,40 @@ proc genBreakStmt(p: BProc, t: PNode) =
     # an unnamed 'break' can only break a loop after 'transf' pass:
     while idx >= 0 and not p.blocks[idx].isLoop: dec idx
     if idx < 0 or not p.blocks[idx].isLoop:
-      internalError(t.info, "no loop to break")
+      internalError(p.config, t.info, "no loop to break")
   let label = assignLabel(p.blocks[idx])
   blockLeaveActions(p,
     p.nestedTryStmts.len - p.blocks[idx].nestedTryStmts,
-    p.inExceptBlock - p.blocks[idx].nestedExceptStmts)
+    p.inExceptBlockLen - p.blocks[idx].nestedExceptStmts)
   genLineDir(p, t)
   lineF(p, cpsStmts, "goto $1;$n", [label])
 
-proc getRaiseFrmt(p: BProc): string =
-  result = "#raiseException((#Exception*)$1, $2);$n"
-
 proc genRaiseStmt(p: BProc, t: PNode) =
-  if p.inExceptBlock > 0:
+  if p.module.compileToCpp:
+    discard cgsym(p.module, "popCurrentExceptionEx")
+  if p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
     # if the current try stmt have a finally block,
     # we must execute it before reraising
-    var finallyBlock = p.nestedTryStmts[p.nestedTryStmts.len - 1].lastSon
+    var finallyBlock = p.nestedTryStmts[^1].n[^1]
     if finallyBlock.kind == nkFinally:
-      genSimpleBlock(p, finallyBlock.sons[0])
-  if t.sons[0].kind != nkEmpty:
+      genSimpleBlock(p, finallyBlock[0])
+  if t[0].kind != nkEmpty:
     var a: TLoc
-    initLocExpr(p, t.sons[0], a)
+    initLocExprSingleUse(p, t[0], a)
     var e = rdLoc(a)
-    var typ = skipTypes(t.sons[0].typ, abstractPtrs)
+    var typ = skipTypes(t[0].typ, abstractPtrs)
     genLineDir(p, t)
-    lineCg(p, cpsStmts, getRaiseFrmt(p), [e, makeCString(typ.sym.name.s)])
+    if isImportedException(typ, p.config):
+      lineF(p, cpsStmts, "throw $1;$n", [e])
+    else:
+      lineCg(p, cpsStmts, "#raiseExceptionEx((#Exception*)$1, $2, $3, $4, $5);$n",
+          [e, makeCString(typ.sym.name.s),
+          makeCString(if p.prc != nil: p.prc.name.s else: p.module.module.name.s),
+          makeCString(toFileName(p.config, t.info)), rope(toLinenumber(t.info))])
   else:
     genLineDir(p, t)
     # reraise the last exception:
-    if p.module.compileToCpp and optNoCppExceptions notin gGlobalOptions:
+    if p.module.compileToCpp and optNoCppExceptions notin p.config.globalOptions:
       line(p, cpsStmts, ~"throw;$n")
     else:
       linefmt(p, cpsStmts, "#reraiseException();$n")
@@ -645,7 +689,7 @@ proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
     assert(b.sons[i].kind != nkRange)
     initLocExpr(p, b.sons[i], x)
     assert(b.sons[i].kind in {nkStrLit..nkTripleStrLit})
-    var j = int(hashString(b.sons[i].strVal) and high(branches))
+    var j = int(hashString(p.config, b.sons[i].strVal) and high(branches))
     appcg(p.module, branches[j], "if (#eqStrings($1, $2)) goto $3;$n",
          [rdLoc(e), rdLoc(x), labl])
 
@@ -698,7 +742,7 @@ proc ifSwitchSplitPoint(p: BProc, n: PNode): int =
     var stmtBlock = lastSon(branch)
     if stmtBlock.stmtsContainPragma(wLinearScanEnd):
       result = i
-    elif hasSwitchRange notin CC[cCompiler].props:
+    elif hasSwitchRange notin CC[p.config.cCompiler].props:
       if branch.kind == nkOfBranch and branchHasTooBigRange(branch):
         result = i
 
@@ -706,7 +750,7 @@ proc genCaseRange(p: BProc, branch: PNode) =
   var length = branch.len
   for j in 0 .. length-2:
     if branch[j].kind == nkRange:
-      if hasSwitchRange in CC[cCompiler].props:
+      if hasSwitchRange in CC[p.config.cCompiler].props:
         lineF(p, cpsStmts, "case $1 ... $2:$n", [
             genLiteral(p, branch[j][0]),
             genLiteral(p, branch[j][1])])
@@ -734,7 +778,7 @@ proc genOrdinalCase(p: BProc, n: PNode, d: var TLoc) =
   if splitPoint+1 < n.len:
     lineF(p, cpsStmts, "switch ($1) {$n", [rdCharLoc(a)])
     var hasDefault = false
-    for i in splitPoint+1 .. < n.len:
+    for i in splitPoint+1 ..< n.len:
       # bug #4230: avoid false sharing between branches:
       if d.k == locTemp and isEmptyType(n.typ): d.k = locNone
       var branch = n[i]
@@ -746,7 +790,7 @@ proc genOrdinalCase(p: BProc, n: PNode, d: var TLoc) =
         hasDefault = true
       exprBlock(p, branch.lastSon, d)
       lineF(p, cpsStmts, "break;$n", [])
-    if (hasAssume in CC[cCompiler].props) and not hasDefault:
+    if (hasAssume in CC[p.config.cCompiler].props) and not hasDefault:
       lineF(p, cpsStmts, "default: __assume(0);$n", [])
     lineF(p, cpsStmts, "}$n", [])
   if lend != nil: fixLabel(p, lend)
@@ -767,91 +811,82 @@ proc genCase(p: BProc, t: PNode, d: var TLoc) =
     else:
       genOrdinalCase(p, t, d)
 
+proc genRestoreFrameAfterException(p: BProc) =
+  if optStackTrace in p.module.config.options:
+    if not p.hasCurFramePointer:
+      p.hasCurFramePointer = true
+      p.procSec(cpsLocals).add(ropecg(p.module, "\tTFrame* _nimCurFrame;$n", []))
+      p.procSec(cpsInit).add(ropecg(p.module, "\t_nimCurFrame = #getFrame();$n", []))
+    linefmt(p, cpsStmts, "#setFrame(_nimCurFrame);$n")
+
 proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
   # code to generate:
   #
-  # XXX: There should be a standard dispatch algorithm
-  # that's used both here and with multi-methods
-  #
   #   try
   #   {
   #      myDiv(4, 9);
-  #   } catch (NimException& exp) {
-  #      if (isObj(exp, EIO) {
-  #        ...
-  #      } else if (isObj(exp, ESystem) {
-  #        ...
-  #        finallyPart()
-  #        raise;
-  #      } else {
-  #        // general handler
-  #      }
-  #  }
-  #  finallyPart();
+  #   } catch (NimExceptionType1&) {
+  #      body
+  #   } catch (NimExceptionType2&) {
+  #      finallyPart()
+  #      raise;
+  #   }
+  #   catch(...) {
+  #     general_handler_body
+  #   }
+  #   finallyPart();
+
+  template genExceptBranchBody(body: PNode) {.dirty.} =
+    genRestoreFrameAfterException(p)
+    expr(p, body, d)
+
   if not isEmptyType(t.typ) and d.k == locNone:
     getTemp(p, t.typ, d)
   genLineDir(p, t)
-  let exc = getTempName(p.module)
-  if getCompilerProc("Exception") != nil:
-    discard cgsym(p.module, "Exception")
-  else:
-    discard cgsym(p.module, "E_Base")
-  add(p.nestedTryStmts, t)
+  discard cgsym(p.module, "popCurrentExceptionEx")
+  add(p.nestedTryStmts, (t, false))
   startBlock(p, "try {$n")
-  expr(p, t.sons[0], d)
-  let length = sonsLen(t)
-  endBlock(p, ropecg(p.module, "} catch (NimException& $1) {$n", [exc]))
-  if optStackTrace in p.options:
-    linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n")
-  inc p.inExceptBlock
-  var i = 1
+  expr(p, t[0], d)
+  endBlock(p)
+
   var catchAllPresent = false
-  while (i < length) and (t.sons[i].kind == nkExceptBranch):
+
+  p.nestedTryStmts[^1].inExcept = true
+  for i in 1..<t.len:
+    if t[i].kind != nkExceptBranch: break
+
     # bug #4230: avoid false sharing between branches:
     if d.k == locTemp and isEmptyType(t.typ): d.k = locNone
-    let blen = sonsLen(t.sons[i])
-    if i > 1: addf(p.s(cpsStmts), "else ", [])
-    if blen == 1:
+
+    if t[i].len == 1:
       # general except section:
       catchAllPresent = true
-      startBlock(p)
-      expr(p, t.sons[i].sons[0], d)
-      linefmt(p, cpsStmts, "#popCurrentException();$n")
+      startBlock(p, "catch (...) {$n")
+      genExceptBranchBody(t[i][0])
       endBlock(p)
     else:
-      var orExpr: Rope = nil
-      for j in countup(0, blen - 2):
-        assert(t.sons[i].sons[j].kind == nkType)
-        if orExpr != nil: add(orExpr, "||")
-        appcg(p.module, orExpr,
-              "#isObj($1.exp->m_type, $2)",
-              [exc, genTypeInfo(p.module, t.sons[i].sons[j].typ)])
-      lineF(p, cpsStmts, "if ($1) ", [orExpr])
-      startBlock(p)
-      expr(p, t.sons[i].sons[blen-1], d)
-      linefmt(p, cpsStmts, "#popCurrentException();$n")
-      endBlock(p)
-    inc(i)
-
-  # reraise the exception if there was no catch all
-  # and none of the handlers matched
-  if not catchAllPresent:
-    if i > 1: lineF(p, cpsStmts, "else ", [])
-    startBlock(p)
-    var finallyBlock = t.lastSon
-    if finallyBlock.kind == nkFinally:
-      #expr(p, finallyBlock.sons[0], d)
-      genStmts(p, finallyBlock.sons[0])
+      for j in 0..t[i].len-2:
+        if t[i][j].isInfixAs():
+          let exvar = t[i][j][2] # ex1 in `except ExceptType as ex1:`
+          fillLoc(exvar.sym.loc, locTemp, exvar, mangleLocalName(p, exvar.sym), OnUnknown)
+          startBlock(p, "catch ($1& $2) {$n", getTypeDesc(p.module, t[i][j][1].typ), rdLoc(exvar.sym.loc))
+        else:
+          startBlock(p, "catch ($1&) {$n", getTypeDesc(p.module, t[i][j].typ))
+        genExceptBranchBody(t[i][^1])  # exception handler body will duplicated for every type
+        endBlock(p)
 
-    line(p, cpsStmts, ~"throw;$n")
-    endBlock(p)
+  discard pop(p.nestedTryStmts)
 
-  lineF(p, cpsStmts, "}$n", []) # end of catch block
-  dec p.inExceptBlock
+  if t[^1].kind == nkFinally:
+    # c++ does not have finally, therefore code needs to be generated twice
+    if not catchAllPresent:
+      # finally requires catch all presence
+      startBlock(p, "catch (...) {$n")
+      genStmts(p, t[^1][0])
+      line(p, cpsStmts, ~"throw;$n")
+      endBlock(p)
 
-  discard pop(p.nestedTryStmts)
-  if (i < length) and (t.sons[i].kind == nkFinally):
-    genSimpleBlock(p, t.sons[i].sons[0])
+    genSimpleBlock(p, t[^1][0])
 
 proc genTry(p: BProc, t: PNode, d: var TLoc) =
   # code to generate:
@@ -887,31 +922,27 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
   p.module.includeHeader("<setjmp.h>")
   genLineDir(p, t)
   var safePoint = getTempName(p.module)
-  if getCompilerProc("Exception") != nil:
-    discard cgsym(p.module, "Exception")
-  else:
-    discard cgsym(p.module, "E_Base")
+  discard cgsym(p.module, "Exception")
   linefmt(p, cpsLocals, "#TSafePoint $1;$n", safePoint)
   linefmt(p, cpsStmts, "#pushSafePoint(&$1);$n", safePoint)
-  if isDefined("nimStdSetjmp"):
+  if isDefined(p.config, "nimStdSetjmp"):
     linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint)
-  elif isDefined("nimSigSetjmp"):
+  elif isDefined(p.config, "nimSigSetjmp"):
     linefmt(p, cpsStmts, "$1.status = sigsetjmp($1.context, 0);$n", safePoint)
-  elif isDefined("nimRawSetjmp"):
+  elif isDefined(p.config, "nimRawSetjmp"):
     linefmt(p, cpsStmts, "$1.status = _setjmp($1.context);$n", safePoint)
   else:
     linefmt(p, cpsStmts, "$1.status = setjmp($1.context);$n", safePoint)
   startBlock(p, "if ($1.status == 0) {$n", [safePoint])
   var length = sonsLen(t)
-  add(p.nestedTryStmts, t)
+  add(p.nestedTryStmts, (t, false))
   expr(p, t.sons[0], d)
   linefmt(p, cpsStmts, "#popSafePoint();$n")
   endBlock(p)
   startBlock(p, "else {$n")
   linefmt(p, cpsStmts, "#popSafePoint();$n")
-  if optStackTrace in p.options:
-    linefmt(p, cpsStmts, "#setFrame((TFrame*)&FR_);$n")
-  inc p.inExceptBlock
+  genRestoreFrameAfterException(p)
+  p.nestedTryStmts[^1].inExcept = true
   var i = 1
   while (i < length) and (t.sons[i].kind == nkExceptBranch):
     # bug #4230: avoid false sharing between branches:
@@ -934,7 +965,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
           "#isObj(#getCurrentException()->Sup.m_type, $1)"
           else: "#isObj(#getCurrentException()->m_type, $1)"
         appcg(p.module, orExpr, isObjFormat,
-              [genTypeInfo(p.module, t.sons[i].sons[j].typ)])
+              [genTypeInfo(p.module, t[i][j].typ, t[i][j].info)])
       if i > 1: line(p, cpsStmts, "else ")
       startBlock(p, "if ($1) {$n", [orExpr])
       linefmt(p, cpsStmts, "$1.status = 0;$n", safePoint)
@@ -942,7 +973,6 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
       linefmt(p, cpsStmts, "#popCurrentException();$n")
       endBlock(p)
     inc(i)
-  dec p.inExceptBlock
   discard pop(p.nestedTryStmts)
   endBlock(p) # end of else block
   if i < length and t.sons[i].kind == nkFinally:
@@ -953,19 +983,20 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
 
 proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope =
   var res = ""
-  for i in countup(0, sonsLen(t) - 1):
-    case t.sons[i].kind
+  for it in t.sons:
+    case it.kind
     of nkStrLit..nkTripleStrLit:
-      res.add(t.sons[i].strVal)
+      res.add(it.strVal)
     of nkSym:
-      var sym = t.sons[i].sym
-      if sym.kind in {skProc, skIterator, skMethod}:
+      var sym = it.sym
+      if sym.kind in {skProc, skFunc, skIterator, skMethod}:
         var a: TLoc
-        initLocExpr(p, t.sons[i], a)
+        initLocExpr(p, it, a)
         res.add($rdLoc(a))
       elif sym.kind == skType:
         res.add($getTypeDesc(p.module, sym.typ))
       else:
+        discard getTypeDesc(p.module, skipTypes(sym.typ, abstractPtrs))
         var r = sym.loc.r
         if r == nil:
           # if no name has already been given,
@@ -974,28 +1005,29 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope =
           sym.loc.r = r       # but be consequent!
         res.add($r)
     of nkTypeOfExpr:
-      res.add($getTypeDesc(p.module, t.sons[i].typ))
+      res.add($getTypeDesc(p.module, it.typ))
     else:
+      discard getTypeDesc(p.module, skipTypes(it.typ, abstractPtrs))
       var a: TLoc
-      initLocExpr(p, t.sons[i], a)
+      initLocExpr(p, it, a)
       res.add($a.rdLoc)
-      #internalError(t.sons[i].info, "genAsmOrEmitStmt()")
 
-  if isAsmStmt and hasGnuAsm in CC[cCompiler].props:
+  if isAsmStmt and hasGnuAsm in CC[p.config.cCompiler].props:
     for x in splitLines(res):
       var j = 0
-      while x[j] in {' ', '\t'}: inc(j)
-      if x[j] in {'"', ':'}:
-        # don't modify the line if already in quotes or
-        # some clobber register list:
-        add(result, x); add(result, tnl)
-      elif x[j] != '\0':
-        # ignore empty lines
-        add(result, "\"")
-        add(result, x)
-        add(result, "\\n\"\n")
+      while j < x.len and x[j] in {' ', '\t'}: inc(j)
+      if j < x.len:
+        if x[j] in {'"', ':'}:
+          # don't modify the line if already in quotes or
+          # some clobber register list:
+          add(result, x); add(result, "\L")
+        else:
+          # ignore empty lines
+          add(result, "\"")
+          add(result, x)
+          add(result, "\\n\"\n")
   else:
-    res.add(tnl)
+    res.add("\L")
     result = res.rope
 
 proc genAsmStmt(p: BProc, t: PNode) =
@@ -1007,9 +1039,9 @@ proc genAsmStmt(p: BProc, t: PNode) =
   # work:
   if p.prc == nil:
     # top level asm statement?
-    addf(p.module.s[cfsProcHeaders], CC[cCompiler].asmStmtFrmt, [s])
+    addf(p.module.s[cfsProcHeaders], CC[p.config.cCompiler].asmStmtFrmt, [s])
   else:
-    lineF(p, cpsStmts, CC[cCompiler].asmStmtFrmt, [s])
+    lineF(p, cpsStmts, CC[p.config.cCompiler].asmStmtFrmt, [s])
 
 proc determineSection(n: PNode): TCFileSection =
   result = cfsProcHeaders
@@ -1024,7 +1056,7 @@ proc genEmit(p: BProc, t: PNode) =
   if p.prc == nil:
     # top level emit pragma?
     let section = determineSection(t[1])
-    genCLineDir(p.module.s[section], t.info)
+    genCLineDir(p.module.s[section], t.info, p.config)
     add(p.module.s[section], s)
   else:
     genLineDir(p, t)
@@ -1042,7 +1074,7 @@ proc genBreakPoint(p: BProc, t: PNode) =
     genLineDir(p, t)          # BUGFIX
     appcg(p.module, p.module.g.breakpoints,
          "#dbgRegisterBreakpoint($1, (NCSTRING)$2, (NCSTRING)$3);$n", [
-        rope(toLinenumber(t.info)), makeCString(toFilename(t.info)),
+        rope(toLinenumber(t.info)), makeCString(toFilename(p.config, t.info)),
         makeCString(name)])
 
 proc genWatchpoint(p: BProc, n: PNode) =
@@ -1051,12 +1083,11 @@ proc genWatchpoint(p: BProc, n: PNode) =
   initLocExpr(p, n.sons[1], a)
   let typ = skipTypes(n.sons[1].typ, abstractVarRange)
   lineCg(p, cpsStmts, "#dbgRegisterWatchpoint($1, (NCSTRING)$2, $3);$n",
-        [a.addrLoc, makeCString(renderTree(n.sons[1])),
-        genTypeInfo(p.module, typ)])
+        [addrLoc(p.config, a), makeCString(renderTree(n.sons[1])),
+        genTypeInfo(p.module, typ, n.info)])
 
 proc genPragma(p: BProc, n: PNode) =
-  for i in countup(0, sonsLen(n) - 1):
-    var it = n.sons[i]
+  for it in n.sons:
     case whichPragma(it)
     of wEmit: genEmit(p, it)
     of wBreakpoint: genBreakPoint(p, it)
@@ -1082,8 +1113,8 @@ proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType,
                           field: PSym) =
   var t = skipTypes(objtype, abstractVar)
   assert t.kind == tyObject
-  discard genTypeInfo(p.module, t)
-  var L = lengthOrd(field.typ)
+  discard genTypeInfo(p.module, t, a.lode.info)
+  var L = lengthOrd(p.config, field.typ)
   if not containsOrIncl(p.module.declaredThings, field.id):
     appcg(p.module, cfsVars, "extern $1",
           discriminatorTableDecl(p.module, t, field))
@@ -1102,19 +1133,46 @@ proc asgnFieldDiscriminant(p: BProc, e: PNode) =
   genDiscriminantCheck(p, a, tmp, dotExpr.sons[0].typ, dotExpr.sons[1].sym)
   genAssignment(p, a, tmp, {})
 
+proc patchAsgnStmtListExpr(father, orig, n: PNode) =
+  case n.kind
+  of nkDerefExpr, nkHiddenDeref:
+    let asgn = copyNode(orig)
+    asgn.add orig[0]
+    asgn.add n
+    father.add asgn
+  of nkStmtList, nkStmtListExpr:
+    for x in n:
+      patchAsgnStmtListExpr(father, orig, x)
+  else:
+    father.add n
+
 proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
   if e.sons[0].kind == nkSym and sfGoto in e.sons[0].sym.flags:
     genLineDir(p, e)
     genGotoVar(p, e.sons[1])
   elif not fieldDiscriminantCheckNeeded(p, e):
+    # this fixes bug #6422 but we really need to change the representation of
+    # arrays in the backend...
+    let le = e[0]
+    let ri = e[1]
+    var needsRepair = false
+    var it = ri
+    while it.kind in {nkStmtList, nkStmtListExpr}:
+      it = it.lastSon
+      needsRepair = true
+    if it.kind in {nkDerefExpr, nkHiddenDeref} and needsRepair:
+      var patchedTree = newNodeI(nkStmtList, e.info)
+      patchAsgnStmtListExpr(patchedTree, e, ri)
+      genStmts(p, patchedTree)
+      return
     var a: TLoc
-    if e[0].kind in {nkDerefExpr, nkHiddenDeref}:
-      genDeref(p, e[0], a, enforceDeref=true)
+    discard getTypeDesc(p.module, le.typ.skipTypes(skipPtrs))
+    if le.kind in {nkDerefExpr, nkHiddenDeref}:
+      genDeref(p, le, a, enforceDeref=true)
     else:
-      initLocExpr(p, e.sons[0], a)
+      initLocExpr(p, le, a)
     if fastAsgn: incl(a.flags, lfNoDeepCopy)
     assert(a.t != nil)
-    let ri = e.sons[1]
     genLineDir(p, ri)
     loadInto(p, e.sons[0], ri, a)
   else:
@@ -1123,5 +1181,9 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
 
 proc genStmts(p: BProc, t: PNode) =
   var a: TLoc
+
+  let isPush = hintExtendedContext in p.config.notes
+  if isPush: pushInfoContext(p.config, t.info)
   expr(p, t, a)
-  internalAssert a.k in {locNone, locTemp, locLocalVar}
+  if isPush: popInfoContext(p.config)
+  internalAssert p.config, a.k in {locNone, locTemp, locLocalVar, locExpr}
diff --git a/compiler/ccgthreadvars.nim b/compiler/ccgthreadvars.nim
index 505b69eab..3e8a87041 100644
--- a/compiler/ccgthreadvars.nim
+++ b/compiler/ccgthreadvars.nim
@@ -12,52 +12,39 @@
 
 # included from cgen.nim
 
-proc emulatedThreadVars(): bool =
-  result = {optThreads, optTlsEmulation} <= gGlobalOptions
+proc emulatedThreadVars(conf: ConfigRef): bool =
+  result = {optThreads, optTlsEmulation} <= conf.globalOptions
 
 proc accessThreadLocalVar(p: BProc, s: PSym) =
-  if emulatedThreadVars() and not p.threadVarAccessed:
+  if emulatedThreadVars(p.config) and not p.threadVarAccessed:
     p.threadVarAccessed = true
     incl p.module.flags, usesThreadVars
     addf(p.procSec(cpsLocals), "\tNimThreadVars* NimTV_;$n", [])
     add(p.procSec(cpsInit),
       ropecg(p.module, "\tNimTV_ = (NimThreadVars*) #GetThreadLocalVars();$n"))
 
-var
-  nimtv: Rope                 # Nim thread vars; the struct body
-  nimtvDeps: seq[PType] = @[]  # type deps: every module needs whole struct
-  nimtvDeclared = initIntSet() # so that every var/field exists only once
-                               # in the struct
-
-# 'nimtv' is incredibly hard to modularize! Best effort is to store all thread
-# vars in a ROD section and with their type deps and load them
-# unconditionally...
-
-# nimtvDeps is VERY hard to cache because it's not a list of IDs nor can it be
-# made to be one.
-
 proc declareThreadVar(m: BModule, s: PSym, isExtern: bool) =
-  if emulatedThreadVars():
+  if emulatedThreadVars(m.config):
     # we gather all thread locals var into a struct; we need to allocate
     # storage for that somehow, can't use the thread local storage
     # allocator for it :-(
-    if not containsOrIncl(nimtvDeclared, s.id):
-      nimtvDeps.add(s.loc.t)
-      addf(nimtv, "$1 $2;$n", [getTypeDesc(m, s.loc.t), s.loc.r])
+    if not containsOrIncl(m.g.nimtvDeclared, s.id):
+      m.g.nimtvDeps.add(s.loc.t)
+      addf(m.g.nimtv, "$1 $2;$n", [getTypeDesc(m, s.loc.t), s.loc.r])
   else:
     if isExtern: add(m.s[cfsVars], "extern ")
-    if optThreads in gGlobalOptions: add(m.s[cfsVars], "NIM_THREADVAR ")
+    if optThreads in m.config.globalOptions: add(m.s[cfsVars], "NIM_THREADVAR ")
     add(m.s[cfsVars], getTypeDesc(m, s.loc.t))
     addf(m.s[cfsVars], " $1;$n", [s.loc.r])
 
 proc generateThreadLocalStorage(m: BModule) =
-  if nimtv != nil and (usesThreadVars in m.flags or sfMainModule in m.module.flags):
-    for t in items(nimtvDeps): discard getTypeDesc(m, t)
-    addf(m.s[cfsSeqTypes], "typedef struct {$1} NimThreadVars;$n", [nimtv])
+  if m.g.nimtv != nil and (usesThreadVars in m.flags or sfMainModule in m.module.flags):
+    for t in items(m.g.nimtvDeps): discard getTypeDesc(m, t)
+    addf(m.s[cfsSeqTypes], "typedef struct {$1} NimThreadVars;$n", [m.g.nimtv])
 
 proc generateThreadVarsSize(m: BModule) =
-  if nimtv != nil:
-    let externc = if gCmd == cmdCompileToCpp or
+  if m.g.nimtv != nil:
+    let externc = if m.config.cmd == cmdCompileToCpp or
                        sfCompileToCpp in m.module.flags: "extern \"C\" "
                   else: ""
     addf(m.s[cfsProcs],
diff --git a/compiler/ccgtrav.nim b/compiler/ccgtrav.nim
index 982f88cbd..c69bb2c80 100644
--- a/compiler/ccgtrav.nim
+++ b/compiler/ccgtrav.nim
@@ -7,8 +7,7 @@
 #    distribution, for details about the copyright.
 #
 
-## Generates traversal procs for the C backend. Traversal procs are only an
-## optimization; the GC works without them too.
+## Generates traversal procs for the C backend.
 
 # included from cgen.nim
 
@@ -17,11 +16,11 @@ type
     p: BProc
     visitorFrmt: string
 
-proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType)
+proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType)
 proc genCaseRange(p: BProc, branch: PNode)
 proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false)
 
-proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, n: PNode;
+proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode;
                      typ: PType) =
   if n == nil: return
   case n.kind
@@ -29,12 +28,12 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, n: PNode;
     for i in countup(0, sonsLen(n) - 1):
       genTraverseProc(c, accessor, n.sons[i], typ)
   of nkRecCase:
-    if (n.sons[0].kind != nkSym): internalError(n.info, "genTraverseProc")
+    if (n.sons[0].kind != nkSym): internalError(c.p.config, n.info, "genTraverseProc")
     var p = c.p
     let disc = n.sons[0].sym
     if disc.loc.r == nil: fillObjectFields(c.p.module, typ)
     if disc.loc.t == nil:
-      internalError(n.info, "genTraverseProc()")
+      internalError(c.p.config, n.info, "genTraverseProc()")
     lineF(p, cpsStmts, "switch ($1.$2) {$n", [accessor, disc.loc.r])
     for i in countup(1, sonsLen(n) - 1):
       let branch = n.sons[i]
@@ -51,9 +50,9 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, n: PNode;
     if field.typ.kind == tyVoid: return
     if field.loc.r == nil: fillObjectFields(c.p.module, typ)
     if field.loc.t == nil:
-      internalError(n.info, "genTraverseProc()")
+      internalError(c.p.config, n.info, "genTraverseProc()")
     genTraverseProc(c, "$1.$2" % [accessor, field.loc.r], field.loc.t)
-  else: internalError(n.info, "genTraverseProc()")
+  else: internalError(c.p.config, n.info, "genTraverseProc()")
 
 proc parentObj(accessor: Rope; m: BModule): Rope {.inline.} =
   if not m.compileToCpp:
@@ -61,21 +60,29 @@ proc parentObj(accessor: Rope; m: BModule): Rope {.inline.} =
   else:
     result = accessor
 
-proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) =
+proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType)
+proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType) =
   if typ == nil: return
 
   var p = c.p
   case typ.kind
-  of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct:
+  of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred,
+     tySink:
     genTraverseProc(c, accessor, lastSon(typ))
   of tyArray:
-    let arraySize = lengthOrd(typ.sons[0])
+    let arraySize = lengthOrd(c.p.config, typ.sons[0])
     var i: TLoc
-    getTemp(p, getSysType(tyInt), i)
+    getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo(), tyInt), i)
+    let oldCode = p.s(cpsStmts)
     linefmt(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",
             i.r, arraySize.rope)
-    genTraverseProc(c, rfmt(nil, "$1[$2]", accessor, i.r), typ.sons[1])
-    lineF(p, cpsStmts, "}$n", [])
+    let oldLen = p.s(cpsStmts).len
+    genTraverseProc(c, ropecg(c.p.module, "$1[$2]", accessor, i.r), typ.sons[1])
+    if p.s(cpsStmts).len == oldLen:
+      # do not emit dummy long loops for faster debug builds:
+      p.s(cpsStmts) = oldCode
+    else:
+      lineF(p, cpsStmts, "}$n", [])
   of tyObject:
     for i in countup(0, sonsLen(typ) - 1):
       var x = typ.sons[i]
@@ -85,35 +92,49 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) =
   of tyTuple:
     let typ = getUniqueType(typ)
     for i in countup(0, sonsLen(typ) - 1):
-      genTraverseProc(c, rfmt(nil, "$1.Field$2", accessor, i.rope), typ.sons[i])
-  of tyRef, tyString, tySequence:
+      genTraverseProc(c, ropecg(c.p.module, "$1.Field$2", accessor, i.rope), typ.sons[i])
+  of tyRef:
     lineCg(p, cpsStmts, c.visitorFrmt, accessor)
+  of tySequence:
+    if tfHasAsgn notin typ.flags:
+      lineCg(p, cpsStmts, c.visitorFrmt, accessor)
+    elif containsGarbageCollectedRef(typ.lastSon):
+      # destructor based seqs are themselves not traced but their data is, if
+      # they contain a GC'ed type:
+      genTraverseProcSeq(c, accessor, typ)
+  of tyString:
+    if tfHasAsgn notin typ.flags:
+      lineCg(p, cpsStmts, c.visitorFrmt, accessor)
   of tyProc:
     if typ.callConv == ccClosure:
-      lineCg(p, cpsStmts, c.visitorFrmt, rfmt(nil, "$1.ClE_0", accessor))
+      lineCg(p, cpsStmts, c.visitorFrmt, ropecg(c.p.module, "$1.ClE_0", accessor))
   else:
     discard
 
-proc genTraverseProcSeq(c: var TTraversalClosure, accessor: Rope, typ: PType) =
+proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType) =
   var p = c.p
   assert typ.kind == tySequence
   var i: TLoc
-  getTemp(p, getSysType(tyInt), i)
-  lineF(p, cpsStmts, "for ($1 = 0; $1 < $2->$3; $1++) {$n",
-      [i.r, accessor, rope(if c.p.module.compileToCpp: "len" else: "Sup.len")])
-  genTraverseProc(c, "$1->data[$2]" % [accessor, i.r], typ.sons[0])
-  lineF(p, cpsStmts, "}$n", [])
-
-proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash;
-                     reason: TTypeInfoReason): Rope =
+  getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo(), tyInt), i)
+  let oldCode = p.s(cpsStmts)
+  var a: TLoc
+  a.r = accessor
+
+  lineF(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",
+      [i.r, lenExpr(c.p, a)])
+  let oldLen = p.s(cpsStmts).len
+  genTraverseProc(c, "$1$3[$2]" % [accessor, i.r, dataField(c.p)], typ.sons[0])
+  if p.s(cpsStmts).len == oldLen:
+    # do not emit dummy long loops for faster debug builds:
+    p.s(cpsStmts) = oldCode
+  else:
+    lineF(p, cpsStmts, "}$n", [])
+
+proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash): Rope =
   var c: TTraversalClosure
   var p = newProc(nil, m)
   result = "Marker_" & getTypeName(m, origTyp, sig)
-  let typ = origTyp.skipTypes(abstractInst)
-
-  case reason
-  of tiNew: c.visitorFrmt = "#nimGCvisit((void*)$1, op);$n"
-  else: assert false
+  var typ = origTyp.skipTypes(abstractInst)
 
   let header = "static N_NIMCALL(void, $1)(void* p, NI op)" % [result]
 
@@ -122,6 +143,8 @@ proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash;
   lineF(p, cpsInit, "a = ($1)p;$n", [t])
 
   c.p = p
+  c.visitorFrmt = "#nimGCvisit((void*)$1, op);$n"
+
   assert typ.kind != tyTypeDesc
   if typ.kind == tySequence:
     genTraverseProcSeq(c, "a".rope, typ)
@@ -138,15 +161,15 @@ proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash;
   m.s[cfsProcHeaders].addf("$1;$n", [header])
   m.s[cfsProcs].add(generatedProc)
 
-proc genTraverseProcForGlobal(m: BModule, s: PSym): Rope =
-  discard genTypeInfo(m, s.loc.t)
+proc genTraverseProcForGlobal(m: BModule, s: PSym; info: TLineInfo): Rope =
+  discard genTypeInfo(m, s.loc.t, info)
 
   var c: TTraversalClosure
   var p = newProc(nil, m)
   var sLoc = s.loc.r
   result = getTempName(m)
 
-  if sfThread in s.flags and emulatedThreadVars():
+  if sfThread in s.flags and emulatedThreadVars(m.config):
     accessThreadLocalVar(p, s)
     sLoc = "NimTV_->" & sLoc
 
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index 8fdd97428..266f63647 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -12,6 +12,9 @@
 # ------------------------- Name Mangling --------------------------------
 
 import sighashes
+from lowerings import createObj
+
+proc genProcHeader(m: BModule, prc: PSym): Rope
 
 proc isKeyword(w: PIdent): bool =
   # Nim and C++ share some keywords
@@ -41,40 +44,13 @@ when false:
     assert p.kind == skPackage
     result = gDebugInfo.register(p.name.s, m.name.s)
 
-proc idOrSig(m: BModule; s: PSym): Rope =
-  if s.kind in routineKinds and s.typ != nil:
-    # signatures for exported routines are reliable enough to
-    # produce a unique name and this means produced C++ is more stable wrt
-    # Nim changes:
-    let sig = hashProc(s)
-    result = rope($sig)
-    #let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m
-    let counter = m.sigConflicts.getOrDefault(sig)
-    #if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains":
-    #  echo "counter ", counter, " ", s.id
-    if counter != 0:
-      result.add "_" & rope(counter+1)
-    # this minor hack is necessary to make tests/collections/thashes compile.
-    # The inlined hash function's original module is ambiguous so we end up
-    # generating duplicate names otherwise:
-    if s.typ.callConv == ccInline:
-      result.add rope(m.module.name.s)
-    m.sigConflicts.inc(sig)
-  else:
-    let sig = hashNonProc(s)
-    result = rope($sig)
-    let counter = m.sigConflicts.getOrDefault(sig)
-    if counter != 0:
-      result.add "_" & rope(counter+1)
-    m.sigConflicts.inc(sig)
-
 proc mangleName(m: BModule; s: PSym): Rope =
   result = s.loc.r
   if result == nil:
     result = s.name.s.mangle.rope
-    add(result, m.idOrSig(s))
+    add(result, idOrSig(s, m.module.name.s.mangle, m.sigConflicts))
     s.loc.r = result
-    writeMangledName(m.ndi, s)
+    writeMangledName(m.ndi, s, m.config)
 
 proc mangleParamName(m: BModule; s: PSym): Rope =
   ## we cannot use 'sigConflicts' here since we have a BModule, not a BProc.
@@ -87,11 +63,11 @@ proc mangleParamName(m: BModule; s: PSym): Rope =
       res.add "_0"
     result = res.rope
     s.loc.r = result
-    writeMangledName(m.ndi, s)
+    writeMangledName(m.ndi, s, m.config)
 
 proc mangleLocalName(p: BProc; s: PSym): Rope =
   assert s.kind in skLocalVars+{skTemp}
-  assert sfGlobal notin s.flags
+  #assert sfGlobal notin s.flags
   result = s.loc.r
   if result == nil:
     var key = s.name.s.mangle
@@ -105,7 +81,7 @@ proc mangleLocalName(p: BProc; s: PSym): Rope =
       result.add "_" & rope(counter+1)
     p.sigConflicts.inc(key)
     s.loc.r = result
-    if s.kind != skTemp: writeMangledName(p.module.ndi, s)
+    if s.kind != skTemp: writeMangledName(p.module.ndi, s, p.config)
 
 proc scopeMangledParam(p: BProc; param: PSym) =
   ## parameter generation only takes BModule, not a BProc, so we have to
@@ -118,14 +94,15 @@ proc scopeMangledParam(p: BProc; param: PSym) =
 
 const
   irrelevantForBackend = {tyGenericBody, tyGenericInst, tyGenericInvocation,
-                          tyDistinct, tyRange, tyStatic, tyAlias}
+                          tyDistinct, tyRange, tyStatic, tyAlias, tySink, tyInferred}
 
 proc typeName(typ: PType): Rope =
   let typ = typ.skipTypes(irrelevantForBackend)
-  result = if typ.sym != nil and typ.kind in {tyObject, tyEnum}:
-             typ.sym.name.s.mangle.rope
-           else:
-             ~"TY"
+  result =
+    if typ.sym != nil and typ.kind in {tyObject, tyEnum}:
+      rope($typ.kind & '_' & typ.sym.name.s.mangle)
+    else:
+      rope($typ.kind)
 
 proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope =
   var t = typ
@@ -137,7 +114,7 @@ proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope =
       t = t.lastSon
     else:
       break
-  let typ = if typ.kind == tyAlias: typ.lastSon else: typ
+  let typ = if typ.kind in {tyAlias, tySink}: typ.lastSon else: typ
   if typ.loc.r == nil:
     typ.loc.r = typ.typeName & $sig
   else:
@@ -145,46 +122,49 @@ proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope =
       # check consistency:
       assert($typ.loc.r == $(typ.typeName & $sig))
   result = typ.loc.r
-  if result == nil: internalError("getTypeName: " & $typ.kind)
+  if result == nil: internalError(m.config, "getTypeName: " & $typ.kind)
 
-proc mapSetType(typ: PType): TCTypeKind =
-  case int(getSize(typ))
+proc mapSetType(conf: ConfigRef; typ: PType): TCTypeKind =
+  case int(getSize(conf, typ))
   of 1: result = ctInt8
   of 2: result = ctInt16
   of 4: result = ctInt32
   of 8: result = ctInt64
   else: result = ctArray
 
-proc mapType(typ: PType): TCTypeKind =
+proc mapType(conf: ConfigRef; typ: PType): TCTypeKind =
   ## Maps a Nim type to a C type
   case typ.kind
   of tyNone, tyStmt: result = ctVoid
   of tyBool: result = ctBool
   of tyChar: result = ctChar
-  of tySet: result = mapSetType(typ)
-  of tyOpenArray, tyArray, tyVarargs: result = ctArray
+  of tySet: result = mapSetType(conf, typ)
+  of tyOpenArray, tyArray, tyVarargs, tyUncheckedArray: result = ctArray
   of tyObject, tyTuple: result = ctStruct
+  of tyUserTypeClasses:
+    doAssert typ.isResolvedUserTypeClass
+    return mapType(conf, typ.lastSon)
   of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal,
-     tyTypeDesc, tyAlias:
-    result = mapType(lastSon(typ))
+     tyTypeDesc, tyAlias, tySink, tyInferred:
+    result = mapType(conf, lastSon(typ))
   of tyEnum:
-    if firstOrd(typ) < 0:
+    if firstOrd(conf, typ) < 0:
       result = ctInt32
     else:
-      case int(getSize(typ))
+      case int(getSize(conf, typ))
       of 1: result = ctUInt8
       of 2: result = ctUInt16
       of 4: result = ctInt32
       of 8: result = ctInt64
-      else: internalError("mapType")
-  of tyRange: result = mapType(typ.sons[0])
-  of tyPtr, tyVar, tyRef:
+      else: result = ctInt32
+  of tyRange: result = mapType(conf, typ.sons[0])
+  of tyPtr, tyVar, tyLent, tyRef, tyOptAsRef:
     var base = skipTypes(typ.lastSon, typedescInst)
     case base.kind
-    of tyOpenArray, tyArray, tyVarargs: result = ctPtrToArray
-    #of tySet:
-    #  if mapSetType(base) == ctArray: result = ctPtrToArray
-    #  else: result = ctPtr
+    of tyOpenArray, tyArray, tyVarargs, tyUncheckedArray: result = ctPtrToArray
+    of tySet:
+      if mapSetType(conf, base) == ctArray: result = ctPtrToArray
+      else: result = ctPtr
     # XXX for some reason this breaks the pegs module
     else: result = ctPtr
   of tyPointer: result = ctPtr
@@ -195,14 +175,14 @@ proc mapType(typ: PType): TCTypeKind =
   of tyInt..tyUInt64:
     result = TCTypeKind(ord(typ.kind) - ord(tyInt) + ord(ctInt))
   of tyStatic:
-    if typ.n != nil: result = mapType(lastSon typ)
-    else: internalError("mapType")
-  else: internalError("mapType")
+    if typ.n != nil: result = mapType(conf, lastSon typ)
+    else: doAssert(false, "mapType")
+  else: doAssert(false, "mapType")
 
-proc mapReturnType(typ: PType): TCTypeKind =
+proc mapReturnType(conf: ConfigRef; typ: PType): TCTypeKind =
   #if skipTypes(typ, typedescInst).kind == tyArray: result = ctPtr
   #else:
-  result = mapType(typ)
+  result = mapType(conf, typ)
 
 proc isImportedType(t: PType): bool =
   result = t.sym != nil and sfImportc in t.sym.flags
@@ -213,28 +193,26 @@ proc isImportedCppType(t: PType): bool =
            (x.sym != nil and sfInfixCall in x.sym.flags)
 
 proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope
-proc needsComplexAssignment(typ: PType): bool =
-  result = containsGarbageCollectedRef(typ)
 
 proc isObjLackingTypeField(typ: PType): bool {.inline.} =
   result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and
       (typ.sons[0] == nil) or isPureObject(typ))
 
-proc isInvalidReturnType(rettype: PType): bool =
+proc isInvalidReturnType(conf: ConfigRef; rettype: PType): bool =
   # Arrays and sets cannot be returned by a C procedure, because C is
   # such a poor programming language.
   # We exclude records with refs too. This enhances efficiency and
   # is necessary for proper code generation of assignments.
   if rettype == nil: result = true
   else:
-    case mapType(rettype)
+    case mapType(conf, rettype)
     of ctArray:
       result = not (skipTypes(rettype, typedescInst).kind in
-          {tyVar, tyRef, tyPtr})
+          {tyVar, tyLent, tyRef, tyPtr})
     of ctStruct:
       let t = skipTypes(rettype, typedescInst)
       if rettype.isImportedCppType or t.isImportedCppType: return false
-      result = needsComplexAssignment(t) or
+      result = containsGarbageCollectedRef(t) or
           (t.kind == tyObject and not isObjLackingTypeField(t))
     else: result = false
 
@@ -252,21 +230,20 @@ proc cacheGetType(tab: TypeCache; sig: SigHash): Rope =
   result = tab.getOrDefault(sig)
 
 proc addAbiCheck(m: BModule, t: PType, name: Rope) =
-  if isDefined("checkabi"):
-    addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [name, rope(getSize(t))])
-
-proc getTempName(m: BModule): Rope =
-  result = m.tmpBase & rope(m.labels)
-  inc m.labels
+  if isDefined(m.config, "checkabi"):
+    addf(m.s[cfsTypeInfo], "NIM_CHECK_SIZE($1, $2);$n", [name, rope(getSize(m.config, t))])
 
-proc ccgIntroducedPtr(s: PSym): bool =
+proc ccgIntroducedPtr(conf: ConfigRef; s: PSym): bool =
   var pt = skipTypes(s.typ, typedescInst)
   assert skResult != s.kind
   if tfByRef in pt.flags: return true
   elif tfByCopy in pt.flags: return false
   case pt.kind
   of tyObject:
-    if (optByRef in s.options) or (getSize(pt) > platform.floatSize * 2):
+    if s.typ.sym != nil and sfForward in s.typ.sym.flags:
+      # forwarded objects are *always* passed by pointers for consistency!
+      result = true
+    elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3):
       result = true           # requested anyway
     elif (tfFinal in pt.flags) and (pt.sons[0] == nil):
       result = false          # no need, because no subtyping possible
@@ -274,15 +251,16 @@ proc ccgIntroducedPtr(s: PSym): bool =
       result = true           # ordinary objects are always passed by reference,
                               # otherwise casting doesn't work
   of tyTuple:
-    result = (getSize(pt) > platform.floatSize*2) or (optByRef in s.options)
+    result = (getSize(conf, pt) > conf.target.floatSize*3) or (optByRef in s.options)
   else: result = false
 
-proc fillResult(param: PSym) =
-  fillLoc(param.loc, locParam, param.typ, ~"Result",
+proc fillResult(conf: ConfigRef; param: PNode) =
+  fillLoc(param.sym.loc, locParam, param, ~"Result",
           OnStack)
-  if mapReturnType(param.typ) != ctArray and isInvalidReturnType(param.typ):
-    incl(param.loc.flags, lfIndirect)
-    param.loc.s = OnUnknown
+  let t = param.sym.typ
+  if mapReturnType(conf, t) != ctArray and isInvalidReturnType(conf, t):
+    incl(param.sym.loc.flags, lfIndirect)
+    param.sym.loc.storage = OnUnknown
 
 proc typeNameOrLiteral(m: BModule; t: PType, literal: string): Rope =
   if t.sym != nil and sfImportc in t.sym.flags and t.sym.magic == mNone:
@@ -300,19 +278,24 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope =
   of tyPointer:
     result = typeNameOrLiteral(m, typ, "void*")
   of tyString:
-    discard cgsym(m, "NimStringDesc")
-    result = typeNameOrLiteral(m, typ, "NimStringDesc*")
+    case detectStrVersion(m)
+    of 2:
+      discard cgsym(m, "NimStringV2")
+      result = typeNameOrLiteral(m, typ, "NimStringV2")
+    else:
+      discard cgsym(m, "NimStringDesc")
+      result = typeNameOrLiteral(m, typ, "NimStringDesc*")
   of tyCString: result = typeNameOrLiteral(m, typ, "NCSTRING")
   of tyBool: result = typeNameOrLiteral(m, typ, "NIM_BOOL")
   of tyChar: result = typeNameOrLiteral(m, typ, "NIM_CHAR")
-  of tyNil: result = typeNameOrLiteral(m, typ, "0")
+  of tyNil: result = typeNameOrLiteral(m, typ, "void*")
   of tyInt..tyUInt64:
     result = typeNameOrLiteral(m, typ, NumericalTypeToStr[typ.kind])
   of tyDistinct, tyRange, tyOrdinal: result = getSimpleTypeDesc(m, typ.sons[0])
   of tyStatic:
     if typ.n != nil: result = getSimpleTypeDesc(m, lastSon typ)
-    else: internalError("tyStatic for getSimpleTypeDesc")
-  of tyGenericInst, tyAlias:
+    else: internalError(m.config, "tyStatic for getSimpleTypeDesc")
+  of tyGenericInst, tyAlias, tySink:
     result = getSimpleTypeDesc(m, lastSon typ)
   else: result = nil
 
@@ -332,18 +315,23 @@ proc getTypePre(m: BModule, typ: PType; sig: SigHash): Rope =
     if result == nil: result = cacheGetType(m.typeCache, sig)
 
 proc structOrUnion(t: PType): Rope =
+  let t = t.skipTypes({tyAlias, tySink})
   (if tfUnion in t.flags: rope("union") else: rope("struct"))
 
 proc getForwardStructFormat(m: BModule): string =
   if m.compileToCpp: result = "$1 $2;$n"
   else: result = "typedef $1 $2 $2;$n"
 
+proc seqStar(m: BModule): string =
+  if m.config.selectedGC == gcDestructors: result = ""
+  else: result = "*"
+
 proc getTypeForward(m: BModule, typ: PType; sig: SigHash): Rope =
   result = cacheGetType(m.forwTypeCache, sig)
   if result != nil: return
   result = getTypePre(m, typ, sig)
   if result != nil: return
-  let concrete = typ.skipTypes(abstractInst)
+  let concrete = typ.skipTypes(abstractInst + {tyOpt})
   case concrete.kind
   of tySequence, tyTuple, tyObject:
     result = getTypeName(m, typ, sig)
@@ -351,8 +339,10 @@ proc getTypeForward(m: BModule, typ: PType; sig: SigHash): Rope =
     if not isImportedType(concrete):
       addf(m.s[cfsForwardTypes], getForwardStructFormat(m),
           [structOrUnion(typ), result])
+    else:
+      pushType(m, concrete)
     doAssert m.forwTypeCache[sig] == result
-  else: internalError("getTypeForward(" & $typ.kind & ')')
+  else: internalError(m.config, "getTypeForward(" & $typ.kind & ')')
 
 proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet): Rope =
   ## like getTypeDescAux but creates only a *weak* dependency. In other words
@@ -367,13 +357,16 @@ proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet): Rope =
       result = getTypeForward(m, t, hashType(t))
       pushType(m, t)
   of tySequence:
-    result = getTypeForward(m, t, hashType(t)) & "*"
-    pushType(m, t)
+    if m.config.selectedGC == gcDestructors:
+      result = getTypeDescAux(m, t, check)
+    else:
+      result = getTypeForward(m, t, hashType(t)) & seqStar(m)
+      pushType(m, t)
   else:
     result = getTypeDescAux(m, t, check)
 
 proc paramStorageLoc(param: PSym): TStorageLoc =
-  if param.typ.skipTypes({tyVar, tyTypeDesc}).kind notin {
+  if param.typ.skipTypes({tyVar, tyLent, tyTypeDesc}).kind notin {
           tyArray, tyOpenArray, tyVarargs}:
     result = OnStack
   else:
@@ -383,22 +376,22 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope,
                    check: var IntSet, declareEnvironment=true;
                    weakDep=false) =
   params = nil
-  if t.sons[0] == nil or isInvalidReturnType(t.sons[0]):
+  if t.sons[0] == nil or isInvalidReturnType(m.config, t.sons[0]):
     rettype = ~"void"
   else:
     rettype = getTypeDescAux(m, t.sons[0], check)
   for i in countup(1, sonsLen(t.n) - 1):
-    if t.n.sons[i].kind != nkSym: internalError(t.n.info, "genProcParams")
+    if t.n.sons[i].kind != nkSym: internalError(m.config, t.n.info, "genProcParams")
     var param = t.n.sons[i].sym
     if isCompileTimeOnly(param.typ): continue
     if params != nil: add(params, ~", ")
-    fillLoc(param.loc, locParam, param.typ, mangleParamName(m, param),
+    fillLoc(param.loc, locParam, t.n.sons[i], mangleParamName(m, param),
             param.paramStorageLoc)
-    if ccgIntroducedPtr(param):
+    if ccgIntroducedPtr(m.config, param):
       add(params, getTypeDescWeak(m, param.typ, check))
       add(params, ~"*")
       incl(param.loc.flags, lfIndirect)
-      param.loc.s = OnUnknown
+      param.loc.storage = OnUnknown
     elif weakDep:
       add(params, getTypeDescWeak(m, param.typ, check))
     else:
@@ -407,19 +400,19 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope,
     add(params, param.loc.r)
     # declare the len field for open arrays:
     var arr = param.typ
-    if arr.kind == tyVar: arr = arr.sons[0]
+    if arr.kind in {tyVar, tyLent}: arr = arr.lastSon
     var j = 0
     while arr.kind in {tyOpenArray, tyVarargs}:
       # this fixes the 'sort' bug:
-      if param.typ.kind == tyVar: param.loc.s = OnUnknown
+      if param.typ.kind in {tyVar, tyLent}: param.loc.storage = OnUnknown
       # need to pass hidden parameter:
       addf(params, ", NI $1Len_$2", [param.loc.r, j.rope])
       inc(j)
       arr = arr.sons[0]
-  if t.sons[0] != nil and isInvalidReturnType(t.sons[0]):
+  if t.sons[0] != nil and isInvalidReturnType(m.config, t.sons[0]):
     var arr = t.sons[0]
     if params != nil: add(params, ", ")
-    if mapReturnType(t.sons[0]) != ctArray:
+    if mapReturnType(m.config, t.sons[0]) != ctArray:
       add(params, getTypeDescWeak(m, arr, check))
       add(params, "*")
     else:
@@ -435,13 +428,12 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope,
   else: add(params, ")")
   params = "(" & params
 
-proc mangleRecFieldName(m: BModule; field: PSym, rectype: PType): Rope =
-  if (rectype.sym != nil) and
-      ({sfImportc, sfExportc} * rectype.sym.flags != {}):
+proc mangleRecFieldName(m: BModule; field: PSym): Rope =
+  if {sfImportc, sfExportc} * field.flags != {}:
     result = field.loc.r
   else:
     result = rope(mangleField(m, field.name))
-  if result == nil: internalError(field.info, "mangleRecFieldName")
+  if result == nil: internalError(m.config, field.info, "mangleRecFieldName")
 
 proc genRecordFieldsAux(m: BModule, n: PNode,
                         accessExpr: Rope, rectype: PType,
@@ -452,9 +444,11 @@ proc genRecordFieldsAux(m: BModule, n: PNode,
     for i in countup(0, sonsLen(n) - 1):
       add(result, genRecordFieldsAux(m, n.sons[i], accessExpr, rectype, check))
   of nkRecCase:
-    if n.sons[0].kind != nkSym: internalError(n.info, "genRecordFieldsAux")
+    if n.sons[0].kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux")
     add(result, genRecordFieldsAux(m, n.sons[0], accessExpr, rectype, check))
-    let uname = rope(mangle(n.sons[0].sym.name.s) & 'U')
+    # prefix mangled name with "_U" to avoid clashes with other field names,
+    # since identifiers are not allowed to start with '_'
+    let uname = rope("_U" & mangle(n.sons[0].sym.name.s))
     let ae = if accessExpr != nil: "$1.$2" % [accessExpr, uname]
              else: uname
     var unionBody: Rope = nil
@@ -467,31 +461,39 @@ proc genRecordFieldsAux(m: BModule, n: PNode,
           let a = genRecordFieldsAux(m, k, "$1.$2" % [ae, sname], rectype,
                                      check)
           if a != nil:
-            add(unionBody, "struct {")
+            if tfPacked notin rectype.flags:
+              add(unionBody, "struct {")
+            else:
+              if hasAttribute in CC[m.config.cCompiler].props:
+                add(unionBody, "struct __attribute__((__packed__)){" )
+              else:
+                addf(unionBody, "#pragma pack(push, 1)$nstruct{", [])
             add(unionBody, a)
             addf(unionBody, "} $1;$n", [sname])
+            if tfPacked in rectype.flags and hasAttribute notin CC[m.config.cCompiler].props:
+              addf(unionBody, "#pragma pack(pop)$n", [])
         else:
           add(unionBody, genRecordFieldsAux(m, k, ae, rectype, check))
-      else: internalError("genRecordFieldsAux(record case branch)")
+      else: internalError(m.config, "genRecordFieldsAux(record case branch)")
     if unionBody != nil:
       addf(result, "union{$n$1} $2;$n", [unionBody, uname])
   of nkSym:
     let field = n.sym
     if field.typ.kind == tyVoid: return
     #assert(field.ast == nil)
-    let sname = mangleRecFieldName(m, field, rectype)
+    let sname = mangleRecFieldName(m, field)
     let ae = if accessExpr != nil: "$1.$2" % [accessExpr, sname]
              else: sname
-    fillLoc(field.loc, locField, field.typ, ae, OnUnknown)
+    fillLoc(field.loc, locField, n, ae, OnUnknown)
     # for importcpp'ed objects, we only need to set field.loc, but don't
     # have to recurse via 'getTypeDescAux'. And not doing so prevents problems
     # with heavily templatized C++ code:
     if not isImportedCppType(rectype):
-      let fieldType = field.loc.t.skipTypes(abstractInst)
-      if fieldType.kind == tyArray and tfUncheckedArray in fieldType.flags:
+      let fieldType = field.loc.lode.typ.skipTypes(abstractInst)
+      if fieldType.kind == tyUncheckedArray:
         addf(result, "$1 $2[SEQ_DECL_SIZE];$n",
             [getTypeDescAux(m, fieldType.elemType, check), sname])
-      elif fieldType.kind == tySequence:
+      elif fieldType.kind == tySequence and m.config.selectedGC != gcDestructors:
         # we need to use a weak dependency here for trecursive_table.
         addf(result, "$1 $2;$n", [getTypeDescWeak(m, field.loc.t, check), sname])
       elif field.bitsize != 0:
@@ -500,7 +502,7 @@ proc genRecordFieldsAux(m: BModule, n: PNode,
         # don't use fieldType here because we need the
         # tyGenericInst for C++ template support
         addf(result, "$1 $2;$n", [getTypeDescAux(m, field.loc.t, check), sname])
-  else: internalError(n.info, "genRecordFieldsAux()")
+  else: internalError(m.config, n.info, "genRecordFieldsAux()")
 
 proc getRecordFields(m: BModule, typ: PType, check: var IntSet): Rope =
   result = genRecordFieldsAux(m, typ.n, nil, typ, check)
@@ -516,12 +518,16 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
   # declare the record:
   var hasField = false
 
-  var attribute: Rope =
-    if tfPacked in typ.flags: rope(CC[cCompiler].packedPragma)
-    else: nil
+  if tfPacked in typ.flags:
+    if hasAttribute in CC[m.config.cCompiler].props:
+      result = structOrUnion(typ) & " __attribute__((__packed__))"
+    else:
+      result = "#pragma pack(push, 1)\L" & structOrUnion(typ)
+  else:
+    result = structOrUnion(typ)
 
-  result = ropecg(m, CC[cCompiler].structStmtFmt,
-    [structOrUnion(typ), name, attribute])
+  result.add " "
+  result.add name
 
   if typ.kind == tyObject:
 
@@ -529,11 +535,21 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
       if (typ.sym != nil and sfPure in typ.sym.flags) or tfFinal in typ.flags:
         appcg(m, result, " {$n", [])
       else:
-        appcg(m, result, " {$n#TNimType* m_type;$n", [name, attribute])
+        appcg(m, result, " {$n#TNimType* m_type;$n", [])
         hasField = true
     elif m.compileToCpp:
       appcg(m, result, " : public $1 {$n",
                       [getTypeDescAux(m, typ.sons[0].skipTypes(skipPtrs), check)])
+      if typ.isException:
+        appcg(m, result, "virtual void raise() {throw *this;}$n") # required for polymorphic exceptions
+        if typ.sym.magic == mException:
+          # Add cleanup destructor to Exception base class
+          appcg(m, result, "~$1() {if(this->raiseId) popCurrentExceptionEx(this->raiseId);}$n", [name])
+          # hack: forward declare popCurrentExceptionEx() on top of type description,
+          # proper request to generate popCurrentExceptionEx not possible for 2 reasons:
+          # generated function will be below declared Exception type and circular dependency
+          # between Exception and popCurrentExceptionEx function
+          result = genProcHeader(m, magicsys.getCompilerProc(m.g.graph, "popCurrentExceptionEx")) & ";" & result
       hasField = true
     else:
       appcg(m, result, " {$n  $1 Sup;$n",
@@ -547,7 +563,9 @@ proc getRecordDesc(m: BModule, typ: PType, name: Rope,
     addf(result, "char dummy;$n", [])
   else:
     add(result, desc)
-  add(result, "};" & tnl)
+  add(result, "};\L")
+  if tfPacked in typ.flags and hasAttribute notin CC[m.config.cCompiler].props:
+    result.add "#pragma pack(pop)\L"
 
 proc getTupleDesc(m: BModule, typ: PType, name: Rope,
                   check: var IntSet): Rope =
@@ -556,9 +574,9 @@ proc getTupleDesc(m: BModule, typ: PType, name: Rope,
   for i in countup(0, sonsLen(typ) - 1):
     addf(desc, "$1 Field$2;$n",
          [getTypeDescAux(m, typ.sons[i], check), rope(i)])
-  if desc == nil: add(result, "char dummy;" & tnl)
+  if desc == nil: add(result, "char dummy;\L")
   else: add(result, desc)
-  add(result, "};" & tnl)
+  add(result, "};\L")
 
 proc scanCppGenericSlot(pat: string, cursor, outIdx, outStars: var int): bool =
   # A helper proc for handling cppimport patterns, involving numeric
@@ -578,8 +596,10 @@ proc scanCppGenericSlot(pat: string, cursor, outIdx, outStars: var int): bool =
     return false
 
 proc resolveStarsInCppType(typ: PType, idx, stars: int): PType =
-  # XXX: we should catch this earlier and report it as a semantic error
-  if idx >= typ.len: internalError "invalid apostrophe type parameter index"
+  # Make sure the index refers to one of the generic params of the type.
+  # XXX: we should catch this earlier and report it as a semantic error.
+  if idx >= typ.len:
+    doAssert false, "invalid apostrophe type parameter index"
 
   result = typ.sons[idx]
   for i in 1..stars:
@@ -587,12 +607,21 @@ proc resolveStarsInCppType(typ: PType, idx, stars: int): PType =
       result = if result.kind == tyGenericInst: result.sons[1]
                else: result.elemType
 
+proc getSeqPayloadType(m: BModule; t: PType): Rope =
+  result = getTypeForward(m, t, hashType(t)) & "_Content"
+  when false:
+    var check = initIntSet()
+    # XXX remove this duplication:
+    appcg(m, m.s[cfsSeqTypes],
+      "struct $2_Content { NI cap; void* allocator; $1 data[SEQ_DECL_SIZE]; };$n",
+      [getTypeDescAux(m, t.sons[0], check), result])
+
 proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
   # returns only the type's name
   var t = origTyp.skipTypes(irrelevantForBackend)
   if containsOrIncl(check, t.id):
     if not (isImportedCppType(origTyp) or isImportedCppType(t)):
-      internalError("cannot generate C type for: " & typeToString(origTyp))
+      internalError(m.config, "cannot generate C type for: " & typeToString(origTyp))
     # XXX: this BUG is hard to fix -> we need to introduce helper structs,
     # but determining when this needs to be done is hard. We should split
     # C type generation into an analysis and a code generation phase somehow.
@@ -604,15 +633,16 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
     excl(check, t.id)
     return
   case t.kind
-  of tyRef, tyPtr, tyVar:
+  of tyRef, tyOptAsRef, tyPtr, tyVar, tyLent:
     var star = if t.kind == tyVar and tfVarIsPtr notin origTyp.flags and
                     compileToCpp(m): "&" else: "*"
     var et = origTyp.skipTypes(abstractInst).lastSon
     var etB = et.skipTypes(abstractInst)
-    if etB.kind in {tyArray, tyOpenArray, tyVarargs}:
-      # this is correct! sets have no proper base type, so we treat
-      # ``var set[char]`` in `getParamTypeDesc`
-      et = elemType(etB)
+    if mapType(m.config, t) == ctPtrToArray:
+      if etB.kind == tySet:
+        et = getSysType(m.g.graph, unknownLineInfo(), tyUInt8)
+      else:
+        et = elemType(etB)
       etB = et.skipTypes(abstractInst)
       star[0] = '*'
     case etB.kind
@@ -624,11 +654,10 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
         let name = getTypeForward(m, et, hashType et)
         result = name & star
         m.typeCache[sig] = result
-        pushType(m, et)
     of tySequence:
       # no restriction! We have a forward declaration for structs
       let name = getTypeForward(m, et, hashType et)
-      result = name & "*" & star
+      result = name & seqStar(m) & star
       m.typeCache[sig] = result
       pushType(m, et)
     else:
@@ -646,17 +675,17 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
           (sfImportc in t.sym.flags and t.sym.magic == mNone)):
         m.typeCache[sig] = result
         var size: int
-        if firstOrd(t) < 0:
+        if firstOrd(m.config, t) < 0:
           addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result])
           size = 4
         else:
-          size = int(getSize(t))
+          size = int(getSize(m.config, t))
           case size
           of 1: addf(m.s[cfsTypes], "typedef NU8 $1;$n", [result])
           of 2: addf(m.s[cfsTypes], "typedef NU16 $1;$n", [result])
           of 4: addf(m.s[cfsTypes], "typedef NI32 $1;$n", [result])
           of 8: addf(m.s[cfsTypes], "typedef NI64 $1;$n", [result])
-          else: internalError(t.sym.info, "getTypeDescAux: enum")
+          else: internalError(m.config, t.sym.info, "getTypeDescAux: enum")
         when false:
           let owner = hashOwner(t.sym)
           if not gDebugInfo.hasEnum(t.sym.name.s, t.sym.info.line, owner):
@@ -692,22 +721,37 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
             [structOrUnion(t), result])
       m.forwTypeCache[sig] = result
     assert(cacheGetType(m.typeCache, sig) == nil)
-    m.typeCache[sig] = result & "*"
+    m.typeCache[sig] = result & seqStar(m)
     if not isImportedType(t):
       if skipTypes(t.sons[0], typedescInst).kind != tyEmpty:
         const
           cppSeq = "struct $2 : #TGenericSeq {$n"
           cSeq = "struct $2 {$n" &
                  "  #TGenericSeq Sup;$n"
-        appcg(m, m.s[cfsSeqTypes],
-            (if m.compileToCpp: cppSeq else: cSeq) &
-            "  $1 data[SEQ_DECL_SIZE];$n" &
+        if m.config.selectedGC == gcDestructors:
+          appcg(m, m.s[cfsTypes],
+            "typedef struct{ NI cap;void* allocator;$1 data[SEQ_DECL_SIZE];}$2_Content;$n" &
+            "struct $2 {$n" &
+            "  NI len; $2_Content* p;$n" &
             "};$n", [getTypeDescAux(m, t.sons[0], check), result])
+        else:
+          appcg(m, m.s[cfsSeqTypes],
+              (if m.compileToCpp: cppSeq else: cSeq) &
+              "  $1 data[SEQ_DECL_SIZE];$n" &
+              "};$n", [getTypeDescAux(m, t.sons[0], check), result])
+      elif m.config.selectedGC == gcDestructors:
+        internalError(m.config, "cannot map the empty seq type to a C type")
       else:
         result = rope("TGenericSeq")
-    add(result, "*")
+    add(result, seqStar(m))
+  of tyUncheckedArray:
+    result = getTypeName(m, origTyp, sig)
+    m.typeCache[sig] = result
+    if not isImportedType(t):
+      let foo = getTypeDescAux(m, t.sons[0], check)
+      addf(m.s[cfsTypes], "typedef $1 $2[1];$n", [foo, result])
   of tyArray:
-    var n: BiggestInt = lengthOrd(t)
+    var n: BiggestInt = lengthOrd(m.config, t)
     if n <= 0: n = 1   # make an array of at least one element
     result = getTypeName(m, origTyp, sig)
     m.typeCache[sig] = result
@@ -718,24 +762,29 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
     else: addAbiCheck(m, t, result)
   of tyObject, tyTuple:
     if isImportedCppType(t) and origTyp.kind == tyGenericInst:
-      # for instantiated templates we do not go through the type cache as the
-      # the type cache is not aware of 'tyGenericInst'.
       let cppName = getTypeName(m, t, sig)
       var i = 0
       var chunkStart = 0
+
+      template addResultType(ty: untyped) =
+        if ty == nil or ty.kind == tyVoid:
+          result.add(~"void")
+        elif ty.kind == tyStatic:
+          internalAssert m.config, ty.n != nil
+          result.add ty.n.renderTree
+        else:
+          result.add getTypeDescAux(m, ty, check)
+
       while i < cppName.data.len:
         if cppName.data[i] == '\'':
-          var chunkEnd = <i
+          var chunkEnd = i-1
           var idx, stars: int
           if scanCppGenericSlot(cppName.data, i, idx, stars):
             result.add cppName.data.substr(chunkStart, chunkEnd)
             chunkStart = i
 
             let typeInSlot = resolveStarsInCppType(origTyp, idx + 1, stars)
-            if typeInSlot == nil or typeInSlot.kind == tyVoid:
-              result.add(~"void")
-            else:
-              result.add getTypeDescAux(m, typeInSlot, check)
+            addResultType(typeInSlot)
         else:
           inc i
 
@@ -745,11 +794,18 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
         result = cppName & "<"
         for i in 1 .. origTyp.len-2:
           if i > 1: result.add(" COMMA ")
-          result.add(getTypeDescAux(m, origTyp.sons[i], check))
+          addResultType(origTyp.sons[i])
         result.add("> ")
       # always call for sideeffects:
       assert t.kind != tyTuple
       discard getRecordDesc(m, t, result, check)
+      # The resulting type will include commas and these won't play well
+      # with the C macros for defining procs such as N_NIMCALL. We must
+      # create a typedef for the type and use it in the proc signature:
+      let typedefName = ~"TY" & $sig
+      addf(m.s[cfsTypes], "typedef $1 $2;$n", [result, typedefName])
+      m.typeCache[sig] = typedefName
+      result = typedefName
     else:
       when false:
         if t.sym != nil and t.sym.name.s == "KeyValuePair":
@@ -773,24 +829,26 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
              [structOrUnion(t), result])
         assert m.forwTypeCache[sig] == result
       m.typeCache[sig] = result # always call for sideeffects:
-      let recdesc = if t.kind != tyTuple: getRecordDesc(m, t, result, check)
-                    else: getTupleDesc(m, t, result, check)
-      if not isImportedType(t):
-        add(m.s[cfsTypes], recdesc)
-      elif tfIncompleteStruct notin t.flags: addAbiCheck(m, t, result)
+      if not incompleteType(t):
+        let recdesc = if t.kind != tyTuple: getRecordDesc(m, t, result, check)
+                      else: getTupleDesc(m, t, result, check)
+        if not isImportedType(t):
+          add(m.s[cfsTypes], recdesc)
+        elif tfIncompleteStruct notin t.flags: addAbiCheck(m, t, result)
   of tySet:
-    result = getTypeName(m, t.lastSon, hashType t.lastSon) & "_Set"
+    result = $t.kind & '_' & getTypeName(m, t.lastSon, hashType t.lastSon)
     m.typeCache[sig] = result
     if not isImportedType(t):
-      let s = int(getSize(t))
+      let s = int(getSize(m.config, t))
       case s
       of 1, 2, 4, 8: addf(m.s[cfsTypes], "typedef NU$2 $1;$n", [result, rope(s*8)])
       else: addf(m.s[cfsTypes], "typedef NU8 $1[$2];$n",
-             [result, rope(getSize(t))])
-  of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias:
+             [result, rope(getSize(m.config, t))])
+  of tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tySink,
+     tyUserTypeClass, tyUserTypeClassInst, tyInferred:
     result = getTypeDescAux(m, lastSon(t), check)
   else:
-    internalError("getTypeDescAux(" & $t.kind & ')')
+    internalError(m.config, "getTypeDescAux(" & $t.kind & ')')
     result = nil
   # fixes bug #145:
   excl(check, t.id)
@@ -800,8 +858,10 @@ proc getTypeDesc(m: BModule, typ: PType): Rope =
   result = getTypeDescAux(m, typ, check)
 
 type
-  TClosureTypeKind = enum
-    clHalf, clHalfWithEnv, clFull
+  TClosureTypeKind = enum ## In C closures are mapped to 3 different things.
+    clHalf,           ## fn(args) type without the trailing 'void* env' parameter
+    clHalfWithEnv,    ## fn(args, void* env) type with trailing 'void* env' parameter
+    clFull            ## struct {fn(args, void* env), env}
 
 proc getClosureType(m: BModule, t: PType, kind: TClosureTypeKind): Rope =
   assert t.kind == tyProc
@@ -830,7 +890,7 @@ template cgDeclFrmt*(s: PSym): string = s.constraint.strVal
 proc genProcHeader(m: BModule, prc: PSym): Rope =
   var
     rettype, params: Rope
-  genCLineDir(result, prc.info)
+  genCLineDir(result, prc.info, m.config)
   # using static is needed for inline procs
   if lfExportLib in prc.loc.flags:
     if isHeaderFile in m.flags:
@@ -839,8 +899,10 @@ proc genProcHeader(m: BModule, prc: PSym): Rope =
       result.add "N_LIB_EXPORT "
   elif prc.typ.callConv == ccInline:
     result.add "static "
+  elif {sfImportc, sfExportc} * prc.flags == {}:
+    result.add "N_LIB_PRIVATE "
   var check = initIntSet()
-  fillLoc(prc.loc, locProc, prc.typ, mangleName(m, prc), OnUnknown)
+  fillLoc(prc.loc, locProc, prc.ast[namePos], mangleName(m, prc), OnUnknown)
   genProcParams(m, prc.typ, rettype, params, check)
   # careful here! don't access ``prc.ast`` as that could reload large parts of
   # the object graph!
@@ -853,12 +915,13 @@ proc genProcHeader(m: BModule, prc: PSym): Rope =
 
 # ------------------ type info generation -------------------------------------
 
-proc genTypeInfo(m: BModule, t: PType): Rope
+proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope
 proc getNimNode(m: BModule): Rope =
   result = "$1[$2]" % [m.typeNodesName, rope(m.typeNodes)]
   inc(m.typeNodes)
 
-proc genTypeInfoAuxBase(m: BModule; typ, origType: PType; name, base: Rope) =
+proc genTypeInfoAuxBase(m: BModule; typ, origType: PType;
+                        name, base: Rope; info: TLineInfo) =
   var nimtypeKind: int
   #allocMemTI(m, typ, name)
   if isObjLackingTypeField(typ):
@@ -880,23 +943,31 @@ proc genTypeInfoAuxBase(m: BModule; typ, origType: PType; name, base: Rope) =
   if flags != 0:
     addf(m.s[cfsTypeInit3], "$1.flags = $2;$n", [name, rope(flags)])
   discard cgsym(m, "TNimType")
-  if isDefined("nimTypeNames"):
+  if isDefined(m.config, "nimTypeNames"):
+    var typename = typeToString(if origType.typeInst != nil: origType.typeInst
+                                else: origType, preferName)
+    if typename == "ref object" and origType.skipTypes(skipPtrs).sym != nil:
+      typename = "anon ref object from " & m.config$origType.skipTypes(skipPtrs).sym.info
     addf(m.s[cfsTypeInit3], "$1.name = $2;$n",
-        [name, makeCstring typeToString(origType, preferName)])
+        [name, makeCstring typename])
     discard cgsym(m, "nimTypeRoot")
     addf(m.s[cfsTypeInit3], "$1.nextType = nimTypeRoot; nimTypeRoot=&$1;$n",
          [name])
   addf(m.s[cfsVars], "TNimType $1;$n", [name])
 
-proc genTypeInfoAux(m: BModule, typ, origType: PType, name: Rope) =
+proc genTypeInfoAux(m: BModule, typ, origType: PType, name: Rope;
+                    info: TLineInfo) =
   var base: Rope
   if sonsLen(typ) > 0 and typ.lastSon != nil:
     var x = typ.lastSon
     if typ.kind == tyObject: x = x.skipTypes(skipPtrs)
-    base = genTypeInfo(m, x)
+    if typ.kind == tyPtr and x.kind == tyObject and incompleteType(x):
+      base = rope("0")
+    else:
+      base = genTypeInfo(m, x, info)
   else:
     base = rope("0")
-  genTypeInfoAuxBase(m, typ, origType, name, base)
+  genTypeInfoAuxBase(m, typ, origType, name, base, info)
 
 proc discriminatorTableName(m: BModule, objtype: PType, d: PSym): Rope =
   # bugfix: we need to search the type that contains the discriminator:
@@ -904,27 +975,28 @@ proc discriminatorTableName(m: BModule, objtype: PType, d: PSym): Rope =
   while lookupInRecord(objtype.n, d.name) == nil:
     objtype = objtype.sons[0]
   if objtype.sym == nil:
-    internalError(d.info, "anonymous obj with discriminator")
+    internalError(m.config, d.info, "anonymous obj with discriminator")
   result = "NimDT_$1_$2" % [rope($hashType(objtype)), rope(d.name.s.mangle)]
 
 proc discriminatorTableDecl(m: BModule, objtype: PType, d: PSym): Rope =
   discard cgsym(m, "TNimNode")
   var tmp = discriminatorTableName(m, objtype, d)
-  result = "TNimNode* $1[$2];$n" % [tmp, rope(lengthOrd(d.typ)+1)]
+  result = "TNimNode* $1[$2];$n" % [tmp, rope(lengthOrd(m.config, d.typ)+1)]
 
-proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope) =
+proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope;
+                     info: TLineInfo) =
   case n.kind
   of nkRecList:
     var L = sonsLen(n)
     if L == 1:
-      genObjectFields(m, typ, origType, n.sons[0], expr)
+      genObjectFields(m, typ, origType, n.sons[0], expr, info)
     elif L > 0:
       var tmp = getTempName(m)
       addf(m.s[cfsTypeInit1], "static TNimNode* $1[$2];$n", [tmp, rope(L)])
       for i in countup(0, L-1):
         var tmp2 = getNimNode(m)
         addf(m.s[cfsTypeInit3], "$1[$2] = &$3;$n", [tmp, rope(i), tmp2])
-        genObjectFields(m, typ, origType, n.sons[i], tmp2)
+        genObjectFields(m, typ, origType, n.sons[i], tmp2, info)
       addf(m.s[cfsTypeInit3], "$1.len = $2; $1.kind = 2; $1.sons = &$3[0];$n",
            [expr, rope(L), tmp])
     else:
@@ -933,27 +1005,27 @@ proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope) =
     assert(n.sons[0].kind == nkSym)
     var field = n.sons[0].sym
     var tmp = discriminatorTableName(m, typ, field)
-    var L = lengthOrd(field.typ)
+    var L = lengthOrd(m.config, field.typ)
     assert L > 0
     if field.loc.r == nil: fillObjectFields(m, typ)
     if field.loc.t == nil:
-      internalError(n.info, "genObjectFields")
+      internalError(m.config, n.info, "genObjectFields")
     addf(m.s[cfsTypeInit3], "$1.kind = 3;$n" &
         "$1.offset = offsetof($2, $3);$n" & "$1.typ = $4;$n" &
         "$1.name = $5;$n" & "$1.sons = &$6[0];$n" &
         "$1.len = $7;$n", [expr, getTypeDesc(m, origType), field.loc.r,
-                           genTypeInfo(m, field.typ),
+                           genTypeInfo(m, field.typ, info),
                            makeCString(field.name.s),
                            tmp, rope(L)])
     addf(m.s[cfsData], "TNimNode* $1[$2];$n", [tmp, rope(L+1)])
     for i in countup(1, sonsLen(n)-1):
       var b = n.sons[i]           # branch
       var tmp2 = getNimNode(m)
-      genObjectFields(m, typ, origType, lastSon(b), tmp2)
+      genObjectFields(m, typ, origType, lastSon(b), tmp2, info)
       case b.kind
       of nkOfBranch:
         if sonsLen(b) < 2:
-          internalError(b.info, "genObjectFields; nkOfBranch broken")
+          internalError(m.config, b.info, "genObjectFields; nkOfBranch broken")
         for j in countup(0, sonsLen(b) - 2):
           if b.sons[j].kind == nkRange:
             var x = int(getOrdValue(b.sons[j].sons[0]))
@@ -967,25 +1039,30 @@ proc genObjectFields(m: BModule, typ, origType: PType, n: PNode, expr: Rope) =
       of nkElse:
         addf(m.s[cfsTypeInit3], "$1[$2] = &$3;$n",
              [tmp, rope(L), tmp2])
-      else: internalError(n.info, "genObjectFields(nkRecCase)")
+      else: internalError(m.config, n.info, "genObjectFields(nkRecCase)")
   of nkSym:
     var field = n.sym
     if field.bitsize == 0:
       if field.loc.r == nil: fillObjectFields(m, typ)
       if field.loc.t == nil:
-        internalError(n.info, "genObjectFields")
+        internalError(m.config, n.info, "genObjectFields")
       addf(m.s[cfsTypeInit3], "$1.kind = 1;$n" &
           "$1.offset = offsetof($2, $3);$n" & "$1.typ = $4;$n" &
           "$1.name = $5;$n", [expr, getTypeDesc(m, origType),
-          field.loc.r, genTypeInfo(m, field.typ), makeCString(field.name.s)])
-  else: internalError(n.info, "genObjectFields")
+          field.loc.r, genTypeInfo(m, field.typ, info), makeCString(field.name.s)])
+  else: internalError(m.config, n.info, "genObjectFields")
 
-proc genObjectInfo(m: BModule, typ, origType: PType, name: Rope) =
-  if typ.kind == tyObject: genTypeInfoAux(m, typ, origType, name)
-  else: genTypeInfoAuxBase(m, typ, origType, name, rope("0"))
+proc genObjectInfo(m: BModule, typ, origType: PType, name: Rope; info: TLineInfo) =
+  if typ.kind == tyObject:
+    if incompleteType(typ):
+      localError(m.config, info, "request for RTTI generation for incomplete object: " &
+                        typeToString(typ))
+    genTypeInfoAux(m, typ, origType, name, info)
+  else:
+    genTypeInfoAuxBase(m, typ, origType, name, rope("0"), info)
   var tmp = getNimNode(m)
-  if not isImportedCppType(typ):
-    genObjectFields(m, typ, origType, typ.n, tmp)
+  if not isImportedType(typ):
+    genObjectFields(m, typ, origType, typ.n, tmp, info)
   addf(m.s[cfsTypeInit3], "$1.node = &$2;$n", [name, tmp])
   var t = typ.sons[0]
   while t != nil:
@@ -993,8 +1070,8 @@ proc genObjectInfo(m: BModule, typ, origType: PType, name: Rope) =
     t.flags.incl tfObjHasKids
     t = t.sons[0]
 
-proc genTupleInfo(m: BModule, typ, origType: PType, name: Rope) =
-  genTypeInfoAuxBase(m, typ, typ, name, rope("0"))
+proc genTupleInfo(m: BModule, typ, origType: PType, name: Rope; info: TLineInfo) =
+  genTypeInfoAuxBase(m, typ, typ, name, rope("0"), info)
   var expr = getNimNode(m)
   var length = sonsLen(typ)
   if length > 0:
@@ -1008,7 +1085,7 @@ proc genTupleInfo(m: BModule, typ, origType: PType, name: Rope) =
           "$1.offset = offsetof($2, Field$3);$n" &
           "$1.typ = $4;$n" &
           "$1.name = \"Field$3\";$n",
-           [tmp2, getTypeDesc(m, origType), rope(i), genTypeInfo(m, a)])
+           [tmp2, getTypeDesc(m, origType), rope(i), genTypeInfo(m, a, info)])
     addf(m.s[cfsTypeInit3], "$1.len = $2; $1.kind = 2; $1.sons = &$3[0];$n",
          [expr, rope(length), tmp])
   else:
@@ -1016,12 +1093,12 @@ proc genTupleInfo(m: BModule, typ, origType: PType, name: Rope) =
          [expr, rope(length)])
   addf(m.s[cfsTypeInit3], "$1.node = &$2;$n", [name, expr])
 
-proc genEnumInfo(m: BModule, typ: PType, name: Rope) =
+proc genEnumInfo(m: BModule, typ: PType, name: Rope; info: TLineInfo) =
   # Type information for enumerations is quite heavy, so we do some
   # optimizations here: The ``typ`` field is never set, as it is redundant
   # anyway. We generate a cstring array and a loop over it. Exceptional
   # positions will be reset after the loop.
-  genTypeInfoAux(m, typ, typ, name)
+  genTypeInfoAux(m, typ, typ, name, info)
   var nodePtrs = getTempName(m)
   var length = sonsLen(typ.n)
   addf(m.s[cfsTypeInit1], "static TNimNode* $1[$2];$n",
@@ -1038,7 +1115,7 @@ proc genEnumInfo(m: BModule, typ: PType, name: Rope) =
       add(enumNames, makeCString(field.name.s))
     else:
       add(enumNames, makeCString(field.ast.strVal))
-    if i < length - 1: add(enumNames, ", " & tnl)
+    if i < length - 1: add(enumNames, ", \L")
     if field.position != i or tfEnumHasHoles in typ.flags:
       addf(specialCases, "$1.offset = $2;$n", [elemNode, rope(field.position)])
       hasHoles = true
@@ -1059,28 +1136,25 @@ proc genEnumInfo(m: BModule, typ: PType, name: Rope) =
     # 1 << 2 is {ntfEnumHole}
     addf(m.s[cfsTypeInit3], "$1.flags = 1<<2;$n", [name])
 
-proc genSetInfo(m: BModule, typ: PType, name: Rope) =
+proc genSetInfo(m: BModule, typ: PType, name: Rope; info: TLineInfo) =
   assert(typ.sons[0] != nil)
-  genTypeInfoAux(m, typ, typ, name)
+  genTypeInfoAux(m, typ, typ, name, info)
   var tmp = getNimNode(m)
   addf(m.s[cfsTypeInit3], "$1.len = $2; $1.kind = 0;$n" & "$3.node = &$1;$n",
-       [tmp, rope(firstOrd(typ)), name])
+       [tmp, rope(firstOrd(m.config, typ)), name])
 
-proc genArrayInfo(m: BModule, typ: PType, name: Rope) =
-  genTypeInfoAuxBase(m, typ, typ, name, genTypeInfo(m, typ.sons[1]))
+proc genArrayInfo(m: BModule, typ: PType, name: Rope; info: TLineInfo) =
+  genTypeInfoAuxBase(m, typ, typ, name, genTypeInfo(m, typ.sons[1], info), info)
 
-proc fakeClosureType(owner: PSym): PType =
+proc fakeClosureType(m: BModule; owner: PSym): PType =
   # we generate the same RTTI as for a tuple[pointer, ref tuple[]]
   result = newType(tyTuple, owner)
   result.rawAddSon(newType(tyPointer, owner))
   var r = newType(tyRef, owner)
-  r.rawAddSon(newType(tyTuple, owner))
+  let obj = createObj(m.g.graph, owner, owner.info, final=false)
+  r.rawAddSon(obj)
   result.rawAddSon(r)
 
-type
-  TTypeInfoReason = enum  ## for what do we need the type info?
-    tiNew,                ## for 'new'
-
 include ccgtrav
 
 proc genDeepCopyProc(m: BModule; s: PSym; result: Rope) =
@@ -1088,9 +1162,9 @@ proc genDeepCopyProc(m: BModule; s: PSym; result: Rope) =
   addf(m.s[cfsTypeInit3], "$1.deepcopy =(void* (N_RAW_NIMCALL*)(void*))$2;$n",
      [result, s.loc.r])
 
-proc genTypeInfo(m: BModule, t: PType): Rope =
+proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
   let origType = t
-  var t = skipTypes(origType, irrelevantForBackend)
+  var t = skipTypes(origType, irrelevantForBackend + tyUserTypeClasses)
 
   let sig = hashType(origType)
   result = m.typeInfoMarker.getOrDefault(sig)
@@ -1112,7 +1186,8 @@ proc genTypeInfo(m: BModule, t: PType): Rope =
   let owner = t.skipTypes(typedescPtrs).owner.getModule
   if owner != m.module:
     # make sure the type info is created in the owner module
-    discard genTypeInfo(m.g.modules[owner.position], origType)
+    assert m.g.modules[owner.position] != nil
+    discard genTypeInfo(m.g.modules[owner.position], origType, info)
     # reference the type info as extern here
     discard cgsym(m, "TNimType")
     discard cgsym(m, "TNimNode")
@@ -1122,34 +1197,43 @@ proc genTypeInfo(m: BModule, t: PType): Rope =
   m.g.typeInfoMarker[sig] = result
   case t.kind
   of tyEmpty, tyVoid: result = rope"0"
-  of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar:
-    genTypeInfoAuxBase(m, t, t, result, rope"0")
+  of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
+    genTypeInfoAuxBase(m, t, t, result, rope"0", info)
   of tyStatic:
-    if t.n != nil: result = genTypeInfo(m, lastSon t)
-    else: internalError("genTypeInfo(" & $t.kind & ')')
+    if t.n != nil: result = genTypeInfo(m, lastSon t, info)
+    else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
+  of tyUserTypeClasses:
+    internalAssert m.config, t.isResolvedUserTypeClass
+    return genTypeInfo(m, t.lastSon, info)
   of tyProc:
     if t.callConv != ccClosure:
-      genTypeInfoAuxBase(m, t, t, result, rope"0")
+      genTypeInfoAuxBase(m, t, t, result, rope"0", info)
     else:
-      let x = fakeClosureType(t.owner)
-      genTupleInfo(m, x, x, result)
-  of tySequence, tyRef:
-    genTypeInfoAux(m, t, t, result)
-    if gSelectedGC >= gcMarkAndSweep:
-      let markerProc = genTraverseProc(m, origType, sig, tiNew)
+      let x = fakeClosureType(m, t.owner)
+      genTupleInfo(m, x, x, result, info)
+  of tySequence:
+    genTypeInfoAux(m, t, t, result, info)
+    if m.config.selectedGC != gcDestructors:
+      if m.config.selectedGC >= gcMarkAndSweep:
+        let markerProc = genTraverseProc(m, origType, sig)
+        addf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc])
+  of tyRef, tyOptAsRef:
+    genTypeInfoAux(m, t, t, result, info)
+    if m.config.selectedGC >= gcMarkAndSweep:
+      let markerProc = genTraverseProc(m, origType, sig)
       addf(m.s[cfsTypeInit3], "$1.marker = $2;$n", [result, markerProc])
-  of tyPtr, tyRange: genTypeInfoAux(m, t, t, result)
-  of tyArray: genArrayInfo(m, t, result)
-  of tySet: genSetInfo(m, t, result)
-  of tyEnum: genEnumInfo(m, t, result)
-  of tyObject: genObjectInfo(m, t, origType, result)
+  of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info)
+  of tyArray: genArrayInfo(m, t, result, info)
+  of tySet: genSetInfo(m, t, result, info)
+  of tyEnum: genEnumInfo(m, t, result, info)
+  of tyObject: genObjectInfo(m, t, origType, result, info)
   of tyTuple:
     # if t.n != nil: genObjectInfo(m, t, result)
     # else:
     # BUGFIX: use consistently RTTI without proper field names; otherwise
     # results are not deterministic!
-    genTupleInfo(m, t, origType, result)
-  else: internalError("genTypeInfo(" & $t.kind & ')')
+    genTupleInfo(m, t, origType, result, info)
+  else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
   if t.deepCopy != nil:
     genDeepCopyProc(m, t.deepCopy, result)
   elif origType.deepCopy != nil:
diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim
index ff8f768bd..6d2f33f2d 100644
--- a/compiler/ccgutils.nim
+++ b/compiler/ccgutils.nim
@@ -11,25 +11,25 @@
 
 import
   ast, astalgo, ropes, hashes, strutils, types, msgs, wordrecg,
-  platform, trees
+  platform, trees, options
 
 proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode =
   case n.kind
   of nkStmtList:
-    for i in 0 .. < n.len:
+    for i in 0 ..< n.len:
       result = getPragmaStmt(n[i], w)
       if result != nil: break
   of nkPragma:
-    for i in 0 .. < n.len:
+    for i in 0 ..< n.len:
       if whichPragma(n[i]) == w: return n[i]
   else: discard
 
 proc stmtsContainPragma*(n: PNode, w: TSpecialWord): bool =
   result = getPragmaStmt(n, w) != nil
 
-proc hashString*(s: string): BiggestInt =
+proc hashString*(conf: ConfigRef; s: string): BiggestInt =
   # has to be the same algorithm as system.hashString!
-  if CPU[targetCPU].bit == 64:
+  if CPU[conf.target.targetCPU].bit == 64:
     # we have to use the same bitwidth
     # as the target CPU
     var b = 0'i64
@@ -52,115 +52,12 @@ proc hashString*(s: string): BiggestInt =
     a = a +% `shl`(a, 15'i32)
     result = a
 
-var
-  gTypeTable: array[TTypeKind, TIdTable]
-  gCanonicalTypes: array[TTypeKind, PType]
-
-proc initTypeTables() =
-  for i in countup(low(TTypeKind), high(TTypeKind)): initIdTable(gTypeTable[i])
-
-proc resetCaches* =
-  ## XXX: fix that more properly
-  initTypeTables()
-  for i in low(gCanonicalTypes)..high(gCanonicalTypes):
-    gCanonicalTypes[i] = nil
-
-when false:
-  proc echoStats*() =
-    for i in countup(low(TTypeKind), high(TTypeKind)):
-      echo i, " ", gTypeTable[i].counter
-
-proc slowSearch(key: PType; k: TTypeKind): PType =
-  # tuples are quite horrible as C does not support them directly and
-  # tuple[string, string] is a (strange) subtype of
-  # tuple[nameA, nameB: string]. This bites us here, so we
-  # use 'sameBackendType' instead of 'sameType'.
-  if idTableHasObjectAsKey(gTypeTable[k], key): return key
-  for h in countup(0, high(gTypeTable[k].data)):
-    var t = PType(gTypeTable[k].data[h].key)
-    if t != nil and sameBackendType(t, key):
-      return t
-  idTablePut(gTypeTable[k], key, key)
-  result = key
-
-proc getUniqueType*(key: PType): PType =
-  # this is a hotspot in the compiler!
-  result = key
-  when false:
-    if key == nil: return
-    var k = key.kind
-    case k
-    of tyBool, tyChar, tyInt..tyUInt64:
-      # no canonicalization for integral types, so that e.g. ``pid_t`` is
-      # produced instead of ``NI``.
-      result = key
-    of  tyEmpty, tyNil, tyExpr, tyStmt, tyPointer, tyString,
-        tyCString, tyNone, tyVoid:
-      result = gCanonicalTypes[k]
-      if result == nil:
-        gCanonicalTypes[k] = key
-        result = key
-    of tyTypeDesc, tyTypeClasses, tyGenericParam, tyFromExpr, tyFieldAccessor:
-      if key.sym != nil:
-        internalError(key.sym.info, "metatype not eliminated")
-      else:
-        internalError("metatype not eliminated")
-    of tyDistinct:
-      if key.deepCopy != nil: result = key
-      else: result = getUniqueType(lastSon(key))
-    of tyGenericInst, tyOrdinal, tyStatic, tyAlias:
-      result = getUniqueType(lastSon(key))
-      #let obj = lastSon(key)
-      #if obj.sym != nil and obj.sym.name.s == "TOption":
-      #  echo "for ", typeToString(key), " I returned "
-      #  debug result
-    of tyPtr, tyRef, tyVar:
-      let elemType = lastSon(key)
-      if elemType.kind in {tyBool, tyChar, tyInt..tyUInt64}:
-        # no canonicalization for integral types, so that e.g. ``ptr pid_t`` is
-        # produced instead of ``ptr NI``.
-        result = key
-      else:
-        result = slowSearch(key, k)
-    of tyGenericInvocation, tyGenericBody,
-       tyOpenArray, tyArray, tySet, tyRange, tyTuple,
-       tySequence, tyForward, tyVarargs, tyProxy:
-      # we have to do a slow linear search because types may need
-      # to be compared by their structure:
-      result = slowSearch(key, k)
-    of tyObject:
-      if tfFromGeneric notin key.flags:
-        # fast case; lookup per id suffices:
-        result = PType(idTableGet(gTypeTable[k], key))
-        if result == nil:
-          idTablePut(gTypeTable[k], key, key)
-          result = key
-      else:
-        # ugly slow case: need to compare by structure
-        if idTableHasObjectAsKey(gTypeTable[k], key): return key
-        for h in countup(0, high(gTypeTable[k].data)):
-          var t = PType(gTypeTable[k].data[h].key)
-          if t != nil and sameBackendType(t, key):
-            return t
-        idTablePut(gTypeTable[k], key, key)
-        result = key
-    of tyEnum:
-      result = PType(idTableGet(gTypeTable[k], key))
-      if result == nil:
-        idTablePut(gTypeTable[k], key, key)
-        result = key
-    of tyProc:
-      if key.callConv != ccClosure:
-        result = key
-      else:
-        # ugh, we need the canon here:
-        result = slowSearch(key, k)
-    of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("getUniqueType")
+template getUniqueType*(key: PType): PType = key
 
 proc makeSingleLineCString*(s: string): string =
   result = "\""
   for c in items(s):
-    result.add(c.toCChar)
+    c.toCChar(result)
   result.add('\"')
 
 proc mangle*(name: string): string =
@@ -195,6 +92,7 @@ proc mangle*(name: string): string =
     of '+': special "plus"
     of '-': special "minus"
     of '/': special "slash"
+    of '\\': special "backslash"
     of '=': special "eq"
     of '<': special "lt"
     of '>': special "gt"
@@ -208,21 +106,3 @@ proc mangle*(name: string): string =
       requiresUnderscore = true
   if requiresUnderscore:
     result.add "_"
-
-proc makeLLVMString*(s: string): Rope =
-  const MaxLineLength = 64
-  result = nil
-  var res = "c\""
-  for i in countup(0, len(s) - 1):
-    if (i + 1) mod MaxLineLength == 0:
-      add(result, rope(res))
-      setLen(res, 0)
-    case s[i]
-    of '\0'..'\x1F', '\x80'..'\xFF', '\"', '\\':
-      add(res, '\\')
-      add(res, toHex(ord(s[i]), 2))
-    else: add(res, s[i])
-  add(res, "\\00\"")
-  add(result, rope(res))
-
-initTypeTables()
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index c829a463e..457a6e176 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -11,14 +11,21 @@
 
 import
   ast, astalgo, hashes, trees, platform, magicsys, extccomp, options, intsets,
-  nversion, nimsets, msgs, securehash, bitsets, idents, types,
-  ccgutils, os, ropes, math, passes, rodread, wordrecg, treetab, cgmeth,
+  nversion, nimsets, msgs, std / sha1, bitsets, idents, types,
+  ccgutils, os, ropes, math, passes, wordrecg, treetab, cgmeth,
   condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases,
-  lowerings, semparallel, tables, sets, ndi
+  lowerings, tables, sets, ndi, lineinfos, pathutils, transf
+
+import system/helpers2
+
+when not defined(leanCompiler):
+  import semparallel
 
 import strutils except `%` # collides with ropes.`%`
 
-from modulegraphs import ModuleGraph
+from modulegraphs import ModuleGraph, PPassContext
+from lineinfos import
+  warnGcMem, errXMustBeCompileTime, hintDependency, errGenerated, errCannotOpenFile
 import dynlib
 
 when not declared(dynlib.libCandidates):
@@ -40,43 +47,43 @@ when options.hasTinyCBackend:
 # implementation
 
 proc addForwardedProc(m: BModule, prc: PSym) =
-  m.forwardedProcs.add(prc)
-  inc(m.g.forwardedProcsCounter)
+  m.g.forwardedProcs.add(prc)
 
 proc findPendingModule(m: BModule, s: PSym): BModule =
   var ms = getModule(s)
   result = m.g.modules[ms.position]
 
-proc emitLazily(s: PSym): bool {.inline.} =
-  result = optDeadCodeElim in gGlobalOptions or
-           sfDeadCodeElim in getModule(s).flags
-
-proc initLoc(result: var TLoc, k: TLocKind, typ: PType, s: TStorageLoc) =
+proc initLoc(result: var TLoc, k: TLocKind, lode: PNode, s: TStorageLoc) =
   result.k = k
-  result.s = s
-  result.t = typ
+  result.storage = s
+  result.lode = lode
   result.r = nil
   result.flags = {}
 
-proc fillLoc(a: var TLoc, k: TLocKind, typ: PType, r: Rope, s: TStorageLoc) =
+proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, r: Rope, s: TStorageLoc) =
   # fills the loc if it is not already initialized
   if a.k == locNone:
     a.k = k
-    a.t = typ
-    a.s = s
+    a.lode = lode
+    a.storage = s
     if a.r == nil: a.r = r
 
+proc t(a: TLoc): PType {.inline.} =
+  if a.lode.kind == nkSym:
+    result = a.lode.sym.typ
+  else:
+    result = a.lode.typ
+
+proc lodeTyp(t: PType): PNode =
+  result = newNode(nkEmpty)
+  result.typ = t
+
 proc isSimpleConst(typ: PType): bool =
   let t = skipTypes(typ, abstractVar)
   result = t.kind notin
       {tyTuple, tyObject, tyArray, tySet, tySequence} and not
       (t.kind == tyProc and t.callConv == ccClosure)
 
-proc useStringh(m: BModule) =
-  if includesStringh notin m.flags:
-    incl m.flags, includesStringh
-    m.includeHeader("<string.h>")
-
 proc useHeader(m: BModule, sym: PSym) =
   if lfHeader in sym.loc.flags:
     assert(sym.annex != nil)
@@ -86,6 +93,7 @@ proc useHeader(m: BModule, sym: PSym) =
 proc cgsym(m: BModule, name: string): Rope
 
 proc ropecg(m: BModule, frmt: FormatStr, args: varargs[Rope]): Rope =
+  assert m != nil
   var i = 0
   var length = len(frmt)
   result = nil
@@ -109,15 +117,15 @@ proc ropecg(m: BModule, frmt: FormatStr, args: varargs[Rope]): Rope =
           if i >= length or not (frmt[i] in {'0'..'9'}): break
         num = j
         if j > high(args) + 1:
-          internalError("ropes: invalid format string $" & $j)
+          internalError(m.config, "ropes: invalid format string $" & $j)
         add(result, args[j-1])
       of 'n':
-        if optLineDir notin gOptions: add(result, rnl)
+        if optLineDir notin m.config.options: add(result, "\L")
         inc(i)
       of 'N':
-        add(result, rnl)
+        add(result, "\L")
         inc(i)
-      else: internalError("ropes: invalid format string $" & frmt[i])
+      else: internalError(m.config, "ropes: invalid format string $" & frmt[i])
     elif frmt[i] == '#' and frmt[i+1] in IdentStartChars:
       inc(i)
       var j = i
@@ -139,8 +147,10 @@ proc ropecg(m: BModule, frmt: FormatStr, args: varargs[Rope]): Rope =
     if i - 1 >= start:
       add(result, substr(frmt, start, i - 1))
 
-template rfmt(m: BModule, fmt: string, args: varargs[Rope]): untyped =
-  ropecg(m, fmt, args)
+proc indentLine(p: BProc, r: Rope): Rope =
+  result = r
+  for i in countup(0, p.blocks.len-1):
+    prepend(result, "\t".rope)
 
 proc appcg(m: BModule, c: var Rope, frmt: FormatStr,
            args: varargs[Rope]) =
@@ -154,11 +164,6 @@ proc appcg(p: BProc, s: TCProcSection, frmt: FormatStr,
            args: varargs[Rope]) =
   add(p.s(s), ropecg(p.module, frmt, args))
 
-var indent = "\t".rope
-proc indentLine(p: BProc, r: Rope): Rope =
-  result = r
-  for i in countup(0, p.blocks.len-1): prepend(result, indent)
-
 proc line(p: BProc, s: TCProcSection, r: Rope) =
   add(p.s(s), indentLine(p, r))
 
@@ -181,14 +186,14 @@ proc safeLineNm(info: TLineInfo): int =
   result = toLinenumber(info)
   if result < 0: result = 0 # negative numbers are not allowed in #line
 
-proc genCLineDir(r: var Rope, filename: string, line: int) =
+proc genCLineDir(r: var Rope, filename: string, line: int; conf: ConfigRef) =
   assert line >= 0
-  if optLineDir in gOptions:
+  if optLineDir in conf.options:
     addf(r, "$N#line $2 $1$N",
         [rope(makeSingleLineCString(filename)), rope(line)])
 
-proc genCLineDir(r: var Rope, info: TLineInfo) =
-  genCLineDir(r, info.toFullPath, info.safeLineNm)
+proc genCLineDir(r: var Rope, info: TLineInfo; conf: ConfigRef) =
+  genCLineDir(r, toFullPath(conf, info), info.safeLineNm, conf)
 
 proc freshLineInfo(p: BProc; info: TLineInfo): bool =
   if p.lastLineInfo.line != info.line or
@@ -198,50 +203,65 @@ proc freshLineInfo(p: BProc; info: TLineInfo): bool =
     result = true
 
 proc genLineDir(p: BProc, t: PNode) =
-  var tt = t
-  #while tt.kind in {nkStmtListExpr}+nkCallKinds:
-  #  tt = tt.lastSon
-  if tt.kind in nkCallKinds and tt.len > 1:
-    tt = tt.sons[1]
-  let line = tt.info.safeLineNm
-
-  if optEmbedOrigSrc in gGlobalOptions:
-    add(p.s(cpsStmts), ~"//" & tt.info.sourceLine & rnl)
-  genCLineDir(p.s(cpsStmts), tt.info.toFullPath, line)
+  let line = t.info.safeLineNm
+
+  if optEmbedOrigSrc in p.config.globalOptions:
+    add(p.s(cpsStmts), ~"//" & sourceLine(p.config, t.info) & "\L")
+  genCLineDir(p.s(cpsStmts), toFullPath(p.config, t.info), line, p.config)
   if ({optStackTrace, optEndb} * p.options == {optStackTrace, optEndb}) and
       (p.prc == nil or sfPure notin p.prc.flags):
-    if freshLineInfo(p, tt.info):
-      linefmt(p, cpsStmts, "#endb($1, $2);$n",
-              line.rope, makeCString(toFilename(tt.info)))
+    if freshLineInfo(p, t.info):
+      linefmt(p, cpsStmts, "#endb($1, $2);$N",
+              line.rope, makeCString(toFilename(p.config, t.info)))
   elif ({optLineTrace, optStackTrace} * p.options ==
       {optLineTrace, optStackTrace}) and
-      (p.prc == nil or sfPure notin p.prc.flags) and tt.info.fileIndex >= 0:
-    if freshLineInfo(p, tt.info):
+      (p.prc == nil or sfPure notin p.prc.flags) and t.info.fileIndex != InvalidFileIDX:
+    if freshLineInfo(p, t.info):
       linefmt(p, cpsStmts, "nimln_($1, $2);$n",
-              line.rope, tt.info.quotedFilename)
+              line.rope, quotedFilename(p.config, t.info))
 
 proc postStmtActions(p: BProc) {.inline.} =
   add(p.s(cpsStmts), p.module.injectStmt)
 
 proc accessThreadLocalVar(p: BProc, s: PSym)
-proc emulatedThreadVars(): bool {.inline.}
+proc emulatedThreadVars(conf: ConfigRef): bool {.inline.}
 proc genProc(m: BModule, prc: PSym)
 
 template compileToCpp(m: BModule): untyped =
-  gCmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags
-
-include "ccgtypes.nim"
+  m.config.cmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags
 
-# ------------------------------ Manager of temporaries ------------------
+proc getTempName(m: BModule): Rope =
+  result = m.tmpBase & rope(m.labels)
+  inc m.labels
 
 proc rdLoc(a: TLoc): Rope =
   # 'read' location (deref if indirect)
   result = a.r
   if lfIndirect in a.flags: result = "(*$1)" % [result]
 
-proc addrLoc(a: TLoc): Rope =
+proc lenField(p: BProc): Rope =
+  result = rope(if p.module.compileToCpp: "len" else: "Sup.len")
+
+proc lenExpr(p: BProc; a: TLoc): Rope =
+  if p.config.selectedGc == gcDestructors:
+    result = rdLoc(a) & ".len"
+  else:
+    result = "($1 ? $1->$2 : 0)" % [rdLoc(a), lenField(p)]
+
+proc dataField(p: BProc): Rope =
+  if p.config.selectedGc == gcDestructors:
+    result = rope".p->data"
+  else:
+    result = rope"->data"
+
+include ccgliterals
+include ccgtypes
+
+# ------------------------------ Manager of temporaries ------------------
+
+proc addrLoc(conf: ConfigRef; a: TLoc): Rope =
   result = a.r
-  if lfIndirect notin a.flags and mapType(a.t) != ctArray:
+  if lfIndirect notin a.flags and mapType(conf, a.t) != ctArray:
     result = "(&" & result & ")"
 
 proc rdCharLoc(a: TLoc): Rope =
@@ -252,6 +272,11 @@ proc rdCharLoc(a: TLoc): Rope =
 
 proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc,
                    takeAddr: bool) =
+  if p.module.compileToCpp and t.isException and not isDefined(p.config, "noCppExceptions"):
+    # init vtable in Exception object for polymorphic exceptions
+    includeHeader(p.module, "<new>")
+    linefmt(p, section, "new ($1) $2;$n", rdLoc(a), getTypeDesc(p.module, t))
+
   case analyseObjectWithTypeField(t)
   of frNone:
     discard
@@ -263,21 +288,21 @@ proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc,
       while (s.kind == tyObject) and (s.sons[0] != nil):
         add(r, ".Sup")
         s = skipTypes(s.sons[0], skipPtrs)
-    linefmt(p, section, "$1.m_type = $2;$n", r, genTypeInfo(p.module, t))
+    linefmt(p, section, "$1.m_type = $2;$n", r, genTypeInfo(p.module, t, a.lode.info))
   of frEmbedded:
     # worst case for performance:
-    var r = if takeAddr: addrLoc(a) else: rdLoc(a)
-    linefmt(p, section, "#objectInit($1, $2);$n", r, genTypeInfo(p.module, t))
+    var r = if takeAddr: addrLoc(p.config, a) else: rdLoc(a)
+    linefmt(p, section, "#objectInit($1, $2);$n", r, genTypeInfo(p.module, t, a.lode.info))
 
 type
   TAssignmentFlag = enum
-    needToCopy, afDestIsNil, afDestIsNotNil, afSrcIsNil, afSrcIsNotNil
+    needToCopy
   TAssignmentFlags = set[TAssignmentFlag]
 
-proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
+proc genRefAssign(p: BProc, dest, src: TLoc)
 
 proc isComplexValueType(t: PType): bool {.inline.} =
-  let t = t.skipTypes(abstractInst)
+  let t = t.skipTypes(abstractInst + tyUserTypeClasses)
   result = t.kind in {tyArray, tySet, tyTuple, tyObject} or
     (t.kind == tyProc and t.callConv == ccClosure)
 
@@ -288,41 +313,43 @@ proc resetLoc(p: BProc, loc: var TLoc) =
   if not isComplexValueType(typ):
     if containsGcRef:
       var nilLoc: TLoc
-      initLoc(nilLoc, locTemp, loc.t, OnStack)
+      initLoc(nilLoc, locTemp, loc.lode, OnStack)
       nilLoc.r = rope("NIM_NIL")
-      genRefAssign(p, loc, nilLoc, {afSrcIsNil})
+      genRefAssign(p, loc, nilLoc)
     else:
       linefmt(p, cpsStmts, "$1 = 0;$n", rdLoc(loc))
   else:
     if optNilCheck in p.options:
-      linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(loc))
-    if loc.s != OnStack:
+      linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(p.config, loc))
+    if loc.storage != OnStack:
       linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n",
-              addrLoc(loc), genTypeInfo(p.module, loc.t))
+              addrLoc(p.config, loc), genTypeInfo(p.module, loc.t, loc.lode.info))
       # XXX: generated reset procs should not touch the m_type
       # field, so disabling this should be safe:
       genObjectInit(p, cpsStmts, loc.t, loc, true)
     else:
-      useStringh(p.module)
-      linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n",
-              addrLoc(loc), rdLoc(loc))
+      # array passed as argument decayed into pointer, bug #7332
+      # so we use getTypeDesc here rather than rdLoc(loc)
+      linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n",
+              addrLoc(p.config, loc), getTypeDesc(p.module, loc.t))
       # XXX: We can be extra clever here and call memset only
       # on the bytes following the m_type field?
       genObjectInit(p, cpsStmts, loc.t, loc, true)
 
 proc constructLoc(p: BProc, loc: TLoc, isTemp = false) =
   let typ = loc.t
-  if not isComplexValueType(typ):
+  if p.config.selectedGc == gcDestructors and skipTypes(typ, abstractInst).kind in {tyString, tySequence}:
+    linefmt(p, cpsStmts, "$1.len = 0; $1.p = NIM_NIL;$n", rdLoc(loc))
+  elif not isComplexValueType(typ):
     linefmt(p, cpsStmts, "$1 = ($2)0;$n", rdLoc(loc),
       getTypeDesc(p.module, typ))
   else:
     if not isTemp or containsGarbageCollectedRef(loc.t):
-      # don't use memset for temporary values for performance if we can
+      # don't use nimZeroMem for temporary values for performance if we can
       # avoid it:
       if not isImportedCppType(typ):
-        useStringh(p.module)
-        linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n",
-                addrLoc(loc), rdLoc(loc))
+        linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n",
+                addrLoc(p.config, loc), getTypeDesc(p.module, typ))
     genObjectInit(p, cpsStmts, loc.t, loc, true)
 
 proc initLocalVar(p: BProc, v: PSym, immediateAsgn: bool) =
@@ -342,11 +369,20 @@ proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false) =
   result.r = "T" & rope(p.labels) & "_"
   linefmt(p, cpsLocals, "$1 $2;$n", getTypeDesc(p.module, t), result.r)
   result.k = locTemp
-  result.t = t
-  result.s = OnStack
+  result.lode = lodeTyp t
+  result.storage = OnStack
   result.flags = {}
   constructLoc(p, result, not needsInit)
 
+proc getIntTemp(p: BProc, result: var TLoc) =
+  inc(p.labels)
+  result.r = "T" & rope(p.labels) & "_"
+  linefmt(p, cpsLocals, "NI $1;$n", result.r)
+  result.k = locTemp
+  result.storage = OnStack
+  result.lode = lodeTyp getSysType(p.module.g.graph, unknownLineInfo(), tyInt)
+  result.flags = {}
+
 proc initGCFrame(p: BProc): Rope =
   if p.gcFrameId > 0: result = "struct {$1} GCFRAME_;$n" % [p.gcFrameType]
 
@@ -360,17 +396,18 @@ proc localDebugInfo(p: BProc, s: PSym) =
   # XXX work around a bug: No type information for open arrays possible:
   if skipTypes(s.typ, abstractVar).kind in {tyOpenArray, tyVarargs}: return
   var a = "&" & s.loc.r
-  if s.kind == skParam and ccgIntroducedPtr(s): a = s.loc.r
+  if s.kind == skParam and ccgIntroducedPtr(p.config, s): a = s.loc.r
   lineF(p, cpsInit,
        "FR_.s[$1].address = (void*)$3; FR_.s[$1].typ = $4; FR_.s[$1].name = $2;$n",
        [p.maxFrameLen.rope, makeCString(normalize(s.name.s)), a,
-        genTypeInfo(p.module, s.loc.t)])
+        genTypeInfo(p.module, s.loc.t, s.info)])
   inc(p.maxFrameLen)
   inc p.blocks[p.blocks.len-1].frameLen
 
-proc localVarDecl(p: BProc; s: PSym): Rope =
+proc localVarDecl(p: BProc; n: PNode): Rope =
+  let s = n.sym
   if s.loc.k == locNone:
-    fillLoc(s.loc, locLocalVar, s.typ, mangleLocalName(p, s), OnStack)
+    fillLoc(s.loc, locLocalVar, n, mangleLocalName(p, s), OnStack)
     if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy)
   result = getTypeDesc(p.module, s.typ)
   if s.constraint.isNil:
@@ -383,22 +420,24 @@ proc localVarDecl(p: BProc; s: PSym): Rope =
   else:
     result = s.cgDeclFrmt % [result, s.loc.r]
 
-proc assignLocalVar(p: BProc, s: PSym) =
+proc assignLocalVar(p: BProc, n: PNode) =
   #assert(s.loc.k == locNone) # not yet assigned
   # this need not be fulfilled for inline procs; they are regenerated
   # for each module that uses them!
-  let decl = localVarDecl(p, s) & ";" & tnl
+  let nl = if optLineDir in p.config.options: "" else: "\L"
+  let decl = localVarDecl(p, n) & ";" & nl
   line(p, cpsLocals, decl)
-  localDebugInfo(p, s)
+  localDebugInfo(p, n.sym)
 
 include ccgthreadvars
 
 proc varInDynamicLib(m: BModule, sym: PSym)
 proc mangleDynLibProc(sym: PSym): Rope
 
-proc assignGlobalVar(p: BProc, s: PSym) =
+proc assignGlobalVar(p: BProc, n: PNode) =
+  let s = n.sym
   if s.loc.k == locNone:
-    fillLoc(s.loc, locGlobalVar, s.typ, mangleName(p.module, s), OnHeap)
+    fillLoc(s.loc, locGlobalVar, n, mangleName(p.module, s), OnHeap)
 
   if lfDynamicLib in s.loc.flags:
     var q = findPendingModule(p.module, s)
@@ -409,20 +448,21 @@ proc assignGlobalVar(p: BProc, s: PSym) =
     return
   useHeader(p.module, s)
   if lfNoDecl in s.loc.flags: return
-  if sfThread in s.flags:
-    declareThreadVar(p.module, s, sfImportc in s.flags)
-  else:
-    var decl: Rope = nil
-    var td = getTypeDesc(p.module, s.loc.t)
-    if s.constraint.isNil:
-      if sfImportc in s.flags: add(decl, "extern ")
-      add(decl, td)
-      if sfRegister in s.flags: add(decl, " register")
-      if sfVolatile in s.flags: add(decl, " volatile")
-      addf(decl, " $1;$n", [s.loc.r])
+  if not containsOrIncl(p.module.declaredThings, s.id):
+    if sfThread in s.flags:
+      declareThreadVar(p.module, s, sfImportc in s.flags)
     else:
-      decl = (s.cgDeclFrmt & ";$n") % [td, s.loc.r]
-    add(p.module.s[cfsVars], decl)
+      var decl: Rope = nil
+      var td = getTypeDesc(p.module, s.loc.t)
+      if s.constraint.isNil:
+        if sfImportc in s.flags: add(decl, "extern ")
+        add(decl, td)
+        if sfRegister in s.flags: add(decl, " register")
+        if sfVolatile in s.flags: add(decl, " volatile")
+        addf(decl, " $1;$n", [s.loc.r])
+      else:
+        decl = (s.cgDeclFrmt & ";$n") % [td, s.loc.r]
+      add(p.module.s[cfsVars], decl)
   if p.withinLoop > 0:
     # fixes tests/run/tzeroarray:
     resetLoc(p, s.loc)
@@ -431,16 +471,17 @@ proc assignGlobalVar(p: BProc, s: PSym) =
     appcg(p.module, p.module.s[cfsDebugInit],
           "#dbgRegisterGlobal($1, &$2, $3);$n",
          [makeCString(normalize(s.owner.name.s & '.' & s.name.s)),
-          s.loc.r, genTypeInfo(p.module, s.typ)])
+          s.loc.r, genTypeInfo(p.module, s.typ, n.info)])
 
 proc assignParam(p: BProc, s: PSym) =
   assert(s.loc.r != nil)
   scopeMangledParam(p, s)
   localDebugInfo(p, s)
 
-proc fillProcLoc(m: BModule; sym: PSym) =
+proc fillProcLoc(m: BModule; n: PNode) =
+  let sym = n.sym
   if sym.loc.k == locNone:
-    fillLoc(sym.loc, locProc, sym.typ, mangleName(m, sym), OnStack)
+    fillLoc(sym.loc, locProc, n, mangleName(m, sym), OnStack)
 
 proc getLabel(p: BProc): TLabel =
   inc(p.labels)
@@ -449,7 +490,7 @@ proc getLabel(p: BProc): TLabel =
 proc fixLabel(p: BProc, labl: TLabel) =
   lineF(p, cpsStmts, "$1: ;$n", [labl])
 
-proc genVarPrototype(m: BModule, sym: PSym)
+proc genVarPrototype(m: BModule, n: PNode)
 proc requestConstImpl(p: BProc, sym: PSym)
 proc genStmts(p: BProc, t: PNode)
 proc expr(p: BProc, n: PNode, d: var TLoc)
@@ -461,18 +502,40 @@ proc genLiteral(p: BProc, n: PNode): Rope
 proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope
 
 proc initLocExpr(p: BProc, e: PNode, result: var TLoc) =
-  initLoc(result, locNone, e.typ, OnUnknown)
+  initLoc(result, locNone, e, OnUnknown)
   expr(p, e, result)
 
 proc initLocExprSingleUse(p: BProc, e: PNode, result: var TLoc) =
-  initLoc(result, locNone, e.typ, OnUnknown)
+  initLoc(result, locNone, e, OnUnknown)
   result.flags.incl lfSingleUse
   expr(p, e, result)
 
-proc lenField(p: BProc): Rope =
-  result = rope(if p.module.compileToCpp: "len" else: "Sup.len")
+include ccgcalls, "ccgstmts.nim"
 
-include ccgcalls, "ccgstmts.nim", "ccgexprs.nim"
+proc initFrame(p: BProc, procname, filename: Rope): Rope =
+  discard cgsym(p.module, "nimFrame")
+  if p.maxFrameLen > 0:
+    discard cgsym(p.module, "VarSlot")
+    result = ropecg(p.module, "\tnimfrs_($1, $2, $3, $4);$n",
+                  procname, filename, p.maxFrameLen.rope,
+                  p.blocks[0].frameLen.rope)
+  else:
+    result = ropecg(p.module, "\tnimfr_($1, $2);$n", procname, filename)
+
+proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope =
+  discard cgsym(p.module, "nimFrame")
+  addf(p.blocks[0].sections[cpsLocals], "TFrame $1;$n", [frame])
+  result = ropecg(p.module, "\t$1.procname = $2; $1.filename = $3; " &
+                      " $1.line = $4; $1.len = -1; nimFrame(&$1);$n",
+                      frame, procname, filename, rope(line))
+
+proc deinitFrameNoDebug(p: BProc; frame: Rope): Rope =
+  result = ropecg(p.module, "\t#popFrameOfAddr(&$1);$n", frame)
+
+proc deinitFrame(p: BProc): Rope =
+  result = ropecg(p.module, "\t#popFrame();$n")
+
+include ccgexprs
 
 # ----------------------------- dynamic library handling -----------------
 # We don't finalize dynamic libs as the OS does this for us.
@@ -493,21 +556,28 @@ proc loadDynamicLib(m: BModule, lib: PLib) =
     if lib.path.kind in {nkStrLit..nkTripleStrLit}:
       var s: TStringSeq = @[]
       libCandidates(lib.path.strVal, s)
-      rawMessage(hintDependency, lib.path.strVal)
+      rawMessage(m.config, hintDependency, lib.path.strVal)
       var loadlib: Rope = nil
       for i in countup(0, high(s)):
         inc(m.labels)
         if i > 0: add(loadlib, "||")
-        appcg(m, loadlib, "($1 = #nimLoadLibrary((#NimStringDesc*) &$2))$n",
-              [tmp, getStrLit(m, s[i])])
+        let n = newStrNode(nkStrLit, s[i])
+        n.info = lib.path.info
+        appcg(m, loadlib, "($1 = #nimLoadLibrary($2))$n",
+              [tmp, genStringLiteral(m, n)])
       appcg(m, m.s[cfsDynLibInit],
-            "if (!($1)) #nimLoadLibraryError((#NimStringDesc*) &$2);$n",
-            [loadlib, getStrLit(m, lib.path.strVal)])
+            "if (!($1)) #nimLoadLibraryError($2);$n",
+            [loadlib, genStringLiteral(m, lib.path)])
     else:
       var p = newProc(nil, m)
       p.options = p.options - {optStackTrace, optEndb}
       var dest: TLoc
-      initLocExpr(p, lib.path, dest)
+      initLoc(dest, locTemp, lib.path, OnStack)
+      dest.r = getTempName(m)
+      appcg(m, m.s[cfsDynLibInit],"$1 $2;$n",
+           [getTypeDesc(m, lib.path.typ), rdLoc(dest)])
+      expr(p, lib.path, dest)
+
       add(m.s[cfsVars], p.s(cpsLocals))
       add(m.s[cfsDynLibInit], p.s(cpsInit))
       add(m.s[cfsDynLibInit], p.s(cpsStmts))
@@ -515,14 +585,16 @@ proc loadDynamicLib(m: BModule, lib: PLib) =
            "if (!($1 = #nimLoadLibrary($2))) #nimLoadLibraryError($2);$n",
            [tmp, rdLoc(dest)])
 
-  if lib.name == nil: internalError("loadDynamicLib")
+  if lib.name == nil: internalError(m.config, "loadDynamicLib")
 
 proc mangleDynLibProc(sym: PSym): Rope =
+  # we have to build this as a single rope in order not to trip the
+  # optimization in genInfixCall
   if sfCompilerProc in sym.flags:
     # NOTE: sym.loc.r is the external name!
     result = rope(sym.name.s)
   else:
-    result = "Dl_$1_" % [rope(sym.id)]
+    result = rope(strutils.`%`("Dl_$1_", $sym.id))
 
 proc symInDynamicLib(m: BModule, sym: PSym) =
   var lib = sym.annex
@@ -546,14 +618,14 @@ proc symInDynamicLib(m: BModule, sym: PSym) =
         [tmp, getTypeDesc(m, sym.typ), params, makeCString($extname)]
     var last = lastSon(n)
     if last.kind == nkHiddenStdConv: last = last.sons[1]
-    internalAssert(last.kind == nkStrLit)
+    internalAssert(m.config, last.kind == nkStrLit)
     let idx = last.strVal
     if idx.len == 0:
       add(m.initProc.s(cpsStmts), load)
     elif idx.len == 1 and idx[0] in {'0'..'9'}:
       add(m.extensionLoaders[idx[0]], load)
     else:
-      internalError(sym.info, "wrong index: " & idx)
+      internalError(m.config, sym.info, "wrong index: " & idx)
   else:
     appcg(m, m.s[cfsDynLibInit],
         "\t$1 = ($2) #nimGetProcAddr($3, $4);$n",
@@ -579,68 +651,87 @@ proc symInDynamicLibPartial(m: BModule, sym: PSym) =
   sym.typ.sym = nil           # generate a new name
 
 proc cgsym(m: BModule, name: string): Rope =
-  var sym = magicsys.getCompilerProc(name)
+  let sym = magicsys.getCompilerProc(m.g.graph, name)
   if sym != nil:
     case sym.kind
-    of skProc, skMethod, skConverter, skIterator: genProc(m, sym)
-    of skVar, skResult, skLet: genVarPrototype(m, sym)
+    of skProc, skFunc, skMethod, skConverter, skIterator: genProc(m, sym)
+    of skVar, skResult, skLet: genVarPrototype(m, newSymNode sym)
     of skType: discard getTypeDesc(m, sym.typ)
-    else: internalError("cgsym: " & name & ": " & $sym.kind)
+    else: internalError(m.config, "cgsym: " & name & ": " & $sym.kind)
   else:
     # we used to exclude the system module from this check, but for DLL
     # generation support this sloppyness leads to hard to detect bugs, so
     # we're picky here for the system module too:
-    rawMessage(errSystemNeeds, name)
+    rawMessage(m.config, errGenerated, "system module needs: " & name)
   result = sym.loc.r
 
 proc generateHeaders(m: BModule) =
-  add(m.s[cfsHeaders], tnl & "#include \"nimbase.h\"" & tnl)
+  add(m.s[cfsHeaders], "\L#include \"nimbase.h\"\L")
 
   for it in m.headerFiles:
     if it[0] == '#':
-      add(m.s[cfsHeaders], rope(it.replace('`', '"') & tnl))
+      add(m.s[cfsHeaders], rope(it.replace('`', '"') & "\L"))
     elif it[0] notin {'\"', '<'}:
       addf(m.s[cfsHeaders], "#include \"$1\"$N", [rope(it)])
     else:
       addf(m.s[cfsHeaders], "#include $1$N", [rope(it)])
-  add(m.s[cfsHeaders], "#undef linux" & tnl)
-
-proc initFrame(p: BProc, procname, filename: Rope): Rope =
-  discard cgsym(p.module, "nimFrame")
-  if p.maxFrameLen > 0:
-    discard cgsym(p.module, "VarSlot")
-    result = rfmt(nil, "\tnimfrs_($1, $2, $3, $4)$N",
-                  procname, filename, p.maxFrameLen.rope,
-                  p.blocks[0].frameLen.rope)
-  else:
-    result = rfmt(nil, "\tnimfr_($1, $2)$N", procname, filename)
-
-proc deinitFrame(p: BProc): Rope =
-  result = rfmt(p.module, "\t#popFrame();$n")
+  add(m.s[cfsHeaders], "#undef LANGUAGE_C\L")
+  add(m.s[cfsHeaders], "#undef MIPSEB\L")
+  add(m.s[cfsHeaders], "#undef MIPSEL\L")
+  add(m.s[cfsHeaders], "#undef PPC\L")
+  add(m.s[cfsHeaders], "#undef R3000\L")
+  add(m.s[cfsHeaders], "#undef R4000\L")
+  add(m.s[cfsHeaders], "#undef i386\L")
+  add(m.s[cfsHeaders], "#undef linux\L")
+  add(m.s[cfsHeaders], "#undef mips\L")
+  add(m.s[cfsHeaders], "#undef near\L")
+  add(m.s[cfsHeaders], "#undef far\L")
+  add(m.s[cfsHeaders], "#undef powerpc\L")
+  add(m.s[cfsHeaders], "#undef unix\L")
+
+proc openNamespaceNim(namespace: string): Rope =
+  result.add("namespace ")
+  result.add(namespace)
+  result.add(" {\L")
+
+proc closeNamespaceNim(): Rope =
+  result.add("}\L")
 
 proc closureSetup(p: BProc, prc: PSym) =
   if tfCapturesEnv notin prc.typ.flags: return
   # prc.ast[paramsPos].last contains the type we're after:
   var ls = lastSon(prc.ast[paramsPos])
   if ls.kind != nkSym:
-    internalError(prc.info, "closure generation failed")
+    internalError(p.config, prc.info, "closure generation failed")
   var env = ls.sym
   #echo "created environment: ", env.id, " for ", prc.name.s
-  assignLocalVar(p, env)
+  assignLocalVar(p, ls)
   # generate cast assignment:
-  linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n",
-          rdLoc(env.loc), getTypeDesc(p.module, env.typ))
+  if p.config.selectedGC == gcGo:
+    linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, ($2) ClE_0);$n",
+            addrLoc(p.config, env.loc), getTypeDesc(p.module, env.typ))
+  else:
+    linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n",
+            rdLoc(env.loc), getTypeDesc(p.module, env.typ))
+
+proc containsResult(n: PNode): bool =
+  if n.kind == nkSym and n.sym.kind == skResult:
+    result = true
+  else:
+    for i in 0..<n.safeLen:
+      if containsResult(n[i]): return true
+
+const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt, nkTemplateDef, nkMacroDef} +
+                  declarativeDefs
 
 proc easyResultAsgn(n: PNode): PNode =
-  const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt} +
-                    declarativeDefs
   case n.kind
   of nkStmtList, nkStmtListExpr:
     var i = 0
     while i < n.len and n[i].kind in harmless: inc i
     if i < n.len: result = easyResultAsgn(n[i])
   of nkAsgn, nkFastAsgn:
-    if n[0].kind == nkSym and skResult == n[0].sym.kind:
+    if n[0].kind == nkSym and n[0].sym.kind == skResult and not containsResult(n[1]):
       incl n.flags, nfPreventCg
       return n[1]
   of nkReturnStmt:
@@ -649,57 +740,175 @@ proc easyResultAsgn(n: PNode): PNode =
       if result != nil: incl n.flags, nfPreventCg
   else: discard
 
+type
+  InitResultEnum = enum Unknown, InitSkippable, InitRequired
+
+proc allPathsAsgnResult(n: PNode): InitResultEnum =
+  # Exceptions coming from calls don't have not be considered here:
+  #
+  # proc bar(): string = raise newException(...)
+  #
+  # proc foo(): string =
+  #   # optimized out: 'reset(result)'
+  #   result = bar()
+  #
+  # try:
+  #   a = foo()
+  # except:
+  #   echo "a was not written to"
+  #
+  template allPathsInBranch(it) =
+    let a = allPathsAsgnResult(it)
+    case a
+    of InitRequired: return InitRequired
+    of InitSkippable: discard
+    of Unknown:
+      # sticky, but can be overwritten by InitRequired:
+      result = Unknown
+
+  result = Unknown
+  case n.kind
+  of nkStmtList, nkStmtListExpr:
+    for it in n:
+      result = allPathsAsgnResult(it)
+      if result != Unknown: return result
+  of nkAsgn, nkFastAsgn:
+    if n[0].kind == nkSym and n[0].sym.kind == skResult:
+      if not containsResult(n[1]): result = InitSkippable
+      else: result = InitRequired
+    elif containsResult(n):
+      result = InitRequired
+  of nkReturnStmt:
+    if n.len > 0:
+      if n[0].kind == nkEmpty and result != InitSkippable:
+        # This is a bare `return` statement, if `result` was not initialized
+        # anywhere else (or if we're not sure about this) let's require it to be
+        # initialized. This avoids cases like #9286 where this heuristic lead to
+        # wrong code being generated.
+        result = InitRequired
+      else: result = allPathsAsgnResult(n[0])
+  of nkIfStmt, nkIfExpr:
+    var exhaustive = false
+    result = InitSkippable
+    for it in n:
+      # Every condition must not use 'result':
+      if it.len == 2 and containsResult(it[0]):
+        return InitRequired
+      if it.len == 1: exhaustive = true
+      allPathsInBranch(it.lastSon)
+    # if the 'if' statement is not exhaustive and yet it touched 'result'
+    # in some way, say Unknown.
+    if not exhaustive: result = Unknown
+  of nkCaseStmt:
+    if containsResult(n[0]): return InitRequired
+    result = InitSkippable
+    var exhaustive = skipTypes(n[0].typ,
+        abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString}
+    for i in 1..<n.len:
+      let it = n[i]
+      allPathsInBranch(it.lastSon)
+      if it.kind == nkElse: exhaustive = true
+    if not exhaustive: result = Unknown
+  of nkWhileStmt:
+    # some dubious code can assign the result in the 'while'
+    # condition and that would be fine. Everything else isn't:
+    result = allPathsAsgnResult(n[0])
+    if result == Unknown:
+      result = allPathsAsgnResult(n[1])
+      # we cannot assume that the 'while' loop is really executed at least once:
+      if result == InitSkippable: result = Unknown
+  of harmless:
+    result = Unknown
+  of nkGotoState, nkBreakState:
+    # give up for now.
+    result = InitRequired
+  of nkSym:
+    # some path reads from 'result' before it was written to!
+    if n.sym.kind == skResult: result = InitRequired
+  of nkTryStmt:
+    # We need to watch out for the following problem:
+    # try:
+    #   result = stuffThatRaises()
+    # except:
+    #   discard "result was not set"
+    #
+    # So ... even if the assignment to 'result' is the very first
+    # assignment this is not good enough! The only pattern we allow for
+    # is 'finally: result = x'
+    result = InitSkippable
+    for it in n:
+      if it.kind == nkFinally:
+        result = allPathsAsgnResult(it.lastSon)
+      else:
+        allPathsInBranch(it.lastSon)
+  else:
+    for i in 0..<safeLen(n):
+      allPathsInBranch(n[i])
+
 proc genProcAux(m: BModule, prc: PSym) =
   var p = newProc(prc, m)
   var header = genProcHeader(m, prc)
   var returnStmt: Rope = nil
   assert(prc.ast != nil)
+  let procBody = transformBody(m.g.graph, prc, cache = false)
+
   if sfPure notin prc.flags and prc.typ.sons[0] != nil:
     if resultPos >= prc.ast.len:
-      internalError(prc.info, "proc has no result symbol")
-    var res = prc.ast.sons[resultPos].sym # get result symbol
-    if not isInvalidReturnType(prc.typ.sons[0]):
+      internalError(m.config, prc.info, "proc has no result symbol")
+    let resNode = prc.ast.sons[resultPos]
+    let res = resNode.sym # get result symbol
+    if not isInvalidReturnType(m.config, prc.typ.sons[0]):
       if sfNoInit in prc.flags: incl(res.flags, sfNoInit)
-      if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(prc.getBody); val != nil):
-        var decl = localVarDecl(p, res)
+      if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(procBody); val != nil):
+        var decl = localVarDecl(p, resNode)
         var a: TLoc
         initLocExprSingleUse(p, val, a)
         linefmt(p, cpsStmts, "$1 = $2;$n", decl, rdLoc(a))
       else:
         # declare the result symbol:
-        assignLocalVar(p, res)
+        assignLocalVar(p, resNode)
         assert(res.loc.r != nil)
         initLocalVar(p, res, immediateAsgn=false)
-      returnStmt = rfmt(nil, "\treturn $1;$n", rdLoc(res.loc))
+      returnStmt = ropecg(p.module, "\treturn $1;$n", rdLoc(res.loc))
     else:
-      fillResult(res)
+      fillResult(p.config, resNode)
       assignParam(p, res)
+      # We simplify 'unsureAsgn(result, nil); unsureAsgn(result, x)'
+      # to 'unsureAsgn(result, x)'
+      # Sketch why this is correct: If 'result' points to a stack location
+      # the 'unsureAsgn' is a nop. If it points to a global variable the
+      # global is either 'nil' or points to valid memory and so the RC operation
+      # succeeds without touching not-initialized memory.
+      if sfNoInit in prc.flags: discard
+      elif allPathsAsgnResult(procBody) == InitSkippable: discard
+      else:
+        resetLoc(p, res.loc)
       if skipTypes(res.typ, abstractInst).kind == tyArray:
         #incl(res.loc.flags, lfIndirect)
-        res.loc.s = OnUnknown
+        res.loc.storage = OnUnknown
 
   for i in countup(1, sonsLen(prc.typ.n) - 1):
-    var param = prc.typ.n.sons[i].sym
+    let param = prc.typ.n.sons[i].sym
     if param.typ.isCompileTimeOnly: continue
     assignParam(p, param)
   closureSetup(p, prc)
-  genStmts(p, prc.getBody) # modifies p.locals, p.init, etc.
+  genStmts(p, procBody) # modifies p.locals, p.init, etc.
   var generatedProc: Rope
   if sfNoReturn in prc.flags:
-    if hasDeclspec in extccomp.CC[extccomp.cCompiler].props:
+    if hasDeclspec in extccomp.CC[p.config.cCompiler].props:
       header = "__declspec(noreturn) " & header
   if sfPure in prc.flags:
-    if hasDeclspec in extccomp.CC[extccomp.cCompiler].props:
+    if hasDeclspec in extccomp.CC[p.config.cCompiler].props:
       header = "__declspec(naked) " & header
-    generatedProc = rfmt(nil, "$N$1 {$n$2$3$4}$N$N",
+    generatedProc = ropecg(p.module, "$N$1 {$n$2$3$4}$N$N",
                          header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts))
   else:
-    generatedProc = rfmt(nil, "$N$1 {$N", header)
+    generatedProc = ropecg(p.module, "$N$1 {$N", header)
     add(generatedProc, initGCFrame(p))
     if optStackTrace in prc.options:
       add(generatedProc, p.s(cpsLocals))
       var procname = makeCString(prc.name.s)
-      add(generatedProc, initFrame(p, procname, prc.info.quotedFilename))
+      add(generatedProc, initFrame(p, procname, quotedFilename(p.config, prc.info)))
     else:
       add(generatedProc, p.s(cpsLocals))
     if optProfiler in prc.options:
@@ -718,10 +927,10 @@ proc genProcAux(m: BModule, prc: PSym) =
 proc requiresExternC(m: BModule; sym: PSym): bool {.inline.} =
   result = (sfCompileToCpp in m.module.flags and
            sfCompileToCpp notin sym.getModule().flags and
-           gCmd != cmdCompileToCpp) or (
+           m.config.cmd != cmdCompileToCpp) or (
            sym.flags * {sfImportc, sfInfixCall, sfCompilerProc} == {sfImportc} and
            sym.magic == mNone and
-           gCmd == cmdCompileToCpp)
+           m.config.cmd == cmdCompileToCpp)
 
 proc genProcPrototype(m: BModule, sym: PSym) =
   useHeader(m, sym)
@@ -729,29 +938,29 @@ proc genProcPrototype(m: BModule, sym: PSym) =
   if lfDynamicLib in sym.loc.flags:
     if getModule(sym).id != m.module.id and
         not containsOrIncl(m.declaredThings, sym.id):
-      add(m.s[cfsVars], rfmt(nil, "extern $1 $2;$n",
+      add(m.s[cfsVars], ropecg(m, "extern $1 $2;$n",
                         getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym)))
   elif not containsOrIncl(m.declaredProtos, sym.id):
     var header = genProcHeader(m, sym)
-    if sfNoReturn in sym.flags and hasDeclspec in extccomp.CC[cCompiler].props:
+    if sfNoReturn in sym.flags and hasDeclspec in extccomp.CC[m.config.cCompiler].props:
       header = "__declspec(noreturn) " & header
     if sym.typ.callConv != ccInline and requiresExternC(m, sym):
       header = "extern \"C\" " & header
-    if sfPure in sym.flags and hasAttribute in CC[cCompiler].props:
+    if sfPure in sym.flags and hasAttribute in CC[m.config.cCompiler].props:
       header.add(" __attribute__((naked))")
-    if sfNoReturn in sym.flags and hasAttribute in CC[cCompiler].props:
+    if sfNoReturn in sym.flags and hasAttribute in CC[m.config.cCompiler].props:
       header.add(" __attribute__((noreturn))")
-    add(m.s[cfsProcHeaders], rfmt(nil, "$1;$n", header))
+    add(m.s[cfsProcHeaders], ropecg(m, "$1;$n", header))
 
 proc genProcNoForward(m: BModule, prc: PSym) =
   if lfImportCompilerProc in prc.loc.flags:
-    fillProcLoc(m, prc)
+    fillProcLoc(m, prc.ast[namePos])
     useHeader(m, prc)
     # dependency to a compilerproc:
     discard cgsym(m, prc.name.s)
     return
   if lfNoDecl in prc.loc.flags:
-    fillProcLoc(m, prc)
+    fillProcLoc(m, prc.ast[namePos])
     useHeader(m, prc)
     genProcPrototype(m, prc)
   elif prc.typ.callConv == ccInline:
@@ -760,7 +969,7 @@ proc genProcNoForward(m: BModule, prc: PSym) =
     # a check for ``m.declaredThings``.
     if not containsOrIncl(m.declaredThings, prc.id):
       #if prc.loc.k == locNone:
-      fillProcLoc(m, prc)
+      fillProcLoc(m, prc.ast[namePos])
       #elif {sfExportc, sfImportc} * prc.flags == {}:
       #  # reset name to restore consistency in case of hashing collisions:
       #  echo "resetting ", prc.id, " by ", m.module.name.s
@@ -771,7 +980,7 @@ proc genProcNoForward(m: BModule, prc: PSym) =
       genProcAux(m, prc)
   elif lfDynamicLib in prc.loc.flags:
     var q = findPendingModule(m, prc)
-    fillProcLoc(q, prc)
+    fillProcLoc(q, prc.ast[namePos])
     useHeader(m, prc)
     genProcPrototype(m, prc)
     if q != nil and not containsOrIncl(q.declaredThings, prc.id):
@@ -780,13 +989,13 @@ proc genProcNoForward(m: BModule, prc: PSym) =
       symInDynamicLibPartial(m, prc)
   elif sfImportc notin prc.flags:
     var q = findPendingModule(m, prc)
-    fillProcLoc(q, prc)
+    fillProcLoc(q, prc.ast[namePos])
     useHeader(m, prc)
     genProcPrototype(m, prc)
     if q != nil and not containsOrIncl(q.declaredThings, prc.id):
       genProcAux(q, prc)
   else:
-    fillProcLoc(m, prc)
+    fillProcLoc(m, prc.ast[namePos])
     useHeader(m, prc)
     if sfInfixCall notin prc.flags: genProcPrototype(m, prc)
 
@@ -794,7 +1003,7 @@ proc requestConstImpl(p: BProc, sym: PSym) =
   var m = p.module
   useHeader(m, sym)
   if sym.loc.k == locNone:
-    fillLoc(sym.loc, locData, sym.typ, mangleName(p.module, sym), OnStatic)
+    fillLoc(sym.loc, locData, sym.ast, mangleName(p.module, sym), OnStatic)
   if lfNoDecl in sym.loc.flags: return
   # declare implementation:
   var q = findPendingModule(m, sym)
@@ -817,7 +1026,7 @@ proc genProc(m: BModule, prc: PSym) =
   if sfBorrow in prc.flags or not isActivated(prc): return
   if sfForward in prc.flags:
     addForwardedProc(m, prc)
-    fillProcLoc(m, prc)
+    fillProcLoc(m, prc.ast[namePos])
   else:
     genProcNoForward(m, prc)
     if {sfExportc, sfCompilerProc} * prc.flags == {sfExportc} and
@@ -827,11 +1036,12 @@ proc genProc(m: BModule, prc: PSym) =
         if not containsOrIncl(m.g.generatedHeader.declaredThings, prc.id):
           genProcAux(m.g.generatedHeader, prc)
 
-proc genVarPrototypeAux(m: BModule, sym: PSym) =
+proc genVarPrototype(m: BModule, n: PNode) =
   #assert(sfGlobal in sym.flags)
+  let sym = n.sym
   useHeader(m, sym)
-  fillLoc(sym.loc, locGlobalVar, sym.typ, mangleName(m, sym), OnHeap)
-  if (lfNoDecl in sym.loc.flags) or containsOrIncl(m.declaredThings, sym.id):
+  fillLoc(sym.loc, locGlobalVar, n, mangleName(m, sym), OnHeap)
+  if (lfNoDecl in sym.loc.flags) or contains(m.declaredThings, sym.id):
     return
   if sym.owner.id != m.module.id:
     # else we already have the symbol generated!
@@ -846,41 +1056,43 @@ proc genVarPrototypeAux(m: BModule, sym: PSym) =
       if sfVolatile in sym.flags: add(m.s[cfsVars], " volatile")
       addf(m.s[cfsVars], " $1;$n", [sym.loc.r])
 
-proc genVarPrototype(m: BModule, sym: PSym) =
-  genVarPrototypeAux(m, sym)
-
-proc addIntTypes(result: var Rope) {.inline.} =
-  addf(result, "#define NIM_NEW_MANGLING_RULES" & tnl &
-               "#define NIM_INTBITS $1" & tnl, [
-    platform.CPU[targetCPU].intSize.rope])
-
-proc getCopyright(cfile: Cfile): Rope =
-  if optCompileOnly in gGlobalOptions:
+proc addIntTypes(result: var Rope; conf: ConfigRef) {.inline.} =
+  addf(result, "#define NIM_NEW_MANGLING_RULES\L" &
+               "#define NIM_INTBITS $1\L", [
+    platform.CPU[conf.target.targetCPU].intSize.rope])
+  if conf.cppCustomNamespace.len > 0:
+    result.add("#define USE_NIM_NAMESPACE ")
+    result.add(conf.cppCustomNamespace)
+    result.add("\L")
+
+proc getCopyright(conf: ConfigRef; cfile: Cfile): Rope =
+  if optCompileOnly in conf.globalOptions:
     result = ("/* Generated by Nim Compiler v$1 */$N" &
-        "/*   (c) " & CompileDate.substr(0, 3) & " Andreas Rumpf */$N" &
+        "/*   (c) " & copyrightYear & " Andreas Rumpf */$N" &
         "/* The generated code is subject to the original license. */$N") %
         [rope(VersionAsString)]
   else:
     result = ("/* Generated by Nim Compiler v$1 */$N" &
-        "/*   (c) " & CompileDate.substr(0, 3) & " Andreas Rumpf */$N" &
+        "/*   (c) " & copyrightYear & " Andreas Rumpf */$N" &
         "/* The generated code is subject to the original license. */$N" &
         "/* Compiled for: $2, $3, $4 */$N" &
         "/* Command for C compiler:$n   $5 */$N") %
         [rope(VersionAsString),
-        rope(platform.OS[targetOS].name),
-        rope(platform.CPU[targetCPU].name),
-        rope(extccomp.CC[extccomp.cCompiler].name),
-        rope(getCompileCFileCmd(cfile))]
+        rope(platform.OS[conf.target.targetOS].name),
+        rope(platform.CPU[conf.target.targetCPU].name),
+        rope(extccomp.CC[conf.cCompiler].name),
+        rope(getCompileCFileCmd(conf, cfile))]
 
-proc getFileHeader(cfile: Cfile): Rope =
-  result = getCopyright(cfile)
-  addIntTypes(result)
+proc getFileHeader(conf: ConfigRef; cfile: Cfile): Rope =
+  result = getCopyright(conf, cfile)
+  addIntTypes(result, conf)
 
 proc genFilenames(m: BModule): Rope =
   discard cgsym(m, "dbgRegisterFilename")
   result = nil
-  for i in 0.. <fileInfos.len:
-    result.addf("dbgRegisterFilename($1);$N", [fileInfos[i].projPath.makeCString])
+  for i in 0..<m.config.m.fileInfos.len:
+    result.addf("dbgRegisterFilename($1);$N",
+      [m.config.m.fileInfos[i].projPath.string.makeCString])
 
 proc genMainProc(m: BModule) =
   const
@@ -888,14 +1100,14 @@ proc genMainProc(m: BModule) =
     # prevents inlining of the NimMainInner function and dependent
     # functions, which might otherwise merge their stack frames.
     PreMainBody =
-      "void PreMainInner() {$N" &
+      "void PreMainInner(void) {$N" &
       "\tsystemInit000();$N" &
       "$1" &
       "$2" &
       "$3" &
       "}$N$N" &
-      "void PreMain() {$N" &
-      "\tvoid (*volatile inner)();$N" &
+      "void PreMain(void) {$N" &
+      "\tvoid (*volatile inner)(void);$N" &
       "\tsystemDatInit000();$N" &
       "\tinner = PreMainInner;$N" &
       "$4$5" &
@@ -914,7 +1126,7 @@ proc genMainProc(m: BModule) =
 
     NimMainProc =
       "N_CDECL(void, NimMain)(void) {$N" &
-        "\tvoid (*volatile inner)();$N" &
+        "\tvoid (*volatile inner)(void);$N" &
         "\tPreMain();$N" &
         "\tinner = NimMainInner;$N" &
         "$2" &
@@ -965,36 +1177,57 @@ proc genMainProc(m: BModule) =
         MainProcs &
       "}$N$N"
 
+    GenodeNimMain =
+      "extern Genode::Env *nim_runtime_env;$N" &
+      "extern void nim_component_construct(Genode::Env*);$N$N" &
+      NimMainBody
+
+    ComponentConstruct =
+      "void Libc::Component::construct(Libc::Env &env) {$N" &
+      "\t// Set Env used during runtime initialization$N" &
+      "\tnim_runtime_env = &env;$N" &
+      "\tLibc::with_libc([&] () {$N\t" &
+      "\t// Initialize runtime and globals$N" &
+      MainProcs &
+      "\t// Call application construct$N" &
+      "\t\tnim_component_construct(&env);$N" &
+      "\t});$N" &
+      "}$N$N"
+
   var nimMain, otherMain: FormatStr
-  if platform.targetOS == osWindows and
-      gGlobalOptions * {optGenGuiApp, optGenDynLib} != {}:
-    if optGenGuiApp in gGlobalOptions:
+  if m.config.target.targetOS == osWindows and
+      m.config.globalOptions * {optGenGuiApp, optGenDynLib} != {}:
+    if optGenGuiApp in m.config.globalOptions:
       nimMain = WinNimMain
       otherMain = WinCMain
     else:
       nimMain = WinNimDllMain
       otherMain = WinCDllMain
     m.includeHeader("<windows.h>")
-  elif optGenDynLib in gGlobalOptions:
+  elif m.config.target.targetOS == osGenode:
+    nimMain = GenodeNimMain
+    otherMain = ComponentConstruct
+    m.includeHeader("<libc/component.h>")
+  elif optGenDynLib in m.config.globalOptions:
     nimMain = PosixNimDllMain
     otherMain = PosixCDllMain
-  elif platform.targetOS == osStandalone:
+  elif m.config.target.targetOS == osStandalone:
     nimMain = PosixNimMain
     otherMain = StandaloneCMain
   else:
     nimMain = PosixNimMain
     otherMain = PosixCMain
   if m.g.breakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint")
-  if optEndb in gOptions:
+  if optEndb in m.config.options:
     m.g.breakpoints.add(m.genFilenames)
 
   let initStackBottomCall =
-    if platform.targetOS == osStandalone or gSelectedGC == gcNone: "".rope
+    if m.config.target.targetOS == osStandalone or m.config.selectedGC == gcNone: "".rope
     else: ropecg(m, "\t#initStackBottomWith((void *)&inner);$N")
   inc(m.labels)
   appcg(m, m.s[cfsProcs], PreMainBody, [
     m.g.mainDatInit, m.g.breakpoints, m.g.otherModsInit,
-     if emulatedThreadVars() and platform.targetOS != osStandalone:
+     if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
        ropecg(m, "\t#initThreadVarsEmulation();$N")
      else:
        "".rope,
@@ -1002,8 +1235,12 @@ proc genMainProc(m: BModule) =
 
   appcg(m, m.s[cfsProcs], nimMain,
         [m.g.mainModInit, initStackBottomCall, rope(m.labels)])
-  if optNoMain notin gGlobalOptions:
+  if optNoMain notin m.config.globalOptions:
+    if m.config.cppCustomNamespace.len > 0:
+      m.s[cfsProcs].add closeNamespaceNim() & "using namespace " & m.config.cppCustomNamespace & ";\L"
+
     appcg(m, m.s[cfsProcs], otherMain, [])
+    if m.config.cppCustomNamespace.len > 0: m.s[cfsProcs].add openNamespaceNim(m.config.cppCustomNamespace)
 
 proc getSomeInitName(m: PSym, suffix: string): Rope =
   assert m.kind == skModule
@@ -1011,7 +1248,7 @@ proc getSomeInitName(m: PSym, suffix: string): Rope =
   if {sfSystemModule, sfMainModule} * m.flags == {}:
     result = m.owner.name.s.mangle.rope
     result.add "_"
-  result.add m.name.s
+  result.add m.name.s.mangle
   result.add suffix
 
 proc getInitName(m: PSym): Rope =
@@ -1027,8 +1264,8 @@ proc registerModuleToMain(g: BModuleList; m: PSym) =
   var
     init = m.getInitName
     datInit = m.getDatInitName
-  addf(g.mainModProcs, "NIM_EXTERNC N_NOINLINE(void, $1)(void);$N", [init])
-  addf(g.mainModProcs, "NIM_EXTERNC N_NOINLINE(void, $1)(void);$N", [datInit])
+  addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [init])
+  addf(g.mainModProcs, "N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [datInit])
   if sfSystemModule notin m.flags:
     addf(g.mainDatInit, "\t$1();$N", [datInit])
     let initCall = "\t$1();$N" % [init]
@@ -1039,7 +1276,7 @@ proc registerModuleToMain(g: BModuleList; m: PSym) =
 
 proc genInitCode(m: BModule) =
   var initname = getInitName(m.module)
-  var prc = "NIM_EXTERNC N_NOINLINE(void, $1)(void) {$N" % [initname]
+  var prc = "N_LIB_PRIVATE N_NIMCALL(void, $1)(void) {$N" % [initname]
   if m.typeNodes > 0:
     appcg(m, m.s[cfsTypeInit1], "static #TNimNode $1[$2];$n",
           [m.typeNodesName, rope(m.typeNodes)])
@@ -1047,13 +1284,30 @@ proc genInitCode(m: BModule) =
     appcg(m, m.s[cfsTypeInit1], "static #TNimType $1[$2];$n",
           [m.nimTypesName, rope(m.nimTypes)])
 
+  # Give this small function its own scope
+  addf(prc, "{$N", [])
+  block:
+    # Keep a bogus frame in case the code needs one
+    add(prc, ~"\tTFrame FR_; FR_.len = 0;$N")
+
+    add(prc, genSectionStart(cpsLocals, m.config))
+    add(prc, m.preInitProc.s(cpsLocals))
+    add(prc, genSectionEnd(cpsLocals, m.config))
+
+    add(prc, genSectionStart(cpsInit, m.config))
+    add(prc, m.preInitProc.s(cpsInit))
+    add(prc, genSectionEnd(cpsInit, m.config))
+
+    add(prc, genSectionStart(cpsStmts, m.config))
+    add(prc, m.preInitProc.s(cpsStmts))
+    add(prc, genSectionEnd(cpsStmts, m.config))
+  addf(prc, "}$N", [])
+
   add(prc, initGCFrame(m.initProc))
 
-  add(prc, genSectionStart(cpsLocals))
-  add(prc, m.preInitProc.s(cpsLocals))
+  add(prc, genSectionStart(cpsLocals, m.config))
   add(prc, m.initProc.s(cpsLocals))
-  add(prc, m.postInitProc.s(cpsLocals))
-  add(prc, genSectionEnd(cpsLocals))
+  add(prc, genSectionEnd(cpsLocals, m.config))
 
   if optStackTrace in m.initProc.options and frameDeclared notin m.flags:
     # BUT: the generated init code might depend on a current frame, so
@@ -1061,33 +1315,30 @@ proc genInitCode(m: BModule) =
     incl m.flags, frameDeclared
     if preventStackTrace notin m.flags:
       var procname = makeCString(m.module.name.s)
-      add(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename))
+      add(prc, initFrame(m.initProc, procname, quotedFilename(m.config, m.module.info)))
     else:
       add(prc, ~"\tTFrame FR_; FR_.len = 0;$N")
 
-  add(prc, genSectionStart(cpsInit))
-  add(prc, m.preInitProc.s(cpsInit))
+  add(prc, genSectionStart(cpsInit, m.config))
   add(prc, m.initProc.s(cpsInit))
-  add(prc, m.postInitProc.s(cpsInit))
-  add(prc, genSectionEnd(cpsInit))
+  add(prc, genSectionEnd(cpsInit, m.config))
 
-  add(prc, genSectionStart(cpsStmts))
-  add(prc, m.preInitProc.s(cpsStmts))
+  add(prc, genSectionStart(cpsStmts, m.config))
   add(prc, m.initProc.s(cpsStmts))
-  add(prc, m.postInitProc.s(cpsStmts))
-  add(prc, genSectionEnd(cpsStmts))
+  add(prc, genSectionEnd(cpsStmts, m.config))
+
   if optStackTrace in m.initProc.options and preventStackTrace notin m.flags:
     add(prc, deinitFrame(m.initProc))
   add(prc, deinitGCFrame(m.initProc))
   addf(prc, "}$N$N", [])
 
-  prc.addf("NIM_EXTERNC N_NOINLINE(void, $1)(void) {$N",
+  prc.addf("N_LIB_PRIVATE N_NIMCALL(void, $1)(void) {$N",
            [getDatInitName(m.module)])
 
   for i in cfsTypeInit1..cfsDynLibInit:
-    add(prc, genSectionStart(i))
+    add(prc, genSectionStart(i, m.config))
     add(prc, m.s[i])
-    add(prc, genSectionEnd(i))
+    add(prc, genSectionEnd(i, m.config))
 
   addf(prc, "}$N$N", [])
   # we cannot simply add the init proc to ``m.s[cfsProcs]`` anymore because
@@ -1097,37 +1348,37 @@ proc genInitCode(m: BModule) =
 
   for i, el in pairs(m.extensionLoaders):
     if el != nil:
-      let ex = "N_NIMCALL(void, nimLoadProcs$1)(void) {$2}$N$N" %
+      let ex = "NIM_EXTERNC N_NIMCALL(void, nimLoadProcs$1)(void) {$2}$N$N" %
         [(i.ord - '0'.ord).rope, el]
       add(m.s[cfsInitProc], ex)
 
 proc genModule(m: BModule, cfile: Cfile): Rope =
-  result = getFileHeader(cfile)
+  result = getFileHeader(m.config, cfile)
   result.add(genMergeInfo(m))
 
   generateThreadLocalStorage(m)
   generateHeaders(m)
   for i in countup(cfsHeaders, cfsProcs):
-    add(result, genSectionStart(i))
+    add(result, genSectionStart(i, m.config))
     add(result, m.s[i])
-    add(result, genSectionEnd(i))
+    add(result, genSectionEnd(i, m.config))
+    if m.config.cppCustomNamespace.len > 0 and i == cfsHeaders:
+      result.add openNamespaceNim(m.config.cppCustomNamespace)
   add(result, m.s[cfsInitProc])
+  if m.config.cppCustomNamespace.len > 0: result.add closeNamespaceNim()
 
 proc newPreInitProc(m: BModule): BProc =
   result = newProc(nil, m)
   # little hack so that unique temporaries are generated:
   result.labels = 100_000
 
-proc newPostInitProc(m: BModule): BProc =
-  result = newProc(nil, m)
-  # little hack so that unique temporaries are generated:
-  result.labels = 200_000
-
 proc initProcOptions(m: BModule): TOptions =
-  if sfSystemModule in m.module.flags: gOptions-{optStackTrace} else: gOptions
+  let opts = m.config.options
+  if sfSystemModule in m.module.flags: opts-{optStackTrace} else: opts
 
-proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule =
+proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule =
   new(result)
+  result.g = g
   result.tmpBase = rope("TM" & $hashOwner(module) & "_")
   result.headerFiles = @[]
   result.declaredThings = initIntSet()
@@ -1142,170 +1393,119 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule =
   result.initProc = newProc(nil, result)
   result.initProc.options = initProcOptions(result)
   result.preInitProc = newPreInitProc(result)
-  result.postInitProc = newPostInitProc(result)
   initNodeTable(result.dataCache)
   result.typeStack = @[]
-  result.forwardedProcs = @[]
   result.typeNodesName = getTempName(result)
   result.nimTypesName = getTempName(result)
-  result.g = g
   # no line tracing for the init sections of the system module so that we
   # don't generate a TFrame which can confuse the stack botton initialization:
   if sfSystemModule in module.flags:
     incl result.flags, preventStackTrace
     excl(result.preInitProc.options, optStackTrace)
-    excl(result.postInitProc.options, optStackTrace)
-  let ndiName = if optCDebug in gGlobalOptions: changeFileExt(completeCFilePath(filename), "ndi")
-                else: ""
-  open(result.ndi, ndiName)
+  let ndiName = if optCDebug in g.config.globalOptions: changeFileExt(completeCFilePath(g.config, filename), "ndi")
+                else: AbsoluteFile""
+  open(result.ndi, ndiName, g.config)
 
 proc nullify[T](arr: var T) =
   for i in low(arr)..high(arr):
     arr[i] = Rope(nil)
 
-proc resetModule*(m: BModule) =
-  # between two compilations in CAAS mode, we can throw
-  # away all the data that was written to disk
-  m.headerFiles = @[]
-  m.declaredProtos = initIntSet()
-  m.forwTypeCache = initTable[SigHash, Rope]()
-  m.initProc = newProc(nil, m)
-  m.initProc.options = initProcOptions(m)
-  m.preInitProc = newPreInitProc(m)
-  m.postInitProc = newPostInitProc(m)
-  initNodeTable(m.dataCache)
-  m.typeStack = @[]
-  m.forwardedProcs = @[]
-  m.typeNodesName = getTempName(m)
-  m.nimTypesName = getTempName(m)
-  if sfSystemModule in m.module.flags:
-    incl m.flags, preventStackTrace
-  else:
-    excl m.flags, preventStackTrace
-  nullify m.s
-  m.typeNodes = 0
-  m.nimTypes = 0
-  nullify m.extensionLoaders
-
-  # indicate that this is now cached module
-  # the cache will be invalidated by nullifying gModules
-  m.fromCache = true
-  m.g = nil
-
-  # we keep only the "merge info" information for the module
-  # and the properties that can't change:
-  # m.filename
-  # m.cfilename
-  # m.isHeaderFile
-  # m.module ?
-  # m.typeCache
-  # m.declaredThings
-  # m.typeInfoMarker
-  # m.labels
-  # m.FrameDeclared
-
-proc resetCgenModules*(g: BModuleList) =
-  for m in cgenModules(g): resetModule(m)
-
-proc rawNewModule(g: BModuleList; module: PSym): BModule =
-  result = rawNewModule(g, module, module.position.int32.toFullPath)
-
-proc newModule(g: BModuleList; module: PSym): BModule =
+proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
+  result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex))
+
+proc newModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
   # we should create only one cgen module for each module sym
-  result = rawNewModule(g, module)
-  growCache g.modules, module.position
+  result = rawNewModule(g, module, conf)
+  if module.position >= g.modules.len:
+    setLen(g.modules, module.position + 1)
+  #growCache g.modules, module.position
   g.modules[module.position] = result
 
-  if (optDeadCodeElim in gGlobalOptions):
-    if (sfDeadCodeElim in module.flags):
-      internalError("added pending module twice: " & module.filename)
-
-template injectG(config) {.dirty.} =
+template injectG() {.dirty.} =
   if graph.backend == nil:
-    graph.backend = newModuleList(config)
+    graph.backend = newModuleList(graph)
   let g = BModuleList(graph.backend)
 
-proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
-  injectG(graph.config)
-  result = newModule(g, module)
-  if optGenIndex in gGlobalOptions and g.generatedHeader == nil:
-    let f = if graph.config.headerFile.len > 0: graph.config.headerFile else: gProjectFull
+proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
+  injectG()
+  result = newModule(g, module, graph.config)
+  if optGenIndex in graph.config.globalOptions and g.generatedHeader == nil:
+    let f = if graph.config.headerFile.len > 0: AbsoluteFile graph.config.headerFile
+            else: graph.config.projectFull
     g.generatedHeader = rawNewModule(g, module,
-      changeFileExt(completeCFilePath(f), hExt))
+      changeFileExt(completeCFilePath(graph.config, f), hExt))
     incl g.generatedHeader.flags, isHeaderFile
 
 proc writeHeader(m: BModule) =
   var result = ("/* Generated by Nim Compiler v$1 */$N" &
-        "/*   (c) " & CompileDate.substr(0, 3) & " Andreas Rumpf */$N" &
+        "/*   (c) 2017 Andreas Rumpf */$N" &
         "/* The generated code is subject to the original license. */$N") %
         [rope(VersionAsString)]
 
   var guard = "__$1__" % [m.filename.splitFile.name.rope]
   result.addf("#ifndef $1$n#define $1$n", [guard])
-  addIntTypes(result)
+  addIntTypes(result, m.config)
   generateHeaders(m)
 
   generateThreadLocalStorage(m)
   for i in countup(cfsHeaders, cfsProcs):
-    add(result, genSectionStart(i))
+    add(result, genSectionStart(i, m.config))
     add(result, m.s[i])
-    add(result, genSectionEnd(i))
+    add(result, genSectionEnd(i, m.config))
+    if m.config.cppCustomNamespace.len > 0 and i == cfsHeaders: result.add openNamespaceNim(m.config.cppCustomNamespace)
   add(result, m.s[cfsInitProc])
 
-  if optGenDynLib in gGlobalOptions:
+  if optGenDynLib in m.config.globalOptions:
     result.add("N_LIB_IMPORT ")
   result.addf("N_CDECL(void, NimMain)(void);$n", [])
+  if m.config.cppCustomNamespace.len > 0: result.add closeNamespaceNim()
   result.addf("#endif /* $1 */$n", [guard])
-  writeRope(result, m.filename)
+  if not writeRope(result, m.filename):
+    rawMessage(m.config, errCannotOpenFile, m.filename.string)
 
-proc getCFile(m: BModule): string =
+proc getCFile(m: BModule): AbsoluteFile =
   let ext =
       if m.compileToCpp: ".cpp"
-      elif gCmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m"
+      elif m.config.cmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m"
       else: ".c"
-  result = changeFileExt(completeCFilePath(m.cfilename.withPackageName), ext)
+  result = changeFileExt(completeCFilePath(m.config, withPackageName(m.config, m.cfilename)), ext)
 
-proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext =
-  injectG(graph.config)
-  assert optSymbolFiles in gGlobalOptions
-  var m = newModule(g, module)
-  readMergeInfo(getCFile(m), m)
-  result = m
+when false:
+  proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext =
+    injectG()
+    var m = newModule(g, module, graph.config)
+    readMergeInfo(getCFile(m), m)
+    result = m
 
 proc myProcess(b: PPassContext, n: PNode): PNode =
   result = n
-  if b == nil or passes.skipCodegen(n): return
+  if b == nil: return
   var m = BModule(b)
+  if passes.skipCodegen(m.config, n): return
   m.initProc.options = initProcOptions(m)
-  genStmts(m.initProc, n)
+  #softRnl = if optLineDir in m.config.options: noRnl else: rnl
+  # XXX replicate this logic!
+  let tranformed_n = transformStmt(m.g.graph, m.module, n)
+  genStmts(m.initProc, tranformed_n)
 
-proc finishModule(m: BModule) =
-  var i = 0
-  while i <= high(m.forwardedProcs):
-    # Note: ``genProc`` may add to ``m.forwardedProcs``, so we cannot use
-    # a ``for`` loop here
-    var prc = m.forwardedProcs[i]
-    if sfForward in prc.flags:
-      internalError(prc.info, "still forwarded: " & prc.name.s)
-    genProcNoForward(m, prc)
-    inc(i)
-  assert(m.g.forwardedProcsCounter >= i)
-  dec(m.g.forwardedProcsCounter, i)
-  setLen(m.forwardedProcs, 0)
-
-proc shouldRecompile(code: Rope, cfile: Cfile): bool =
+proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
   result = true
-  if optForceFullMake notin gGlobalOptions:
+  if optForceFullMake notin m.config.globalOptions:
     if not equalsFile(code, cfile.cname):
-      if isDefined("nimdiff"):
-        copyFile(cfile.cname, cfile.cname & ".backup")
-        echo "diff ", cfile.cname, ".backup ", cfile.cname
-      writeRope(code, cfile.cname)
+      if m.config.symbolFiles == readOnlySf: #isDefined(m.config, "nimdiff"):
+        if fileExists(cfile.cname):
+          copyFile(cfile.cname.string, cfile.cname.string & ".backup")
+          echo "diff ", cfile.cname.string, ".backup ", cfile.cname.string
+        else:
+          echo "new file ", cfile.cname.string
+      if not writeRope(code, cfile.cname):
+        rawMessage(m.config, errCannotOpenFile, cfile.cname.string)
       return
-    if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname):
+    if fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string):
       result = false
   else:
-    writeRope(code, cfile.cname)
+    if not writeRope(code, cfile.cname):
+      rawMessage(m.config, errCannotOpenFile, cfile.cname.string)
 
 # We need 2 different logics here: pending modules (including
 # 'nim__dat') may require file merging for the combination of dead code
@@ -1317,7 +1517,7 @@ proc writeModule(m: BModule, pending: bool) =
   # generate code for the init statements of the module:
   let cfile = getCFile(m)
 
-  if not m.fromCache or optForceFullMake in gGlobalOptions:
+  if true or optForceFullMake in m.config.globalOptions:
     genInitCode(m)
     finishTypeDescriptions(m)
     if sfMainModule in m.module.flags:
@@ -1325,35 +1525,36 @@ proc writeModule(m: BModule, pending: bool) =
       add(m.s[cfsProcHeaders], m.g.mainModProcs)
       generateThreadVarsSize(m)
 
-    var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
+    var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {})
     var code = genModule(m, cf)
     when hasTinyCBackend:
-      if gCmd == cmdRun:
+      if conf.cmd == cmdRun:
         tccgen.compileCCode($code)
         return
 
-    if not shouldRecompile(code, cf): cf.flags = {CfileFlag.Cached}
-    addFileToCompile(cf)
+    if not shouldRecompile(m, code, cf): cf.flags = {CfileFlag.Cached}
+    addFileToCompile(m.config, cf)
   elif pending and mergeRequired(m) and sfMainModule notin m.module.flags:
-    let cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
+    let cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {})
     mergeFiles(cfile, m)
     genInitCode(m)
     finishTypeDescriptions(m)
     var code = genModule(m, cf)
-    writeRope(code, cfile)
-    addFileToCompile(cf)
+    if not writeRope(code, cfile):
+      rawMessage(m.config, errCannotOpenFile, cfile.string)
+    addFileToCompile(m.config, cf)
   else:
     # Consider: first compilation compiles ``system.nim`` and produces
     # ``system.c`` but then compilation fails due to an error. This means
     # that ``system.o`` is missing, so we need to call the C compiler for it:
-    var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
-    if not existsFile(cf.obj): cf.flags = {CfileFlag.Cached}
-    addFileToCompile(cf)
+    var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {})
+    if not fileExists(cf.obj): cf.flags = {CfileFlag.Cached}
+    addFileToCompile(m.config, cf)
   close(m.ndi)
 
 proc updateCachedModule(m: BModule) =
   let cfile = getCFile(m)
-  var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
+  var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {})
 
   if mergeRequired(m) and sfMainModule notin m.module.flags:
     mergeFiles(cfile, m)
@@ -1361,15 +1562,20 @@ proc updateCachedModule(m: BModule) =
     finishTypeDescriptions(m)
 
     var code = genModule(m, cf)
-    writeRope(code, cfile)
+    if not writeRope(code, cfile):
+      rawMessage(m.config, errCannotOpenFile, cfile.string)
   else:
     cf.flags = {CfileFlag.Cached}
-  addFileToCompile(cf)
+  addFileToCompile(m.config, cf)
 
 proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
   result = n
-  if b == nil or passes.skipCodegen(n): return
+  if b == nil: return
   var m = BModule(b)
+  if passes.skipCodegen(m.config, n): return
+  # if the module is cached, we don't regenerate the main proc
+  # nor the dispatchers? But if the dispatchers changed?
+  # XXX emit the dispatchers into its own .c file?
   if n != nil:
     m.initProc.options = initProcOptions(m)
     genStmts(m.initProc, n)
@@ -1377,29 +1583,41 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
   registerModuleToMain(m.g, m.module)
 
   if sfMainModule in m.module.flags:
-    if m.g.forwardedProcsCounter == 0:
+    if m.g.forwardedProcs.len == 0:
       incl m.flags, objHasKidsValid
     let disp = generateMethodDispatchers(graph)
     for x in disp: genProcAux(m, x.sym)
     genMainProc(m)
 
+proc genForwardedProcs(g: BModuleList) =
+  # Forward declared proc:s lack bodies when first encountered, so they're given
+  # a second pass here
+  # Note: ``genProcNoForward`` may add to ``forwardedProcs``
+  while g.forwardedProcs.len > 0:
+    let
+      prc = g.forwardedProcs.pop()
+      ms = getModule(prc)
+      m = g.modules[ms.position]
+    if sfForward in prc.flags:
+      internalError(m.config, prc.info, "still forwarded: " & prc.name.s)
+
+    genProcNoForward(m, prc)
+
 proc cgenWriteModules*(backend: RootRef, config: ConfigRef) =
   let g = BModuleList(backend)
   # we need to process the transitive closure because recursive module
   # deps are allowed (and the system module is processed in the wrong
   # order anyway)
   g.config = config
-  if g.generatedHeader != nil: finishModule(g.generatedHeader)
-  while g.forwardedProcsCounter > 0:
-    for m in cgenModules(g):
-      if not m.fromCache:
-        finishModule(m)
+  let (outDir, _, _) = splitFile(config.outfile)
+  if not outDir.isEmpty:
+    createDir(outDir)
+
+  genForwardedProcs(g)
+
   for m in cgenModules(g):
-    if m.fromCache:
-      m.updateCachedModule
-    else:
-      m.writeModule(pending=true)
-  writeMapping(g.mapping)
+    m.writeModule(pending=true)
+  writeMapping(config, g.mapping)
   if g.generatedHeader != nil: writeHeader(g.generatedHeader)
 
-const cgenPass* = makePass(myOpen, myOpenCached, myProcess, myClose)
+const cgenPass* = makePass(myOpen, myProcess, myClose)
diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim
index be087095f..28e36364e 100644
--- a/compiler/cgendata.nim
+++ b/compiler/cgendata.nim
@@ -11,9 +11,9 @@
 
 import
   ast, astalgo, ropes, passes, options, intsets, platform, sighashes,
-  tables, ndi
+  tables, ndi, lineinfos, pathutils
 
-from msgs import TLineInfo
+from modulegraphs import ModuleGraph, PPassContext
 
 type
   TLabel* = Rope              # for the C generator a label is just a rope
@@ -54,7 +54,7 @@ type
   TCProcSections* = array[TCProcSection, Rope] # represents a generated C proc
   BModule* = ref TCGen
   BProc* = ref TCProc
-  TBlock*{.final.} = object
+  TBlock* = object
     id*: int                  # the ID of the label; positive means that it
     label*: Rope              # generated text for the label
                               # nil if label is not used
@@ -64,17 +64,18 @@ type
     nestedExceptStmts*: int16 # how many except statements is it nested into
     frameLen*: int16
 
-  TCProc{.final.} = object    # represents C proc that is currently generated
+  TCProc = object             # represents C proc that is currently generated
     prc*: PSym                # the Nim proc that this C proc belongs to
     beforeRetNeeded*: bool    # true iff 'BeforeRet' label for proc is needed
     threadVarAccessed*: bool  # true if the proc already accessed some threadvar
+    hasCurFramePointer*: bool # true if _nimCurFrame var needed to recover after
+                              # exception is generated
     lastLineInfo*: TLineInfo  # to avoid generating excessive 'nimln' statements
     currLineInfo*: TLineInfo  # AST codegen will make this superfluous
-    nestedTryStmts*: seq[PNode]   # in how many nested try statements we are
-                                  # (the vars must be volatile then)
-    inExceptBlock*: int       # are we currently inside an except block?
-                              # leaving such scopes by raise or by return must
-                              # execute any applicable finally blocks
+    nestedTryStmts*: seq[tuple[n: PNode, inExcept: bool]]
+                              # in how many nested try statements we are
+                              # (the vars must be volatile then)
+                              # bool is true when are in the except part of a try block
     finallySafePoints*: seq[Rope]  # For correctly cleaning up exceptions when
                                    # using return in finally statements
     labels*: Natural          # for generating unique labels in the C proc
@@ -111,19 +112,32 @@ type
     mainModProcs*, mainModInit*, otherModsInit*, mainDatInit*: Rope
     mapping*: Rope             # the generated mapping file (if requested)
     modules*: seq[BModule]     # list of all compiled modules
-    forwardedProcsCounter*: int
+    forwardedProcs*: seq[PSym] # proc:s that did not yet have a body
     generatedHeader*: BModule
     breakPointId*: int
     breakpoints*: Rope # later the breakpoints are inserted into the main proc
     typeInfoMarker*: TypeCache
     config*: ConfigRef
-
-  TCGen = object of TPassContext # represents a C source file
+    graph*: ModuleGraph
+    strVersion*, seqVersion*: int # version of the string/seq implementation to use
+
+    nimtv*: Rope            # Nim thread vars; the struct body
+    nimtvDeps*: seq[PType]  # type deps: every module needs whole struct
+    nimtvDeclared*: IntSet  # so that every var/field exists only once
+                            # in the struct
+                            # 'nimtv' is incredibly hard to modularize! Best
+                            # effort is to store all thread vars in a ROD
+                            # section and with their type deps and load them
+                            # unconditionally...
+                            # nimtvDeps is VERY hard to cache because it's
+                            # not a list of IDs nor can it be made to be one.
+
+  TCGen = object of PPassContext # represents a C source file
     s*: TCFileSections        # sections of the C file
     flags*: set[Codegenflag]
     module*: PSym
-    filename*: string
-    cfilename*: string        # filename of the module (including path,
+    filename*: AbsoluteFile
+    cfilename*: AbsoluteFile  # filename of the module (including path,
                               # without extension)
     tmpBase*: Rope            # base for temp identifier generation
     typeCache*: TypeCache     # cache the generated types
@@ -133,11 +147,9 @@ type
     headerFiles*: seq[string] # needed headers to include
     typeInfoMarker*: TypeCache # needed for generating type information
     initProc*: BProc          # code for init procedure
-    postInitProc*: BProc      # code to be executed after the init proc
     preInitProc*: BProc       # code executed before the init proc
     typeStack*: TTypeSeq      # used for type generation
     dataCache*: TNodeTable
-    forwardedProcs*: TSymSeq  # keep forwarded procs here
     typeNodes*, nimTypes*: int # used for type info generation
     typeNodesName*, nimTypesName*: Rope # used for type info generation
     labels*: Natural          # for generating unique module-scope names
@@ -148,13 +160,16 @@ type
     g*: BModuleList
     ndi*: NdiFile
 
+template config*(m: BModule): ConfigRef = m.g.config
+template config*(p: BProc): ConfigRef = p.module.g.config
+
 proc includeHeader*(this: BModule; header: string) =
   if not this.headerFiles.contains header:
     this.headerFiles.add header
 
 proc s*(p: BProc, s: TCProcSection): var Rope {.inline.} =
   # section in the current block
-  result = p.blocks[^1].sections[s]
+  result = p.blocks[p.blocks.len-1].sections[s]
 
 proc procSec*(p: BProc, s: TCProcSection): var Rope {.inline.} =
   # top level proc sections
@@ -165,18 +180,19 @@ proc newProc*(prc: PSym, module: BModule): BProc =
   result.prc = prc
   result.module = module
   if prc != nil: result.options = prc.options
-  else: result.options = gOptions
+  else: result.options = module.config.options
   newSeq(result.blocks, 1)
   result.nestedTryStmts = @[]
   result.finallySafePoints = @[]
   result.sigConflicts = initCountTable[string]()
 
-proc newModuleList*(config: ConfigRef): BModuleList =
-  BModuleList(modules: @[], typeInfoMarker: initTable[SigHash, Rope](), config: config)
+proc newModuleList*(g: ModuleGraph): BModuleList =
+  BModuleList(typeInfoMarker: initTable[SigHash, Rope](), config: g.config,
+    graph: g, nimtvDeclared: initIntSet())
 
 iterator cgenModules*(g: BModuleList): BModule =
-  for i in 0..high(g.modules):
+  for m in g.modules:
     # ultimately, we are iterating over the file ids here.
     # some "files" won't have an associated cgen module (like stdin)
     # and we must skip over them.
-    if g.modules[i] != nil: yield g.modules[i]
+    if m != nil: yield m
diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim
index d05e395b9..f5014fa8c 100644
--- a/compiler/cgmeth.nim
+++ b/compiler/cgmeth.nim
@@ -11,9 +11,9 @@
 
 import
   intsets, options, ast, astalgo, msgs, idents, renderer, types, magicsys,
-  sempass2, strutils, modulegraphs
+  sempass2, strutils, modulegraphs, lineinfos
 
-proc genConv(n: PNode, d: PType, downcast: bool): PNode =
+proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
   var dest = skipTypes(d, abstractPtrs)
   var source = skipTypes(n.typ, abstractPtrs)
   if (source.kind == tyObject) and (dest.kind == tyObject):
@@ -24,12 +24,12 @@ proc genConv(n: PNode, d: PType, downcast: bool): PNode =
     elif diff < 0:
       result = newNodeIT(nkObjUpConv, n.info, d)
       addSon(result, n)
-      if downcast: internalError(n.info, "cgmeth.genConv: no upcast allowed")
+      if downcast: internalError(conf, n.info, "cgmeth.genConv: no upcast allowed")
     elif diff > 0:
       result = newNodeIT(nkObjDownConv, n.info, d)
       addSon(result, n)
       if not downcast:
-        internalError(n.info, "cgmeth.genConv: no downcast allowed")
+        internalError(conf, n.info, "cgmeth.genConv: no downcast allowed")
     else:
       result = n
   else:
@@ -42,7 +42,7 @@ proc getDispatcher*(s: PSym): PSym =
     let disp = dispn.sym
     if sfDispatcher in disp.flags: result = disp
 
-proc methodCall*(n: PNode): PNode =
+proc methodCall*(n: PNode; conf: ConfigRef): PNode =
   result = n
   # replace ordinary method by dispatcher method:
   let disp = getDispatcher(result.sons[0].sym)
@@ -50,9 +50,9 @@ proc methodCall*(n: PNode): PNode =
     result.sons[0].sym = disp
     # change the arguments to up/downcasts to fit the dispatcher's parameters:
     for i in countup(1, sonsLen(result)-1):
-      result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true)
+      result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true, conf)
   else:
-    localError(n.info, "'" & $result.sons[0] & "' lacks a dispatcher")
+    localError(conf, n.info, "'" & $result.sons[0] & "' lacks a dispatcher")
 
 type
   MethodResult = enum No, Invalid, Yes
@@ -68,12 +68,12 @@ proc sameMethodBucket(a, b: PSym): MethodResult =
     while true:
       aa = skipTypes(aa, {tyGenericInst, tyAlias})
       bb = skipTypes(bb, {tyGenericInst, tyAlias})
-      if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef}:
+      if aa.kind == bb.kind and aa.kind in {tyVar, tyPtr, tyRef, tyLent}:
         aa = aa.lastSon
         bb = bb.lastSon
       else:
         break
-    if sameType(aa, bb):
+    if sameType(a.typ.sons[i], b.typ.sons[i]):
       if aa.kind == tyObject and result != Invalid:
         result = Yes
     elif aa.kind == tyObject and bb.kind == tyObject:
@@ -83,7 +83,7 @@ proc sameMethodBucket(a, b: PSym): MethodResult =
           result = Yes
         else:
           return No
-      elif diff != high(int):
+      elif diff != high(int) and sfFromGeneric notin (a.flags+b.flags):
         result = Invalid
       else:
         return No
@@ -115,7 +115,7 @@ proc createDispatcher(s: PSym): PSym =
   # we can't inline the dispatcher itself (for now):
   if disp.typ.callConv == ccInline: disp.typ.callConv = ccDefault
   disp.ast = copyTree(s.ast)
-  disp.ast.sons[bodyPos] = ast.emptyNode
+  disp.ast.sons[bodyPos] = newNodeI(nkEmpty, s.info)
   disp.loc.r = nil
   if s.typ.sons[0] != nil:
     if disp.ast.sonsLen > resultPos:
@@ -124,20 +124,20 @@ proc createDispatcher(s: PSym): PSym =
       # We've encountered a method prototype without a filled-in
       # resultPos slot. We put a placeholder in there that will
       # be updated in fixupDispatcher().
-      disp.ast.addSon(ast.emptyNode)
+      disp.ast.addSon(newNodeI(nkEmpty, s.info))
   attachDispatcher(s, newSymNode(disp))
   # attach to itself to prevent bugs:
   attachDispatcher(disp, newSymNode(disp))
   return disp
 
-proc fixupDispatcher(meth, disp: PSym) =
+proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
   # We may have constructed the dispatcher from a method prototype
   # and need to augment the incomplete dispatcher with information
   # from later definitions, particularly the resultPos slot. Also,
   # the lock level of the dispatcher needs to be updated/checked
   # against that of the method.
   if disp.ast.sonsLen > resultPos and meth.ast.sonsLen > resultPos and
-     disp.ast.sons[resultPos] == ast.emptyNode:
+     disp.ast.sons[resultPos].kind == nkEmpty:
     disp.ast.sons[resultPos] = copyTree(meth.ast.sons[resultPos])
 
   # The following code works only with lock levels, so we disable
@@ -149,7 +149,7 @@ proc fixupDispatcher(meth, disp: PSym) =
       disp.typ.lockLevel = meth.typ.lockLevel
     elif meth.typ.lockLevel != UnspecifiedLockLevel and
          meth.typ.lockLevel != disp.typ.lockLevel:
-      message(meth.info, warnLockLevel,
+      message(conf, meth.info, warnLockLevel,
         "method has lock level $1, but another method has $2" %
         [$meth.typ.lockLevel, $disp.typ.lockLevel])
       # XXX The following code silences a duplicate warning in
@@ -166,13 +166,13 @@ proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) =
     of Yes:
       add(g.methods[i].methods, s)
       attachDispatcher(s, lastSon(disp.ast))
-      fixupDispatcher(s, disp)
+      fixupDispatcher(s, disp, g.config)
       #echo "fixup ", disp.name.s, " ", disp.id
-      when useEffectSystem: checkMethodEffects(disp, s)
+      when useEffectSystem: checkMethodEffects(g, disp, s)
       if {sfBase, sfFromGeneric} * s.flags == {sfBase} and
            g.methods[i].methods[0] != s:
         # already exists due to forwarding definition?
-        localError(s.info, "method is not a base")
+        localError(g.config, s.info, "method is not a base")
       return
     of No: discard
     of Invalid:
@@ -183,12 +183,12 @@ proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) =
   #if fromCache:
   #  internalError(s.info, "no method dispatcher found")
   if witness != nil:
-    localError(s.info, "invalid declaration order; cannot attach '" & s.name.s &
-                       "' to method defined here: " & $witness.info)
+    localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
+                       "' to method defined here: " & g.config$witness.info)
   elif sfBase notin s.flags:
-    message(s.info, warnUseBase)
+    message(g.config, s.info, warnUseBase)
 
-proc relevantCol(methods: TSymSeq, col: int): bool =
+proc relevantCol(methods: seq[PSym], col: int): bool =
   # returns true iff the position is relevant
   var t = methods[0].typ.sons[col].skipTypes(skipPtrs)
   if t.kind == tyObject:
@@ -206,7 +206,7 @@ proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
       if (d != high(int)) and d != 0:
         return d
 
-proc sortBucket(a: var TSymSeq, relevantCols: IntSet) =
+proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
   # we use shellsort here; fast and simple
   var n = len(a)
   var h = 1
@@ -225,37 +225,47 @@ proc sortBucket(a: var TSymSeq, relevantCols: IntSet) =
       a[j] = v
     if h == 1: break
 
-proc genDispatcher(methods: TSymSeq, relevantCols: IntSet): PSym =
+proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet): PSym =
   var base = lastSon(methods[0].ast).sym
   result = base
   var paramLen = sonsLen(base.typ)
+  var nilchecks = newNodeI(nkStmtList, base.info)
   var disp = newNodeI(nkIfStmt, base.info)
-  var ands = getSysSym("and")
-  var iss = getSysSym("of")
+  var ands = getSysMagic(g, unknownLineInfo(), "and", mAnd)
+  var iss = getSysMagic(g, unknownLineInfo(), "of", mOf)
+  let boolType = getSysType(g, unknownLineInfo(), tyBool)
+  for col in countup(1, paramLen - 1):
+    if contains(relevantCols, col):
+      let param = base.typ.n.sons[col].sym
+      if param.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
+        addSon(nilchecks, newTree(nkCall,
+            newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(param)))
   for meth in countup(0, high(methods)):
     var curr = methods[meth]      # generate condition:
     var cond: PNode = nil
     for col in countup(1, paramLen - 1):
       if contains(relevantCols, col):
-        var isn = newNodeIT(nkCall, base.info, getSysType(tyBool))
+        var isn = newNodeIT(nkCall, base.info, boolType)
         addSon(isn, newSymNode(iss))
-        addSon(isn, newSymNode(base.typ.n.sons[col].sym))
+        let param = base.typ.n.sons[col].sym
+        addSon(isn, newSymNode(param))
         addSon(isn, newNodeIT(nkType, base.info, curr.typ.sons[col]))
         if cond != nil:
-          var a = newNodeIT(nkCall, base.info, getSysType(tyBool))
+          var a = newNodeIT(nkCall, base.info, boolType)
           addSon(a, newSymNode(ands))
           addSon(a, cond)
           addSon(a, isn)
           cond = a
         else:
           cond = isn
-    var call = newNodeI(nkCall, base.info)
+    let retTyp = base.typ.sons[0]
+    let call = newNodeIT(nkCall, base.info, retTyp)
     addSon(call, newSymNode(curr))
     for col in countup(1, paramLen - 1):
       addSon(call, genConv(newSymNode(base.typ.n.sons[col].sym),
-                           curr.typ.sons[col], false))
+                           curr.typ.sons[col], false, g.config))
     var ret: PNode
-    if base.typ.sons[0] != nil:
+    if retTyp != nil:
       var a = newNodeI(nkFastAsgn, base.info)
       addSon(a, newSymNode(base.ast.sons[resultPos].sym))
       addSon(a, call)
@@ -270,7 +280,9 @@ proc genDispatcher(methods: TSymSeq, relevantCols: IntSet): PSym =
       addSon(disp, a)
     else:
       disp = ret
-  result.ast.sons[bodyPos] = disp
+  nilchecks.add disp
+  nilchecks.flags.incl nfTransf # should not be further transformed
+  result.ast.sons[bodyPos] = nilchecks
 
 proc generateMethodDispatchers*(g: ModuleGraph): PNode =
   result = newNode(nkStmtList)
@@ -280,4 +292,4 @@ proc generateMethodDispatchers*(g: ModuleGraph): PNode =
       if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col)
     sortBucket(g.methods[bucket].methods, relevantCols)
     addSon(result,
-           newSymNode(genDispatcher(g.methods[bucket].methods, relevantCols)))
+           newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols)))
diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim
new file mode 100644
index 000000000..5ded6d054
--- /dev/null
+++ b/compiler/closureiters.nim
@@ -0,0 +1,1332 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Nim Contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# This file implements closure iterator transformations.
+# The main idea is to split the closure iterator body to top level statements.
+# The body is split by yield statement.
+#
+# Example:
+#  while a > 0:
+#    echo "hi"
+#    yield a
+#    dec a
+#
+# Should be transformed to:
+#  STATE0:
+#    if a > 0:
+#      echo "hi"
+#      :state = 1 # Next state
+#      return a # yield
+#    else:
+#      :state = 2 # Next state
+#      break :stateLoop # Proceed to the next state
+#  STATE1:
+#    dec a
+#    :state = 0 # Next state
+#    break :stateLoop # Proceed to the next state
+#  STATE2:
+#    :state = -1 # End of execution
+
+# The transformation should play well with lambdalifting, however depending
+# on situation, it can be called either before or after lambdalifting
+# transformation. As such we behave slightly differently, when accessing
+# iterator state, or using temp variables. If lambdalifting did not happen,
+# we just create local variables, so that they will be lifted further on.
+# Otherwise, we utilize existing env, created by lambdalifting.
+
+# Lambdalifting treats :state variable specially, it should always end up
+# as the first field in env. Currently C codegen depends on this behavior.
+
+# One special subtransformation is nkStmtListExpr lowering.
+# Example:
+#   template foo(): int =
+#     yield 1
+#     2
+#
+#   iterator it(): int {.closure.} =
+#     if foo() == 2:
+#       yield 3
+#
+# If a nkStmtListExpr has yield inside, it has first to be lowered to:
+#   yield 1
+#   :tmpSlLower = 2
+#   if :tmpSlLower == 2:
+#     yield 3
+
+# nkTryStmt Transformations:
+# If the iter has an nkTryStmt with a yield inside
+#  - the closure iter is promoted to have exceptions (ctx.hasExceptions = true)
+#  - exception table is created. This is a const array, where
+#    `abs(exceptionTable[i])` is a state idx to which we should jump from state
+#    `i` should exception be raised in state `i`. For all states in `try` block
+#    the target state is `except` block. For all states in `except` block
+#    the target state is `finally` block. For all other states there is no
+#    target state (0, as the first block can never be neither except nor finally).
+#    `exceptionTable[i]` is < 0 if `abs(exceptionTable[i])` is except block,
+#    and > 0, for finally block.
+#  - local variable :curExc is created
+#  - the iter body is wrapped into a
+#      try:
+#       closureIterSetupExc(:curExc)
+#       ...body...
+#      catch:
+#        :state = exceptionTable[:state]
+#        if :state == 0: raise # No state that could handle exception
+#        :unrollFinally = :state > 0 # Target state is finally
+#        if :state < 0:
+#           :state = -:state
+#        :curExc = getCurrentException()
+#
+# nkReturnStmt within a try/except/finally now has to behave differently as we
+# want the nearest finally block to be executed before the return, thus it is
+# transformed to:
+#  :tmpResult = returnValue (if return doesn't have a value, this is skipped)
+#  :unrollFinally = true
+#  goto nearestFinally (or -1 if not exists)
+#
+# Every finally block calls closureIterEndFinally() upon its successful
+# completion.
+#
+# Example:
+#
+# try:
+#  yield 0
+#  raise ...
+# except:
+#  yield 1
+#  return 3
+# finally:
+#  yield 2
+#
+# Is transformed to (yields are left in place for example simplicity,
+#    in reality the code is subdivided even more, as described above):
+#
+# STATE0: # Try
+#   yield 0
+#   raise ...
+#   :state = 2 # What would happen should we not raise
+#   break :stateLoop
+# STATE1: # Except
+#   yield 1
+#   :tmpResult = 3           # Return
+#   :unrollFinally = true # Return
+#   :state = 2 # Goto Finally
+#   break :stateLoop
+#   :state = 2 # What would happen should we not return
+#   break :stateLoop
+# STATE2: # Finally
+#   yield 2
+#   if :unrollFinally: # This node is created by `newEndFinallyNode`
+#     if :curExc.isNil:
+#       return :tmpResult
+#     else:
+#       raise
+#   state = -1 # Goto next state. In this case we just exit
+#   break :stateLoop
+
+import
+  intsets, strutils, options, ast, astalgo, trees, treetab, msgs, idents,
+  renderer, types, magicsys, lowerings, lambdalifting, modulegraphs, lineinfos
+
+type
+  Ctx = object
+    g: ModuleGraph
+    fn: PSym
+    stateVarSym: PSym # :state variable. nil if env already introduced by lambdalifting
+    tmpResultSym: PSym # Used when we return, but finally has to interfere
+    unrollFinallySym: PSym # Indicates that we're unrolling finally states (either exception happened or premature return)
+    curExcSym: PSym # Current exception
+
+    states: seq[PNode] # The resulting states. Every state is an nkState node.
+    blockLevel: int # Temp used to transform break and continue stmts
+    stateLoopLabel: PSym # Label to break on, when jumping between states.
+    exitStateIdx: int # index of the last state
+    tempVarId: int # unique name counter
+    tempVars: PNode # Temp var decls, nkVarSection
+    exceptionTable: seq[int] # For state `i` jump to state `exceptionTable[i]` if exception is raised
+    hasExceptions: bool # Does closure have yield in try?
+    curExcHandlingState: int # Negative for except, positive for finally
+    nearestFinally: int # Index of the nearest finally block. For try/except it
+                    # is their finally. For finally it is parent finally. Otherwise -1
+
+const
+  nkSkip = { nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt,
+            nkCommentStmt } + procDefs
+
+proc newStateAccess(ctx: var Ctx): PNode =
+  if ctx.stateVarSym.isNil:
+    result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)),
+        getStateField(ctx.g, ctx.fn), ctx.fn.info)
+  else:
+    result = newSymNode(ctx.stateVarSym)
+
+proc newStateAssgn(ctx: var Ctx, toValue: PNode): PNode =
+  # Creates state assignment:
+  #   :state = toValue
+  newTree(nkAsgn, ctx.newStateAccess(), toValue)
+
+proc newStateAssgn(ctx: var Ctx, stateNo: int = -2): PNode =
+  # Creates state assignment:
+  #   :state = stateNo
+  ctx.newStateAssgn(newIntTypeNode(nkIntLit, stateNo, ctx.g.getSysType(TLineInfo(), tyInt)))
+
+proc newEnvVar(ctx: var Ctx, name: string, typ: PType): PSym =
+  result = newSym(skVar, getIdent(ctx.g.cache, name), ctx.fn, ctx.fn.info)
+  result.typ = typ
+  assert(not typ.isNil)
+
+  if not ctx.stateVarSym.isNil:
+    # We haven't gone through labmda lifting yet, so just create a local var,
+    # it will be lifted later
+    if ctx.tempVars.isNil:
+      ctx.tempVars = newNodeI(nkVarSection, ctx.fn.info)
+      addVar(ctx.tempVars, newSymNode(result))
+  else:
+    let envParam = getEnvParam(ctx.fn)
+    # let obj = envParam.typ.lastSon
+    result = addUniqueField(envParam.typ.lastSon, result, ctx.g.cache)
+
+proc newEnvVarAccess(ctx: Ctx, s: PSym): PNode =
+  if ctx.stateVarSym.isNil:
+    result = rawIndirectAccess(newSymNode(getEnvParam(ctx.fn)), s, ctx.fn.info)
+  else:
+    result = newSymNode(s)
+
+proc newTmpResultAccess(ctx: var Ctx): PNode =
+  if ctx.tmpResultSym.isNil:
+    ctx.tmpResultSym = ctx.newEnvVar(":tmpResult", ctx.fn.typ[0])
+  ctx.newEnvVarAccess(ctx.tmpResultSym)
+
+proc newUnrollFinallyAccess(ctx: var Ctx, info: TLineInfo): PNode =
+  if ctx.unrollFinallySym.isNil:
+    ctx.unrollFinallySym = ctx.newEnvVar(":unrollFinally", ctx.g.getSysType(info, tyBool))
+  ctx.newEnvVarAccess(ctx.unrollFinallySym)
+
+proc newCurExcAccess(ctx: var Ctx): PNode =
+  if ctx.curExcSym.isNil:
+    ctx.curExcSym = ctx.newEnvVar(":curExc", ctx.g.callCodegenProc("getCurrentException").typ)
+  ctx.newEnvVarAccess(ctx.curExcSym)
+
+proc newState(ctx: var Ctx, n, gotoOut: PNode): int =
+  # Creates a new state, adds it to the context fills out `gotoOut` so that it
+  # will goto this state.
+  # Returns index of the newly created state
+
+  result = ctx.states.len
+  let resLit = ctx.g.newIntLit(n.info, result)
+  let s = newNodeI(nkState, n.info)
+  s.add(resLit)
+  s.add(n)
+  ctx.states.add(s)
+  ctx.exceptionTable.add(ctx.curExcHandlingState)
+
+  if not gotoOut.isNil:
+    assert(gotoOut.len == 0)
+    gotoOut.add(ctx.g.newIntLit(gotoOut.info, result))
+
+proc toStmtList(n: PNode): PNode =
+  result = n
+  if result.kind notin {nkStmtList, nkStmtListExpr}:
+    result = newNodeI(nkStmtList, n.info)
+    result.add(n)
+
+proc addGotoOut(n: PNode, gotoOut: PNode): PNode =
+  # Make sure `n` is a stmtlist, and ends with `gotoOut`
+  result = toStmtList(n)
+  if result.len == 0 or result.sons[^1].kind != nkGotoState:
+    result.add(gotoOut)
+
+proc newTempVar(ctx: var Ctx, typ: PType): PSym =
+  result = ctx.newEnvVar(":tmpSlLower" & $ctx.tempVarId, typ)
+  inc ctx.tempVarId
+
+proc hasYields(n: PNode): bool =
+  # TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt.
+  case n.kind
+  of nkYieldStmt:
+    result = true
+  of nkSkip:
+    discard
+  else:
+    for c in n:
+      if c.hasYields:
+        result = true
+        break
+
+proc transformBreaksAndContinuesInWhile(ctx: var Ctx, n: PNode, before, after: PNode): PNode =
+  result = n
+  case n.kind
+  of nkSkip:
+    discard
+  of nkWhileStmt: discard # Do not recurse into nested whiles
+  of nkContinueStmt:
+    result = before
+  of nkBlockStmt:
+    inc ctx.blockLevel
+    result[1] = ctx.transformBreaksAndContinuesInWhile(result[1], before, after)
+    dec ctx.blockLevel
+  of nkBreakStmt:
+    if ctx.blockLevel == 0:
+      result = after
+  else:
+    for i in 0 ..< n.len:
+      n[i] = ctx.transformBreaksAndContinuesInWhile(n[i], before, after)
+
+proc transformBreaksInBlock(ctx: var Ctx, n: PNode, label, after: PNode): PNode =
+  result = n
+  case n.kind
+  of nkSkip:
+    discard
+  of nkBlockStmt, nkWhileStmt:
+    inc ctx.blockLevel
+    result[1] = ctx.transformBreaksInBlock(result[1], label, after)
+    dec ctx.blockLevel
+  of nkBreakStmt:
+    if n[0].kind == nkEmpty:
+      if ctx.blockLevel == 0:
+        result = after
+    else:
+      if label.kind == nkSym and n[0].sym == label.sym:
+        result = after
+  else:
+    for i in 0 ..< n.len:
+      n[i] = ctx.transformBreaksInBlock(n[i], label, after)
+
+proc newNullifyCurExc(ctx: var Ctx, info: TLineInfo): PNode =
+  # :curEcx = nil
+  let curExc = ctx.newCurExcAccess()
+  curExc.info = info
+  let nilnode = newNode(nkNilLit)
+  nilnode.typ = curExc.typ
+  result = newTree(nkAsgn, curExc, nilnode)
+
+proc newOr(g: ModuleGraph, a, b: PNode): PNode {.inline.} =
+  result = newTree(nkCall, newSymNode(g.getSysMagic(a.info, "or", mOr)), a, b)
+  result.typ = g.getSysType(a.info, tyBool)
+  result.info = a.info
+
+proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
+  var ifStmt = newNodeI(nkIfStmt, n.info)
+  let g = ctx.g
+  for c in n:
+    if c.kind == nkExceptBranch:
+      var ifBranch: PNode
+
+      if c.len > 1:
+        var cond: PNode
+        for i in 0 .. c.len - 2:
+          assert(c[i].kind == nkType)
+          let nextCond = newTree(nkCall,
+            newSymNode(g.getSysMagic(c.info, "of", mOf)),
+            g.callCodegenProc("getCurrentException"),
+            c[i])
+          nextCond.typ = ctx.g.getSysType(c.info, tyBool)
+          nextCond.info = c.info
+
+          if cond.isNil:
+            cond = nextCond
+          else:
+            cond = g.newOr(cond, nextCond)
+
+        ifBranch = newNodeI(nkElifBranch, c.info)
+        ifBranch.add(cond)
+      else:
+        if ifStmt.len == 0:
+          ifStmt = newNodeI(nkStmtList, c.info)
+          ifBranch = newNodeI(nkStmtList, c.info)
+        else:
+          ifBranch = newNodeI(nkElse, c.info)
+
+      ifBranch.add(c[^1])
+      ifStmt.add(ifBranch)
+
+  if ifStmt.len != 0:
+    result = newTree(nkStmtList, ctx.newNullifyCurExc(n.info), ifStmt)
+  else:
+    result = ctx.g.emptyNode
+
+proc addElseToExcept(ctx: var Ctx, n: PNode) =
+  if n.kind == nkStmtList and n[1].kind == nkIfStmt and n[1][^1].kind != nkElse:
+    # Not all cases are covered
+    let branchBody = newNodeI(nkStmtList, n.info)
+
+    block: # :unrollFinally = true
+      branchBody.add(newTree(nkAsgn,
+        ctx.newUnrollFinallyAccess(n.info),
+        newIntTypeNode(nkIntLit, 1, ctx.g.getSysType(n.info, tyBool))))
+
+    block: # :curExc = getCurrentException()
+      branchBody.add(newTree(nkAsgn,
+        ctx.newCurExcAccess(),
+        ctx.g.callCodegenProc("getCurrentException")))
+
+    block: # goto nearestFinally
+      branchBody.add(newTree(nkGotoState, ctx.g.newIntLit(n.info, ctx.nearestFinally)))
+
+    let elseBranch = newTree(nkElse, branchBody)
+    n[1].add(elseBranch)
+
+proc getFinallyNode(ctx: var Ctx, n: PNode): PNode =
+  result = n[^1]
+  if result.kind == nkFinally:
+    result = result[0]
+  else:
+    result = ctx.g.emptyNode
+
+proc hasYieldsInExpressions(n: PNode): bool =
+  case n.kind
+  of nkSkip:
+    discard
+  of nkStmtListExpr:
+    if isEmptyType(n.typ):
+      for c in n:
+        if c.hasYieldsInExpressions:
+          return true
+    else:
+      result = n.hasYields
+  else:
+    for c in n:
+      if c.hasYieldsInExpressions:
+        return true
+
+proc exprToStmtList(n: PNode): tuple[s, res: PNode] =
+  assert(n.kind == nkStmtListExpr)
+  result.s = newNodeI(nkStmtList, n.info)
+  result.s.sons = @[]
+
+  var n = n
+  while n.kind == nkStmtListExpr:
+    result.s.sons.add(n.sons)
+    result.s.sons.setLen(result.s.sons.len - 1) # delete last son
+    n = n[^1]
+
+  result.res = n
+
+
+proc newEnvVarAsgn(ctx: Ctx, s: PSym, v: PNode): PNode =
+  result = newTree(nkFastAsgn, ctx.newEnvVarAccess(s), v)
+  result.info = v.info
+
+proc addExprAssgn(ctx: Ctx, output, input: PNode, sym: PSym) =
+  if input.kind == nkStmtListExpr:
+    let (st, res) = exprToStmtList(input)
+    output.add(st)
+    output.add(ctx.newEnvVarAsgn(sym, res))
+  else:
+    output.add(ctx.newEnvVarAsgn(sym, input))
+
+proc convertExprBodyToAsgn(ctx: Ctx, exprBody: PNode, res: PSym): PNode =
+  result = newNodeI(nkStmtList, exprBody.info)
+  ctx.addExprAssgn(result, exprBody, res)
+
+proc newNotCall(g: ModuleGraph; e: PNode): PNode =
+  result = newTree(nkCall, newSymNode(g.getSysMagic(e.info, "not", mNot), e.info), e)
+  result.typ = g.getSysType(e.info, tyBool)
+
+proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
+  result = n
+  case n.kind
+  of nkSkip:
+    discard
+
+  of nkYieldStmt:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      result = newNodeI(nkStmtList, n.info)
+      let (st, ex) = exprToStmtList(n[0])
+      result.add(st)
+      n[0] = ex
+      result.add(n)
+
+    needsSplit = true
+
+  of nkPar, nkObjConstr, nkTupleConstr, nkBracket:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+
+      result = newNodeI(nkStmtListExpr, n.info)
+      if n.typ.isNil: internalError(ctx.g.config, "lowerStmtListExprs: constr typ.isNil")
+      result.typ = n.typ
+
+      for i in 0 ..< n.len:
+        case n[i].kind
+        of nkExprColonExpr:
+          if n[i][1].kind == nkStmtListExpr:
+            let (st, ex) = exprToStmtList(n[i][1])
+            result.add(st)
+            n[i][1] = ex
+        of nkStmtListExpr:
+          let (st, ex) = exprToStmtList(n[i])
+          result.add(st)
+          n[i] = ex
+        else: discard
+      result.add(n)
+
+  of nkIfStmt, nkIfExpr:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+      var tmp: PSym
+      var s: PNode
+      let isExpr = not isEmptyType(n.typ)
+      if isExpr:
+        tmp = ctx.newTempVar(n.typ)
+        result = newNodeI(nkStmtListExpr, n.info)
+        result.typ = n.typ
+      else:
+        result = newNodeI(nkStmtList, n.info)
+
+      var curS = result
+
+      for branch in n:
+        case branch.kind
+        of nkElseExpr, nkElse:
+          if isExpr:
+            let branchBody = newNodeI(nkStmtList, branch.info)
+            ctx.addExprAssgn(branchBody, branch[0], tmp)
+            let newBranch = newTree(nkElse, branchBody)
+            curS.add(newBranch)
+          else:
+            curS.add(branch)
+
+        of nkElifExpr, nkElifBranch:
+          var newBranch: PNode
+          if branch[0].kind == nkStmtListExpr:
+            let (st, res) = exprToStmtList(branch[0])
+            let elseBody = newTree(nkStmtList, st)
+
+            newBranch = newTree(nkElifBranch, res, branch[1])
+
+            let newIf = newTree(nkIfStmt, newBranch)
+            elseBody.add(newIf)
+            if curS.kind == nkIfStmt:
+              let newElse = newNodeI(nkElse, branch.info)
+              newElse.add(elseBody)
+              curS.add(newElse)
+            else:
+              curS.add(elseBody)
+            curS = newIf
+          else:
+            newBranch = branch
+            if curS.kind == nkIfStmt:
+              curS.add(newBranch)
+            else:
+              let newIf = newTree(nkIfStmt, newBranch)
+              curS.add(newIf)
+              curS = newIf
+
+          if isExpr:
+            let branchBody = newNodeI(nkStmtList, branch[1].info)
+            ctx.addExprAssgn(branchBody, branch[1], tmp)
+            newBranch[1] = branchBody
+
+        else:
+          internalError(ctx.g.config, "lowerStmtListExpr(nkIf): " & $branch.kind)
+
+      if isExpr: result.add(ctx.newEnvVarAccess(tmp))
+
+  of nkTryStmt:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+      let isExpr = not isEmptyType(n.typ)
+
+      if isExpr:
+        result = newNodeI(nkStmtListExpr, n.info)
+        result.typ = n.typ
+        let tmp = ctx.newTempVar(n.typ)
+
+        n[0] = ctx.convertExprBodyToAsgn(n[0], tmp)
+        for i in 1 ..< n.len:
+          let branch = n[i]
+          case branch.kind
+          of nkExceptBranch:
+            if branch[0].kind == nkType:
+              branch[1] = ctx.convertExprBodyToAsgn(branch[1], tmp)
+            else:
+              branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp)
+          of nkFinally:
+            discard
+          else:
+            internalError(ctx.g.config, "lowerStmtListExpr(nkTryStmt): " & $branch.kind)
+        result.add(n)
+        result.add(ctx.newEnvVarAccess(tmp))
+
+  of nkCaseStmt:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+
+      let isExpr = not isEmptyType(n.typ)
+
+      if isExpr:
+        let tmp = ctx.newTempVar(n.typ)
+        result = newNodeI(nkStmtListExpr, n.info)
+        result.typ = n.typ
+
+        if n[0].kind == nkStmtListExpr:
+          let (st, ex) = exprToStmtList(n[0])
+          result.add(st)
+          n[0] = ex
+
+        for i in 1 ..< n.len:
+          let branch = n[i]
+          case branch.kind
+          of nkOfBranch:
+            branch[^1] = ctx.convertExprBodyToAsgn(branch[^1], tmp)
+          of nkElse:
+            branch[0] = ctx.convertExprBodyToAsgn(branch[0], tmp)
+          else:
+            internalError(ctx.g.config, "lowerStmtListExpr(nkCaseStmt): " & $branch.kind)
+        result.add(n)
+        result.add(ctx.newEnvVarAccess(tmp))
+
+  of nkCallKinds:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+      let isExpr = not isEmptyType(n.typ)
+
+      if isExpr:
+        result = newNodeI(nkStmtListExpr, n.info)
+        result.typ = n.typ
+      else:
+        result = newNodeI(nkStmtList, n.info)
+
+      if n[0].kind == nkSym and n[0].sym.magic in {mAnd, mOr}: # `and`/`or` short cirquiting
+        var cond = n[1]
+        if cond.kind == nkStmtListExpr:
+          let (st, ex) = exprToStmtList(cond)
+          result.add(st)
+          cond = ex
+
+        let tmp = ctx.newTempVar(cond.typ)
+        result.add(ctx.newEnvVarAsgn(tmp, cond))
+
+        var check = ctx.newEnvVarAccess(tmp)
+        if n[0].sym.magic == mOr:
+          check = ctx.g.newNotCall(check)
+
+        cond = n[2]
+        let ifBody = newNodeI(nkStmtList, cond.info)
+        if cond.kind == nkStmtListExpr:
+          let (st, ex) = exprToStmtList(cond)
+          ifBody.add(st)
+          cond = ex
+        ifBody.add(ctx.newEnvVarAsgn(tmp, cond))
+
+        let ifBranch = newTree(nkElifBranch, check, ifBody)
+        let ifNode = newTree(nkIfStmt, ifBranch)
+        result.add(ifNode)
+        result.add(ctx.newEnvVarAccess(tmp))
+      else:
+        for i in 0 ..< n.len:
+          if n[i].kind == nkStmtListExpr:
+            let (st, ex) = exprToStmtList(n[i])
+            result.add(st)
+            n[i] = ex
+
+          if n[i].kind in nkCallKinds: # XXX: This should better be some sort of side effect tracking
+            let tmp = ctx.newTempVar(n[i].typ)
+            result.add(ctx.newEnvVarAsgn(tmp, n[i]))
+            n[i] = ctx.newEnvVarAccess(tmp)
+
+        result.add(n)
+
+  of nkVarSection, nkLetSection:
+    result = newNodeI(nkStmtList, n.info)
+    for c in n:
+      let varSect = newNodeI(n.kind, n.info)
+      varSect.add(c)
+      var ns = false
+      c[^1] = ctx.lowerStmtListExprs(c[^1], ns)
+      if ns:
+        needsSplit = true
+        let (st, ex) = exprToStmtList(c[^1])
+        result.add(st)
+        c[^1] = ex
+      result.add(varSect)
+
+  of nkDiscardStmt, nkReturnStmt, nkRaiseStmt:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+      result = newNodeI(nkStmtList, n.info)
+      let (st, ex) = exprToStmtList(n[0])
+      result.add(st)
+      n[0] = ex
+      result.add(n)
+
+  of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+      result = newNodeI(nkStmtListExpr, n.info)
+      result.typ = n.typ
+      let (st, ex) = exprToStmtList(n[^1])
+      result.add(st)
+      n[^1] = ex
+      result.add(n)
+
+  of nkAsgn, nkFastAsgn:
+    var ns = false
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], ns)
+
+    if ns:
+      needsSplit = true
+      result = newNodeI(nkStmtList, n.info)
+      if n[0].kind == nkStmtListExpr:
+        let (st, ex) = exprToStmtList(n[0])
+        result.add(st)
+        n[0] = ex
+
+      if n[1].kind == nkStmtListExpr:
+        let (st, ex) = exprToStmtList(n[1])
+        result.add(st)
+        n[1] = ex
+
+      result.add(n)
+
+  of nkBracketExpr:
+    var lhsNeedsSplit = false
+    var rhsNeedsSplit = false
+    n[0] = ctx.lowerStmtListExprs(n[0], lhsNeedsSplit)
+    n[1] = ctx.lowerStmtListExprs(n[1], rhsNeedsSplit)
+    if lhsNeedsSplit or rhsNeedsSplit:
+      needsSplit = true
+      result = newNodeI(nkStmtListExpr, n.info)
+      if lhsNeedsSplit:
+        let (st, ex) = exprToStmtList(n[0])
+        result.add(st)
+        n[0] = ex
+
+      if rhsNeedsSplit:
+        let (st, ex) = exprToStmtList(n[1])
+        result.add(st)
+        n[1] = ex
+      result.add(n)
+
+  of nkWhileStmt:
+    var ns = false
+
+    var condNeedsSplit = false
+    n[0] = ctx.lowerStmtListExprs(n[0], condNeedsSplit)
+    var bodyNeedsSplit = false
+    n[1] = ctx.lowerStmtListExprs(n[1], bodyNeedsSplit)
+
+    if condNeedsSplit or bodyNeedsSplit:
+      needsSplit = true
+
+      if condNeedsSplit:
+        let (st, ex) = exprToStmtList(n[0])
+        let brk = newTree(nkBreakStmt, ctx.g.emptyNode)
+        let branch = newTree(nkElifBranch, ctx.g.newNotCall(ex), brk)
+        let check = newTree(nkIfStmt, branch)
+        let newBody = newTree(nkStmtList, st, check, n[1])
+
+        n[0] = newSymNode(ctx.g.getSysSym(n[0].info, "true"))
+        n[1] = newBody
+
+  of nkDotExpr:
+    var ns = false
+    n[0] = ctx.lowerStmtListExprs(n[0], ns)
+    if ns:
+      needsSplit = true
+      result = newNodeI(nkStmtListExpr, n.info)
+      result.typ = n.typ
+      let (st, ex) = exprToStmtList(n[0])
+      result.add(st)
+      n[0] = ex
+      result.add(n)
+
+  of nkBlockExpr:
+    var ns = false
+    n[1] = ctx.lowerStmtListExprs(n[1], ns)
+    if ns:
+      needsSplit = true
+      result = newNodeI(nkStmtListExpr, n.info)
+      result.typ = n.typ
+      let (st, ex) = exprToStmtList(n[1])
+      n.kind = nkBlockStmt
+      n.typ = nil
+      n[1] = st
+      result.add(n)
+      result.add(ex)
+
+  else:
+    for i in 0 ..< n.len:
+      n[i] = ctx.lowerStmtListExprs(n[i], needsSplit)
+
+proc newEndFinallyNode(ctx: var Ctx, info: TLineInfo): PNode =
+  # Generate the following code:
+  #   if :unrollFinally:
+  #       if :curExc.isNil:
+  #         return :tmpResult
+  #       else:
+  #         raise
+  let curExc = ctx.newCurExcAccess()
+  let nilnode = newNode(nkNilLit)
+  nilnode.typ = curExc.typ
+  let cmp = newTree(nkCall, newSymNode(ctx.g.getSysMagic(info, "==", mEqRef), info), curExc, nilnode)
+  cmp.typ = ctx.g.getSysType(info, tyBool)
+
+  let asgn = newTree(nkFastAsgn,
+    newSymNode(getClosureIterResult(ctx.g, ctx.fn), info),
+    ctx.newTmpResultAccess())
+
+  let retStmt = newTree(nkReturnStmt, asgn)
+  let branch = newTree(nkElifBranch, cmp, retStmt)
+
+  # The C++ backend requires `getCurrentException` here.
+  let raiseStmt = newTree(nkRaiseStmt, ctx.g.callCodegenProc("getCurrentException"))
+  raiseStmt.info = info
+  let elseBranch = newTree(nkElse, raiseStmt)
+
+  let ifBody = newTree(nkIfStmt, branch, elseBranch)
+  let elifBranch = newTree(nkElifBranch, ctx.newUnrollFinallyAccess(info), ifBody)
+  elifBranch.info = info
+  result = newTree(nkIfStmt, elifBranch)
+
+proc transformReturnsInTry(ctx: var Ctx, n: PNode): PNode =
+  result = n
+  # TODO: This is very inefficient. It traverses the node, looking for nkYieldStmt.
+  case n.kind
+  of nkReturnStmt:
+    # We're somewhere in try, transform to finally unrolling
+    assert(ctx.nearestFinally != 0)
+
+    result = newNodeI(nkStmtList, n.info)
+
+    block: # :unrollFinally = true
+      let asgn = newNodeI(nkAsgn, n.info)
+      asgn.add(ctx.newUnrollFinallyAccess(n.info))
+      asgn.add(newIntTypeNode(nkIntLit, 1, ctx.g.getSysType(n.info, tyBool)))
+      result.add(asgn)
+
+    if n[0].kind != nkEmpty:
+      let asgnTmpResult = newNodeI(nkAsgn, n.info)
+      asgnTmpResult.add(ctx.newTmpResultAccess())
+      asgnTmpResult.add(n[0])
+      result.add(asgnTmpResult)
+
+    result.add(ctx.newNullifyCurExc(n.info))
+
+    let goto = newTree(nkGotoState, ctx.g.newIntLit(n.info, ctx.nearestFinally))
+    result.add(goto)
+
+  of nkSkip:
+    discard
+  else:
+    for i in 0 ..< n.len:
+      n[i] = ctx.transformReturnsInTry(n[i])
+
+proc transformClosureIteratorBody(ctx: var Ctx, n: PNode, gotoOut: PNode): PNode =
+  result = n
+  case n.kind:
+    of nkSkip:
+      discard
+
+    of nkStmtList, nkStmtListExpr:
+      result = addGotoOut(result, gotoOut)
+      for i in 0 ..< n.len:
+        if n[i].hasYields:
+          # Create a new split
+          let go = newNodeI(nkGotoState, n[i].info)
+          n[i] = ctx.transformClosureIteratorBody(n[i], go)
+
+          let s = newNodeI(nkStmtList, n[i + 1].info)
+          for j in i + 1 ..< n.len:
+            s.add(n[j])
+
+          n.sons.setLen(i + 1)
+          discard ctx.newState(s, go)
+          if ctx.transformClosureIteratorBody(s, gotoOut) != s:
+            internalError(ctx.g.config, "transformClosureIteratorBody != s")
+          break
+
+    of nkYieldStmt:
+      result = newNodeI(nkStmtList, n.info)
+      result.add(n)
+      result.add(gotoOut)
+
+    of nkElse, nkElseExpr:
+      result[0] = addGotoOut(result[0], gotoOut)
+      result[0] = ctx.transformClosureIteratorBody(result[0], gotoOut)
+
+    of nkElifBranch, nkElifExpr, nkOfBranch:
+      result[^1] = addGotoOut(result[^1], gotoOut)
+      result[^1] = ctx.transformClosureIteratorBody(result[^1], gotoOut)
+
+    of nkIfStmt, nkCaseStmt:
+      for i in 0 ..< n.len:
+        n[i] = ctx.transformClosureIteratorBody(n[i], gotoOut)
+      if n[^1].kind != nkElse:
+        # We don't have an else branch, but every possible branch has to end with
+        # gotoOut, so add else here.
+        let elseBranch = newTree(nkElse, gotoOut)
+        n.add(elseBranch)
+
+    of nkWhileStmt:
+      # while e:
+      #   s
+      # ->
+      # BEGIN_STATE:
+      #   if e:
+      #     s
+      #     goto BEGIN_STATE
+      #   else:
+      #     goto OUT
+
+      result = newNodeI(nkGotoState, n.info)
+
+      let s = newNodeI(nkStmtList, n.info)
+      discard ctx.newState(s, result)
+      let ifNode = newNodeI(nkIfStmt, n.info)
+      let elifBranch = newNodeI(nkElifBranch, n.info)
+      elifBranch.add(n[0])
+
+      var body = addGotoOut(n[1], result)
+
+      body = ctx.transformBreaksAndContinuesInWhile(body, result, gotoOut)
+      body = ctx.transformClosureIteratorBody(body, result)
+
+      elifBranch.add(body)
+      ifNode.add(elifBranch)
+
+      let elseBranch = newTree(nkElse, gotoOut)
+      ifNode.add(elseBranch)
+      s.add(ifNode)
+
+    of nkBlockStmt:
+      result[1] = addGotoOut(result[1], gotoOut)
+      result[1] = ctx.transformBreaksInBlock(result[1], result[0], gotoOut)
+      result[1] = ctx.transformClosureIteratorBody(result[1], gotoOut)
+
+    of nkTryStmt:
+      # See explanation above about how this works
+      ctx.hasExceptions = true
+
+      result = newNodeI(nkGotoState, n.info)
+      var tryBody = toStmtList(n[0])
+      var exceptBody = ctx.collectExceptState(n)
+      var finallyBody = newTree(nkStmtList, getFinallyNode(ctx, n))
+      finallyBody = ctx.transformReturnsInTry(finallyBody)
+      finallyBody.add(ctx.newEndFinallyNode(finallyBody.info))
+
+      # The following index calculation is based on the knowledge how state
+      # indexes are assigned
+      let tryIdx = ctx.states.len
+      var exceptIdx, finallyIdx: int
+      if exceptBody.kind != nkEmpty:
+        exceptIdx = -(tryIdx + 1)
+        finallyIdx = tryIdx + 2
+      else:
+        exceptIdx = tryIdx + 1
+        finallyIdx = tryIdx + 1
+
+      let outToFinally = newNodeI(nkGotoState, finallyBody.info)
+
+      block: # Create initial states.
+        let oldExcHandlingState = ctx.curExcHandlingState
+        ctx.curExcHandlingState = exceptIdx
+        let realTryIdx = ctx.newState(tryBody, result)
+        assert(realTryIdx == tryIdx)
+
+        if exceptBody.kind != nkEmpty:
+          ctx.curExcHandlingState = finallyIdx
+          let realExceptIdx = ctx.newState(exceptBody, nil)
+          assert(realExceptIdx == -exceptIdx)
+
+        ctx.curExcHandlingState = oldExcHandlingState
+        let realFinallyIdx = ctx.newState(finallyBody, outToFinally)
+        assert(realFinallyIdx == finallyIdx)
+
+      block: # Subdivide the states
+        let oldNearestFinally = ctx.nearestFinally
+        ctx.nearestFinally = finallyIdx
+
+        let oldExcHandlingState = ctx.curExcHandlingState
+
+        ctx.curExcHandlingState = exceptIdx
+
+        if ctx.transformReturnsInTry(tryBody) != tryBody:
+          internalError(ctx.g.config, "transformReturnsInTry != tryBody")
+        if ctx.transformClosureIteratorBody(tryBody, outToFinally) != tryBody:
+          internalError(ctx.g.config, "transformClosureIteratorBody != tryBody")
+
+        ctx.curExcHandlingState = finallyIdx
+        ctx.addElseToExcept(exceptBody)
+        if ctx.transformReturnsInTry(exceptBody) != exceptBody:
+          internalError(ctx.g.config, "transformReturnsInTry != exceptBody")
+        if ctx.transformClosureIteratorBody(exceptBody, outToFinally) != exceptBody:
+          internalError(ctx.g.config, "transformClosureIteratorBody != exceptBody")
+
+        ctx.curExcHandlingState = oldExcHandlingState
+        ctx.nearestFinally = oldNearestFinally
+        if ctx.transformClosureIteratorBody(finallyBody, gotoOut) != finallyBody:
+          internalError(ctx.g.config, "transformClosureIteratorBody != finallyBody")
+
+    of nkGotoState, nkForStmt:
+      internalError(ctx.g.config, "closure iter " & $n.kind)
+
+    else:
+      for i in 0 ..< n.len:
+        n[i] = ctx.transformClosureIteratorBody(n[i], gotoOut)
+
+proc stateFromGotoState(n: PNode): int =
+  assert(n.kind == nkGotoState)
+  result = n[0].intVal.int
+
+proc transformStateAssignments(ctx: var Ctx, n: PNode): PNode =
+  # This transforms 3 patterns:
+  ########################## 1
+  # yield e
+  # goto STATE
+  # ->
+  # :state = STATE
+  # return e
+  ########################## 2
+  # goto STATE
+  # ->
+  # :state = STATE
+  # break :stateLoop
+  ########################## 3
+  # return e
+  # ->
+  # :state = -1
+  # return e
+  #
+  result = n
+  case n.kind
+  of nkStmtList, nkStmtListExpr:
+    if n.len != 0 and n[0].kind == nkYieldStmt:
+      assert(n.len == 2)
+      assert(n[1].kind == nkGotoState)
+
+      result = newNodeI(nkStmtList, n.info)
+      result.add(ctx.newStateAssgn(stateFromGotoState(n[1])))
+
+      var retStmt = newNodeI(nkReturnStmt, n.info)
+      if n[0].sons[0].kind != nkEmpty:
+        var a = newNodeI(nkAsgn, n[0].sons[0].info)
+        var retVal = n[0].sons[0] #liftCapturedVars(n.sons[0], owner, d, c)
+        addSon(a, newSymNode(getClosureIterResult(ctx.g, ctx.fn)))
+        addSon(a, retVal)
+        retStmt.add(a)
+      else:
+        retStmt.add(ctx.g.emptyNode)
+
+      result.add(retStmt)
+    else:
+      for i in 0 ..< n.len:
+        n[i] = ctx.transformStateAssignments(n[i])
+
+  of nkSkip:
+    discard
+
+  of nkReturnStmt:
+    result = newNodeI(nkStmtList, n.info)
+    result.add(ctx.newStateAssgn(-1))
+    result.add(n)
+
+  of nkGotoState:
+    result = newNodeI(nkStmtList, n.info)
+    result.add(ctx.newStateAssgn(stateFromGotoState(n)))
+
+    let breakState = newNodeI(nkBreakStmt, n.info)
+    breakState.add(newSymNode(ctx.stateLoopLabel))
+    result.add(breakState)
+
+  else:
+    for i in 0 ..< n.len:
+      n[i] = ctx.transformStateAssignments(n[i])
+
+proc skipStmtList(ctx: Ctx; n: PNode): PNode =
+  result = n
+  while result.kind in {nkStmtList}:
+    if result.len == 0: return ctx.g.emptyNode
+    result = result[0]
+
+proc skipEmptyStates(ctx: Ctx, stateIdx: int): int =
+  # Returns first non-empty state idx for `stateIdx`. Returns `stateIdx` if
+  # it is not empty
+  var maxJumps = ctx.states.len # maxJumps used only for debugging purposes.
+  var stateIdx = stateIdx
+  while true:
+    let label = stateIdx
+    if label == ctx.exitStateIdx: break
+    var newLabel = label
+    if label == -1:
+      newLabel = ctx.exitStateIdx
+    else:
+      let fs = skipStmtList(ctx, ctx.states[label][1])
+      if fs.kind == nkGotoState:
+        newLabel = fs[0].intVal.int
+    if label == newLabel: break
+    stateIdx = newLabel
+    dec maxJumps
+    if maxJumps == 0:
+      assert(false, "Internal error")
+
+  result = ctx.states[stateIdx][0].intVal.int
+
+proc skipThroughEmptyStates(ctx: var Ctx, n: PNode): PNode =
+  result = n
+  case n.kind
+  of nkSkip:
+    discard
+  of nkGotoState:
+    result = copyTree(n)
+    result[0].intVal = ctx.skipEmptyStates(result[0].intVal.int)
+  else:
+    for i in 0 ..< n.len:
+      n[i] = ctx.skipThroughEmptyStates(n[i])
+
+proc newArrayType(g: ModuleGraph; n: int, t: PType, owner: PSym): PType =
+  result = newType(tyArray, owner)
+
+  let rng = newType(tyRange, owner)
+  rng.n = newTree(nkRange, g.newIntLit(owner.info, 0), g.newIntLit(owner.info, n))
+  rng.rawAddSon(t)
+
+  result.rawAddSon(rng)
+  result.rawAddSon(t)
+
+proc createExceptionTable(ctx: var Ctx): PNode {.inline.} =
+  result = newNodeI(nkBracket, ctx.fn.info)
+  result.typ = ctx.g.newArrayType(ctx.exceptionTable.len, ctx.g.getSysType(ctx.fn.info, tyInt16), ctx.fn)
+
+  for i in ctx.exceptionTable:
+    let elem = newIntNode(nkIntLit, i)
+    elem.typ = ctx.g.getSysType(ctx.fn.info, tyInt16)
+    result.add(elem)
+
+proc newCatchBody(ctx: var Ctx, info: TLineInfo): PNode {.inline.} =
+  # Generates the code:
+  # :state = exceptionTable[:state]
+  # if :state == 0: raise
+  # :unrollFinally = :state > 0
+  # if :state < 0:
+  #   :state = -:state
+  # :curExc = getCurrentException()
+
+  result = newNodeI(nkStmtList, info)
+
+  let intTyp = ctx.g.getSysType(info, tyInt)
+  let boolTyp = ctx.g.getSysType(info, tyBool)
+
+  # :state = exceptionTable[:state]
+  block:
+
+    # exceptionTable[:state]
+    let getNextState = newTree(nkBracketExpr,
+      ctx.createExceptionTable(),
+      ctx.newStateAccess())
+    getNextState.typ = intTyp
+
+    # :state = exceptionTable[:state]
+    result.add(ctx.newStateAssgn(getNextState))
+
+  # if :state == 0: raise
+  block:
+    let cond = newTree(nkCall,
+      ctx.g.getSysMagic(info, "==", mEqI).newSymNode(),
+      ctx.newStateAccess(),
+      newIntTypeNode(nkIntLit, 0, intTyp))
+    cond.typ = boolTyp
+
+    let raiseStmt = newTree(nkRaiseStmt, ctx.g.emptyNode)
+    let ifBranch = newTree(nkElifBranch, cond, raiseStmt)
+    let ifStmt = newTree(nkIfStmt, ifBranch)
+    result.add(ifStmt)
+
+  # :unrollFinally = :state > 0
+  block:
+    let cond = newTree(nkCall,
+      ctx.g.getSysMagic(info, "<", mLtI).newSymNode,
+      newIntTypeNode(nkIntLit, 0, intTyp),
+      ctx.newStateAccess())
+    cond.typ = boolTyp
+
+    let asgn = newTree(nkAsgn, ctx.newUnrollFinallyAccess(info), cond)
+    result.add(asgn)
+
+  # if :state < 0: :state = -:state
+  block:
+    let cond = newTree(nkCall,
+      ctx.g.getSysMagic(info, "<", mLtI).newSymNode,
+      ctx.newStateAccess(),
+      newIntTypeNode(nkIntLit, 0, intTyp))
+    cond.typ = boolTyp
+
+    let negateState = newTree(nkCall,
+      ctx.g.getSysMagic(info, "-", mUnaryMinusI).newSymNode,
+      ctx.newStateAccess())
+    negateState.typ = intTyp
+
+    let ifBranch = newTree(nkElifBranch, cond, ctx.newStateAssgn(negateState))
+    let ifStmt = newTree(nkIfStmt, ifBranch)
+    result.add(ifStmt)
+
+  # :curExc = getCurrentException()
+  block:
+    result.add(newTree(nkAsgn,
+      ctx.newCurExcAccess(),
+      ctx.g.callCodegenProc("getCurrentException")))
+
+proc wrapIntoTryExcept(ctx: var Ctx, n: PNode): PNode {.inline.} =
+  let setupExc = newTree(nkCall,
+    newSymNode(ctx.g.getCompilerProc("closureIterSetupExc")),
+    ctx.newCurExcAccess())
+
+  let tryBody = newTree(nkStmtList, setupExc, n)
+  let exceptBranch = newTree(nkExceptBranch, ctx.newCatchBody(ctx.fn.info))
+
+  result = newTree(nkTryStmt, tryBody, exceptBranch)
+
+proc wrapIntoStateLoop(ctx: var Ctx, n: PNode): PNode =
+  # while true:
+  #   block :stateLoop:
+  #     gotoState :state
+  #     local vars decl (if needed)
+  #     body # Might get wrapped in try-except
+  let loopBody = newNodeI(nkStmtList, n.info)
+  result = newTree(nkWhileStmt, newSymNode(ctx.g.getSysSym(n.info, "true")), loopBody)
+  result.info = n.info
+
+  let localVars = newNodeI(nkStmtList, n.info)
+  if not ctx.stateVarSym.isNil:
+    let varSect = newNodeI(nkVarSection, n.info)
+    addVar(varSect, newSymNode(ctx.stateVarSym))
+    localVars.add(varSect)
+
+    if not ctx.tempVars.isNil:
+      localVars.add(ctx.tempVars)
+
+  let blockStmt = newNodeI(nkBlockStmt, n.info)
+  blockStmt.add(newSymNode(ctx.stateLoopLabel))
+
+  let gs = newNodeI(nkGotoState, n.info)
+  gs.add(ctx.newStateAccess())
+  gs.add(ctx.g.newIntLit(n.info, ctx.states.len - 1))
+
+  var blockBody = newTree(nkStmtList, gs, localVars, n)
+  if ctx.hasExceptions:
+    blockBody = ctx.wrapIntoTryExcept(blockBody)
+
+  blockStmt.add(blockBody)
+  loopBody.add(blockStmt)
+
+proc deleteEmptyStates(ctx: var Ctx) =
+  let goOut = newTree(nkGotoState, ctx.g.newIntLit(TLineInfo(), -1))
+  ctx.exitStateIdx = ctx.newState(goOut, nil)
+
+  # Apply new state indexes and mark unused states with -1
+  var iValid = 0
+  for i, s in ctx.states:
+    let body = skipStmtList(ctx, s[1])
+    if body.kind == nkGotoState and i != ctx.states.len - 1 and i != 0:
+      # This is an empty state. Mark with -1.
+      s[0].intVal = -1
+    else:
+      s[0].intVal = iValid
+      inc iValid
+
+  for i, s in ctx.states:
+    let body = skipStmtList(ctx, s[1])
+    if body.kind != nkGotoState or i == 0:
+      discard ctx.skipThroughEmptyStates(s)
+      let excHandlState = ctx.exceptionTable[i]
+      if excHandlState < 0:
+        ctx.exceptionTable[i] = -ctx.skipEmptyStates(-excHandlState)
+      elif excHandlState != 0:
+        ctx.exceptionTable[i] = ctx.skipEmptyStates(excHandlState)
+
+  var i = 0
+  while i < ctx.states.len - 1:
+    let fs = skipStmtList(ctx, ctx.states[i][1])
+    if fs.kind == nkGotoState and i != 0:
+      ctx.states.delete(i)
+      ctx.exceptionTable.delete(i)
+    else:
+      inc i
+
+proc transformClosureIterator*(g: ModuleGraph; fn: PSym, n: PNode): PNode =
+  var ctx: Ctx
+  ctx.g = g
+  ctx.fn = fn
+
+  if getEnvParam(fn).isNil:
+    # Lambda lifting was not done yet. Use temporary :state sym, which
+    # be handled specially by lambda lifting. Local temp vars (if needed)
+    # should folllow the same logic.
+    ctx.stateVarSym = newSym(skVar, getIdent(ctx.g.cache, ":state"), fn, fn.info)
+    ctx.stateVarSym.typ = g.createClosureIterStateType(fn)
+  ctx.stateLoopLabel = newSym(skLabel, getIdent(ctx.g.cache, ":stateLoop"), fn, fn.info)
+  var n = n.toStmtList
+
+  discard ctx.newState(n, nil)
+  let gotoOut = newTree(nkGotoState, g.newIntLit(n.info, -1))
+
+  var ns = false
+  n = ctx.lowerStmtListExprs(n, ns)
+
+  if n.hasYieldsInExpressions():
+    internalError(ctx.g.config, "yield in expr not lowered")
+
+  # Splitting transformation
+  discard ctx.transformClosureIteratorBody(n, gotoOut)
+
+  # Optimize empty states away
+  ctx.deleteEmptyStates()
+
+  # Make new body by concating the list of states
+  result = newNodeI(nkStmtList, n.info)
+  for s in ctx.states:
+    assert(s.len == 2)
+    let body = s[1]
+    s.sons.del(1)
+    result.add(s)
+    result.add(body)
+
+  result = ctx.transformStateAssignments(result)
+  result = ctx.wrapIntoStateLoop(result)
+
+  # echo "TRANSFORM TO STATES: "
+  # echo renderTree(result)
+
+  # echo "exception table:"
+  # for i, e in ctx.exceptionTable:
+  #   echo i, " -> ", e
diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim
new file mode 100644
index 000000000..8bd073314
--- /dev/null
+++ b/compiler/cmdlinehelper.nim
@@ -0,0 +1,92 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Nim contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Helpers for binaries that use compiler passes, eg: nim, nimsuggest, nimfix
+
+import
+  options, idents, nimconf, scriptconfig, extccomp, commands, msgs,
+  lineinfos, modulegraphs, condsyms, os, pathutils
+
+type
+  NimProg* = ref object
+    suggestMode*: bool
+    supportsStdinFile*: bool
+    processCmdLine*: proc(pass: TCmdLinePass, cmd: string; config: ConfigRef)
+    mainCommand*: proc(graph: ModuleGraph)
+
+proc initDefinesProg*(self: NimProg, conf: ConfigRef, name: string) =
+  condsyms.initDefines(conf.symbols)
+  defineSymbol conf.symbols, name
+
+proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) =
+  self.processCmdLine(passCmd1, "", conf)
+  if self.supportsStdinFile and conf.projectName == "-":
+    conf.projectName = "stdinfile"
+    conf.projectFull = AbsoluteFile "stdinfile"
+    conf.projectPath = AbsoluteDir getCurrentDir()
+    conf.projectIsStdin = true
+  elif conf.projectName != "":
+    try:
+      conf.projectFull = canonicalizePath(conf, AbsoluteFile conf.projectName)
+    except OSError:
+      conf.projectFull = AbsoluteFile conf.projectName
+    let p = splitFile(conf.projectFull)
+    let dir = if p.dir.isEmpty: AbsoluteDir getCurrentDir() else: p.dir
+    conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile dir)
+    conf.projectName = p.name
+  else:
+    conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile getCurrentDir())
+
+proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: ConfigRef): bool =
+  loadConfigs(DefaultConfig, cache, conf) # load all config files
+  if self.suggestMode:
+    conf.command = "nimsuggest"
+
+  proc runNimScriptIfExists(path: AbsoluteFile)=
+    if fileExists(path):
+      runNimScript(cache, path, freshDefines = false, conf)
+
+  # Caution: make sure this stays in sync with `loadConfigs`
+  if optSkipSystemConfigFile notin conf.globalOptions:
+    runNimScriptIfExists(getSystemConfigPath(conf, DefaultConfigNims))
+
+  if optSkipUserConfigFile notin conf.globalOptions:
+    runNimScriptIfExists(getUserConfigPath(DefaultConfigNims))
+
+  if optSkipParentConfigFiles notin conf.globalOptions:
+    for dir in parentDirs(conf.projectPath.string, fromRoot = true, inclusive = false):
+      runNimScriptIfExists(AbsoluteDir(dir) / DefaultConfigNims)
+
+  if optSkipProjConfigFile notin conf.globalOptions:
+    runNimScriptIfExists(conf.projectPath / DefaultConfigNims)
+  block:
+    let scriptFile = conf.projectFull.changeFileExt("nims")
+    if not self.suggestMode:
+      runNimScriptIfExists(scriptFile)
+      # 'nim foo.nims' means to just run the NimScript file and do nothing more:
+      if fileExists(scriptFile) and scriptFile == conf.projectFull:
+        return false
+    else:
+      if scriptFile != conf.projectFull:
+        runNimScriptIfExists(scriptFile)
+      else:
+        # 'nimsuggest foo.nims' means to just auto-complete the NimScript file
+        discard
+
+  # now process command line arguments again, because some options in the
+  # command line can overwite the config file's settings
+  extccomp.initVars(conf)
+  self.processCmdLine(passCmd2, "", conf)
+  if conf.command == "":
+    rawMessage(conf, errGenerated, "command missing")
+
+  let graph = newModuleGraph(cache, conf)
+  graph.suggestMode = self.suggestMode
+  self.mainCommand(graph)
+  return true
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 1b42a191e..b090a09a5 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -18,7 +18,6 @@ template bootSwitch(name, expr, userString) =
 
 bootSwitch(usedRelease, defined(release), "-d:release")
 bootSwitch(usedGnuReadline, defined(useLinenoise), "-d:useLinenoise")
-bootSwitch(usedNoCaas, defined(noCaas), "-d:noCaas")
 bootSwitch(usedBoehm, defined(boehmgc), "--gc:boehm")
 bootSwitch(usedMarkAndSweep, defined(gcmarkandsweep), "--gc:markAndSweep")
 bootSwitch(usedGenerational, defined(gcgenerational), "--gc:generational")
@@ -27,88 +26,102 @@ bootSwitch(usedNoGC, defined(nogc), "--gc:none")
 
 import
   os, msgs, options, nversion, condsyms, strutils, extccomp, platform,
-  wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils
+  wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos,
+  pathutils
 
 # but some have deps to imported modules. Yay.
 bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
-bootSwitch(usedAvoidTimeMachine, noTimeMachine, "-d:avoidTimeMachine")
 bootSwitch(usedNativeStacktrace,
   defined(nativeStackTrace) and nativeStackTraceSupported,
   "-d:nativeStackTrace")
 bootSwitch(usedFFI, hasFFI, "-d:useFFI")
 
-
-proc writeCommandLineUsage*()
-
 type
   TCmdLinePass* = enum
     passCmd1,                 # first pass over the command line
     passCmd2,                 # second pass over the command line
     passPP                    # preprocessor called processCommand()
 
-proc processCommand*(switch: string, pass: TCmdLinePass)
-proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
-                    config: ConfigRef = nil)
-
-# implementation
-
 const
-  HelpMessage = "Nim Compiler Version $1 (" & CompileDate & ") [$2: $3]\n" &
-      "Copyright (c) 2006-" & CompileDate.substr(0, 3) & " by Andreas Rumpf\n"
+  HelpMessage = "Nim Compiler Version $1 [$2: $3]\n" &
+      "Compiled at $4\n" &
+      "Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n"
 
 const
   Usage = slurp"../doc/basicopt.txt".replace("//", "")
-  AdvancedUsage = slurp"../doc/advopt.txt".replace("//", "")
-
-proc getCommandLineDesc(): string =
-  result = (HelpMessage % [VersionAsString, platform.OS[platform.hostOS].name,
-                           CPU[platform.hostCPU].name]) & Usage
+  FeatureDesc = block:
+    var x = ""
+    for f in low(Feature)..high(Feature):
+      if x.len > 0: x.add "|"
+      x.add $f
+    x
+  AdvancedUsage = slurp"../doc/advopt.txt".replace("//", "") % FeatureDesc
+
+proc getCommandLineDesc(conf: ConfigRef): string =
+  result = (HelpMessage % [VersionAsString, platform.OS[conf.target.hostOS].name,
+                           CPU[conf.target.hostCPU].name, CompileDate]) &
+                           Usage
+
+proc helpOnError(conf: ConfigRef; pass: TCmdLinePass) =
+  if pass == passCmd1:
+    msgWriteln(conf, getCommandLineDesc(conf), {msgStdout})
+    msgQuit(0)
 
-proc helpOnError(pass: TCmdLinePass) =
+proc writeAdvancedUsage(conf: ConfigRef; pass: TCmdLinePass) =
   if pass == passCmd1:
-    msgWriteln(getCommandLineDesc(), {msgStdout})
+    msgWriteln(conf, (HelpMessage % [VersionAsString,
+                                 platform.OS[conf.target.hostOS].name,
+                                 CPU[conf.target.hostCPU].name, CompileDate]) &
+                                 AdvancedUsage,
+               {msgStdout})
     msgQuit(0)
 
-proc writeAdvancedUsage(pass: TCmdLinePass) =
+proc writeFullhelp(conf: ConfigRef; pass: TCmdLinePass) =
   if pass == passCmd1:
-    msgWriteln(`%`(HelpMessage, [VersionAsString,
-                                 platform.OS[platform.hostOS].name,
-                                 CPU[platform.hostCPU].name]) & AdvancedUsage,
+    msgWriteln(conf, `%`(HelpMessage, [VersionAsString,
+                                 platform.OS[conf.target.hostOS].name,
+                                 CPU[conf.target.hostCPU].name, CompileDate]) &
+                                 Usage & AdvancedUsage,
                {msgStdout})
     msgQuit(0)
 
-proc writeVersionInfo(pass: TCmdLinePass) =
+proc writeVersionInfo(conf: ConfigRef; pass: TCmdLinePass) =
   if pass == passCmd1:
-    msgWriteln(`%`(HelpMessage, [VersionAsString,
-                                 platform.OS[platform.hostOS].name,
-                                 CPU[platform.hostCPU].name]))
+    msgWriteln(conf, `%`(HelpMessage, [VersionAsString,
+                                 platform.OS[conf.target.hostOS].name,
+                                 CPU[conf.target.hostCPU].name, CompileDate]),
+               {msgStdout})
 
     const gitHash = gorge("git log -n 1 --format=%H").strip
     when gitHash.len == 40:
-      msgWriteln("git hash: " & gitHash)
+      msgWriteln(conf, "git hash: " & gitHash, {msgStdout})
 
-    msgWriteln("active boot switches:" & usedRelease & usedAvoidTimeMachine &
-      usedTinyC & usedGnuReadline & usedNativeStacktrace & usedNoCaas &
-      usedFFI & usedBoehm & usedMarkAndSweep & usedGenerational & usedGoGC & usedNoGC)
+    msgWriteln(conf, "active boot switches:" & usedRelease &
+      usedTinyC & usedGnuReadline & usedNativeStacktrace &
+      usedFFI & usedBoehm & usedMarkAndSweep & usedGenerational & usedGoGC & usedNoGC,
+               {msgStdout})
     msgQuit(0)
 
-var
-  helpWritten: bool
-
-proc writeCommandLineUsage() =
+proc writeCommandLineUsage*(conf: ConfigRef; helpWritten: var bool) =
   if not helpWritten:
-    msgWriteln(getCommandLineDesc(), {msgStdout})
+    msgWriteln(conf, getCommandLineDesc(conf), {msgStdout})
     helpWritten = true
 
 proc addPrefix(switch: string): string =
   if len(switch) == 1: result = "-" & switch
   else: result = "--" & switch
 
-proc invalidCmdLineOption(pass: TCmdLinePass, switch: string, info: TLineInfo) =
-  if switch == " ": localError(info, errInvalidCmdLineOption, "-")
-  else: localError(info, errInvalidCmdLineOption, addPrefix(switch))
+const
+  errInvalidCmdLineOption = "invalid command line option: '$1'"
+  errOnOrOffExpectedButXFound = "'on' or 'off' expected, but '$1' found"
+  errOnOffOrListExpectedButXFound = "'on', 'off' or 'list' expected, but '$1' found"
+  errOffHintsError = "'off', 'hint' or 'error' expected, but '$1' found"
 
-proc splitSwitch(switch: string, cmd, arg: var string, pass: TCmdLinePass,
+proc invalidCmdLineOption(conf: ConfigRef; pass: TCmdLinePass, switch: string, info: TLineInfo) =
+  if switch == " ": localError(conf, info, errInvalidCmdLineOption % "-")
+  else: localError(conf, info, errInvalidCmdLineOption % addPrefix(switch))
+
+proc splitSwitch(conf: ConfigRef; switch: string, cmd, arg: var string, pass: TCmdLinePass,
                  info: TLineInfo) =
   cmd = ""
   var i = 0
@@ -120,591 +133,673 @@ proc splitSwitch(switch: string, cmd, arg: var string, pass: TCmdLinePass,
     else: break
     inc(i)
   if i >= len(switch): arg = ""
-  elif switch[i] in {':', '=', '['}: arg = substr(switch, i + 1)
-  else: invalidCmdLineOption(pass, switch, info)
+  # cmd:arg => (cmd,arg)
+  elif switch[i] in {':', '='}: arg = substr(switch, i + 1)
+  # cmd[sub]:rest => (cmd,[sub]:rest)
+  elif switch[i] == '[': arg = substr(switch, i)
+  else: invalidCmdLineOption(conf, pass, switch, info)
 
-proc processOnOffSwitch(op: TOptions, arg: string, pass: TCmdLinePass,
+proc processOnOffSwitch(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass,
                         info: TLineInfo) =
   case arg.normalize
-  of "on": gOptions = gOptions + op
-  of "off": gOptions = gOptions - op
-  else: localError(info, errOnOrOffExpectedButXFound, arg)
+  of "on": conf.options = conf.options + op
+  of "off": conf.options = conf.options - op
+  else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
 
-proc processOnOffSwitchOrList(op: TOptions, arg: string, pass: TCmdLinePass,
+proc processOnOffSwitchOrList(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass,
                               info: TLineInfo): bool =
   result = false
   case arg.normalize
-  of "on": gOptions = gOptions + op
-  of "off": gOptions = gOptions - op
-  else:
-    if arg == "list":
-      result = true
-    else:
-      localError(info, errOnOffOrListExpectedButXFound, arg)
+  of "on": conf.options = conf.options + op
+  of "off": conf.options = conf.options - op
+  of "list": result = true
+  else: localError(conf, info, errOnOffOrListExpectedButXFound % arg)
 
-proc processOnOffSwitchG(op: TGlobalOptions, arg: string, pass: TCmdLinePass,
+proc processOnOffSwitchG(conf: ConfigRef; op: TGlobalOptions, arg: string, pass: TCmdLinePass,
                          info: TLineInfo) =
   case arg.normalize
-  of "on": gGlobalOptions = gGlobalOptions + op
-  of "off": gGlobalOptions = gGlobalOptions - op
-  else: localError(info, errOnOrOffExpectedButXFound, arg)
-
-proc expectArg(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
-  if arg == "": localError(info, errCmdLineArgExpected, addPrefix(switch))
+  of "on": conf.globalOptions = conf.globalOptions + op
+  of "off": conf.globalOptions = conf.globalOptions - op
+  else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
 
-proc expectNoArg(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
-  if arg != "": localError(info, errCmdLineNoArgExpected, addPrefix(switch))
+proc expectArg(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
+  if arg == "":
+    localError(conf, info, "argument for command line option expected: '$1'" % addPrefix(switch))
 
-var
-  enableNotes: TNoteKinds
-  disableNotes: TNoteKinds
+proc expectNoArg(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
+  if arg != "":
+    localError(conf, info, "invalid argument for command line option: '$1'" % addPrefix(switch))
 
 proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
-                         info: TLineInfo; orig: string) =
-  var id = ""  # arg = "X]:on|off"
+                         info: TLineInfo; orig: string; conf: ConfigRef) =
+  var id = ""  # arg = key:val or [key]:val;  with val=on|off
   var i = 0
   var n = hintMin
-  while i < len(arg) and (arg[i] != ']'):
+  var isBracket = false
+  if i < len(arg) and arg[i] == '[':
+    isBracket = true
+    inc(i)
+  while i < len(arg) and (arg[i] notin {':', '=', ']'}):
     add(id, arg[i])
     inc(i)
-  if i < len(arg) and (arg[i] == ']'): inc(i)
-  else: invalidCmdLineOption(pass, orig, info)
+  if isBracket:
+    if i < len(arg) and arg[i] == ']': inc(i)
+    else: invalidCmdLineOption(conf, pass, orig, info)
+
   if i < len(arg) and (arg[i] in {':', '='}): inc(i)
-  else: invalidCmdLineOption(pass, orig, info)
+  else: invalidCmdLineOption(conf, pass, orig, info)
   if state == wHint:
-    var x = findStr(msgs.HintsToStr, id)
+    let x = findStr(lineinfos.HintsToStr, id)
     if x >= 0: n = TNoteKind(x + ord(hintMin))
-    else: localError(info, "unknown hint: " & id)
+    else: localError(conf, info, "unknown hint: " & id)
   else:
-    var x = findStr(msgs.WarningsToStr, id)
+    let x = findStr(lineinfos.WarningsToStr, id)
     if x >= 0: n = TNoteKind(x + ord(warnMin))
-    else: localError(info, "unknown warning: " & id)
+    else: localError(conf, info, "unknown warning: " & id)
   case substr(arg, i).normalize
   of "on":
-    incl(gNotes, n)
-    incl(gMainPackageNotes, n)
-    incl(enableNotes, n)
+    incl(conf.notes, n)
+    incl(conf.mainPackageNotes, n)
+    incl(conf.enableNotes, n)
   of "off":
-    excl(gNotes, n)
-    excl(gMainPackageNotes, n)
-    incl(disableNotes, n)
-    excl(ForeignPackageNotes, n)
-  else: localError(info, errOnOrOffExpectedButXFound, arg)
-
-proc processCompile(filename: string) =
-  var found = findFile(filename)
-  if found == "": found = filename
-  extccomp.addExternalFileToCompile(found)
-
-proc testCompileOptionArg*(switch, arg: string, info: TLineInfo): bool =
+    excl(conf.notes, n)
+    excl(conf.mainPackageNotes, n)
+    incl(conf.disableNotes, n)
+    excl(conf.foreignPackageNotes, n)
+  else: localError(conf, info, errOnOrOffExpectedButXFound % arg)
+
+proc processCompile(conf: ConfigRef; filename: string) =
+  var found = findFile(conf, filename)
+  if found.isEmpty: found = AbsoluteFile filename
+  extccomp.addExternalFileToCompile(conf, found)
+
+const
+  errNoneBoehmRefcExpectedButXFound = "'none', 'boehm' or 'refc' expected, but '$1' found"
+  errNoneSpeedOrSizeExpectedButXFound = "'none', 'speed' or 'size' expected, but '$1' found"
+  errGuiConsoleOrLibExpectedButXFound = "'gui', 'console' or 'lib' expected, but '$1' found"
+
+proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo): bool =
   case switch.normalize
   of "gc":
     case arg.normalize
-    of "boehm":        result = gSelectedGC == gcBoehm
-    of "refc":         result = gSelectedGC == gcRefc
-    of "v2":           result = gSelectedGC == gcV2
-    of "markandsweep": result = gSelectedGC == gcMarkAndSweep
-    of "generational": result = gSelectedGC == gcGenerational
-    of "go":           result = gSelectedGC == gcGo
-    of "none":         result = gSelectedGC == gcNone
-    of "stack":        result = gSelectedGC == gcStack
-    else: localError(info, errNoneBoehmRefcExpectedButXFound, arg)
+    of "boehm":        result = conf.selectedGC == gcBoehm
+    of "refc":         result = conf.selectedGC == gcRefc
+    of "v2":           result = conf.selectedGC == gcV2
+    of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
+    of "generational": result = false
+    of "destructors":  result = conf.selectedGC == gcDestructors
+    of "go":           result = conf.selectedGC == gcGo
+    of "none":         result = conf.selectedGC == gcNone
+    of "stack", "regions": result = conf.selectedGC == gcRegions
+    else: localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg)
   of "opt":
     case arg.normalize
-    of "speed": result = contains(gOptions, optOptimizeSpeed)
-    of "size": result = contains(gOptions, optOptimizeSize)
-    of "none": result = gOptions * {optOptimizeSpeed, optOptimizeSize} == {}
-    else: localError(info, errNoneSpeedOrSizeExpectedButXFound, arg)
-  of "verbosity": result = $gVerbosity == arg
+    of "speed": result = contains(conf.options, optOptimizeSpeed)
+    of "size": result = contains(conf.options, optOptimizeSize)
+    of "none": result = conf.options * {optOptimizeSpeed, optOptimizeSize} == {}
+    else: localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg)
+  of "verbosity": result = $conf.verbosity == arg
   of "app":
     case arg.normalize
-    of "gui":       result = contains(gGlobalOptions, optGenGuiApp)
-    of "console":   result = not contains(gGlobalOptions, optGenGuiApp)
-    of "lib":       result = contains(gGlobalOptions, optGenDynLib) and
-                      not contains(gGlobalOptions, optGenGuiApp)
-    of "staticlib": result = contains(gGlobalOptions, optGenStaticLib) and
-                      not contains(gGlobalOptions, optGenGuiApp)
-    else: localError(info, errGuiConsoleOrLibExpectedButXFound, arg)
+    of "gui":       result = contains(conf.globalOptions, optGenGuiApp)
+    of "console":   result = not contains(conf.globalOptions, optGenGuiApp)
+    of "lib":       result = contains(conf.globalOptions, optGenDynLib) and
+                      not contains(conf.globalOptions, optGenGuiApp)
+    of "staticlib": result = contains(conf.globalOptions, optGenStaticLib) and
+                      not contains(conf.globalOptions, optGenGuiApp)
+    else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
   of "dynliboverride":
-    result = isDynlibOverride(arg)
-  else: invalidCmdLineOption(passCmd1, switch, info)
+    result = isDynlibOverride(conf, arg)
+  else: invalidCmdLineOption(conf, passCmd1, switch, info)
 
-proc testCompileOption*(switch: string, info: TLineInfo): bool =
+proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool =
   case switch.normalize
-  of "debuginfo": result = contains(gGlobalOptions, optCDebug)
-  of "compileonly", "c": result = contains(gGlobalOptions, optCompileOnly)
-  of "nolinking": result = contains(gGlobalOptions, optNoLinking)
-  of "nomain": result = contains(gGlobalOptions, optNoMain)
-  of "forcebuild", "f": result = contains(gGlobalOptions, optForceFullMake)
-  of "warnings", "w": result = contains(gOptions, optWarns)
-  of "hints": result = contains(gOptions, optHints)
-  of "threadanalysis": result = contains(gGlobalOptions, optThreadAnalysis)
-  of "stacktrace": result = contains(gOptions, optStackTrace)
-  of "linetrace": result = contains(gOptions, optLineTrace)
-  of "debugger": result = contains(gOptions, optEndb)
-  of "profiler": result = contains(gOptions, optProfiler)
-  of "memtracker": result = contains(gOptions, optMemTracker)
-  of "checks", "x": result = gOptions * ChecksOptions == ChecksOptions
+  of "debuginfo": result = contains(conf.globalOptions, optCDebug)
+  of "compileonly", "c": result = contains(conf.globalOptions, optCompileOnly)
+  of "nolinking": result = contains(conf.globalOptions, optNoLinking)
+  of "nomain": result = contains(conf.globalOptions, optNoMain)
+  of "forcebuild", "f": result = contains(conf.globalOptions, optForceFullMake)
+  of "warnings", "w": result = contains(conf.options, optWarns)
+  of "hints": result = contains(conf.options, optHints)
+  of "threadanalysis": result = contains(conf.globalOptions, optThreadAnalysis)
+  of "stacktrace": result = contains(conf.options, optStackTrace)
+  of "linetrace": result = contains(conf.options, optLineTrace)
+  of "debugger": result = contains(conf.options, optEndb)
+  of "profiler": result = contains(conf.options, optProfiler)
+  of "memtracker": result = contains(conf.options, optMemTracker)
+  of "checks", "x": result = conf.options * ChecksOptions == ChecksOptions
   of "floatchecks":
-    result = gOptions * {optNaNCheck, optInfCheck} == {optNaNCheck, optInfCheck}
-  of "infchecks": result = contains(gOptions, optInfCheck)
-  of "nanchecks": result = contains(gOptions, optNaNCheck)
-  of "objchecks": result = contains(gOptions, optObjCheck)
-  of "fieldchecks": result = contains(gOptions, optFieldCheck)
-  of "rangechecks": result = contains(gOptions, optRangeCheck)
-  of "boundchecks": result = contains(gOptions, optBoundsCheck)
-  of "overflowchecks": result = contains(gOptions, optOverflowCheck)
-  of "linedir": result = contains(gOptions, optLineDir)
-  of "assertions", "a": result = contains(gOptions, optAssert)
-  of "deadcodeelim": result = contains(gGlobalOptions, optDeadCodeElim)
-  of "run", "r": result = contains(gGlobalOptions, optRun)
-  of "symbolfiles": result = contains(gGlobalOptions, optSymbolFiles)
-  of "genscript": result = contains(gGlobalOptions, optGenScript)
-  of "threads": result = contains(gGlobalOptions, optThreads)
-  of "taintmode": result = contains(gGlobalOptions, optTaintMode)
-  of "tlsemulation": result = contains(gGlobalOptions, optTlsEmulation)
-  of "implicitstatic": result = contains(gOptions, optImplicitStatic)
-  of "patterns": result = contains(gOptions, optPatterns)
-  of "experimental": result = gExperimentalMode
-  of "excessivestacktrace": result = contains(gGlobalOptions, optExcessiveStackTrace)
-  else: invalidCmdLineOption(passCmd1, switch, info)
-
-proc processPath(path: string, info: TLineInfo,
-                 notRelativeToProj = false): string =
-  let p = if notRelativeToProj or os.isAbsolute(path) or
-              '$' in path:
+    result = conf.options * {optNaNCheck, optInfCheck} == {optNaNCheck, optInfCheck}
+  of "infchecks": result = contains(conf.options, optInfCheck)
+  of "nanchecks": result = contains(conf.options, optNaNCheck)
+  of "nilchecks": result = contains(conf.options, optNilCheck)
+  of "objchecks": result = contains(conf.options, optObjCheck)
+  of "fieldchecks": result = contains(conf.options, optFieldCheck)
+  of "rangechecks": result = contains(conf.options, optRangeCheck)
+  of "boundchecks": result = contains(conf.options, optBoundsCheck)
+  of "overflowchecks": result = contains(conf.options, optOverflowCheck)
+  of "movechecks": result = contains(conf.options, optMoveCheck)
+  of "linedir": result = contains(conf.options, optLineDir)
+  of "assertions", "a": result = contains(conf.options, optAssert)
+  of "run", "r": result = contains(conf.globalOptions, optRun)
+  of "symbolfiles": result = conf.symbolFiles != disabledSf
+  of "genscript": result = contains(conf.globalOptions, optGenScript)
+  of "threads": result = contains(conf.globalOptions, optThreads)
+  of "taintmode": result = contains(conf.globalOptions, optTaintMode)
+  of "tlsemulation": result = contains(conf.globalOptions, optTlsEmulation)
+  of "implicitstatic": result = contains(conf.options, optImplicitStatic)
+  of "patterns": result = contains(conf.options, optPatterns)
+  of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace)
+  of "nilseqs": result = contains(conf.options, optNilSeqs)
+  else: invalidCmdLineOption(conf, passCmd1, switch, info)
+
+proc processPath(conf: ConfigRef; path: string, info: TLineInfo,
+                 notRelativeToProj = false): AbsoluteDir =
+  let p = if os.isAbsolute(path) or '$' in path:
             path
+          elif notRelativeToProj:
+            getCurrentDir() / path
           else:
-            options.gProjectPath / path
+            conf.projectPath.string / path
   try:
-    result = pathSubs(p, info.toFullPath().splitFile().dir)
+    result = AbsoluteDir pathSubs(conf, p, toFullPath(conf, info).splitFile().dir)
   except ValueError:
-    localError(info, "invalid path: " & p)
-    result = p
+    localError(conf, info, "invalid path: " & p)
+    result = AbsoluteDir p
 
-proc processCfgPath(path: string, info: TLineInfo): string =
-  let path = if path[0] == '"': strutils.unescape(path) else: path
-  let basedir = info.toFullPath().splitFile().dir
+proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): AbsoluteDir =
+  let path = if path.len > 0 and path[0] == '"': strutils.unescape(path)
+             else: path
+  let basedir = toFullPath(conf, info).splitFile().dir
   let p = if os.isAbsolute(path) or '$' in path:
             path
           else:
             basedir / path
   try:
-    result = pathSubs(p, basedir)
+    result = AbsoluteDir pathSubs(conf, p, basedir)
   except ValueError:
-    localError(info, "invalid path: " & p)
-    result = p
+    localError(conf, info, "invalid path: " & p)
+    result = AbsoluteDir p
 
-proc trackDirty(arg: string, info: TLineInfo) =
+const
+  errInvalidNumber = "$1 is not a valid number"
+
+proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) =
   var a = arg.split(',')
-  if a.len != 4: localError(info, errTokenExpected,
-                            "DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN")
+  if a.len != 4: localError(conf, info,
+                            "DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN expected")
   var line, column: int
   if parseUtils.parseInt(a[2], line) <= 0:
-    localError(info, errInvalidNumber, a[1])
+    localError(conf, info, errInvalidNumber % a[1])
   if parseUtils.parseInt(a[3], column) <= 0:
-    localError(info, errInvalidNumber, a[2])
+    localError(conf, info, errInvalidNumber % a[2])
 
-  let dirtyOriginalIdx = a[1].fileInfoIdx
-  if dirtyOriginalIdx >= 0:
-    msgs.setDirtyFile(dirtyOriginalIdx, a[0])
+  let dirtyOriginalIdx = fileInfoIdx(conf, AbsoluteFile a[1])
+  if dirtyOriginalIdx.int32 >= 0:
+    msgs.setDirtyFile(conf, dirtyOriginalIdx, AbsoluteFile a[0])
 
-  gTrackPos = newLineInfo(dirtyOriginalIdx, line, column)
+  conf.m.trackPos = newLineInfo(dirtyOriginalIdx, line, column)
 
-proc track(arg: string, info: TLineInfo) =
+proc track(conf: ConfigRef; arg: string, info: TLineInfo) =
   var a = arg.split(',')
-  if a.len != 3: localError(info, errTokenExpected, "FILE,LINE,COLUMN")
+  if a.len != 3: localError(conf, info, "FILE,LINE,COLUMN expected")
   var line, column: int
   if parseUtils.parseInt(a[1], line) <= 0:
-    localError(info, errInvalidNumber, a[1])
+    localError(conf, info, errInvalidNumber % a[1])
   if parseUtils.parseInt(a[2], column) <= 0:
-    localError(info, errInvalidNumber, a[2])
-  gTrackPos = newLineInfo(a[0], line, column)
+    localError(conf, info, errInvalidNumber % a[2])
+  conf.m.trackPos = newLineInfo(conf, AbsoluteFile a[0], line, column)
 
-proc dynlibOverride(switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
+proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
   if pass in {passCmd2, passPP}:
-    expectArg(switch, arg, pass, info)
-    options.inclDynlibOverride(arg)
+    expectArg(conf, switch, arg, pass, info)
+    options.inclDynlibOverride(conf, arg)
 
-proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
-                   config: ConfigRef = nil) =
+proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
+                    conf: ConfigRef) =
   var
-    theOS: TSystemOS
-    cpu: TSystemCPU
     key, val: string
   case switch.normalize
   of "path", "p":
-    expectArg(switch, arg, pass, info)
-    addPath(if pass == passPP: processCfgPath(arg, info) else: processPath(arg, info), info)
+    expectArg(conf, switch, arg, pass, info)
+    addPath(conf, if pass == passPP: processCfgPath(conf, arg, info)
+                  else: processPath(conf, arg, info), info)
   of "nimblepath", "babelpath":
     # keep the old name for compat
-    if pass in {passCmd2, passPP} and not options.gNoNimblePath:
-      expectArg(switch, arg, pass, info)
-      let path = processPath(arg, info, notRelativeToProj=true)
-      nimblePath(path, info)
+    if pass in {passCmd2, passPP} and optNoNimblePath notin conf.globalOptions:
+      expectArg(conf, switch, arg, pass, info)
+      var path = processPath(conf, arg, info, notRelativeToProj=true)
+      let nimbleDir = AbsoluteDir getEnv("NIMBLE_DIR")
+      if not nimbleDir.isEmpty and pass == passPP:
+        path = nimbleDir / RelativeDir"pkgs"
+      nimblePath(conf, path, info)
   of "nonimblepath", "nobabelpath":
-    expectNoArg(switch, arg, pass, info)
-    disableNimblePath()
+    expectNoArg(conf, switch, arg, pass, info)
+    disableNimblePath(conf)
   of "excludepath":
-    expectArg(switch, arg, pass, info)
-    let path = processPath(arg, info)
-
-    options.searchPaths.keepItIf( cmpPaths(it, path) != 0 )
-    options.lazyPaths.keepItIf( cmpPaths(it, path) != 0 )
-
-    if (len(path) > 0) and (path[len(path) - 1] == DirSep):
-      let strippedPath = path[0 .. (len(path) - 2)]
-      options.searchPaths.keepItIf( cmpPaths(it, strippedPath) != 0 )
-      options.lazyPaths.keepItIf( cmpPaths(it, strippedPath) != 0 )
+    expectArg(conf, switch, arg, pass, info)
+    let path = processPath(conf, arg, info)
+    conf.searchPaths.keepItIf(it != path)
+    conf.lazyPaths.keepItIf(it != path)
   of "nimcache":
-    expectArg(switch, arg, pass, info)
-    options.nimcacheDir = processPath(arg, info, true)
+    expectArg(conf, switch, arg, pass, info)
+    conf.nimcacheDir = processPath(conf, arg, info, true)
   of "out", "o":
-    expectArg(switch, arg, pass, info)
-    options.outFile = arg
+    expectArg(conf, switch, arg, pass, info)
+    conf.outFile = AbsoluteFile arg
   of "docseesrcurl":
-    expectArg(switch, arg, pass, info)
-    options.docSeeSrcUrl = arg
+    expectArg(conf, switch, arg, pass, info)
+    conf.docSeeSrcUrl = arg
   of "mainmodule", "m":
     discard "allow for backwards compatibility, but don't do anything"
   of "define", "d":
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
     if {':', '='} in arg:
-      splitSwitch(arg, key, val, pass, info)
-      defineSymbol(key, val)
+      splitSwitch(conf, arg, key, val, pass, info)
+      defineSymbol(conf.symbols, key, val)
     else:
-      defineSymbol(arg)
+      defineSymbol(conf.symbols, arg)
   of "undef", "u":
-    expectArg(switch, arg, pass, info)
-    undefSymbol(arg)
+    expectArg(conf, switch, arg, pass, info)
+    undefSymbol(conf.symbols, arg)
   of "symbol":
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
     # deprecated, do nothing
   of "compile":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: processCompile(arg)
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}: processCompile(conf, arg)
   of "link":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: addExternalFileToLink(arg)
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}:
+      addExternalFileToLink(conf, AbsoluteFile arg)
   of "debuginfo":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optCDebug)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optCDebug)
   of "embedsrc":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optEmbedOrigSrc)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optEmbedOrigSrc)
   of "compileonly", "c":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optCompileOnly)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optCompileOnly)
   of "nolinking":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optNoLinking)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optNoLinking)
   of "nomain":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optNoMain)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optNoMain)
   of "forcebuild", "f":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optForceFullMake)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optForceFullMake)
   of "project":
-    expectNoArg(switch, arg, pass, info)
-    gWholeProject = true
+    expectNoArg(conf, switch, arg, pass, info)
+    incl conf.globalOptions, optWholeProject
   of "gc":
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
     case arg.normalize
     of "boehm":
-      gSelectedGC = gcBoehm
-      defineSymbol("boehmgc")
+      conf.selectedGC = gcBoehm
+      defineSymbol(conf.symbols, "boehmgc")
     of "refc":
-      gSelectedGC = gcRefc
+      conf.selectedGC = gcRefc
     of "v2":
-      gSelectedGC = gcV2
+      conf.selectedGC = gcV2
     of "markandsweep":
-      gSelectedGC = gcMarkAndSweep
-      defineSymbol("gcmarkandsweep")
-    of "generational":
-      gSelectedGC = gcGenerational
-      defineSymbol("gcgenerational")
+      conf.selectedGC = gcMarkAndSweep
+      defineSymbol(conf.symbols, "gcmarkandsweep")
+    of "destructors":
+      conf.selectedGC = gcDestructors
+      defineSymbol(conf.symbols, "gcdestructors")
     of "go":
-      gSelectedGC = gcGo
-      defineSymbol("gogc")
+      conf.selectedGC = gcGo
+      defineSymbol(conf.symbols, "gogc")
     of "none":
-      gSelectedGC = gcNone
-      defineSymbol("nogc")
-    of "stack":
-      gSelectedGC= gcStack
-      defineSymbol("gcstack")
-    else: localError(info, errNoneBoehmRefcExpectedButXFound, arg)
+      conf.selectedGC = gcNone
+      defineSymbol(conf.symbols, "nogc")
+    of "stack", "regions":
+      conf.selectedGC= gcRegions
+      defineSymbol(conf.symbols, "gcregions")
+    else: localError(conf, info, errNoneBoehmRefcExpectedButXFound % arg)
   of "warnings", "w":
-    if processOnOffSwitchOrList({optWarns}, arg, pass, info): listWarnings()
-  of "warning": processSpecificNote(arg, wWarning, pass, info, switch)
-  of "hint": processSpecificNote(arg, wHint, pass, info, switch)
+    if processOnOffSwitchOrList(conf, {optWarns}, arg, pass, info): listWarnings(conf)
+  of "warning": processSpecificNote(arg, wWarning, pass, info, switch, conf)
+  of "hint": processSpecificNote(arg, wHint, pass, info, switch, conf)
   of "hints":
-    if processOnOffSwitchOrList({optHints}, arg, pass, info): listHints()
-  of "threadanalysis": processOnOffSwitchG({optThreadAnalysis}, arg, pass, info)
-  of "stacktrace": processOnOffSwitch({optStackTrace}, arg, pass, info)
-  of "excessivestacktrace": processOnOffSwitchG({optExcessiveStackTrace}, arg, pass, info)
-  of "linetrace": processOnOffSwitch({optLineTrace}, arg, pass, info)
+    if processOnOffSwitchOrList(conf, {optHints}, arg, pass, info): listHints(conf)
+  of "threadanalysis": processOnOffSwitchG(conf, {optThreadAnalysis}, arg, pass, info)
+  of "stacktrace": processOnOffSwitch(conf, {optStackTrace}, arg, pass, info)
+  of "excessivestacktrace": processOnOffSwitchG(conf, {optExcessiveStackTrace}, arg, pass, info)
+  of "linetrace": processOnOffSwitch(conf, {optLineTrace}, arg, pass, info)
   of "debugger":
     case arg.normalize
     of "on", "endb":
-      gOptions.incl optEndb
-      defineSymbol("endb")
+      conf.options.incl optEndb
+      defineSymbol(conf.symbols, "endb")
     of "off":
-      gOptions.excl optEndb
-      undefSymbol("endb")
+      conf.options.excl optEndb
+      undefSymbol(conf.symbols, "endb")
     of "native", "gdb":
-      incl(gGlobalOptions, optCDebug)
-      gOptions = gOptions + {optLineDir} - {optEndb}
-      undefSymbol("endb")
+      incl(conf.globalOptions, optCDebug)
+      conf.options = conf.options + {optLineDir} - {optEndb}
+      #defineSymbol(conf.symbols, "nimTypeNames") # type names are used in gdb pretty printing
+      undefSymbol(conf.symbols, "endb")
     else:
-      localError(info, "expected endb|gdb but found " & arg)
+      localError(conf, info, "expected endb|gdb but found " & arg)
   of "profiler":
-    processOnOffSwitch({optProfiler}, arg, pass, info)
-    if optProfiler in gOptions: defineSymbol("profiler")
-    else: undefSymbol("profiler")
+    processOnOffSwitch(conf, {optProfiler}, arg, pass, info)
+    if optProfiler in conf.options: defineSymbol(conf.symbols, "profiler")
+    else: undefSymbol(conf.symbols, "profiler")
   of "memtracker":
-    processOnOffSwitch({optMemTracker}, arg, pass, info)
-    if optMemTracker in gOptions: defineSymbol("memtracker")
-    else: undefSymbol("memtracker")
-  of "checks", "x": processOnOffSwitch(ChecksOptions, arg, pass, info)
+    processOnOffSwitch(conf, {optMemTracker}, arg, pass, info)
+    if optMemTracker in conf.options: defineSymbol(conf.symbols, "memtracker")
+    else: undefSymbol(conf.symbols, "memtracker")
+  of "hotcodereloading":
+    processOnOffSwitch(conf, {optHotCodeReloading}, arg, pass, info)
+    if optHotCodeReloading in conf.options: defineSymbol(conf.symbols, "hotcodereloading")
+    else: undefSymbol(conf.symbols, "hotcodereloading")
+  of "oldnewlines":
+    case arg.normalize
+    of "on":
+      conf.oldNewlines = true
+      defineSymbol(conf.symbols, "nimOldNewlines")
+    of "off":
+      conf.oldNewlines = false
+      undefSymbol(conf.symbols, "nimOldNewlines")
+    else:
+      localError(conf, info, errOnOrOffExpectedButXFound % arg)
+  of "laxstrings": processOnOffSwitch(conf, {optLaxStrings}, arg, pass, info)
+  of "nilseqs": processOnOffSwitch(conf, {optNilSeqs}, arg, pass, info)
+  of "checks", "x": processOnOffSwitch(conf, ChecksOptions, arg, pass, info)
   of "floatchecks":
-    processOnOffSwitch({optNaNCheck, optInfCheck}, arg, pass, info)
-  of "infchecks": processOnOffSwitch({optInfCheck}, arg, pass, info)
-  of "nanchecks": processOnOffSwitch({optNaNCheck}, arg, pass, info)
-  of "objchecks": processOnOffSwitch({optObjCheck}, arg, pass, info)
-  of "fieldchecks": processOnOffSwitch({optFieldCheck}, arg, pass, info)
-  of "rangechecks": processOnOffSwitch({optRangeCheck}, arg, pass, info)
-  of "boundchecks": processOnOffSwitch({optBoundsCheck}, arg, pass, info)
-  of "overflowchecks": processOnOffSwitch({optOverflowCheck}, arg, pass, info)
-  of "linedir": processOnOffSwitch({optLineDir}, arg, pass, info)
-  of "assertions", "a": processOnOffSwitch({optAssert}, arg, pass, info)
-  of "deadcodeelim": processOnOffSwitchG({optDeadCodeElim}, arg, pass, info)
-  of "reportconceptfailures":
-    processOnOffSwitchG({optReportConceptFailures}, arg, pass, info)
+    processOnOffSwitch(conf, {optNaNCheck, optInfCheck}, arg, pass, info)
+  of "infchecks": processOnOffSwitch(conf, {optInfCheck}, arg, pass, info)
+  of "nanchecks": processOnOffSwitch(conf, {optNaNCheck}, arg, pass, info)
+  of "nilchecks": processOnOffSwitch(conf, {optNilCheck}, arg, pass, info)
+  of "objchecks": processOnOffSwitch(conf, {optObjCheck}, arg, pass, info)
+  of "fieldchecks": processOnOffSwitch(conf, {optFieldCheck}, arg, pass, info)
+  of "rangechecks": processOnOffSwitch(conf, {optRangeCheck}, arg, pass, info)
+  of "boundchecks": processOnOffSwitch(conf, {optBoundsCheck}, arg, pass, info)
+  of "overflowchecks": processOnOffSwitch(conf, {optOverflowCheck}, arg, pass, info)
+  of "movechecks": processOnOffSwitch(conf, {optMoveCheck}, arg, pass, info)
+  of "linedir": processOnOffSwitch(conf, {optLineDir}, arg, pass, info)
+  of "assertions", "a": processOnOffSwitch(conf, {optAssert}, arg, pass, info)
+  of "deadcodeelim": discard # deprecated, dead code elim always on
   of "threads":
-    processOnOffSwitchG({optThreads}, arg, pass, info)
-    #if optThreads in gGlobalOptions: incl(gNotes, warnGcUnsafe)
-  of "tlsemulation": processOnOffSwitchG({optTlsEmulation}, arg, pass, info)
-  of "taintmode": processOnOffSwitchG({optTaintMode}, arg, pass, info)
+    processOnOffSwitchG(conf, {optThreads}, arg, pass, info)
+    #if optThreads in conf.globalOptions: incl(conf.notes, warnGcUnsafe)
+  of "tlsemulation": processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info)
+  of "taintmode": processOnOffSwitchG(conf, {optTaintMode}, arg, pass, info)
   of "implicitstatic":
-    processOnOffSwitch({optImplicitStatic}, arg, pass, info)
+    processOnOffSwitch(conf, {optImplicitStatic}, arg, pass, info)
   of "patterns":
-    processOnOffSwitch({optPatterns}, arg, pass, info)
+    processOnOffSwitch(conf, {optPatterns}, arg, pass, info)
   of "opt":
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
     case arg.normalize
     of "speed":
-      incl(gOptions, optOptimizeSpeed)
-      excl(gOptions, optOptimizeSize)
+      incl(conf.options, optOptimizeSpeed)
+      excl(conf.options, optOptimizeSize)
     of "size":
-      excl(gOptions, optOptimizeSpeed)
-      incl(gOptions, optOptimizeSize)
+      excl(conf.options, optOptimizeSpeed)
+      incl(conf.options, optOptimizeSize)
     of "none":
-      excl(gOptions, optOptimizeSpeed)
-      excl(gOptions, optOptimizeSize)
-    else: localError(info, errNoneSpeedOrSizeExpectedButXFound, arg)
+      excl(conf.options, optOptimizeSpeed)
+      excl(conf.options, optOptimizeSize)
+    else: localError(conf, info, errNoneSpeedOrSizeExpectedButXFound % arg)
   of "app":
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
     case arg.normalize
     of "gui":
-      incl(gGlobalOptions, optGenGuiApp)
-      defineSymbol("executable")
-      defineSymbol("guiapp")
+      incl(conf.globalOptions, optGenGuiApp)
+      defineSymbol(conf.symbols, "executable")
+      defineSymbol(conf.symbols, "guiapp")
     of "console":
-      excl(gGlobalOptions, optGenGuiApp)
-      defineSymbol("executable")
-      defineSymbol("consoleapp")
+      excl(conf.globalOptions, optGenGuiApp)
+      defineSymbol(conf.symbols, "executable")
+      defineSymbol(conf.symbols, "consoleapp")
     of "lib":
-      incl(gGlobalOptions, optGenDynLib)
-      excl(gGlobalOptions, optGenGuiApp)
-      defineSymbol("library")
-      defineSymbol("dll")
+      incl(conf.globalOptions, optGenDynLib)
+      excl(conf.globalOptions, optGenGuiApp)
+      defineSymbol(conf.symbols, "library")
+      defineSymbol(conf.symbols, "dll")
     of "staticlib":
-      incl(gGlobalOptions, optGenStaticLib)
-      excl(gGlobalOptions, optGenGuiApp)
-      defineSymbol("library")
-      defineSymbol("staticlib")
-    else: localError(info, errGuiConsoleOrLibExpectedButXFound, arg)
+      incl(conf.globalOptions, optGenStaticLib)
+      excl(conf.globalOptions, optGenGuiApp)
+      defineSymbol(conf.symbols, "library")
+      defineSymbol(conf.symbols, "staticlib")
+    else: localError(conf, info, errGuiConsoleOrLibExpectedButXFound % arg)
   of "passc", "t":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: extccomp.addCompileOptionCmd(arg)
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}: extccomp.addCompileOptionCmd(conf, arg)
   of "passl", "l":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: extccomp.addLinkOptionCmd(arg)
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}: extccomp.addLinkOptionCmd(conf, arg)
   of "cincludes":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: cIncludes.add arg.processPath(info)
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}: conf.cIncludes.add processPath(conf, arg, info)
   of "clibdir":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: cLibs.add arg.processPath(info)
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}: conf.cLibs.add processPath(conf, arg, info)
   of "clib":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: cLinkedLibs.add arg.processPath(info)
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}:
+      conf.cLinkedLibs.add processPath(conf, arg, info).string
   of "header":
-    if config != nil: config.headerFile = arg
-    incl(gGlobalOptions, optGenIndex)
+    if conf != nil: conf.headerFile = arg
+    incl(conf.globalOptions, optGenIndex)
   of "index":
-    processOnOffSwitchG({optGenIndex}, arg, pass, info)
+    processOnOffSwitchG(conf, {optGenIndex}, arg, pass, info)
   of "import":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: implicitImports.add arg
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}: conf.implicitImports.add arg
   of "include":
-    expectArg(switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: implicitIncludes.add arg
+    expectArg(conf, switch, arg, pass, info)
+    if pass in {passCmd2, passPP}: conf.implicitIncludes.add arg
   of "listcmd":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optListCmd)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optListCmd)
   of "genmapping":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optGenMapping)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optGenMapping)
   of "os":
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
     if pass in {passCmd1, passPP}:
-      theOS = platform.nameToOS(arg)
-      if theOS == osNone: localError(info, errUnknownOS, arg)
-      elif theOS != platform.hostOS:
-        setTarget(theOS, targetCPU)
+      let theOS = platform.nameToOS(arg)
+      if theOS == osNone: localError(conf, info, "unknown OS: '$1'" % arg)
+      elif theOS != conf.target.hostOS:
+        setTarget(conf.target, theOS, conf.target.targetCPU)
   of "cpu":
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
     if pass in {passCmd1, passPP}:
-      cpu = platform.nameToCPU(arg)
-      if cpu == cpuNone: localError(info, errUnknownCPU, arg)
-      elif cpu != platform.hostCPU:
-        setTarget(targetOS, cpu)
+      let cpu = platform.nameToCPU(arg)
+      if cpu == cpuNone: localError(conf, info, "unknown CPU: '$1'" % arg)
+      elif cpu != conf.target.hostCPU:
+        setTarget(conf.target, conf.target.targetOS, cpu)
   of "run", "r":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optRun)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optRun)
+  of "errormax":
+    expectArg(conf, switch, arg, pass, info)
+    # Note: `nim check` (etc) can overwrite this.
+    # `0` is meaningless, give it a useful meaning as in clang's -ferror-limit
+    # If user doesn't set this flag and the code doesn't either, it'd
+    # have the same effect as errorMax = 1
+    let ret = parseInt(arg)
+    conf.errorMax = if ret == 0: high(int) else: ret
   of "verbosity":
-    expectArg(switch, arg, pass, info)
-    gVerbosity = parseInt(arg)
-    gNotes = NotesVerbosity[gVerbosity]
-    incl(gNotes, enableNotes)
-    excl(gNotes, disableNotes)
-    gMainPackageNotes = gNotes
+    expectArg(conf, switch, arg, pass, info)
+    let verbosity = parseInt(arg)
+    if verbosity notin {0..3}:
+      localError(conf, info, "invalid verbosity level: '$1'" % arg)
+    conf.verbosity = verbosity
+    conf.notes = NotesVerbosity[conf.verbosity]
+    incl(conf.notes, conf.enableNotes)
+    excl(conf.notes, conf.disableNotes)
+    conf.mainPackageNotes = conf.notes
   of "parallelbuild":
-    expectArg(switch, arg, pass, info)
-    gNumberOfProcessors = parseInt(arg)
+    expectArg(conf, switch, arg, pass, info)
+    conf.numberOfProcessors = parseInt(arg)
   of "version", "v":
-    expectNoArg(switch, arg, pass, info)
-    writeVersionInfo(pass)
+    expectNoArg(conf, switch, arg, pass, info)
+    writeVersionInfo(conf, pass)
   of "advanced":
-    expectNoArg(switch, arg, pass, info)
-    writeAdvancedUsage(pass)
+    expectNoArg(conf, switch, arg, pass, info)
+    writeAdvancedUsage(conf, pass)
+  of "fullhelp":
+    expectNoArg(conf, switch, arg, pass, info)
+    writeFullhelp(conf, pass)
   of "help", "h":
-    expectNoArg(switch, arg, pass, info)
-    helpOnError(pass)
-  of "symbolfiles":
-    processOnOffSwitchG({optSymbolFiles}, arg, pass, info)
+    expectNoArg(conf, switch, arg, pass, info)
+    helpOnError(conf, pass)
+  of "symbolfiles": discard "ignore for backwards compat"
+  of "incremental":
+    when not defined(nimIncremental):
+      localError(conf, info, "the compiler was not built with " &
+        "incremental compilation features; bootstrap with " &
+        "-d:nimIncremental to enable")
+    case arg.normalize
+    of "on": conf.symbolFiles = v2Sf
+    of "off": conf.symbolFiles = disabledSf
+    of "writeonly": conf.symbolFiles = writeOnlySf
+    of "readonly": conf.symbolFiles = readOnlySf
+    of "v2": conf.symbolFiles = v2Sf
+    else: localError(conf, info, "invalid option for --incremental: " & arg)
   of "skipcfg":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optSkipConfigFile)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optSkipSystemConfigFile)
   of "skipprojcfg":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optSkipProjConfigFile)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optSkipProjConfigFile)
   of "skipusercfg":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optSkipUserConfigFile)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optSkipUserConfigFile)
   of "skipparentcfg":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optSkipParentConfigFiles)
-  of "genscript":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optGenScript)
-  of "colors": processOnOffSwitchG({optUseColors}, arg, pass, info)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optSkipParentConfigFiles)
+  of "genscript", "gendeps":
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optGenScript)
+    incl(conf.globalOptions, optCompileOnly)
+  of "colors": processOnOffSwitchG(conf, {optUseColors}, arg, pass, info)
   of "lib":
-    expectArg(switch, arg, pass, info)
-    libpath = processPath(arg, info, notRelativeToProj=true)
+    expectArg(conf, switch, arg, pass, info)
+    conf.libpath = processPath(conf, arg, info, notRelativeToProj=true)
   of "putenv":
-    expectArg(switch, arg, pass, info)
-    splitSwitch(arg, key, val, pass, info)
+    expectArg(conf, switch, arg, pass, info)
+    splitSwitch(conf, arg, key, val, pass, info)
     os.putEnv(key, val)
   of "cc":
-    expectArg(switch, arg, pass, info)
-    setCC(arg)
+    expectArg(conf, switch, arg, pass, info)
+    setCC(conf, arg, info)
   of "track":
-    expectArg(switch, arg, pass, info)
-    track(arg, info)
+    expectArg(conf, switch, arg, pass, info)
+    track(conf, arg, info)
   of "trackdirty":
-    expectArg(switch, arg, pass, info)
-    trackDirty(arg, info)
+    expectArg(conf, switch, arg, pass, info)
+    trackDirty(conf, arg, info)
   of "suggest":
-    expectNoArg(switch, arg, pass, info)
-    gIdeCmd = ideSug
+    expectNoArg(conf, switch, arg, pass, info)
+    conf.ideCmd = ideSug
   of "def":
-    expectNoArg(switch, arg, pass, info)
-    gIdeCmd = ideDef
+    expectNoArg(conf, switch, arg, pass, info)
+    conf.ideCmd = ideDef
   of "eval":
-    expectArg(switch, arg, pass, info)
-    gEvalExpr = arg
+    expectArg(conf, switch, arg, pass, info)
+    conf.evalExpr = arg
   of "context":
-    expectNoArg(switch, arg, pass, info)
-    gIdeCmd = ideCon
+    expectNoArg(conf, switch, arg, pass, info)
+    conf.ideCmd = ideCon
   of "usages":
-    expectNoArg(switch, arg, pass, info)
-    gIdeCmd = ideUse
+    expectNoArg(conf, switch, arg, pass, info)
+    conf.ideCmd = ideUse
   of "stdout":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optStdout)
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optStdout)
   of "listfullpaths":
-    expectNoArg(switch, arg, pass, info)
-    gListFullPaths = true
+    expectNoArg(conf, switch, arg, pass, info)
+    incl conf.globalOptions, optListFullPaths
   of "dynliboverride":
-    dynlibOverride(switch, arg, pass, info)
+    dynlibOverride(conf, switch, arg, pass, info)
+  of "dynliboverrideall":
+    expectNoArg(conf, switch, arg, pass, info)
+    incl conf.globalOptions, optDynlibOverrideAll
   of "cs":
     # only supported for compatibility. Does nothing.
-    expectArg(switch, arg, pass, info)
+    expectArg(conf, switch, arg, pass, info)
   of "experimental":
-    expectNoArg(switch, arg, pass, info)
-    gExperimentalMode = true
+    if arg.len == 0:
+      conf.features.incl oldExperimentalFeatures
+    else:
+      try:
+        conf.features.incl parseEnum[Feature](arg)
+      except ValueError:
+        localError(conf, info, "unknown experimental feature")
   of "nocppexceptions":
-    expectNoArg(switch, arg, pass, info)
-    incl(gGlobalOptions, optNoCppExceptions)
-    defineSymbol("noCppExceptions")
+    expectNoArg(conf, switch, arg, pass, info)
+    incl(conf.globalOptions, optNoCppExceptions)
+    defineSymbol(conf.symbols, "noCppExceptions")
   of "cppdefine":
-    expectArg(switch, arg, pass, info)
-    if config != nil:
-      config.cppDefine(arg)
+    expectArg(conf, switch, arg, pass, info)
+    if conf != nil:
+      conf.cppDefine(arg)
+  of "newruntime":
+    expectNoArg(conf, switch, arg, pass, info)
+    doAssert(conf != nil)
+    incl(conf.features, destructor)
+    defineSymbol(conf.symbols, "nimNewRuntime")
+  of "stylecheck":
+    case arg.normalize
+    of "off": conf.globalOptions = conf.globalOptions - {optStyleHint, optStyleError}
+    of "hint": conf.globalOptions = conf.globalOptions + {optStyleHint}
+    of "error": conf.globalOptions = conf.globalOptions + {optStyleError}
+    else: localError(conf, info, errOffHintsError % arg)
+  of "showallmismatches":
+    processOnOffSwitchG(conf, {optShowAllMismatches}, arg, pass, info)
+  of "cppcompiletonamespace":
+    if arg.len > 0:
+      conf.cppCustomNamespace = arg
+    else:
+      conf.cppCustomNamespace = "Nim"
+    defineSymbol(conf.symbols, "cppCompileToNamespace", conf.cppCustomNamespace)
   else:
-    if strutils.find(switch, '.') >= 0: options.setConfigVar(switch, arg)
-    else: invalidCmdLineOption(pass, switch, info)
+    if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg)
+    else: invalidCmdLineOption(conf, pass, switch, info)
 
-proc processCommand(switch: string, pass: TCmdLinePass) =
-  var cmd, arg: string
-  splitSwitch(switch, cmd, arg, pass, gCmdLineInfo)
-  processSwitch(cmd, arg, pass, gCmdLineInfo)
+template gCmdLineInfo*(): untyped = newLineInfo(config, AbsoluteFile"command line", 1, 1)
 
+proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) =
+  var cmd, arg: string
+  splitSwitch(config, switch, cmd, arg, pass, gCmdLineInfo)
+  processSwitch(cmd, arg, pass, gCmdLineInfo, config)
 
-var
-  arguments* = ""
-    # the arguments to be passed to the program that
-    # should be run
 
-proc processSwitch*(pass: TCmdLinePass; p: OptParser) =
+proc processSwitch*(pass: TCmdLinePass; p: OptParser; config: ConfigRef) =
   # hint[X]:off is parsed as (p.key = "hint[X]", p.val = "off")
-  # we fix this here
+  # we transform it to (key = hint, val = [X]:off)
   var bracketLe = strutils.find(p.key, '[')
   if bracketLe >= 0:
     var key = substr(p.key, 0, bracketLe - 1)
-    var val = substr(p.key, bracketLe + 1) & ':' & p.val
-    processSwitch(key, val, pass, gCmdLineInfo)
+    var val = substr(p.key, bracketLe) & ':' & p.val
+    processSwitch(key, val, pass, gCmdLineInfo, config)
   else:
-    processSwitch(p.key, p.val, pass, gCmdLineInfo)
+    processSwitch(p.key, p.val, pass, gCmdLineInfo, config)
 
 proc processArgument*(pass: TCmdLinePass; p: OptParser;
-                      argsCount: var int): bool =
+                      argsCount: var int; config: ConfigRef): bool =
   if argsCount == 0:
     # nim filename.nims  is the same as "nim e filename.nims":
     if p.key.endswith(".nims"):
-      options.command = "e"
-      options.gProjectName = unixToNativePath(p.key)
-      arguments = cmdLineRest(p)
+      config.command = "e"
+      incl(config.globalOptions, optWasNimscript)
+      config.projectName = unixToNativePath(p.key)
+      config.arguments = cmdLineRest(p)
       result = true
     elif pass != passCmd2:
-      options.command = p.key
+      config.command = p.key
   else:
-    if pass == passCmd1: options.commandArgs.add p.key
+    if pass == passCmd1: config.commandArgs.add p.key
     if argsCount == 1:
       # support UNIX style filenames everywhere for portable build scripts:
-      options.gProjectName = unixToNativePath(p.key)
-      arguments = cmdLineRest(p)
+      config.projectName = unixToNativePath(p.key)
+      config.arguments = cmdLineRest(p)
       result = true
   inc argsCount
diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim
index 4303fd6c8..9a4c1701c 100644
--- a/compiler/condsyms.nim
+++ b/compiler/condsyms.nim
@@ -12,75 +12,33 @@
 import
   strtabs, platform, strutils, idents
 
-# We need to use a StringTableRef here as defined symbols are always guaranteed
-# to be style insensitive. Otherwise hell would break lose.
-var gSymbols: StringTableRef
+from options import Feature
+from lineinfos import HintsToStr, WarningsToStr
 
 const
   catNone = "false"
 
-proc defineSymbol*(symbol: string, value: string = "true") =
-  gSymbols[symbol] = value
+proc defineSymbol*(symbols: StringTableRef; symbol: string, value: string = "true") =
+  symbols[symbol] = value
 
-proc undefSymbol*(symbol: string) =
-  gSymbols[symbol] = catNone
+proc undefSymbol*(symbols: StringTableRef; symbol: string) =
+  symbols[symbol] = catNone
 
-proc isDefined*(symbol: string): bool =
-  if gSymbols.hasKey(symbol):
-    result = gSymbols[symbol] != catNone
-  elif cmpIgnoreStyle(symbol, CPU[targetCPU].name) == 0:
-    result = true
-  elif cmpIgnoreStyle(symbol, platform.OS[targetOS].name) == 0:
-    result = true
-  else:
-    case symbol.normalize
-    of "x86": result = targetCPU == cpuI386
-    of "itanium": result = targetCPU == cpuIa64
-    of "x8664": result = targetCPU == cpuAmd64
-    of "posix", "unix":
-      result = targetOS in {osLinux, osMorphos, osSkyos, osIrix, osPalmos,
-                            osQnx, osAtari, osAix,
-                            osHaiku, osVxWorks, osSolaris, osNetbsd,
-                            osFreebsd, osOpenbsd, osDragonfly, osMacosx}
-    of "bsd":
-      result = targetOS in {osNetbsd, osFreebsd, osOpenbsd, osDragonfly}
-    of "emulatedthreadvars":
-      result = platform.OS[targetOS].props.contains(ospLacksThreadVars)
-    of "msdos": result = targetOS == osDos
-    of "mswindows", "win32": result = targetOS == osWindows
-    of "macintosh": result = targetOS in {osMacos, osMacosx}
-    of "sunos": result = targetOS == osSolaris
-    of "littleendian": result = CPU[targetCPU].endian == platform.littleEndian
-    of "bigendian": result = CPU[targetCPU].endian == platform.bigEndian
-    of "cpu8": result = CPU[targetCPU].bit == 8
-    of "cpu16": result = CPU[targetCPU].bit == 16
-    of "cpu32": result = CPU[targetCPU].bit == 32
-    of "cpu64": result = CPU[targetCPU].bit == 64
-    of "nimrawsetjmp":
-      result = targetOS in {osSolaris, osNetbsd, osFreebsd, osOpenbsd,
-                            osDragonfly, osMacosx}
-    else: discard
+#proc lookupSymbol*(symbols: StringTableRef; symbol: string): string =
+#  result = if isDefined(symbol): gSymbols[symbol] else: nil
 
-proc isDefined*(symbol: PIdent): bool = isDefined(symbol.s)
-
-proc lookupSymbol*(symbol: string): string =
-  result = if isDefined(symbol): gSymbols[symbol] else: nil
-
-proc lookupSymbol*(symbol: PIdent): string = lookupSymbol(symbol.s)
-
-iterator definedSymbolNames*: string =
-  for key, val in pairs(gSymbols):
+iterator definedSymbolNames*(symbols: StringTableRef): string =
+  for key, val in pairs(symbols):
     if val != catNone: yield key
 
-proc countDefinedSymbols*(): int =
+proc countDefinedSymbols*(symbols: StringTableRef): int =
   result = 0
-  for key, val in pairs(gSymbols):
+  for key, val in pairs(symbols):
     if val != catNone: inc(result)
 
-proc initDefines*() =
-  gSymbols = newStringTable(modeStyleInsensitive)
-  defineSymbol("nimrod") # 'nimrod' is always defined
+proc initDefines*(symbols: StringTableRef) =
   # for bootstrapping purposes and old code:
+  template defineSymbol(s) = symbols.defineSymbol(s)
   defineSymbol("nimhygiene")
   defineSymbol("niminheritable")
   defineSymbol("nimmixin")
@@ -89,6 +47,7 @@ proc initDefines*() =
   defineSymbol("nimcomputedgoto")
   defineSymbol("nimunion")
   defineSymbol("nimnewshared")
+  defineSymbol("nimNewTypedesc")
   defineSymbol("nimrequiresnimframe")
   defineSymbol("nimparsebiggestfloatmagic")
   defineSymbol("nimalias")
@@ -99,7 +58,40 @@ proc initDefines*() =
   defineSymbol("nimtypedescfixed")
   defineSymbol("nimKnowsNimvm")
   defineSymbol("nimArrIdx")
+  defineSymbol("nimHasalignOf")
   defineSymbol("nimImmediateDeprecated")
   defineSymbol("nimNewShiftOps")
   defineSymbol("nimDistros")
   defineSymbol("nimHasCppDefine")
+  defineSymbol("nimGenericInOutFlags")
+  when false: defineSymbol("nimHasOpt")
+  defineSymbol("nimNoArrayToCstringConversion")
+  defineSymbol("nimNewRoof")
+  defineSymbol("nimHasRunnableExamples")
+  defineSymbol("nimNewDot")
+  defineSymbol("nimHasNilChecks")
+  defineSymbol("nimSymKind")
+  defineSymbol("nimVmEqIdent")
+  defineSymbol("nimNoNil")
+  defineSymbol("nimNoZeroTerminator")
+  defineSymbol("nimNotNil")
+  defineSymbol("nimVmExportFixed")
+  defineSymbol("nimHasSymOwnerInMacro")
+  defineSymbol("nimNewRuntime")
+  defineSymbol("nimIncrSeqV3")
+  defineSymbol("nimAshr")
+  defineSymbol("nimNoNilSeqs")
+  defineSymbol("nimNoNilSeqs2")
+  defineSymbol("nimHasUserErrors")
+  defineSymbol("nimUncheckedArrayTyp")
+  defineSymbol("nimHasTypeof")
+  defineSymbol("nimErrorProcCanHaveBody")
+  defineSymbol("nimHasInstantiationOfInMacro")
+  defineSymbol("nimHasNilSeqs")
+  for f in low(Feature)..high(Feature):
+    defineSymbol("nimHas" & $f)
+
+  for s in WarningsToStr:
+    defineSymbol("nimHasWarning" & s)
+  for s in HintsToStr:
+    defineSymbol("nimHasHint" & s)
diff --git a/compiler/configuration.nim b/compiler/configuration.nim
new file mode 100644
index 000000000..22e0b834e
--- /dev/null
+++ b/compiler/configuration.nim
@@ -0,0 +1,6 @@
+## Use the module 'lineinfos' instead!
+
+{.deprecated.}
+
+import lineinfos
+export lineinfos
diff --git a/compiler/depends.nim b/compiler/depends.nim
index e8c295a34..d380d637a 100644
--- a/compiler/depends.nim
+++ b/compiler/depends.nim
@@ -10,48 +10,56 @@
 # This module implements a dependency file generator.
 
 import
-  os, options, ast, astalgo, msgs, ropes, idents, passes, importer
+  os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths,
+  pathutils
 
-from modulegraphs import ModuleGraph
-
-proc generateDot*(project: string)
+from modulegraphs import ModuleGraph, PPassContext
 
 type
-  TGen = object of TPassContext
-    module*: PSym
+  TGen = object of PPassContext
+    module: PSym
+    config: ConfigRef
+    graph: ModuleGraph
   PGen = ref TGen
 
-var gDotGraph: Rope # the generated DOT file; we need a global variable
+  Backend = ref object of RootRef
+    dotGraph: Rope
 
-proc addDependencyAux(importing, imported: string) =
-  addf(gDotGraph, "$1 -> \"$2\";$n", [rope(importing), rope(imported)])
+proc addDependencyAux(b: Backend; importing, imported: string) =
+  addf(b.dotGraph, "$1 -> \"$2\";$n", [rope(importing), rope(imported)])
   # s1 -> s2_4[label="[0-9]"];
 
 proc addDotDependency(c: PPassContext, n: PNode): PNode =
   result = n
-  var g = PGen(c)
+  let g = PGen(c)
+  let b = Backend(g.graph.backend)
   case n.kind
   of nkImportStmt:
     for i in countup(0, sonsLen(n) - 1):
-      var imported = getModuleName(n.sons[i])
-      addDependencyAux(g.module.name.s, imported)
+      var imported = getModuleName(g.config, n.sons[i])
+      addDependencyAux(b, g.module.name.s, imported)
   of nkFromStmt, nkImportExceptStmt:
-    var imported = getModuleName(n.sons[0])
-    addDependencyAux(g.module.name.s, imported)
+    var imported = getModuleName(g.config, n.sons[0])
+    addDependencyAux(b, g.module.name.s, imported)
   of nkStmtList, nkBlockStmt, nkStmtListExpr, nkBlockExpr:
     for i in countup(0, sonsLen(n) - 1): discard addDotDependency(c, n.sons[i])
   else:
     discard
 
-proc generateDot(project: string) =
-  writeRope("digraph $1 {$n$2}$n" % [
-      rope(changeFileExt(extractFilename(project), "")), gDotGraph],
+proc generateDot*(graph: ModuleGraph; project: AbsoluteFile) =
+  let b = Backend(graph.backend)
+  discard writeRope("digraph $1 {$n$2}$n" % [
+      rope(project.splitFile.name), b.dotGraph],
             changeFileExt(project, "dot"))
 
-proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
   var g: PGen
   new(g)
   g.module = module
+  g.config = graph.config
+  g.graph = graph
+  if graph.backend == nil:
+    graph.backend = Backend(dotGraph: nil)
   result = g
 
 const gendependPass* = makePass(open = myOpen, process = addDotDependency)
diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim
new file mode 100644
index 000000000..40af11e70
--- /dev/null
+++ b/compiler/destroyer.nim
@@ -0,0 +1,666 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2017 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Injects destructor calls into Nim code as well as
+## an optimizer that optimizes copies to moves. This is implemented as an
+## AST to AST transformation so that every backend benefits from it.
+
+## Rules for destructor injections:
+##
+## foo(bar(X(), Y()))
+## X and Y get destroyed after bar completes:
+##
+## foo( (tmpX = X(); tmpY = Y(); tmpBar = bar(tmpX, tmpY);
+##       destroy(tmpX); destroy(tmpY);
+##       tmpBar))
+## destroy(tmpBar)
+##
+## var x = f()
+## body
+##
+## is the same as:
+##
+##  var x;
+##  try:
+##    move(x, f())
+##  finally:
+##    destroy(x)
+##
+## But this really just an optimization that tries to avoid to
+## introduce too many temporaries, the 'destroy' is caused by
+## the 'f()' call. No! That is not true for 'result = f()'!
+##
+## x = y where y is read only once
+## is the same as:  move(x, y)
+##
+## Actually the more general rule is: The *last* read of ``y``
+## can become a move if ``y`` is the result of a construction.
+##
+## We also need to keep in mind here that the number of reads is
+## control flow dependent:
+## let x = foo()
+## while true:
+##   y = x  # only one read, but the 2nd iteration will fail!
+## This also affects recursions! Only usages that do not cross
+## a loop boundary (scope) and are not used in function calls
+## are safe.
+##
+##
+## x = f() is the same as:  move(x, f())
+##
+## x = y
+## is the same as:  copy(x, y)
+##
+## Reassignment works under this scheme:
+## var x = f()
+## x = y
+##
+## is the same as:
+##
+##  var x;
+##  try:
+##    move(x, f())
+##    copy(x, y)
+##  finally:
+##    destroy(x)
+##
+##  result = f()  must not destroy 'result'!
+##
+## The produced temporaries clutter up the code and might lead to
+## inefficiencies. A better strategy is to collect all the temporaries
+## in a single object that we put into a single try-finally that
+## surrounds the proc body. This means the code stays quite efficient
+## when compiled to C. In fact, we do the same for variables, so
+## destructors are called when the proc returns, not at scope exit!
+## This makes certains idioms easier to support. (Taking the slice
+## of a temporary object.)
+##
+## foo(bar(X(), Y()))
+## X and Y get destroyed after bar completes:
+##
+## var tmp: object
+## foo( (move tmp.x, X(); move tmp.y, Y(); tmp.bar = bar(tmpX, tmpY);
+##       tmp.bar))
+## destroy(tmp.bar)
+## destroy(tmp.x); destroy(tmp.y)
+##
+
+##[
+From https://github.com/nim-lang/Nim/wiki/Destructors
+
+Rule      Pattern                 Transformed into
+----      -------                 ----------------
+1.1	      var x: T; stmts	        var x: T; try stmts
+                                  finally: `=destroy`(x)
+1.2       var x: sink T; stmts    var x: sink T; stmts; ensureEmpty(x)
+2         x = f()                 `=sink`(x, f())
+3         x = lastReadOf z        `=sink`(x, z); wasMoved(z)
+4.1       y = sinkParam           `=sink`(y, sinkParam)
+4.2       x = y                   `=`(x, y) # a copy
+5.1       f_sink(g())             f_sink(g())
+5.2       f_sink(y)               f_sink(copy y); # copy unless we can see it's the last read
+5.3       f_sink(move y)          f_sink(y); wasMoved(y) # explicit moves empties 'y'
+5.4       f_noSink(g())           var tmp = bitwiseCopy(g()); f(tmp); `=destroy`(tmp)
+
+Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently
+  not allowed as a local variable.
+
+``move`` builtin needs to be implemented.
+]##
+
+import
+  intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
+  strutils, options, dfa, lowerings, tables, modulegraphs, msgs,
+  lineinfos, parampatterns
+
+const
+  InterestingSyms = {skVar, skResult, skLet}
+
+type
+  Con = object
+    owner: PSym
+    g: ControlFlowGraph
+    jumpTargets: IntSet
+    destroys, topLevelVars: PNode
+    graph: ModuleGraph
+    emptyNode: PNode
+    otherRead: PNode
+
+
+proc isHarmlessVar*(s: PSym; c: Con): bool =
+  # 's' is harmless if it used only once and its
+  # definition/usage are not split by any labels:
+  #
+  # let s = foo()
+  # while true:
+  #   a[i] = s
+  #
+  # produces:
+  #
+  # def s
+  # L1:
+  #   use s
+  # goto L1
+  #
+  # let s = foo()
+  # if cond:
+  #   a[i] = s
+  # else:
+  #   a[j] = s
+  #
+  # produces:
+  #
+  # def s
+  # fork L2
+  # use s
+  # goto L3
+  # L2:
+  # use s
+  # L3
+  #
+  # So this analysis is for now overly conservative, but correct.
+  var defsite = -1
+  var usages = 0
+  for i in 0..<c.g.len:
+    case c.g[i].kind
+    of def:
+      if c.g[i].sym == s:
+        if defsite < 0: defsite = i
+        else: return false
+    of use:
+      if c.g[i].sym == s:
+        if defsite < 0: return false
+        for j in defsite .. i:
+          # not within the same basic block?
+          if j in c.jumpTargets: return false
+        # if we want to die after the first 'use':
+        if usages > 1: return false
+        inc usages
+    #of useWithinCall:
+    #  if c.g[i].sym == s: return false
+    of goto, fork:
+      discard "we do not perform an abstract interpretation yet"
+  result = usages <= 1
+
+proc isLastRead(n: PNode; c: var Con): bool =
+  # first we need to search for the instruction that belongs to 'n':
+  doAssert n.kind == nkSym
+  c.otherRead = nil
+  var instr = -1
+  for i in 0..<c.g.len:
+    if c.g[i].n == n:
+      if instr < 0: instr = i
+      else:
+        # eh, we found two positions that belong to 'n'?
+        # better return 'false' then:
+        return false
+  if instr < 0: return false
+  # we go through all paths beginning from 'instr+1' and need to
+  # ensure that we don't find another 'use X' instruction.
+  if instr+1 >= c.g.len: return true
+  let s = n.sym
+  var pcs: seq[int] = @[instr+1]
+  var takenGotos: IntSet
+  var takenForks = initIntSet()
+  while pcs.len > 0:
+    var pc = pcs.pop
+
+    takenGotos = initIntSet()
+    while pc < c.g.len:
+      case c.g[pc].kind
+      of def:
+        if c.g[pc].sym == s:
+          # the path lead to a redefinition of 's' --> abandon it.
+          when false:
+            # Too complex thinking ahead: In reality it is enough to find
+            # the 'def x' here on the current path to make the 'use x' valid.
+            # but for this the definition needs to dominate the usage:
+            var dominates = true
+            for j in pc+1 .. instr:
+              # not within the same basic block?
+              if c.g[j].kind in {goto, fork} and (j + c.g[j].dest) in (pc+1 .. instr):
+                #if j in c.jumpTargets:
+                dominates = false
+            if dominates: break
+          break
+        inc pc
+      of use:
+        if c.g[pc].sym == s:
+          c.otherRead = c.g[pc].n
+          return false
+        inc pc
+      of goto:
+        # we must leave endless loops eventually:
+        if not takenGotos.containsOrIncl(pc):
+          pc = pc + c.g[pc].dest
+        else:
+          inc pc
+      of fork:
+        # we follow the next instruction but push the dest onto our "work" stack:
+        if not takenForks.containsOrIncl(pc):
+          pcs.add pc + c.g[pc].dest
+        inc pc
+  #echo c.graph.config $ n.info, " last read here!"
+  return true
+
+template interestingSym(s: PSym): bool =
+  s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
+
+template isUnpackedTuple(s: PSym): bool =
+  ## we move out all elements of unpacked tuples,
+  ## hence unpacked tuples themselves don't need to be destroyed
+  s.kind == skTemp and s.typ.kind == tyTuple
+
+proc patchHead(n: PNode) =
+  if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1:
+    let s = n[0].sym
+    if s.name.s[0] == '=' and s.name.s in ["=sink", "=", "=destroy"]:
+      if sfFromGeneric in s.flags:
+        excl(s.flags, sfFromGeneric)
+        patchHead(s.getBody)
+      let t = n[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
+      template patch(op, field) =
+        if s.name.s == op and field != nil and field != s:
+          n.sons[0].sym = field
+      patch "=sink", t.sink
+      patch "=", t.assignment
+      patch "=destroy", t.destructor
+  for x in n:
+    patchHead(x)
+
+proc patchHead(s: PSym) =
+  if sfFromGeneric in s.flags:
+    patchHead(s.ast[bodyPos])
+
+proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
+  var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">"
+  if opname == "=" and ri != nil:
+    m.add "; requires a copy because it's not the last read of '"
+    m.add renderTree(ri)
+    m.add '\''
+    if c.otherRead != nil:
+      m.add "; another read is done here: "
+      m.add c.graph.config $ c.otherRead.info
+  localError(c.graph.config, ri.info, errGenerated, m)
+
+proc makePtrType(c: Con, baseType: PType): PType =
+  result = newType(tyPtr, c.owner)
+  addSonSkipIntLit(result, baseType)
+
+template genOp(opr, opname, ri) =
+  let op = opr
+  if op == nil:
+    globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t))
+  elif op.ast[genericParamsPos].kind != nkEmpty:
+    globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic")
+  patchHead op
+  if sfError in op.flags: checkForErrorPragma(c, t, ri, opname)
+  let addrExp = newNodeIT(nkHiddenAddr, dest.info, makePtrType(c, dest.typ))
+  addrExp.add(dest)
+  result = newTree(nkCall, newSymNode(op), addrExp)
+
+proc genSink(c: Con; t: PType; dest, ri: PNode): PNode =
+  let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
+  genOp(if t.sink != nil: t.sink else: t.assignment, "=sink", ri)
+
+proc genCopy(c: Con; t: PType; dest, ri: PNode): PNode =
+  let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
+  genOp(t.assignment, "=", ri)
+
+proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
+  let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
+  genOp(t.destructor, "=destroy", nil)
+
+proc addTopVar(c: var Con; v: PNode) =
+  c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)
+
+proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
+  let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.owner, info)
+  sym.typ = typ
+  result = newSymNode(sym)
+  c.addTopVar(result)
+
+proc p(n: PNode; c: var Con): PNode
+
+template recurse(n, dest) =
+  for i in 0..<n.len:
+    dest.add p(n[i], c)
+
+proc isSinkParam(s: PSym): bool {.inline.} =
+  result = s.kind == skParam and s.typ.kind == tySink
+
+proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode =
+  result = newNodeI(nkCall, n.info)
+  result.add(newSymNode(createMagic(c.graph, magicname, m)))
+  result.add n
+
+proc genWasMoved(n: PNode; c: var Con): PNode =
+  # The mWasMoved builtin does not take the address.
+  result = genMagicCall(n, c, "wasMoved", mWasMoved)
+
+proc destructiveMoveVar(n: PNode; c: var Con): PNode =
+  # generate: (let tmp = v; reset(v); tmp)
+  # XXX: Strictly speaking we can only move if there is a ``=sink`` defined
+  # or if no ``=sink`` is defined and also no assignment.
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+
+  var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
+  temp.typ = n.typ
+  var v = newNodeI(nkLetSection, n.info)
+  let tempAsNode = newSymNode(temp)
+
+  var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
+  vpart.sons[0] = tempAsNode
+  vpart.sons[1] = c.emptyNode
+  vpart.sons[2] = n
+  add(v, vpart)
+
+  result.add v
+  result.add genWasMoved(n, c)
+  result.add tempAsNode
+
+proc sinkParamIsLastReadCheck(c: var Con, s: PNode) = 
+  assert s.kind == nkSym and s.sym.kind == skParam
+  if not isLastRead(s, c):
+     localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
+         "` is already consumed at " & toFileLineCol(c. graph.config, s.info))
+
+proc passCopyToSink(n: PNode; c: var Con): PNode =
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+  let tmp = getTemp(c, n.typ, n.info)
+  if hasDestructor(n.typ):
+    var m = genCopy(c, n.typ, tmp, n)
+    m.add p(n, c)
+    result.add m
+    if isLValue(n):
+      message(c.graph.config, n.info, hintPerformance,
+        ("passing '$1' to a sink parameter introduces an implicit copy; " &
+        "use 'move($1)' to prevent it") % $n)
+  else:
+    result.add newTree(nkAsgn, tmp, p(n, c))
+  result.add tmp
+
+proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
+  template pArgIfTyped(arg_part: PNode): PNode =
+    # typ is nil if we are in if/case expr branch with noreturn
+    if arg_part.typ == nil: p(arg_part, c)
+    else: pArg(arg_part, c, isSink)
+
+  if isSink:
+    if arg.kind in nkCallKinds:
+      # recurse but skip the call expression in order to prevent
+      # destructor injections: Rule 5.1 is different from rule 5.4!
+      result = copyNode(arg)
+      let parameters = arg[0].typ
+      let L = if parameters != nil: parameters.len else: 0
+      result.add arg[0]
+      for i in 1..<arg.len:
+        result.add pArg(arg[i], c, i < L and parameters[i].kind == tySink)
+    elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkBracket, nkCharLit..nkFloat128Lit}:
+      discard "object construction to sink parameter: nothing to do"
+      result = arg
+    elif arg.kind == nkSym and isSinkParam(arg.sym):
+      # Sinked params can be consumed only once. We need to reset the memory
+      # to disable the destructor which we have not elided
+      sinkParamIsLastReadCheck(c, arg)
+      result = destructiveMoveVar(arg, c)
+    elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isLastRead(arg, c):
+      # it is the last read, can be sinked. We need to reset the memory
+      # to disable the destructor which we have not elided
+      result = destructiveMoveVar(arg, c)
+    elif arg.kind in {nkBlockExpr, nkBlockStmt}:
+      result = copyNode(arg)
+      result.add arg[0]
+      result.add pArg(arg[1], c, isSink)
+    elif arg.kind == nkStmtListExpr:
+      result = copyNode(arg)
+      for i in 0..arg.len-2:
+        result.add p(arg[i], c)
+      result.add pArg(arg[^1], c, isSink)
+    elif arg.kind in {nkIfExpr, nkIfStmt}:
+      result = copyNode(arg)
+      for i in 0..<arg.len:
+        var branch = copyNode(arg[i])
+        if arg[i].kind in {nkElifBranch, nkElifExpr}:   
+          branch.add p(arg[i][0], c)
+          branch.add pArgIfTyped(arg[i][1])
+        else:
+          branch.add pArgIfTyped(arg[i][0])
+        result.add branch
+    elif arg.kind == nkCaseStmt:
+      result = copyNode(arg)
+      result.add p(arg[0], c)
+      for i in 1..<arg.len:
+        var branch: PNode
+        if arg[i].kind == nkOfbranch:
+          branch = arg[i] # of branch conditions are constants
+          branch[^1] = pArgIfTyped(arg[i][^1])
+        elif arg[i].kind in {nkElifBranch, nkElifExpr}:
+          branch = copyNode(arg[i])   
+          branch.add p(arg[i][0], c)
+          branch.add pArgIfTyped(arg[i][1])
+        else:
+          branch = copyNode(arg[i]) 
+          branch.add pArgIfTyped(arg[i][0])
+        result.add branch     
+    else:
+      # an object that is not temporary but passed to a 'sink' parameter
+      # results in a copy.
+      result = passCopyToSink(arg, c)
+  else:
+    result = p(arg, c)
+
+proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
+  template moveOrCopyIfTyped(ri_part: PNode): PNode =
+    # typ is nil if we are in if/case expr branch with noreturn
+    if ri_part.typ == nil: p(ri_part, c)
+    else: moveOrCopy(dest, ri_part, c)
+
+  case ri.kind
+  of nkCallKinds:
+    result = genSink(c, dest.typ, dest, ri)
+    # watch out and no not transform 'ri' twice if it's a call:
+    let ri2 = copyNode(ri)
+    let parameters = ri[0].typ
+    let L = if parameters != nil: parameters.len else: 0
+    ri2.add ri[0]
+    for i in 1..<ri.len:
+      ri2.add pArg(ri[i], c, i < L and parameters[i].kind == tySink)
+    #recurse(ri, ri2)
+    result.add ri2
+  of nkBracketExpr:
+    if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
+      # unpacking of tuple: move out the elements 
+      result = genSink(c, dest.typ, dest, ri)
+    else:
+      result = genCopy(c, dest.typ, dest, ri)
+    result.add p(ri, c)
+  of nkStmtListExpr:
+    result = newNodeI(nkStmtList, ri.info)
+    for i in 0..ri.len-2:
+      result.add p(ri[i], c)
+    result.add moveOrCopy(dest, ri[^1], c)
+  of nkBlockExpr, nkBlockStmt:
+    result = newNodeI(nkBlockStmt, ri.info)
+    result.add ri[0] ## add label
+    result.add moveOrCopy(dest, ri[1], c)
+  of nkIfExpr, nkIfStmt:
+    result = newNodeI(nkIfStmt, ri.info)
+    for i in 0..<ri.len:
+      var branch = copyNode(ri[i])
+      if ri[i].kind in {nkElifBranch, nkElifExpr}:
+        branch.add p(ri[i][0], c)
+        branch.add moveOrCopyIfTyped(ri[i][1])
+      else:
+        branch.add moveOrCopyIfTyped(ri[i][0])
+      result.add branch
+  of nkCaseStmt:
+    result = newNodeI(nkCaseStmt, ri.info)
+    result.add p(ri[0], c)
+    for i in 1..<ri.len:
+      var branch: PNode
+      if ri[i].kind == nkOfbranch:
+        branch = ri[i] # of branch conditions are constants
+        branch[^1] = moveOrCopyIfTyped(ri[i][^1])
+      elif ri[i].kind in {nkElifBranch, nkElifExpr}:
+        branch = copyNode(ri[i])   
+        branch.add p(ri[i][0], c)
+        branch.add moveOrCopyIfTyped(ri[i][1])
+      else:
+        branch = copyNode(ri[i]) 
+        branch.add moveOrCopyIfTyped(ri[i][0])
+      result.add branch
+  of nkBracket:
+    # array constructor
+    result = genSink(c, dest.typ, dest, ri)
+    let ri2 = copyTree(ri)
+    for i in 0..<ri.len:
+      # everything that is passed to an array constructor is consumed,
+      # so these all act like 'sink' parameters:
+      ri2[i] = pArg(ri[i], c, isSink = true)
+    result.add ri2
+  of nkObjConstr:
+    result = genSink(c, dest.typ, dest, ri)
+    let ri2 = copyTree(ri)
+    for i in 1..<ri.len:
+      # everything that is passed to an object constructor is consumed,
+      # so these all act like 'sink' parameters:
+      ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
+    result.add ri2
+  of nkTupleConstr:
+    result = genSink(c, dest.typ, dest, ri)
+    let ri2 = copyTree(ri)
+    for i in 0..<ri.len:
+      # everything that is passed to an tuple constructor is consumed,
+      # so these all act like 'sink' parameters:
+      if ri[i].kind == nkExprColonExpr:
+        ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
+      else:
+        ri2[i] = pArg(ri[i], c, isSink = true)
+    result.add ri2
+  of nkSym:
+    if isSinkParam(ri.sym):
+      # Rule 3: `=sink`(x, z); wasMoved(z)
+      sinkParamIsLastReadCheck(c, ri)
+      var snk = genSink(c, dest.typ, dest, ri)
+      snk.add ri
+      result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))   
+    elif ri.sym.kind != skParam and isLastRead(ri, c):
+      # Rule 3: `=sink`(x, z); wasMoved(z)
+      var snk = genSink(c, dest.typ, dest, ri)
+      snk.add ri
+      result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
+    else:
+      result = genCopy(c, dest.typ, dest, ri)
+      result.add p(ri, c)
+  else:
+    result = genCopy(c, dest.typ, dest, ri)
+    result.add p(ri, c)
+
+proc p(n: PNode; c: var Con): PNode =
+  case n.kind
+  of nkVarSection, nkLetSection:
+    discard "transform; var x = y to  var x; x op y  where op is a move or copy"
+    result = newNodeI(nkStmtList, n.info)
+
+    for i in 0..<n.len:
+      let it = n[i]
+      let L = it.len-1
+      let ri = it[L]
+      if it.kind == nkVarTuple and hasDestructor(ri.typ):
+        let x = lowerTupleUnpacking(c.graph, it, c.owner)
+        result.add p(x, c)
+      elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
+        for j in 0..L-2:
+          let v = it[j]
+          doAssert v.kind == nkSym
+          # move the variable declaration to the top of the frame:
+          c.addTopVar v
+          # make sure it's destroyed at the end of the proc:
+          if not isUnpackedTuple(it[0].sym):
+            c.destroys.add genDestroy(c, v.typ, v)
+          if ri.kind != nkEmpty:
+            let r = moveOrCopy(v, ri, c)
+            result.add r
+      else:
+        # keep it, but transform 'ri':
+        var varSection = copyNode(n)
+        var itCopy = copyNode(it)
+        for j in 0..L-1:
+          itCopy.add it[j]
+        itCopy.add p(ri, c)
+        varSection.add itCopy
+        result.add varSection
+  of nkCallKinds:
+    let parameters = n[0].typ
+    let L = if parameters != nil: parameters.len else: 0
+    for i in 1 ..< n.len:
+      n.sons[i] = pArg(n[i], c, i < L and parameters[i].kind == tySink)
+    if n.typ != nil and hasDestructor(n.typ):
+      discard "produce temp creation"
+      result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+      let tmp = getTemp(c, n.typ, n.info)
+      var sinkExpr = genSink(c, n.typ, tmp, n)
+      sinkExpr.add n
+      result.add sinkExpr
+      result.add tmp
+      c.destroys.add genDestroy(c, n.typ, tmp)
+    else:
+      result = n
+  of nkAsgn, nkFastAsgn:
+    if hasDestructor(n[0].typ):
+      result = moveOrCopy(n[0], n[1], c)
+    else:
+      result = copyNode(n)
+      recurse(n, result)
+  of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
+      nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
+    result = n
+  else:
+    result = copyNode(n)
+    recurse(n, result)
+
+proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
+  when false: # defined(nimDebugDestroys):
+    echo "injecting into ", n
+  var c: Con
+  c.owner = owner
+  c.destroys = newNodeI(nkStmtList, n.info)
+  c.topLevelVars = newNodeI(nkVarSection, n.info)
+  c.graph = g
+  c.emptyNode = newNodeI(nkEmpty, n.info)
+  let cfg = constructCfg(owner, n)
+  shallowCopy(c.g, cfg)
+  c.jumpTargets = initIntSet()
+  for i in 0..<c.g.len:
+    if c.g[i].kind in {goto, fork}:
+      c.jumpTargets.incl(i+c.g[i].dest)
+  #if owner.name.s == "test0p1":
+  #  echoCfg(c.g)
+  if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
+    let params = owner.typ.n
+    for i in 1 ..< params.len:
+      let param = params[i].sym
+      if param.typ.kind == tySink and hasDestructor(param.typ): 
+        c.destroys.add genDestroy(c, param.typ.skipTypes({tyGenericInst, tyAlias, tySink}), params[i])
+
+  let body = p(n, c)
+  result = newNodeI(nkStmtList, n.info)
+  if c.topLevelVars.len > 0:
+    result.add c.topLevelVars
+  if c.destroys.len > 0:
+    result.add newTryFinally(body, c.destroys)
+  else:
+    result.add body
+
+  when defined(nimDebugDestroys):
+    if true:
+      echo "------------------------------------"
+      echo owner.name.s, " transformed to: "
+      echo result
diff --git a/compiler/dfa.nim b/compiler/dfa.nim
new file mode 100644
index 000000000..cd32d95d5
--- /dev/null
+++ b/compiler/dfa.nim
@@ -0,0 +1,481 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2017 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Data flow analysis for Nim.
+## We transform the AST into a linear list of instructions first to
+## make this easier to handle: There are only 2 different branching
+## instructions: 'goto X' is an unconditional goto, 'fork X'
+## is a conditional goto (either the next instruction or 'X' can be
+## taken). Exhaustive case statements could be translated
+## so that the last branch is transformed into an 'else' branch, but
+## this is currently not done.
+## ``return`` and ``break`` are all covered by 'goto'.
+##
+## Control flow through exception handling:
+## Contrary to popular belief, exception handling doesn't cause
+## many problems for this DFA representation, ``raise`` is a statement
+## that ``goes to`` the outer ``finally`` or ``except`` if there is one,
+## otherwise it is the same as ``return``. Every call is treated as
+## a call that can potentially ``raise``. However, without a surrounding
+## ``try`` we don't emit these ``fork ReturnLabel`` instructions in order
+## to speed up the dataflow analysis passes.
+##
+## The data structures and algorithms used here are inspired by
+## "A Graph–Free Approach to Data–Flow Analysis" by Markus Mohnen.
+## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf
+
+import ast, astalgo, types, intsets, tables, msgs, options, lineinfos
+
+type
+  InstrKind* = enum
+    goto, fork, def, use
+  Instr* = object
+    n*: PNode
+    case kind*: InstrKind
+    of def, use: sym*: PSym
+    of goto, fork: dest*: int
+
+  ControlFlowGraph* = seq[Instr]
+
+  TPosition = distinct int
+  TBlock = object
+    label: PSym
+    fixups: seq[TPosition]
+
+  ValueKind = enum
+    undef, value, valueOrUndef
+
+  Con = object
+    code: ControlFlowGraph
+    inCall, inTryStmt: int
+    blocks: seq[TBlock]
+    tryStmtFixups: seq[TPosition]
+    owner: PSym
+
+proc debugInfo(info: TLineInfo): string =
+  result = $info.line #info.toFilename & ":" & $info.line
+
+proc codeListing(c: ControlFlowGraph, result: var string, start=0; last = -1) =
+  # for debugging purposes
+  # first iteration: compute all necessary labels:
+  var jumpTargets = initIntSet()
+  let last = if last < 0: c.len-1 else: min(last, c.len-1)
+  for i in start..last:
+    if c[i].kind in {goto, fork}:
+      jumpTargets.incl(i+c[i].dest)
+  var i = start
+  while i <= last:
+    if i in jumpTargets: result.add("L" & $i & ":\n")
+    result.add "\t"
+    result.add $c[i].kind
+    result.add "\t"
+    case c[i].kind
+    of def, use:
+      result.add c[i].sym.name.s
+    of goto, fork:
+      result.add "L"
+      result.add c[i].dest+i
+    result.add("\t#")
+    result.add(debugInfo(c[i].n.info))
+    result.add("\n")
+    inc i
+  if i in jumpTargets: result.add("L" & $i & ": End\n")
+
+
+proc echoCfg*(c: ControlFlowGraph; start=0; last = -1) {.deprecated.} =
+  ## echos the ControlFlowGraph for debugging purposes.
+  var buf = ""
+  codeListing(c, buf, start, last)
+  when declared(echo):
+    echo buf
+
+proc forkI(c: var Con; n: PNode): TPosition =
+  result = TPosition(c.code.len)
+  c.code.add Instr(n: n, kind: fork, dest: 0)
+
+proc gotoI(c: var Con; n: PNode): TPosition =
+  result = TPosition(c.code.len)
+  c.code.add Instr(n: n, kind: goto, dest: 0)
+
+proc genLabel(c: Con): TPosition =
+  result = TPosition(c.code.len)
+
+proc jmpBack(c: var Con, n: PNode, p = TPosition(0)) =
+  let dist = p.int - c.code.len
+  doAssert(-0x7fff < dist and dist < 0x7fff)
+  c.code.add Instr(n: n, kind: goto, dest: dist)
+
+proc patch(c: var Con, p: TPosition) =
+  # patch with current index
+  let p = p.int
+  let diff = c.code.len - p
+  doAssert(-0x7fff < diff and diff < 0x7fff)
+  c.code[p].dest = diff
+
+proc popBlock(c: var Con; oldLen: int) =
+  for f in c.blocks[oldLen].fixups:
+    c.patch(f)
+  c.blocks.setLen(oldLen)
+
+template withBlock(labl: PSym; body: untyped) {.dirty.} =
+  var oldLen {.gensym.} = c.blocks.len
+  c.blocks.add TBlock(label: labl, fixups: @[])
+  body
+  popBlock(c, oldLen)
+
+proc isTrue(n: PNode): bool =
+  n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
+    n.kind == nkIntLit and n.intVal != 0
+
+proc gen(c: var Con; n: PNode) # {.noSideEffect.}
+
+proc genWhile(c: var Con; n: PNode) =
+  # L1:
+  #   cond, tmp
+  #   fork tmp, L2
+  #   body
+  #   jmp L1
+  # L2:
+  let L1 = c.genLabel
+  withBlock(nil):
+    if isTrue(n.sons[0]):
+      c.gen(n.sons[1])
+      c.jmpBack(n, L1)
+    else:
+      c.gen(n.sons[0])
+      let L2 = c.forkI(n)
+      c.gen(n.sons[1])
+      c.jmpBack(n, L1)
+      c.patch(L2)
+
+proc genBlock(c: var Con; n: PNode) =
+  withBlock(n.sons[0].sym):
+    c.gen(n.sons[1])
+
+proc genBreak(c: var Con; n: PNode) =
+  let L1 = c.gotoI(n)
+  if n.sons[0].kind == nkSym:
+    #echo cast[int](n.sons[0].sym)
+    for i in countdown(c.blocks.len-1, 0):
+      if c.blocks[i].label == n.sons[0].sym:
+        c.blocks[i].fixups.add L1
+        return
+    #globalError(n.info, "VM problem: cannot find 'break' target")
+  else:
+    c.blocks[c.blocks.high].fixups.add L1
+
+proc genIf(c: var Con, n: PNode) =
+  var endings: seq[TPosition] = @[]
+  for i in countup(0, len(n) - 1):
+    var it = n.sons[i]
+    c.gen(it.sons[0])
+    if it.len == 2:
+      let elsePos = c.forkI(it.sons[1])
+      c.gen(it.sons[1])
+      if i < sonsLen(n)-1:
+        endings.add(c.gotoI(it.sons[1]))
+      c.patch(elsePos)
+  for endPos in endings: c.patch(endPos)
+
+proc genAndOr(c: var Con; n: PNode) =
+  #   asgn dest, a
+  #   fork L1
+  #   asgn dest, b
+  # L1:
+  c.gen(n.sons[1])
+  let L1 = c.forkI(n)
+  c.gen(n.sons[2])
+  c.patch(L1)
+
+proc genCase(c: var Con; n: PNode) =
+  #  if (!expr1) goto L1;
+  #    thenPart
+  #    goto LEnd
+  #  L1:
+  #  if (!expr2) goto L2;
+  #    thenPart2
+  #    goto LEnd
+  #  L2:
+  #    elsePart
+  #  Lend:
+  when false:
+    # XXX Exhaustiveness is not yet mapped to the control flow graph as
+    # it seems to offer no benefits for the 'last read of' question.
+    let isExhaustive = skipTypes(n.sons[0].typ,
+      abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString} or
+      lastSon(n).kind == nkElse
+
+  var endings: seq[TPosition] = @[]
+  c.gen(n.sons[0])
+  for i in 1 ..< n.len:
+    let it = n.sons[i]
+    if it.len == 1:
+      c.gen(it.sons[0])
+    else:
+      let elsePos = c.forkI(it.lastSon)
+      c.gen(it.lastSon)
+      if i < sonsLen(n)-1:
+        endings.add(c.gotoI(it.lastSon))
+      c.patch(elsePos)
+  for endPos in endings: c.patch(endPos)
+
+proc genTry(c: var Con; n: PNode) =
+  var endings: seq[TPosition] = @[]
+  inc c.inTryStmt
+  var newFixups: seq[TPosition]
+  swap(newFixups, c.tryStmtFixups)
+
+  let elsePos = c.forkI(n)
+  c.gen(n.sons[0])
+  dec c.inTryStmt
+  for f in newFixups:
+    c.patch(f)
+  swap(newFixups, c.tryStmtFixups)
+
+  c.patch(elsePos)
+  for i in 1 ..< n.len:
+    let it = n.sons[i]
+    if it.kind != nkFinally:
+      var blen = len(it)
+      let endExcept = c.forkI(it)
+      c.gen(it.lastSon)
+      if i < sonsLen(n)-1:
+        endings.add(c.gotoI(it))
+      c.patch(endExcept)
+  for endPos in endings: c.patch(endPos)
+  let fin = lastSon(n)
+  if fin.kind == nkFinally:
+    c.gen(fin.sons[0])
+
+proc genRaise(c: var Con; n: PNode) =
+  gen(c, n.sons[0])
+  if c.inTryStmt > 0:
+    c.tryStmtFixups.add c.gotoI(n)
+  else:
+    c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
+
+proc genImplicitReturn(c: var Con) =
+  if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len:
+    gen(c, c.owner.ast.sons[resultPos])
+
+proc genReturn(c: var Con; n: PNode) =
+  if n.sons[0].kind != nkEmpty:
+    gen(c, n.sons[0])
+  else:
+    genImplicitReturn(c)
+  c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
+
+const
+  InterestingSyms = {skVar, skResult, skLet, skParam}
+
+proc genUse(c: var Con; n: PNode) =
+  var n = n
+  while n.kind in {nkDotExpr, nkCheckedFieldExpr,
+                   nkBracketExpr, nkDerefExpr, nkHiddenDeref,
+                   nkAddr, nkHiddenAddr}:
+    n = n[0]
+  if n.kind == nkSym and n.sym.kind in InterestingSyms:
+    c.code.add Instr(n: n, kind: use, sym: n.sym)
+
+proc genDef(c: var Con; n: PNode) =
+  if n.kind == nkSym and n.sym.kind in InterestingSyms:
+    c.code.add Instr(n: n, kind: def, sym: n.sym)
+
+proc genCall(c: var Con; n: PNode) =
+  gen(c, n[0])
+  var t = n[0].typ
+  if t != nil: t = t.skipTypes(abstractInst)
+  inc c.inCall
+  for i in 1..<n.len:
+    gen(c, n[i])
+    if t != nil and i < t.len and t.sons[i].kind == tyVar:
+      genDef(c, n[i])
+  # every call can potentially raise:
+  if c.inTryStmt > 0:
+    c.tryStmtFixups.add c.forkI(n)
+  dec c.inCall
+
+proc genMagic(c: var Con; n: PNode; m: TMagic) =
+  case m
+  of mAnd, mOr: c.genAndOr(n)
+  of mNew, mNewFinalize:
+    genDef(c, n[1])
+    for i in 2..<n.len: gen(c, n[i])
+  of mExit:
+    genCall(c, n)
+    c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
+  else:
+    genCall(c, n)
+
+proc genVarSection(c: var Con; n: PNode) =
+  for a in n:
+    if a.kind == nkCommentStmt: continue
+    if a.kind == nkVarTuple:
+      gen(c, a.lastSon)
+      for i in 0 .. a.len-3: genDef(c, a[i])
+    else:
+      gen(c, a.lastSon)
+      if a.lastSon.kind != nkEmpty:
+        genDef(c, a.sons[0])
+
+proc gen(c: var Con; n: PNode) =
+  case n.kind
+  of nkSym: genUse(c, n)
+  of nkCallKinds:
+    if n.sons[0].kind == nkSym:
+      let s = n.sons[0].sym
+      if s.magic != mNone:
+        genMagic(c, n, s.magic)
+      else:
+        genCall(c, n)
+    else:
+      genCall(c, n)
+  of nkCharLit..nkNilLit: discard
+  of nkAsgn, nkFastAsgn:
+    gen(c, n[1])
+    genDef(c, n[0])
+  of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr,
+     nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr:
+    gen(c, n[0])
+  of nkIfStmt, nkIfExpr: genIf(c, n)
+  of nkWhenStmt:
+    # This is "when nimvm" node. Chose the first branch.
+    gen(c, n.sons[0].sons[1])
+  of nkCaseStmt: genCase(c, n)
+  of nkWhileStmt: genWhile(c, n)
+  of nkBlockExpr, nkBlockStmt: genBlock(c, n)
+  of nkReturnStmt: genReturn(c, n)
+  of nkRaiseStmt: genRaise(c, n)
+  of nkBreakStmt: genBreak(c, n)
+  of nkTryStmt: genTry(c, n)
+  of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange,
+     nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr:
+    for x in n: gen(c, x)
+  of nkPragmaBlock: gen(c, n.lastSon)
+  of nkDiscardStmt: gen(c, n.sons[0])
+  of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkExprColonExpr, nkExprEqExpr,
+     nkCast:
+    gen(c, n.sons[1])
+  of nkObjDownConv, nkStringToCString, nkCStringToString: gen(c, n.sons[0])
+  of nkVarSection, nkLetSection: genVarSection(c, n)
+  of nkDefer:
+    doAssert false, "dfa construction pass requires the elimination of 'defer'"
+  else: discard
+
+proc dfa(code: seq[Instr]; conf: ConfigRef) =
+  var u = newSeq[IntSet](code.len) # usages
+  var d = newSeq[IntSet](code.len) # defs
+  var c = newSeq[IntSet](code.len) # consumed
+  var backrefs = initTable[int, int]()
+  for i in 0..<code.len:
+    u[i] = initIntSet()
+    d[i] = initIntSet()
+    c[i] = initIntSet()
+    case code[i].kind
+    of use: u[i].incl(code[i].sym.id)
+    of def: d[i].incl(code[i].sym.id)
+    of fork, goto:
+      let d = i+code[i].dest
+      backrefs.add(d, i)
+
+  var w = @[0]
+  var maxIters = 50
+  var someChange = true
+  var takenGotos = initIntSet()
+  var consuming = -1
+  while w.len > 0 and maxIters > 0: # and someChange:
+    dec maxIters
+    var pc = w.pop() # w[^1]
+    var prevPc = -1
+    # this simulates a single linear control flow execution:
+    while pc < code.len:
+      if prevPc >= 0:
+        someChange = false
+        # merge step and test for changes (we compute the fixpoints here):
+        # 'u' needs to be the union of prevPc, pc
+        # 'd' needs to be the intersection of 'pc'
+        for id in u[prevPc]:
+          if not u[pc].containsOrIncl(id):
+            someChange = true
+        # in (a; b) if ``a`` sets ``v`` so does ``b``. The intersection
+        # is only interesting on merge points:
+        for id in d[prevPc]:
+          if not d[pc].containsOrIncl(id):
+            someChange = true
+        # if this is a merge point, we take the intersection of the 'd' sets:
+        if backrefs.hasKey(pc):
+          var intersect = initIntSet()
+          assign(intersect, d[pc])
+          var first = true
+          for prevPc in backrefs.allValues(pc):
+            for def in d[pc]:
+              if def notin d[prevPc]:
+                excl(intersect, def)
+                someChange = true
+                when defined(debugDfa):
+                  echo "Excluding ", pc, " prev ", prevPc
+          assign d[pc], intersect
+      if consuming >= 0:
+        if not c[pc].containsOrIncl(consuming):
+          someChange = true
+        consuming = -1
+
+      # our interpretation ![I!]:
+      prevPc = pc
+      case code[pc].kind
+      of goto:
+        # we must leave endless loops eventually:
+        if not takenGotos.containsOrIncl(pc) or someChange:
+          pc = pc + code[pc].dest
+        else:
+          inc pc
+      of fork:
+        # we follow the next instruction but push the dest onto our "work" stack:
+        #if someChange:
+        w.add pc + code[pc].dest
+        inc pc
+      of use:
+        #if not d[prevPc].missingOrExcl():
+        # someChange = true
+        consuming = code[pc].sym.id
+        inc pc
+      of def:
+        if not d[pc].containsOrIncl(code[pc].sym.id):
+          someChange = true
+        inc pc
+
+  when defined(useDfa) and defined(debugDfa):
+    for i in 0..<code.len:
+      echo "PC ", i, ": defs: ", d[i], "; uses ", u[i], "; consumes ", c[i]
+
+  # now check the condition we're interested in:
+  for i in 0..<code.len:
+    case code[i].kind
+    of use:
+      let s = code[i].sym
+      if s.id notin d[i]:
+        localError(conf, code[i].n.info, "usage of uninitialized variable: " & s.name.s)
+      if s.id in c[i]:
+        localError(conf, code[i].n.info, "usage of an already consumed variable: " & s.name.s)
+
+    else: discard
+
+proc dataflowAnalysis*(s: PSym; body: PNode; conf: ConfigRef) =
+  var c = Con(code: @[], blocks: @[])
+  gen(c, body)
+  genImplicitReturn(c)
+  when defined(useDfa) and defined(debugDfa): echoCfg(c.code)
+  dfa(c.code, conf)
+
+proc constructCfg*(s: PSym; body: PNode): ControlFlowGraph =
+  ## constructs a control flow graph for ``body``.
+  var c = Con(code: @[], blocks: @[], owner: s)
+  gen(c, body)
+  genImplicitReturn(c)
+  shallowCopy(result, c.code)
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index 26dd889ce..67f4108e1 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -14,15 +14,18 @@
 import
   ast, strutils, strtabs, options, msgs, os, ropes, idents,
   wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast,
-  packages/docutils/rst, packages/docutils/rstgen, times,
-  packages/docutils/highlite, importer, sempass2, json, xmltree, cgi,
-  typesrenderer, astalgo
+  packages/docutils/rst, packages/docutils/rstgen,
+  packages/docutils/highlite, sempass2, json, xmltree, cgi,
+  typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets,
+  pathutils
+
+const
+  exportSection = skTemp
 
 type
   TSections = array[TSymKind, Rope]
   TDocumentor = object of rstgen.RstGenerator
     modDesc: Rope           # module description
-    id: int                  # for generating IDs
     toc, section: TSections
     indexValFilename: string
     analytics: string  # Google Analytics javascript, "" if doesn't exist
@@ -30,6 +33,13 @@ type
     jArray: JsonNode
     types: TStrTable
     isPureRst: bool
+    conf*: ConfigRef
+    cache*: IdentCache
+    exampleCounter: int
+    emitted: IntSet # we need to track which symbols have been emitted
+                    # already. See bug #3655
+    destFile*: AbsoluteFile
+    thisDir*: AbsoluteDir
 
   PDoc* = ref TDocumentor ## Alias to type less.
 
@@ -44,52 +54,74 @@ proc whichType(d: PDoc; n: PNode): PSym =
 
 proc attachToType(d: PDoc; p: PSym): PSym =
   let params = p.ast.sons[paramsPos]
-  # first check the first parameter, then the return type,
-  # then the other parameter:
   template check(i) =
     result = whichType(d, params[i])
     if result != nil: return result
 
+  # first check the first parameter, then the return type,
+  # then the other parameter:
   if params.len > 1: check(1)
   if params.len > 0: check(0)
   for i in 2..<params.len: check(i)
 
-proc compilerMsgHandler(filename: string, line, col: int,
-                        msgKind: rst.MsgKind, arg: string) {.procvar.} =
-  # translate msg kind:
-  var k: msgs.TMsgKind
-  case msgKind
-  of meCannotOpenFile: k = errCannotOpenFile
-  of meExpected: k = errXExpected
-  of meGridTableNotImplemented: k = errGridTableNotImplemented
-  of meNewSectionExpected: k = errNewSectionExpected
-  of meGeneralParseError: k = errGeneralParseError
-  of meInvalidDirective: k = errInvalidDirectiveX
-  of mwRedefinitionOfLabel: k = warnRedefinitionOfLabel
-  of mwUnknownSubstitution: k = warnUnknownSubstitutionX
-  of mwUnsupportedLanguage: k = warnLanguageXNotSupported
-  of mwUnsupportedField: k = warnFieldXNotSupported
-  globalError(newLineInfo(filename, line, col), k, arg)
-
-proc docgenFindFile(s: string): string {.procvar.} =
-  result = options.findFile(s)
-  if result.len == 0:
-    result = getCurrentDir() / s
-    if not existsFile(result): result = ""
+template declareClosures =
+  proc compilerMsgHandler(filename: string, line, col: int,
+                          msgKind: rst.MsgKind, arg: string) {.procvar.} =
+    # translate msg kind:
+    var k: TMsgKind
+    case msgKind
+    of meCannotOpenFile: k = errCannotOpenFile
+    of meExpected: k = errXExpected
+    of meGridTableNotImplemented: k = errGridTableNotImplemented
+    of meNewSectionExpected: k = errNewSectionExpected
+    of meGeneralParseError: k = errGeneralParseError
+    of meInvalidDirective: k = errInvalidDirectiveX
+    of mwRedefinitionOfLabel: k = warnRedefinitionOfLabel
+    of mwUnknownSubstitution: k = warnUnknownSubstitutionX
+    of mwUnsupportedLanguage: k = warnLanguageXNotSupported
+    of mwUnsupportedField: k = warnFieldXNotSupported
+    globalError(conf, newLineInfo(conf, AbsoluteFile filename, line, col), k, arg)
+
+  proc docgenFindFile(s: string): string {.procvar.} =
+    result = options.findFile(conf, s).string
+    if result.len == 0:
+      result = getCurrentDir() / s
+      if not existsFile(result): result = ""
 
 proc parseRst(text, filename: string,
               line, column: int, hasToc: var bool,
-              rstOptions: RstParseOptions): PRstNode =
+              rstOptions: RstParseOptions;
+              conf: ConfigRef): PRstNode =
+  declareClosures()
   result = rstParse(text, filename, line, column, hasToc, rstOptions,
                     docgenFindFile, compilerMsgHandler)
 
-proc newDocumentor*(filename: string, config: StringTableRef): PDoc =
+proc getOutFile2(conf: ConfigRef; filename: RelativeFile,
+                 ext: string, dir: RelativeDir; guessTarget: bool): AbsoluteFile =
+  if optWholeProject in conf.globalOptions:
+    # This is correct, for 'nim doc --project' we interpret the '--out' option as an
+    # absolute directory, not as a filename!
+    let d = if conf.outFile.isEmpty: conf.projectPath / dir else: AbsoluteDir(conf.outFile)
+    createDir(d)
+    result = d / changeFileExt(filename, ext)
+  elif guessTarget:
+    let d = if not conf.outFile.isEmpty: splitFile(conf.outFile).dir
+    else: conf.projectPath
+    createDir(d)
+    result = d / changeFileExt(filename, ext)
+  else:
+    result = getOutFile(conf, filename, ext)
+
+proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef, outExt: string = HtmlExt): PDoc =
+  declareClosures()
   new(result)
-  initRstGenerator(result[], (if gCmd != cmdRst2tex: outHtml else: outLatex),
-                   options.gConfigVars, filename, {roSupportRawDirective},
+  result.conf = conf
+  result.cache = cache
+  initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex),
+                   conf.configVars, filename.string, {roSupportRawDirective},
                    docgenFindFile, compilerMsgHandler)
 
-  if config.hasKey("doc.googleAnalytics"):
+  if conf.configVars.hasKey("doc.googleAnalytics"):
     result.analytics = """
 <script>
   (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
@@ -101,7 +133,7 @@ proc newDocumentor*(filename: string, config: StringTableRef): PDoc =
   ga('send', 'pageview');
 
 </script>
-    """ % [config.getOrDefault"doc.googleAnalytics"]
+    """ % [conf.configVars.getOrDefault"doc.googleAnalytics"]
   else:
     result.analytics = ""
 
@@ -109,9 +141,37 @@ proc newDocumentor*(filename: string, config: StringTableRef): PDoc =
   result.id = 100
   result.jArray = newJArray()
   initStrTable result.types
-
-proc dispA(dest: var Rope, xml, tex: string, args: openArray[Rope]) =
-  if gCmd != cmdRst2tex: addf(dest, xml, args)
+  result.onTestSnippet =
+    proc (gen: var RstGenerator; filename, cmd: string; status: int; content: string) =
+    var d = TDocumentor(gen)
+    var outp: AbsoluteFile
+    if filename.len == 0:
+      inc(d.id)
+      let nameOnly = splitFile(d.filename).name
+      let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly)
+      createDir(subdir)
+      outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim")
+    elif isAbsolute(filename):
+      outp = AbsoluteFile filename
+    else:
+      # Nim's convention: every path is relative to the file it was written in:
+      outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename)
+    # Include the current file if we're parsing a nim file
+    let importStmt = if d.isPureRst: "" else: "import \"$1\"\n" % [d.filename]
+    writeFile(outp, importStmt & content)
+    let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3)
+            else: cmd
+    let c2 = c % quoteShell(outp)
+    rawMessage(conf, hintExecuting, c2)
+    if execShellCmd(c2) != status:
+      rawMessage(conf, errGenerated, "executing of external program failed: " & c2)
+  result.emitted = initIntSet()
+  result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath),
+                                outExt, RelativeDir"htmldocs", false)
+  result.thisDir = result.destFile.splitFile.dir
+
+proc dispA(conf: ConfigRef; dest: var Rope, xml, tex: string, args: openArray[Rope]) =
+  if conf.cmd != cmdRst2tex: addf(dest, xml, args)
   else: addf(dest, tex, args)
 
 proc getVarIdx(varnames: openArray[string], id: string): int =
@@ -120,7 +180,8 @@ proc getVarIdx(varnames: openArray[string], id: string): int =
       return i
   result = -1
 
-proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string],
+proc ropeFormatNamedVars(conf: ConfigRef; frmt: FormatStr,
+                         varnames: openArray[string],
                          varvalues: openArray[Rope]): Rope =
   var i = 0
   var L = len(frmt)
@@ -143,7 +204,8 @@ proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string],
           j = (j * 10) + ord(frmt[i]) - ord('0')
           inc(i)
           if (i > L + 0 - 1) or not (frmt[i] in {'0'..'9'}): break
-        if j > high(varvalues) + 1: internalError("ropeFormatNamedVars")
+        if j > high(varvalues) + 1:
+          rawMessage(conf, errGenerated, "Invalid format string; too many $s: " & frmt)
         num = j
         add(result, varvalues[j - 1])
       of 'A'..'Z', 'a'..'z', '\x80'..'\xFF':
@@ -154,20 +216,23 @@ proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string],
           if not (frmt[i] in {'A'..'Z', '_', 'a'..'z', '\x80'..'\xFF'}): break
         var idx = getVarIdx(varnames, id)
         if idx >= 0: add(result, varvalues[idx])
-        else: rawMessage(errUnknownSubstitionVar, id)
+        else: rawMessage(conf, errGenerated, "unknown substition variable: " & id)
       of '{':
         var id = ""
         inc(i)
-        while frmt[i] != '}':
-          if frmt[i] == '\0': rawMessage(errTokenExpected, "}")
+        while i < frmt.len and frmt[i] != '}':
           add(id, frmt[i])
           inc(i)
-        inc(i)                # skip }
-                              # search for the variable:
-        var idx = getVarIdx(varnames, id)
+        if i >= frmt.len:
+          rawMessage(conf, errGenerated, "expected closing '}'")
+        else:
+          inc(i)                # skip }
+        # search for the variable:
+        let idx = getVarIdx(varnames, id)
         if idx >= 0: add(result, varvalues[idx])
-        else: rawMessage(errUnknownSubstitionVar, id)
-      else: internalError("ropeFormatNamedVars")
+        else: rawMessage(conf, errGenerated, "unknown substition variable: " & id)
+      else:
+        add(result, "$")
     var start = i
     while i < L:
       if frmt[i] != '$': inc(i)
@@ -177,21 +242,34 @@ proc ropeFormatNamedVars(frmt: FormatStr, varnames: openArray[string],
 proc genComment(d: PDoc, n: PNode): string =
   result = ""
   var dummyHasToc: bool
-  if n.comment != nil:
-    renderRstToOut(d[], parseRst(n.comment, toFilename(n.info),
+  if n.comment.len > 0:
+    renderRstToOut(d[], parseRst(n.comment, toFilename(d.conf, n.info),
                                toLinenumber(n.info), toColumn(n.info),
-                               dummyHasToc, d.options), result)
+                               dummyHasToc, d.options, d.conf), result)
 
-proc genRecComment(d: PDoc, n: PNode): Rope =
+proc genRecCommentAux(d: PDoc, n: PNode): Rope =
   if n == nil: return nil
   result = genComment(d, n).rope
   if result == nil:
-    if n.kind notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
+    if n.kind in {nkStmtList, nkStmtListExpr, nkTypeDef, nkConstDef,
+                  nkObjectTy, nkRefTy, nkPtrTy, nkAsgn, nkFastAsgn}:
+      # notin {nkEmpty..nkNilLit, nkEnumTy, nkTupleTy}:
       for i in countup(0, len(n)-1):
-        result = genRecComment(d, n.sons[i])
+        result = genRecCommentAux(d, n.sons[i])
         if result != nil: return
   else:
-    n.comment = nil
+    when defined(nimNoNilSeqs): n.comment = ""
+    else: n.comment = nil
+
+proc genRecComment(d: PDoc, n: PNode): Rope =
+  if n == nil: return nil
+  result = genComment(d, n).rope
+  if result == nil:
+    if n.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
+                  nkMacroDef, nkTemplateDef, nkConverterDef}:
+      result = genRecCommentAux(d, n[bodyPos])
+    else:
+      result = genRecCommentAux(d, n)
 
 proc getPlainDocstring(n: PNode): string =
   ## Gets the plain text docstring of a node non destructively.
@@ -201,36 +279,175 @@ proc getPlainDocstring(n: PNode): string =
   ## the concatenated ``##`` comments of the node.
   result = ""
   if n == nil: return
-  if n.comment != nil and startsWith(n.comment, "##"):
+  if startsWith(n.comment, "##"):
     result = n.comment
   if result.len < 1:
-    if n.kind notin {nkEmpty..nkNilLit}:
-      for i in countup(0, len(n)-1):
-        result = getPlainDocstring(n.sons[i])
-        if result.len > 0: return
-
-when false:
-  proc findDocComment(n: PNode): PNode =
-    if n == nil: return nil
-    if not isNil(n.comment) and startsWith(n.comment, "##"): return n
     for i in countup(0, safeLen(n)-1):
-      result = findDocComment(n.sons[i])
-      if result != nil: return
+      result = getPlainDocstring(n.sons[i])
+      if result.len > 0: return
+
+proc belongsToPackage(conf: ConfigRef; module: PSym): bool =
+  result = module.kind == skModule and module.owner != nil and
+      module.owner.id == conf.mainPackageId
+
+proc externalDep(d: PDoc; module: PSym): string =
+  if optWholeProject in d.conf.globalOptions:
+    let full = AbsoluteFile toFullPath(d.conf, FileIndex module.position)
+    let tmp = getOutFile2(d.conf, full.relativeTo(d.conf.projectPath), HtmlExt,
+        RelativeDir"htmldocs", sfMainModule notin module.flags)
+    result = relativeTo(tmp, d.thisDir, '/').string
+  else:
+    result = extractFilename toFullPath(d.conf, FileIndex module.position)
 
-  proc extractDocComment*(s: PSym, d: PDoc = nil): string =
-    let n = findDocComment(s.ast)
-    result = ""
-    if not n.isNil:
-      if not d.isNil:
-        var dummyHasToc: bool
-        renderRstToOut(d[], parseRst(n.comment, toFilename(n.info),
-                                     toLinenumber(n.info), toColumn(n.info),
-                                     dummyHasToc, d.options + {roSkipPounds}),
-                       result)
+proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) =
+  var r: TSrcGen
+  var literal = ""
+  initTokRender(r, n, renderFlags)
+  var kind = tkEof
+  while true:
+    getNextTok(r, kind, literal)
+    case kind
+    of tkEof:
+      break
+    of tkComment:
+      dispA(d.conf, result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
+            [rope(esc(d.target, literal))])
+    of tokKeywordLow..tokKeywordHigh:
+      dispA(d.conf, result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
+            [rope(literal)])
+    of tkOpr:
+      dispA(d.conf, result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
+            [rope(esc(d.target, literal))])
+    of tkStrLit..tkTripleStrLit:
+      dispA(d.conf, result, "<span class=\"StringLit\">$1</span>",
+            "\\spanStringLit{$1}", [rope(esc(d.target, literal))])
+    of tkCharLit:
+      dispA(d.conf, result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
+            [rope(esc(d.target, literal))])
+    of tkIntLit..tkUInt64Lit:
+      dispA(d.conf, result, "<span class=\"DecNumber\">$1</span>",
+            "\\spanDecNumber{$1}", [rope(esc(d.target, literal))])
+    of tkFloatLit..tkFloat128Lit:
+      dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>",
+            "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
+    of tkSymbol:
+      let s = getTokSym(r)
+      if s != nil and s.kind == skType and sfExported in s.flags and
+          s.owner != nil and belongsToPackage(d.conf, s.owner) and
+          d.target == outHtml:
+        let external = externalDep(d, s.owner)
+        result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
+          [rope changeFileExt(external, "html"), rope literal,
+           rope(esc(d.target, literal))]
       else:
-        result = n.comment.substr(2).replace("\n##", "\n").strip
+        dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
+              "\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
+    of tkSpaces, tkInvalid:
+      add(result, literal)
+    of tkCurlyDotLe:
+      dispA(d.conf, result, "<span>" & # This span is required for the JS to work properly
+        """<span class="Other">{</span><span class="Other pragmadots">...</span><span class="Other">}</span>
+</span>
+<span class="pragmawrap">
+<span class="Other">$1</span>
+<span class="pragma">""".replace("\n", ""),  # Must remove newlines because wrapped in a <pre>
+                    "\\spanOther{$1}",
+                  [rope(esc(d.target, literal))])
+    of tkCurlyDotRi:
+      dispA(d.conf, result, """
+</span>
+<span class="Other">$1</span>
+</span>""".replace("\n", ""),
+                    "\\spanOther{$1}",
+                  [rope(esc(d.target, literal))])
+    of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
+       tkBracketDotLe, tkBracketDotRi, tkParDotLe,
+       tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
+       tkAccent, tkColonColon,
+       tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr,
+       tkBracketLeColon:
+      dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
+            [rope(esc(d.target, literal))])
+
+proc testExample(d: PDoc; ex: PNode) =
+  if d.conf.errorCounter > 0: return
+  let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
+  createDir(outputDir)
+  inc d.exampleCounter
+  let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
+      "_examples" & $d.exampleCounter & ".nim"))
+  #let nimcache = outp.changeFileExt"" & "_nimcache"
+  renderModule(ex, d.filename, outp.string, conf = d.conf)
+  let backend = if isDefined(d.conf, "js"): "js"
+                elif isDefined(d.conf, "cpp"): "cpp"
+                elif isDefined(d.conf, "objc"): "objc"
+                else: "c"
+  if os.execShellCmd(os.getAppFilename() & " " & backend &
+                    " --path:" & quoteShell(d.conf.projectPath) &
+                    " --nimcache:" & quoteShell(outputDir) &
+                    " -r " & quoteShell(outp)) != 0:
+    quit "[Examples] failed: see " & outp.string
+  else:
+    # keep generated source file `outp` to allow inspection.
+    rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
+    removeFile(outp.changeFileExt(ExeExt))
+
+proc extractImports(n: PNode; result: PNode) =
+  if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}:
+    result.add copyTree(n)
+    n.kind = nkEmpty
+    return
+  for i in 0..<n.safeLen: extractImports(n[i], result)
+
+proc prepareExamples(d: PDoc; n: PNode) =
+
+  var docComment = newTree(nkCommentStmt)
+  let loc = d.conf.toFileLineCol(n.info)
+  docComment.comment = "autogenerated by docgen from " & loc
+
+  var runnableExamples = newTree(nkStmtList,
+      docComment,
+      newTree(nkImportStmt, newStrNode(nkStrLit, d.filename)))
+  runnableExamples.info = n.info
+  let imports = newTree(nkStmtList)
+  var savedLastSon = copyTree n.lastSon
+  extractImports(savedLastSon, imports)
+  for imp in imports: runnableExamples.add imp
+  runnableExamples.add newTree(nkBlockStmt, newNode(nkEmpty), copyTree savedLastSon)
+  testExample(d, runnableExamples)
+
+proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) =
+  if n.info.fileIndex != orig.info.fileIndex: return
+  case n.kind
+  of nkCallKinds:
+    if isRunnableExamples(n[0]) and
+        n.len >= 2 and n.lastSon.kind == nkStmtList:
+      prepareExamples(d, n)
+      dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n",
+          "\n\\textbf{$1}\n", [rope"Examples:"])
+      inc d.listingCounter
+      let id = $d.listingCounter
+      dest.add(d.config.getOrDefault"doc.listing_start" % [id, "langNim"])
+      # this is a rather hacky way to get rid of the initial indentation
+      # that the renderer currently produces:
+      var i = 0
+      var body = n.lastSon
+      if body.len == 1 and body.kind == nkStmtList and
+          body.lastSon.kind == nkStmtList:
+        body = body.lastSon
+      for b in body:
+        if i > 0: dest.add "\n"
+        inc i
+        nodeToHighlightedHtml(d, b, dest, {})
+      dest.add(d.config.getOrDefault"doc.listing_end" % id)
+  else: discard
+  for i in 0 ..< n.safeLen:
+    getAllRunnableExamplesRec(d, n[i], orig, dest)
 
-proc isVisible(n: PNode): bool =
+proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) =
+  getAllRunnableExamplesRec(d, n, n, dest)
+
+proc isVisible(d: PDoc; n: PNode): bool =
   result = false
   if n.kind == nkPostfix:
     if n.len == 2 and n.sons[0].kind == nkIdent:
@@ -241,8 +458,10 @@ proc isVisible(n: PNode): bool =
     # exception tracking information here. Instead we copy over the comment
     # from the proc header.
     result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported}
+    if result and containsOrIncl(d.emitted, n.sym.id):
+      result = false
   elif n.kind == nkPragmaExpr:
-    result = isVisible(n.sons[0])
+    result = isVisible(d, n.sons[0])
 
 proc getName(d: PDoc, n: PNode, splitAfter = -1): string =
   case n.kind
@@ -252,26 +471,25 @@ proc getName(d: PDoc, n: PNode, splitAfter = -1): string =
   of nkIdent: result = esc(d.target, n.ident.s, splitAfter)
   of nkAccQuoted:
     result = esc(d.target, "`")
-    for i in 0.. <n.len: result.add(getName(d, n[i], splitAfter))
+    for i in 0..<n.len: result.add(getName(d, n[i], splitAfter))
     result.add esc(d.target, "`")
   of nkOpenSymChoice, nkClosedSymChoice:
     result = getName(d, n[0], splitAfter)
   else:
-    internalError(n.info, "getName()")
     result = ""
 
-proc getNameIdent(n: PNode): PIdent =
+proc getNameIdent(cache: IdentCache; n: PNode): PIdent =
   case n.kind
-  of nkPostfix: result = getNameIdent(n.sons[1])
-  of nkPragmaExpr: result = getNameIdent(n.sons[0])
+  of nkPostfix: result = getNameIdent(cache, n.sons[1])
+  of nkPragmaExpr: result = getNameIdent(cache, n.sons[0])
   of nkSym: result = n.sym.name
   of nkIdent: result = n.ident
   of nkAccQuoted:
     var r = ""
-    for i in 0.. <n.len: r.add(getNameIdent(n[i]).s)
-    result = getIdent(r)
+    for i in 0..<n.len: r.add(getNameIdent(cache, n[i]).s)
+    result = getIdent(cache, r)
   of nkOpenSymChoice, nkClosedSymChoice:
-    result = getNameIdent(n[0])
+    result = getNameIdent(cache, n[0])
   else:
     result = nil
 
@@ -283,11 +501,10 @@ proc getRstName(n: PNode): PRstNode =
   of nkIdent: result = newRstNode(rnLeaf, n.ident.s)
   of nkAccQuoted:
     result = getRstName(n.sons[0])
-    for i in 1 .. <n.len: result.text.add(getRstName(n[i]).text)
+    for i in 1 ..< n.len: result.text.add(getRstName(n[i]).text)
   of nkOpenSymChoice, nkClosedSymChoice:
     result = getRstName(n[0])
   else:
-    internalError(n.info, "getRstName()")
     result = nil
 
 proc newUniquePlainSymbol(d: PDoc, original: string): string =
@@ -299,10 +516,8 @@ proc newUniquePlainSymbol(d: PDoc, original: string): string =
     result = original
     d.seenSymbols[original] = ""
     return
-
   # Iterate over possible numeric variants of the original name.
   var count = 2
-
   while true:
     result = original & "_" & $count
     if not d.seenSymbols.hasKey(result):
@@ -310,7 +525,6 @@ proc newUniquePlainSymbol(d: PDoc, original: string): string =
       break
     count += 1
 
-
 proc complexName(k: TSymKind, n: PNode, baseName: string): string =
   ## Builds a complex unique href name for the node.
   ##
@@ -325,27 +539,24 @@ proc complexName(k: TSymKind, n: PNode, baseName: string): string =
   ## section of ``doc/docgen.txt``.
   result = baseName
   case k:
-  of skProc: result.add(defaultParamSeparator)
+  of skProc, skFunc: result.add(defaultParamSeparator)
   of skMacro: result.add(".m" & defaultParamSeparator)
   of skMethod: result.add(".e" & defaultParamSeparator)
   of skIterator: result.add(".i" & defaultParamSeparator)
   of skTemplate: result.add(".t" & defaultParamSeparator)
   of skConverter: result.add(".c" & defaultParamSeparator)
   else: discard
-
   if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams:
     result.add(renderParamTypes(n[paramsPos]))
 
-
 proc isCallable(n: PNode): bool =
   ## Returns true if `n` contains a callable node.
   case n.kind
   of nkProcDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef,
-    nkConverterDef: result = true
+    nkConverterDef, nkFuncDef: result = true
   else:
     result = false
 
-
 proc docstringSummary(rstText: string): string =
   ## Returns just the first line or a brief chunk of text from a rst string.
   ##
@@ -373,17 +584,17 @@ proc docstringSummary(rstText: string): string =
     result.delete(pos, last)
     result.add("…")
 
-
 proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
-  if not isVisible(nameNode): return
+  if not isVisible(d, nameNode): return
   let
     name = getName(d, nameNode)
     nameRope = name.rope
-    plainDocstring = getPlainDocstring(n) # call here before genRecComment!
+  var plainDocstring = getPlainDocstring(n) # call here before genRecComment!
   var result: Rope = nil
   var literal, plainName = ""
   var kind = tkEof
   var comm = genRecComment(d, n)  # call this here for the side-effect!
+  getAllRunnableExamples(d, n, comm)
   var r: TSrcGen
   # Obtain the plain rendered string for hyperlink titles.
   initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments,
@@ -394,54 +605,8 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
       break
     plainName.add(literal)
 
-  # Render the HTML hyperlink.
-  initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
-  while true:
-    getNextTok(r, kind, literal)
-    case kind
-    of tkEof:
-      break
-    of tkComment:
-      dispA(result, "<span class=\"Comment\">$1</span>", "\\spanComment{$1}",
-            [rope(esc(d.target, literal))])
-    of tokKeywordLow..tokKeywordHigh:
-      dispA(result, "<span class=\"Keyword\">$1</span>", "\\spanKeyword{$1}",
-            [rope(literal)])
-    of tkOpr:
-      dispA(result, "<span class=\"Operator\">$1</span>", "\\spanOperator{$1}",
-            [rope(esc(d.target, literal))])
-    of tkStrLit..tkTripleStrLit:
-      dispA(result, "<span class=\"StringLit\">$1</span>",
-            "\\spanStringLit{$1}", [rope(esc(d.target, literal))])
-    of tkCharLit:
-      dispA(result, "<span class=\"CharLit\">$1</span>", "\\spanCharLit{$1}",
-            [rope(esc(d.target, literal))])
-    of tkIntLit..tkUInt64Lit:
-      dispA(result, "<span class=\"DecNumber\">$1</span>",
-            "\\spanDecNumber{$1}", [rope(esc(d.target, literal))])
-    of tkFloatLit..tkFloat128Lit:
-      dispA(result, "<span class=\"FloatNumber\">$1</span>",
-            "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
-    of tkSymbol:
-      dispA(result, "<span class=\"Identifier\">$1</span>",
-            "\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
-    of tkSpaces, tkInvalid:
-      add(result, literal)
-    of tkCurlyDotLe:
-      dispA(result, """<span class="Other pragmabegin">$1</span><div class="pragma">""",
-                    "\\spanOther{$1}",
-                  [rope(esc(d.target, literal))])
-    of tkCurlyDotRi:
-      dispA(result, "</div><span class=\"Other pragmaend\">$1</span>",
-                    "\\spanOther{$1}",
-                  [rope(esc(d.target, literal))])
-    of tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
-       tkBracketDotLe, tkBracketDotRi, tkParDotLe,
-       tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
-       tkAccent, tkColonColon,
-       tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr:
-      dispA(result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}",
-            [rope(esc(d.target, literal))])
+  nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments,
+    renderDocComments, renderSyms})
 
   inc(d.id)
   let
@@ -456,31 +621,45 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
     symbolOrIdEncRope = encodeUrl(symbolOrId).rope
 
   var seeSrcRope: Rope = nil
-  let docItemSeeSrc = getConfigVar("doc.item.seesrc")
+  let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
   if docItemSeeSrc.len > 0:
-    let cwd = getCurrentDir().canonicalizePath()
-    var path = n.info.toFullPath
-    if path.startsWith(cwd):
-      path = path[cwd.len+1 .. ^1].replace('\\', '/')
-    var commit = getConfigVar("git.commit")
-    if commit.len == 0: commit = "master"
-    dispA(seeSrcRope, "$1", "", [ropeFormatNamedVars(docItemSeeSrc,
-        ["path", "line", "url", "commit"], [rope path,
-        rope($n.info.line), rope getConfigVar("git.url"),
-        rope commit])])
-
-  add(d.section[k], ropeFormatNamedVars(getConfigVar("doc.item"),
+    let path = relativeTo(AbsoluteFile toFullPath(d.conf, n.info), AbsoluteDir getCurrentDir(), '/')
+    when false:
+      let cwd = canonicalizePath(d.conf, getCurrentDir())
+      var path = toFullPath(d.conf, n.info)
+      if path.startsWith(cwd):
+        path = path[cwd.len+1 .. ^1].replace('\\', '/')
+    let gitUrl = getConfigVar(d.conf, "git.url")
+    if gitUrl.len > 0:
+      let commit = getConfigVar(d.conf, "git.commit", "master")
+      let develBranch = getConfigVar(d.conf, "git.devel", "devel")
+      dispA(d.conf, seeSrcRope, "$1", "", [ropeFormatNamedVars(d.conf, docItemSeeSrc,
+          ["path", "line", "url", "commit", "devel"], [rope path.string,
+          rope($n.info.line), rope gitUrl, rope commit, rope develBranch])])
+
+  add(d.section[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item"),
     ["name", "header", "desc", "itemID", "header_plain", "itemSym",
       "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "seeSrc"],
     [nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope,
       symbolOrIdRope, plainSymbolEncRope, symbolOrIdEncRope, seeSrcRope]))
 
+  let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string
+
   var attype: Rope
   if k in routineKinds and nameNode.kind == nkSym:
     let att = attachToType(d, nameNode.sym)
     if att != nil:
       attype = rope esc(d.target, att.name.s)
-  add(d.toc[k], ropeFormatNamedVars(getConfigVar("doc.item.toc"),
+  elif k == skType and nameNode.kind == nkSym and nameNode.sym.typ.kind in {tyEnum, tyBool}:
+    let etyp = nameNode.sym.typ
+    for e in etyp.n:
+      if e.sym.kind != skEnumField: continue
+      let plain = renderPlainSymbolName(e)
+      let symbolOrId = d.newUniquePlainSymbol(plain)
+      setIndexTerm(d[], external, symbolOrId, plain, nameNode.sym.name.s & '.' & plain,
+        xmltree.escape(getPlainDocstring(e).docstringSummary))
+
+  add(d.toc[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item.toc"),
     ["name", "header", "desc", "itemID", "header_plain", "itemSym",
       "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "attype"],
     [rope(getName(d, nameNode, d.splitAfter)), result, comm,
@@ -490,57 +669,91 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
   # Ironically for types the complexSymbol is *cleaner* than the plainName
   # because it doesn't include object fields or documentation comments. So we
   # use the plain one for callable elements, and the complex for the rest.
-  var linkTitle = changeFileExt(extractFilename(d.filename), "") & " : "
+  var linkTitle = changeFileExt(extractFilename(d.filename), "") & ": "
   if n.isCallable: linkTitle.add(xmltree.escape(plainName.strip))
   else: linkTitle.add(xmltree.escape(complexSymbol.strip))
 
-  setIndexTerm(d[], symbolOrId, name, linkTitle,
+  setIndexTerm(d[], external, symbolOrId, name, linkTitle,
     xmltree.escape(plainDocstring.docstringSummary))
   if k == skType and nameNode.kind == nkSym:
     d.types.strTableAdd nameNode.sym
 
 proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
-  if not isVisible(nameNode): return
+  if not isVisible(d, nameNode): return
   var
     name = getName(d, nameNode)
     comm = $genRecComment(d, n)
     r: TSrcGen
-
   initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments})
-
-  result = %{ "name": %name, "type": %($k), "line": %n.info.line,
+  result = %{ "name": %name, "type": %($k), "line": %n.info.line.int,
                  "col": %n.info.col}
-  if comm != nil and comm != "":
+  if comm.len > 0:
     result["description"] = %comm
-  if r.buf != nil:
+  if r.buf.len > 0:
     result["code"] = %r.buf
 
 proc checkForFalse(n: PNode): bool =
   result = n.kind == nkIdent and cmpIgnoreStyle(n.ident.s, "false") == 0
 
-proc traceDeps(d: PDoc, n: PNode) =
+proc traceDeps(d: PDoc, it: PNode) =
   const k = skModule
-  if d.section[k] != nil: add(d.section[k], ", ")
-  dispA(d.section[k],
-        "<a class=\"reference external\" href=\"$1.html\">$1</a>",
-        "$1", [rope(getModuleName(n))])
-
-proc generateDoc*(d: PDoc, n: PNode) =
+  if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
+    let sep = it[0]
+    let dir = it[1]
+    let a = newNodeI(nkInfix, it.info)
+    a.add sep
+    a.add dir
+    a.add sep # dummy entry, replaced in the loop
+    for x in it[2]:
+      a.sons[2] = x
+      traceDeps(d, a)
+  elif it.kind == nkSym and belongsToPackage(d.conf, it.sym):
+    let external = externalDep(d, it.sym)
+    if d.section[k] != nil: add(d.section[k], ", ")
+    dispA(d.conf, d.section[k],
+          "<a class=\"reference external\" href=\"$2\">$1</a>",
+          "$1", [rope esc(d.target, changeFileExt(external, "")),
+          rope changeFileExt(external, "html")])
+
+proc exportSym(d: PDoc; s: PSym) =
+  const k = exportSection
+  if s.kind == skModule and belongsToPackage(d.conf, s):
+    let external = externalDep(d, s)
+    if d.section[k] != nil: add(d.section[k], ", ")
+    dispA(d.conf, d.section[k],
+          "<a class=\"reference external\" href=\"$2\">$1</a>",
+          "$1", [rope esc(d.target, changeFileExt(external, "")),
+          rope changeFileExt(external, "html")])
+  elif s.kind != skModule and s.owner != nil:
+    let module = originatingModule(s)
+    if belongsToPackage(d.conf, module):
+      let external = externalDep(d, module)
+      if d.section[k] != nil: add(d.section[k], ", ")
+      # XXX proper anchor generation here
+      dispA(d.conf, d.section[k],
+            "<a href=\"$2#$1\"><span class=\"Identifier\">$1</span></a>",
+            "$1", [rope esc(d.target, s.name.s),
+            rope changeFileExt(external, "html")])
+
+proc generateDoc*(d: PDoc, n, orig: PNode) =
   case n.kind
   of nkCommentStmt: add(d.modDesc, genComment(d, n))
   of nkProcDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     genItem(d, n, n.sons[namePos], skProc)
+  of nkFuncDef:
+    when useEffectSystem: documentRaises(d.cache, n)
+    genItem(d, n, n.sons[namePos], skFunc)
   of nkMethodDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     genItem(d, n, n.sons[namePos], skMethod)
   of nkIteratorDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     genItem(d, n, n.sons[namePos], skIterator)
   of nkMacroDef: genItem(d, n, n.sons[namePos], skMacro)
   of nkTemplateDef: genItem(d, n, n.sons[namePos], skTemplate)
   of nkConverterDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     genItem(d, n, n.sons[namePos], skConverter)
   of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
     for i in countup(0, sonsLen(n) - 1):
@@ -549,41 +762,52 @@ proc generateDoc*(d: PDoc, n: PNode) =
         genItem(d, n.sons[i], n.sons[i].sons[0],
                 succ(skType, ord(n.kind)-ord(nkTypeSection)))
   of nkStmtList:
-    for i in countup(0, sonsLen(n) - 1): generateDoc(d, n.sons[i])
+    for i in countup(0, sonsLen(n) - 1): generateDoc(d, n.sons[i], orig)
   of nkWhenStmt:
     # generate documentation for the first branch only:
     if not checkForFalse(n.sons[0].sons[0]):
-      generateDoc(d, lastSon(n.sons[0]))
+      generateDoc(d, lastSon(n.sons[0]), orig)
   of nkImportStmt:
-    for i in 0 .. sonsLen(n)-1: traceDeps(d, n.sons[i])
+    for it in n: traceDeps(d, it)
+  of nkExportStmt:
+    for it in n:
+      if it.kind == nkSym: exportSym(d, it.sym)
+  of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept"
   of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0])
+  of nkCallKinds:
+    var comm: Rope = nil
+    getAllRunnableExamples(d, n, comm)
+    if comm != nil: add(d.modDesc, comm)
   else: discard
 
 proc add(d: PDoc; j: JsonNode) =
   if j != nil: d.jArray.add j
 
-proc generateJson*(d: PDoc, n: PNode) =
+proc generateJson*(d: PDoc, n: PNode, includeComments: bool = true) =
   case n.kind
   of nkCommentStmt:
-    if n.comment != nil and startsWith(n.comment, "##"):
-      let stripped = n.comment.substr(2).strip
-      d.add %{ "comment": %stripped, "line": %n.info.line,
-               "col": %n.info.col }
+    if includeComments:
+      d.add %*{"comment": genComment(d, n)}
+    else:
+      add(d.modDesc, genComment(d, n))
   of nkProcDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     d.add genJsonItem(d, n, n.sons[namePos], skProc)
+  of nkFuncDef:
+    when useEffectSystem: documentRaises(d.cache, n)
+    d.add genJsonItem(d, n, n.sons[namePos], skFunc)
   of nkMethodDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     d.add genJsonItem(d, n, n.sons[namePos], skMethod)
   of nkIteratorDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     d.add genJsonItem(d, n, n.sons[namePos], skIterator)
   of nkMacroDef:
     d.add genJsonItem(d, n, n.sons[namePos], skMacro)
   of nkTemplateDef:
     d.add genJsonItem(d, n, n.sons[namePos], skTemplate)
   of nkConverterDef:
-    when useEffectSystem: documentRaises(n)
+    when useEffectSystem: documentRaises(d.cache, n)
     d.add genJsonItem(d, n, n.sons[namePos], skConverter)
   of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
     for i in countup(0, sonsLen(n) - 1):
@@ -593,24 +817,67 @@ proc generateJson*(d: PDoc, n: PNode) =
                 succ(skType, ord(n.kind)-ord(nkTypeSection)))
   of nkStmtList:
     for i in countup(0, sonsLen(n) - 1):
-      generateJson(d, n.sons[i])
+      generateJson(d, n.sons[i], includeComments)
   of nkWhenStmt:
     # generate documentation for the first branch only:
     if not checkForFalse(n.sons[0].sons[0]):
-      generateJson(d, lastSon(n.sons[0]))
+      generateJson(d, lastSon(n.sons[0]), includeComments)
+  else: discard
+
+proc genTagsItem(d: PDoc, n, nameNode: PNode, k: TSymKind): string =
+  result = getName(d, nameNode) & "\n"
+
+proc generateTags*(d: PDoc, n: PNode, r: var Rope) =
+  case n.kind
+  of nkCommentStmt:
+    if startsWith(n.comment, "##"):
+      let stripped = n.comment.substr(2).strip
+      r.add stripped
+  of nkProcDef:
+    when useEffectSystem: documentRaises(d.cache, n)
+    r.add genTagsItem(d, n, n.sons[namePos], skProc)
+  of nkFuncDef:
+    when useEffectSystem: documentRaises(d.cache, n)
+    r.add genTagsItem(d, n, n.sons[namePos], skFunc)
+  of nkMethodDef:
+    when useEffectSystem: documentRaises(d.cache, n)
+    r.add genTagsItem(d, n, n.sons[namePos], skMethod)
+  of nkIteratorDef:
+    when useEffectSystem: documentRaises(d.cache, n)
+    r.add genTagsItem(d, n, n.sons[namePos], skIterator)
+  of nkMacroDef:
+    r.add genTagsItem(d, n, n.sons[namePos], skMacro)
+  of nkTemplateDef:
+    r.add genTagsItem(d, n, n.sons[namePos], skTemplate)
+  of nkConverterDef:
+    when useEffectSystem: documentRaises(d.cache, n)
+    r.add genTagsItem(d, n, n.sons[namePos], skConverter)
+  of nkTypeSection, nkVarSection, nkLetSection, nkConstSection:
+    for i in countup(0, sonsLen(n) - 1):
+      if n.sons[i].kind != nkCommentStmt:
+        # order is always 'type var let const':
+        r.add genTagsItem(d, n.sons[i], n.sons[i].sons[0],
+                succ(skType, ord(n.kind)-ord(nkTypeSection)))
+  of nkStmtList:
+    for i in countup(0, sonsLen(n) - 1):
+      generateTags(d, n.sons[i], r)
+  of nkWhenStmt:
+    # generate documentation for the first branch only:
+    if not checkForFalse(n.sons[0].sons[0]):
+      generateTags(d, lastSon(n.sons[0]), r)
   else: discard
 
 proc genSection(d: PDoc, kind: TSymKind) =
-  const sectionNames: array[skModule..skTemplate, string] = [
-    "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Methods",
-    "Iterators", "Converters", "Macros", "Templates"
+  const sectionNames: array[skTemp..skTemplate, string] = [
+    "Exports", "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs",
+    "Methods", "Iterators", "Converters", "Macros", "Templates"
   ]
   if d.section[kind] == nil: return
   var title = sectionNames[kind].rope
-  d.section[kind] = ropeFormatNamedVars(getConfigVar("doc.section"), [
+  d.section[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section"), [
       "sectionid", "sectionTitle", "sectionTitleID", "content"], [
       ord(kind).rope, title, rope(ord(kind) + 50), d.section[kind]])
-  d.toc[kind] = ropeFormatNamedVars(getConfigVar("doc.section.toc"), [
+  d.toc[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section.toc"), [
       "sectionid", "sectionTitle", "sectionTitleID", "content"], [
       ord(kind).rope, title, rope(ord(kind) + 50), d.toc[kind]])
 
@@ -626,27 +893,28 @@ proc genOutFile(d: PDoc): Rope =
     genSection(d, i)
     add(toc, d.toc[i])
   if toc != nil:
-    toc = ropeFormatNamedVars(getConfigVar("doc.toc"), ["content"], [toc])
+    toc = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.toc"), ["content"], [toc])
   for i in countup(low(TSymKind), high(TSymKind)): add(code, d.section[i])
 
   # Extract the title. Non API modules generate an entry in the index table.
   if d.meta[metaTitle].len != 0:
     title = d.meta[metaTitle]
-    setIndexTerm(d[], "", title)
+    let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string
+    setIndexTerm(d[], external, "", title)
   else:
     # Modules get an automatic title for the HTML, but no entry in the index.
-    title = "Module " & extractFilename(changeFileExt(d.filename, ""))
+    title = extractFilename(changeFileExt(d.filename, ""))
 
   let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group"
                  elif d.hasToc: "doc.body_toc"
                  else: "doc.body_no_toc"
-  content = ropeFormatNamedVars(getConfigVar(bodyname), ["title",
+  content = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, bodyname), ["title",
       "tableofcontents", "moduledesc", "date", "time", "content"],
       [title.rope, toc, d.modDesc, rope(getDateStr()),
       rope(getClockStr()), code])
-  if optCompileOnly notin gGlobalOptions:
+  if optCompileOnly notin d.conf.globalOptions:
     # XXX what is this hack doing here? 'optCompileOnly' means raw output!?
-    code = ropeFormatNamedVars(getConfigVar("doc.file"), ["title",
+    code = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.file"), ["title",
         "tableofcontents", "moduledesc", "date", "time",
         "content", "author", "version", "analytics"],
         [title.rope, toc, d.modDesc, rope(getDateStr()),
@@ -657,93 +925,127 @@ proc genOutFile(d: PDoc): Rope =
   result = code
 
 proc generateIndex*(d: PDoc) =
-  if optGenIndex in gGlobalOptions:
-    writeIndexFile(d[], splitFile(options.outFile).dir /
-                        splitFile(d.filename).name & IndexExt)
-
-proc getOutFile2(filename, ext, dir: string): string =
-  if gWholeProject:
-    let d = if options.outFile != "": options.outFile else: dir
-    createDir(d)
-    result = d / changeFileExt(filename, ext)
-  else:
-    result = getOutFile(filename, ext)
-
-proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) =
+  if optGenIndex in d.conf.globalOptions:
+    let dir = if d.conf.outFile.isEmpty: d.conf.projectPath / RelativeDir"htmldocs"
+              elif optWholeProject in d.conf.globalOptions: AbsoluteDir(d.conf.outFile)
+              else: AbsoluteDir(d.conf.outFile.string.splitFile.dir)
+    createDir(dir)
+    let dest = dir / changeFileExt(relativeTo(AbsoluteFile d.filename,
+                                              d.conf.projectPath), IndexExt)
+    writeIndexFile(d[], dest.string)
+
+proc writeOutput*(d: PDoc, useWarning = false) =
   var content = genOutFile(d)
-  if optStdout in gGlobalOptions:
+  if optStdout in d.conf.globalOptions:
     writeRope(stdout, content)
   else:
-    writeRope(content, getOutFile2(filename, outExt, "htmldocs"), useWarning)
-
-proc writeOutputJson*(d: PDoc, filename, outExt: string,
-                      useWarning = false) =
+    template outfile: untyped = d.destFile
+    #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt, "htmldocs")
+    createDir(outfile.splitFile.dir)
+    if not writeRope(content, outfile):
+      rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile,
+        outfile.string)
+
+proc writeOutputJson*(d: PDoc, useWarning = false) =
+  var modDesc: string
+  for desc in d.modDesc:
+    modDesc &= desc
   let content = %*{"orig": d.filename,
-    "nimble": getPackageName(d.filename),
+    "nimble": getPackageName(d.conf, d.filename),
+    "moduleDescription": modDesc,
     "entries": d.jArray}
-  if optStdout in gGlobalOptions:
+  if optStdout in d.conf.globalOptions:
     write(stdout, $content)
   else:
     var f: File
-    if open(f, getOutFile2(splitFile(filename).name,
-            outExt, "jsondocs"), fmWrite):
+    if open(f, d.destFile.string, fmWrite):
       write(f, $content)
       close(f)
     else:
-      discard "fixme: error report"
+      localError(d.conf, newLineInfo(d.conf, AbsoluteFile d.filename, -1, -1),
+                 warnUser, "unable to open file \"" & d.destFile.string &
+                 "\" for writing")
 
-proc commandDoc*() =
-  var ast = parseFile(gProjectMainIdx, newIdentCache())
+proc commandDoc*(cache: IdentCache, conf: ConfigRef) =
+  var ast = parseFile(conf.projectMainIdx, cache, conf)
   if ast == nil: return
-  var d = newDocumentor(gProjectFull, options.gConfigVars)
+  var d = newDocumentor(conf.projectFull, cache, conf)
   d.hasToc = true
-  generateDoc(d, ast)
-  writeOutput(d, gProjectFull, HtmlExt)
+  generateDoc(d, ast, ast)
+  writeOutput(d)
   generateIndex(d)
 
-proc commandRstAux(filename, outExt: string) =
+proc commandRstAux(cache: IdentCache, conf: ConfigRef;
+                   filename: AbsoluteFile, outExt: string) =
   var filen = addFileExt(filename, "txt")
-  var d = newDocumentor(filen, options.gConfigVars)
+  var d = newDocumentor(filen, cache, conf, outExt)
+
   d.isPureRst = true
-  var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc,
-                     {roSupportRawDirective})
+  var rst = parseRst(readFile(filen.string), filen.string, 0, 1, d.hasToc,
+                     {roSupportRawDirective}, conf)
   var modDesc = newStringOfCap(30_000)
-  #d.modDesc = newMutableRope(30_000)
   renderRstToOut(d[], rst, modDesc)
-  #freezeMutableRope(d.modDesc)
   d.modDesc = rope(modDesc)
-  writeOutput(d, filename, outExt)
+  writeOutput(d)
   generateIndex(d)
 
-proc commandRst2Html*() =
-  commandRstAux(gProjectFull, HtmlExt)
+proc commandRst2Html*(cache: IdentCache, conf: ConfigRef) =
+  commandRstAux(cache, conf, conf.projectFull, HtmlExt)
 
-proc commandRst2TeX*() =
-  splitter = "\\-"
-  commandRstAux(gProjectFull, TexExt)
+proc commandRst2TeX*(cache: IdentCache, conf: ConfigRef) =
+  commandRstAux(cache, conf, conf.projectFull, TexExt)
 
-proc commandJson*() =
-  var ast = parseFile(gProjectMainIdx, newIdentCache())
+proc commandJson*(cache: IdentCache, conf: ConfigRef) =
+  var ast = parseFile(conf.projectMainIdx, cache, conf)
   if ast == nil: return
-  var d = newDocumentor(gProjectFull, options.gConfigVars)
+  var d = newDocumentor(conf.projectFull, cache, conf)
+  d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
+                          status: int; content: string) =
+    localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
+               warnUser, "the ':test:' attribute is not supported by this backend")
   d.hasToc = true
   generateJson(d, ast)
   let json = d.jArray
   let content = rope(pretty(json))
 
-  if optStdout in gGlobalOptions:
+  if optStdout in d.conf.globalOptions:
     writeRope(stdout, content)
   else:
     #echo getOutFile(gProjectFull, JsonExt)
-    writeRope(content, getOutFile(gProjectFull, JsonExt), useWarning = false)
+    let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
+    if not writeRope(content, filename):
+      rawMessage(conf, errCannotOpenFile, filename.string)
+
+proc commandTags*(cache: IdentCache, conf: ConfigRef) =
+  var ast = parseFile(conf.projectMainIdx, cache, conf)
+  if ast == nil: return
+  var d = newDocumentor(conf.projectFull, cache, conf)
+  d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
+                          status: int; content: string) =
+    localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
+               warnUser, "the ':test:' attribute is not supported by this backend")
+  d.hasToc = true
+  var
+    content: Rope
+  generateTags(d, ast, content)
+
+  if optStdout in d.conf.globalOptions:
+    writeRope(stdout, content)
+  else:
+    #echo getOutFile(gProjectFull, TagsExt)
+    let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
+    if not writeRope(content, filename):
+      rawMessage(conf, errCannotOpenFile, filename.string)
 
-proc commandBuildIndex*() =
-  var content = mergeIndexes(gProjectFull).rope
+proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) =
+  var content = mergeIndexes(conf.projectFull.string).rope
 
-  let code = ropeFormatNamedVars(getConfigVar("doc.file"), ["title",
+  let code = ropeFormatNamedVars(conf, getConfigVar(conf, "doc.file"), ["title",
       "tableofcontents", "moduledesc", "date", "time",
       "content", "author", "version", "analytics"],
       ["Index".rope, nil, nil, rope(getDateStr()),
                    rope(getClockStr()), content, nil, nil, nil])
   # no analytics because context is not available
-  writeRope(code, getOutFile("theindex", HtmlExt))
+  let filename = getOutFile(conf, RelativeFile"theindex", HtmlExt)
+  if not writeRope(code, filename):
+    rawMessage(conf, errCannotOpenFile, filename.string)
diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim
index 118f1c7c5..f2ac6c1a9 100644
--- a/compiler/docgen2.nim
+++ b/compiler/docgen2.nim
@@ -11,22 +11,26 @@
 # semantic checking.
 
 import
-  os, options, ast, astalgo, msgs, ropes, idents, passes, docgen
+  os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos,
+  pathutils
 
-from modulegraphs import ModuleGraph
+from modulegraphs import ModuleGraph, PPassContext
 
 type
-  TGen = object of TPassContext
+  TGen = object of PPassContext
     doc: PDoc
     module: PSym
   PGen = ref TGen
 
+template shouldProcess(g): bool =
+  (g.module.owner.id == g.doc.conf.mainPackageId and optWholeProject in g.doc.conf.globalOptions) or
+      sfMainModule in g.module.flags
+
 template closeImpl(body: untyped) {.dirty.} =
   var g = PGen(p)
   let useWarning = sfMainModule notin g.module.flags
   #echo g.module.name.s, " ", g.module.owner.id, " ", gMainPackageId
-  if (g.module.owner.id == gMainPackageId and gWholeProject) or
-    sfMainModule in g.module.flags:
+  if shouldProcess(g):
     body
     try:
       generateIndex(g.doc)
@@ -35,33 +39,42 @@ template closeImpl(body: untyped) {.dirty.} =
 
 proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
   closeImpl:
-    writeOutput(g.doc, g.module.filename, HtmlExt, useWarning)
+    writeOutput(g.doc, useWarning)
 
 proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
   closeImpl:
-    writeOutputJson(g.doc, g.module.filename, ".json", useWarning)
+    writeOutputJson(g.doc, useWarning)
 
 proc processNode(c: PPassContext, n: PNode): PNode =
   result = n
   var g = PGen(c)
-  generateDoc(g.doc, n)
+  if shouldProcess(g):
+    generateDoc(g.doc, n, n)
 
 proc processNodeJson(c: PPassContext, n: PNode): PNode =
   result = n
   var g = PGen(c)
-  generateJson(g.doc, n)
+  if shouldProcess(g):
+    generateJson(g.doc, n, false)
 
-proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
+template myOpenImpl(ext: untyped) {.dirty.} =
   var g: PGen
   new(g)
   g.module = module
-  var d = newDocumentor(module.filename, options.gConfigVars)
+  var d = newDocumentor(AbsoluteFile toFullPath(graph.config, FileIndex module.position),
+      graph.cache, graph.config, ext)
   d.hasToc = true
   g.doc = d
   result = g
 
+proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
+  myOpenImpl(HtmlExt)
+
+proc myOpenJson(graph: ModuleGraph; module: PSym): PPassContext =
+  myOpenImpl(JsonExt)
+
 const docgen2Pass* = makePass(open = myOpen, process = processNode, close = close)
-const docgen2JsonPass* = makePass(open = myOpen, process = processNodeJson,
+const docgen2JsonPass* = makePass(open = myOpenJson, process = processNodeJson,
                                   close = closeJson)
 
 proc finishDoc2Pass*(project: string) =
diff --git a/compiler/evalffi.nim b/compiler/evalffi.nim
index 987cfaf42..e863c8995 100644
--- a/compiler/evalffi.nim
+++ b/compiler/evalffi.nim
@@ -86,10 +86,10 @@ proc mapType(t: ast.PType): ptr libffi.TType =
     else: result = nil
   of tyFloat, tyFloat64: result = addr libffi.type_double
   of tyFloat32: result = addr libffi.type_float
-  of tyVar, tyPointer, tyPtr, tyRef, tyCString, tySequence, tyString, tyExpr,
+  of tyVar, tyLent, tyPointer, tyPtr, tyRef, tyCString, tySequence, tyString, tyExpr,
      tyStmt, tyTypeDesc, tyProc, tyArray, tyStatic, tyNil:
     result = addr libffi.type_pointer
-  of tyDistinct, tyAlias:
+  of tyDistinct, tyAlias, tySink:
     result = mapType(t.sons[0])
   else:
     result = nil
@@ -104,20 +104,20 @@ proc mapCallConv(cc: TCallingConvention, info: TLineInfo): TABI =
   else:
     globalError(info, "cannot map calling convention to FFI")
 
-template rd(T, p: expr): expr {.immediate.} = (cast[ptr T](p))[]
-template wr(T, p, v: expr) {.immediate.} = (cast[ptr T](p))[] = v
-template `+!`(x, y: expr): expr {.immediate.} =
+template rd(T, p: untyped): untyped = (cast[ptr T](p))[]
+template wr(T, p, v: untyped): untyped = (cast[ptr T](p))[] = v
+template `+!`(x, y: untyped): untyped =
   cast[pointer](cast[ByteAddress](x) + y)
 
 proc packSize(v: PNode, typ: PType): int =
   ## computes the size of the blob
   case typ.kind
-  of tyPtr, tyRef, tyVar:
+  of tyPtr, tyRef, tyVar, tyLent:
     if v.kind in {nkNilLit, nkPtrLit}:
       result = sizeof(pointer)
     else:
       result = sizeof(pointer) + packSize(v.sons[0], typ.lastSon)
-  of tyDistinct, tyGenericInst, tyAlias:
+  of tyDistinct, tyGenericInst, tyAlias, tySink:
     result = packSize(v, typ.sons[0])
   of tyArray:
     # consider: ptr array[0..1000_000, int] which is common for interfacing;
@@ -151,7 +151,7 @@ proc getField(n: PNode; position: int): PSym =
   else: discard
 
 proc packObject(x: PNode, typ: PType, res: pointer) =
-  internalAssert x.kind in {nkObjConstr, nkPar}
+  internalAssert x.kind in {nkObjConstr, nkPar, nkTupleConstr}
   # compute the field's offsets:
   discard typ.getSize
   for i in countup(ord(x.kind == nkObjConstr), sonsLen(x) - 1):
@@ -171,7 +171,7 @@ const maxPackDepth = 20
 var packRecCheck = 0
 
 proc pack(v: PNode, typ: PType, res: pointer) =
-  template awr(T, v: expr) {.immediate, dirty.} =
+  template awr(T, v: untyped): untyped =
     wr(T, res, v)
 
   case typ.kind
@@ -209,7 +209,7 @@ proc pack(v: PNode, typ: PType, res: pointer) =
       awr(cstring, cstring(v.strVal))
     else:
       globalError(v.info, "cannot map pointer/proc value to FFI")
-  of tyPtr, tyRef, tyVar:
+  of tyPtr, tyRef, tyVar, tyLent:
     if v.kind == nkNilLit:
       # nothing to do since the memory is 0 initialized anyway
       discard
@@ -225,13 +225,13 @@ proc pack(v: PNode, typ: PType, res: pointer) =
       awr(pointer, res +! sizeof(pointer))
   of tyArray:
     let baseSize = typ.sons[1].getSize
-    for i in 0 .. <v.len:
+    for i in 0 ..< v.len:
       pack(v.sons[i], typ.sons[1], res +! i * baseSize)
   of tyObject, tyTuple:
     packObject(v, typ, res)
   of tyNil:
     discard
-  of tyDistinct, tyGenericInst, tyAlias:
+  of tyDistinct, tyGenericInst, tyAlias, tySink:
     pack(v, typ.sons[0], res)
   else:
     globalError(v.info, "cannot map value to FFI " & typeToString(v.typ))
@@ -260,14 +260,14 @@ proc unpackObject(x: pointer, typ: PType, n: PNode): PNode =
   # iterate over any actual field of 'n' ... if n is nil we need to create
   # the nkPar node:
   if n.isNil:
-    result = newNode(nkPar)
+    result = newNode(nkTupleConstr)
     result.typ = typ
     if typ.n.isNil:
       internalError("cannot unpack unnamed tuple")
     unpackObjectAdd(x, typ.n, result)
   else:
     result = n
-    if result.kind notin {nkObjConstr, nkPar}:
+    if result.kind notin {nkObjConstr, nkPar, nkTupleConstr}:
       globalError(n.info, "cannot map value from FFI")
     if typ.n.isNil:
       globalError(n.info, "cannot unpack unnamed tuple")
@@ -291,7 +291,7 @@ proc unpackArray(x: pointer, typ: PType, n: PNode): PNode =
     if result.kind != nkBracket:
       globalError(n.info, "cannot map value from FFI")
   let baseSize = typ.sons[1].getSize
-  for i in 0 .. < result.len:
+  for i in 0 ..< result.len:
     result.sons[i] = unpack(x +! i * baseSize, typ.sons[1], result.sons[i])
 
 proc canonNodeKind(k: TNodeKind): TNodeKind =
@@ -302,7 +302,7 @@ proc canonNodeKind(k: TNodeKind): TNodeKind =
   else: result = k
 
 proc unpack(x: pointer, typ: PType, n: PNode): PNode =
-  template aw(k, v, field: expr) {.immediate, dirty.} =
+  template aw(k, v, field: untyped): untyped =
     if n.isNil:
       result = newNode(k)
       result.typ = typ
@@ -326,9 +326,9 @@ proc unpack(x: pointer, typ: PType, n: PNode): PNode =
       result.kind = nkNilLit
       result.typ = typ
 
-  template awi(kind, v: expr) {.immediate, dirty.} = aw(kind, v, intVal)
-  template awf(kind, v: expr) {.immediate, dirty.} = aw(kind, v, floatVal)
-  template aws(kind, v: expr) {.immediate, dirty.} = aw(kind, v, strVal)
+  template awi(kind, v: untyped): untyped = aw(kind, v, intVal)
+  template awf(kind, v: untyped): untyped = aw(kind, v, floatVal)
+  template aws(kind, v: untyped): untyped = aw(kind, v, strVal)
 
   case typ.kind
   of tyBool: awi(nkIntLit, rd(bool, x).ord)
@@ -364,7 +364,7 @@ proc unpack(x: pointer, typ: PType, n: PNode): PNode =
       result = n
     else:
       awi(nkPtrLit, cast[ByteAddress](p))
-  of tyPtr, tyRef, tyVar:
+  of tyPtr, tyRef, tyVar, tyLent:
     let p = rd(pointer, x)
     if p.isNil:
       setNil()
@@ -388,14 +388,14 @@ proc unpack(x: pointer, typ: PType, n: PNode): PNode =
       aws(nkStrLit, $p)
   of tyNil:
     setNil()
-  of tyDistinct, tyGenericInst, tyAlias:
+  of tyDistinct, tyGenericInst, tyAlias, tySink:
     result = unpack(x, typ.lastSon, n)
   else:
     # XXX what to do with 'array' here?
     globalError(n.info, "cannot map value from FFI " & typeToString(typ))
 
 proc fficast*(x: PNode, destTyp: PType): PNode =
-  if x.kind == nkPtrLit and x.typ.kind in {tyPtr, tyRef, tyVar, tyPointer,
+  if x.kind == nkPtrLit and x.typ.kind in {tyPtr, tyRef, tyVar, tyLent, tyPointer,
                                            tyProc, tyCString, tyString,
                                            tySequence}:
     result = newNodeIT(x.kind, x.info, destTyp)
@@ -442,7 +442,7 @@ proc callForeignFunction*(call: PNode): PNode =
   libffi.call(cif, fn, retVal, args)
 
   if retVal.isNil:
-    result = emptyNode
+    result = newNode(nkEmpty)
   else:
     result = unpack(retVal, typ.sons[0], nil)
     result.info = call.info
@@ -484,7 +484,7 @@ proc callForeignFunction*(fn: PNode, fntyp: PType,
   libffi.call(cif, fn, retVal, cargs)
 
   if retVal.isNil:
-    result = emptyNode
+    result = newNode(nkEmpty)
   else:
     result = unpack(retVal, fntyp.sons[0], nil)
     result.info = info
diff --git a/compiler/evaltempl.nim b/compiler/evaltempl.nim
index 5bd274a3e..0f9220102 100644
--- a/compiler/evaltempl.nim
+++ b/compiler/evaltempl.nim
@@ -11,14 +11,16 @@
 
 import
   strutils, options, ast, astalgo, msgs, os, idents, wordrecg, renderer,
-  rodread
+  lineinfos
 
 type
-  TemplCtx {.pure, final.} = object
+  TemplCtx = object
     owner, genSymOwner: PSym
     instLines: bool   # use the instantiation lines numbers
+    isDeclarative: bool
     mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
                       # new symbol
+    config: ConfigRef
 
 proc copyNode(ctx: TemplCtx, a, b: PNode): PNode =
   result = copyNode(a)
@@ -35,35 +37,62 @@ proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
   case templ.kind
   of nkSym:
     var s = templ.sym
-    if s.owner.id == c.owner.id:
+    if s.owner == nil or s.owner.id == c.owner.id:
       if s.kind == skParam and sfGenSym notin s.flags:
         handleParam actual.sons[s.position]
-      elif s.kind == skGenericParam or
-           s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam:
+      elif (s.owner != nil) and (s.kind == skGenericParam or
+           s.kind == skType and s.typ != nil and s.typ.kind == tyGenericParam):
         handleParam actual.sons[s.owner.typ.len + s.position - 1]
       else:
-        internalAssert sfGenSym in s.flags
+        internalAssert c.config, sfGenSym in s.flags or s.kind == skType
         var x = PSym(idTableGet(c.mapping, s))
         if x == nil:
-          x = copySym(s, false)
-          x.owner = c.genSymOwner
+          x = copySym(s)
+          # sem'check needs to set the owner properly later, see bug #9476
+          x.owner = nil # c.genSymOwner
+          #if x.kind == skParam and x.owner.kind == skModule:
+          #  internalAssert c.config, false
           idTablePut(c.mapping, s, x)
         result.add newSymNode(x, if c.instLines: actual.info else: templ.info)
     else:
       result.add copyNode(c, templ, actual)
   of nkNone..nkIdent, nkType..nkNilLit: # atom
     result.add copyNode(c, templ, actual)
+  of nkCommentStmt:
+    # for the documentation generator we don't keep documentation comments
+    # in the AST that would confuse it (bug #9432), but only if we are not in a
+    # "declarative" context (bug #9235).
+    if c.isDeclarative:
+      var res = copyNode(c, templ, actual)
+      for i in countup(0, sonsLen(templ) - 1):
+        evalTemplateAux(templ.sons[i], actual, c, res)
+      result.add res
+    else:
+      result.add newNodeI(nkEmpty, templ.info)
   else:
+    var isDeclarative = false
+    if templ.kind in {nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef,
+                      nkMacroDef, nkTemplateDef, nkConverterDef, nkTypeSection,
+                      nkVarSection, nkLetSection, nkConstSection} and
+        not c.isDeclarative:
+      c.isDeclarative = true
+      isDeclarative = true
     var res = copyNode(c, templ, actual)
     for i in countup(0, sonsLen(templ) - 1):
       evalTemplateAux(templ.sons[i], actual, c, res)
     result.add res
+    if isDeclarative: c.isDeclarative = false
+
+const
+  errWrongNumberOfArguments = "wrong number of arguments"
+  errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters"
+  errTemplateInstantiationTooNested = "template instantiation too nested"
 
-proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode =
+proc evalTemplateArgs(n: PNode, s: PSym; conf: ConfigRef; fromHlo: bool): PNode =
   # if the template has zero arguments, it can be called without ``()``
   # `n` is then a nkSym or something similar
   var totalParams = case n.kind
-    of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit: n.len-1
+    of nkCallKinds: n.len-1
     else: 0
 
   var
@@ -77,28 +106,28 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode =
     # now that we have working untyped parameters.
     genericParams = if sfImmediate in s.flags or fromHlo: 0
                     else: s.ast[genericParamsPos].len
-    expectedRegularParams = <s.typ.len
+    expectedRegularParams = s.typ.len-1
     givenRegularParams = totalParams - genericParams
   if givenRegularParams < 0: givenRegularParams = 0
 
   if totalParams > expectedRegularParams + genericParams:
-    globalError(n.info, errWrongNumberOfArguments)
+    globalError(conf, n.info, errWrongNumberOfArguments)
 
   if totalParams < genericParams:
-    globalError(n.info, errMissingGenericParamsForTemplate,
+    globalError(conf, n.info, errMissingGenericParamsForTemplate %
                 n.renderTree)
 
   result = newNodeI(nkArgList, n.info)
   for i in 1 .. givenRegularParams:
-    result.addSon n.sons[i]
+    result.addSon n[i]
 
   # handle parameters with default values, which were
   # not supplied by the user
   for i in givenRegularParams+1 .. expectedRegularParams:
     let default = s.typ.n.sons[i].sym.ast
     if default.isNil or default.kind == nkEmpty:
-      localError(n.info, errWrongNumberOfArguments)
-      addSon(result, ast.emptyNode)
+      localError(conf, n.info, errWrongNumberOfArguments)
+      addSon(result, newNodeI(nkEmpty, n.info))
     else:
       addSon(result, default.copyTree)
 
@@ -106,10 +135,10 @@ proc evalTemplateArgs(n: PNode, s: PSym; fromHlo: bool): PNode =
   for i in 1 .. genericParams:
     result.addSon n.sons[givenRegularParams + i]
 
-var evalTemplateCounter* = 0
-  # to prevent endless recursion in templates instantiation
+# to prevent endless recursion in template instantiation
+const evalTemplateLimit* = 1000
 
-proc wrapInComesFrom*(info: TLineInfo; res: PNode): PNode =
+proc wrapInComesFrom*(info: TLineInfo; sym: PSym; res: PNode): PNode =
   when true:
     result = res
     result.info = info
@@ -124,29 +153,36 @@ proc wrapInComesFrom*(info: TLineInfo; res: PNode): PNode =
           if x[i].kind in nkCallKinds:
             x.sons[i].info = info
   else:
-    result = newNodeI(nkPar, info)
+    result = newNodeI(nkStmtListExpr, info)
+    var d = newNodeI(nkComesFrom, info)
+    d.add newSymNode(sym, info)
+    result.add d
     result.add res
+    result.typ = res.typ
 
-proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode =
-  inc(evalTemplateCounter)
-  if evalTemplateCounter > 100:
-    globalError(n.info, errTemplateInstantiationTooNested)
+proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym;
+                   conf: ConfigRef; fromHlo=false): PNode =
+  inc(conf.evalTemplateCounter)
+  if conf.evalTemplateCounter > evalTemplateLimit:
+    globalError(conf, n.info, errTemplateInstantiationTooNested)
     result = n
 
   # replace each param by the corresponding node:
-  var args = evalTemplateArgs(n, tmpl, fromHlo)
+  var args = evalTemplateArgs(n, tmpl, conf, fromHlo)
   var ctx: TemplCtx
   ctx.owner = tmpl
   ctx.genSymOwner = genSymOwner
+  ctx.config = conf
   initIdTable(ctx.mapping)
 
   let body = tmpl.getBody
+  #echo "instantion of ", renderTree(body, {renderIds})
   if isAtom(body):
     result = newNodeI(nkPar, body.info)
     evalTemplateAux(body, args, ctx, result)
     if result.len == 1: result = result.sons[0]
     else:
-      localError(result.info, errIllFormedAstX,
+      localError(conf, result.info, "illformed AST: " &
                   renderTree(result, {renderNoComments}))
   else:
     result = copyNode(body)
@@ -155,5 +191,9 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym; fromHlo=false): PNode =
     #if ctx.instLines: result.info = n.info
     for i in countup(0, safeLen(body) - 1):
       evalTemplateAux(body.sons[i], args, ctx, result)
-  result = wrapInComesFrom(n.info, result)
-  dec(evalTemplateCounter)
+  result.flags.incl nfFromTemplate
+  result = wrapInComesFrom(n.info, tmpl, result)
+  #if ctx.debugActive:
+  #  echo "instantion of ", renderTree(result, {renderIds})
+  dec(conf.evalTemplateCounter)
+
diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim
index f92fe82d6..4b9e1c6fe 100644
--- a/compiler/extccomp.nim
+++ b/compiler/extccomp.nim
@@ -9,19 +9,14 @@
 
 # Module providing functions for calling the different external C compilers
 # Uses some hard-wired facts about each C/C++ compiler, plus options read
-# from a configuration file, to provide generalized procedures to compile
+# from a lineinfos file, to provide generalized procedures to compile
 # nim files.
 
 import
   ropes, os, strutils, osproc, platform, condsyms, options, msgs,
-  securehash, streams
-
-#from debuginfo import writeDebugInfo
+  lineinfos, std / sha1, streams, pathutils
 
 type
-  TSystemCC* = enum
-    ccNone, ccGcc, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc,
-    ccTcc, ccPcc, ccUcc, ccIcl
   TInfoCCProp* = enum         # properties of the C compiler:
     hasSwitchRange,           # CC allows ranges in switch statements (GNU C)
     hasComputedGoto,          # CC has computed goto (GNU C extension)
@@ -53,7 +48,6 @@ type
                          # used on some platforms
     asmStmtFrmt: string, # format of ASM statement
     structStmtFmt: string, # Format for struct statement
-    packedPragma: string,  # Attribute/pragma to make struct packed (1-byte aligned)
     props: TInfoCCProps] # properties of the C compiler
 
 
@@ -86,7 +80,31 @@ compiler gcc:
     pic: "-fPIC",
     asmStmtFrmt: "asm($1);$n",
     structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
-    packedPragma: "__attribute__((__packed__))",
+    props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
+            hasAttribute})
+
+# GNU C and C++ Compiler
+compiler nintendoSwitchGCC:
+  result = (
+    name: "switch_gcc",
+    objExt: "o",
+    optSpeed: " -O3 -ffast-math ",
+    optSize: " -Os -ffast-math ",
+    compilerExe: "aarch64-none-elf-gcc",
+    cppCompiler: "aarch64-none-elf-g++",
+    compileTmpl: "-w -MMD -MP -MF $dfile -c $options $include -o $objfile $file",
+    buildGui: " -mwindows",
+    buildDll: " -shared",
+    buildLib: "aarch64-none-elf-gcc-ar rcs $libfile $objfiles",
+    linkerExe: "aarch64-none-elf-gcc",
+    linkTmpl: "$buildgui $builddll -Wl,-Map,$mapfile -o $exefile $objfiles $options",
+    includeCmd: " -I",
+    linkDirCmd: " -L",
+    linkLibCmd: " -l$1",
+    debug: "",
+    pic: "-fPIE",
+    asmStmtFrmt: "asm($1);$n",
+    structStmtFmt: "$1 $3 $2 ", # struct|union [packed] $name
     props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard, hasGnuAsm,
             hasAttribute})
 
@@ -97,7 +115,11 @@ compiler llvmGcc:
   result.name = "llvm_gcc"
   result.compilerExe = "llvm-gcc"
   result.cppCompiler = "llvm-g++"
-  result.buildLib = "llvm-ar rcs $libfile $objfiles"
+  when defined(macosx):
+    # OS X has no 'llvm-ar' tool:
+    result.buildLib = "ar rcs $libfile $objfiles"
+  else:
+    result.buildLib = "llvm-ar rcs $libfile $objfiles"
 
 # Clang (LLVM) C/C++ Compiler
 compiler clang:
@@ -129,21 +151,22 @@ compiler vcc:
     pic: "",
     asmStmtFrmt: "__asm{$n$1$n}$n",
     structStmtFmt: "$3$n$1 $2",
-    packedPragma: "#pragma pack(1)",
     props: {hasCpp, hasAssume, hasDeclspec})
 
 # Intel C/C++ Compiler
 compiler icl:
-  # Intel compilers try to imitate the native ones (gcc and msvc)
-  when defined(windows):
-    result = vcc()
-  else:
-    result = gcc()
-
+  result = vcc()
   result.name = "icl"
   result.compilerExe = "icl"
   result.linkerExe = "icl"
 
+# Intel compilers try to imitate the native ones (gcc and msvc)
+compiler icc:
+  result = gcc()
+  result.name = "icc"
+  result.compilerExe = "icc"
+  result.linkerExe = "icc"
+
 # Local C Compiler
 compiler lcc:
   result = (
@@ -166,7 +189,6 @@ compiler lcc:
     pic: "",
     asmStmtFrmt: "_asm{$n$1$n}$n",
     structStmtFmt: "$1 $2",
-    packedPragma: "", # XXX: not supported yet
     props: {})
 
 # Borland C Compiler
@@ -174,10 +196,10 @@ compiler bcc:
   result = (
     name: "bcc",
     objExt: "obj",
-    optSpeed: " -O2 -6 ",
+    optSpeed: " -O3 -6 ",
     optSize: " -O1 -6 ",
-    compilerExe: "bcc32",
-    cppCompiler: "",
+    compilerExe: "bcc32c",
+    cppCompiler: "cpp32c",
     compileTmpl: "-c $options $include -o$objfile $file",
     buildGui: " -tW",
     buildDll: " -tWD",
@@ -191,8 +213,9 @@ compiler bcc:
     pic: "",
     asmStmtFrmt: "__asm{$n$1$n}$n",
     structStmtFmt: "$1 $2",
-    packedPragma: "", # XXX: not supported yet
-    props: {hasCpp})
+    props: {hasSwitchRange, hasComputedGoto, hasCpp, hasGcGuard,
+            hasAttribute})
+
 
 # Digital Mars C Compiler
 compiler dmc:
@@ -216,7 +239,6 @@ compiler dmc:
     pic: "",
     asmStmtFrmt: "__asm{$n$1$n}$n",
     structStmtFmt: "$3$n$1 $2",
-    packedPragma: "#pragma pack(1)",
     props: {hasCpp})
 
 # Watcom C Compiler
@@ -241,7 +263,6 @@ compiler wcc:
     pic: "",
     asmStmtFrmt: "__asm{$n$1$n}$n",
     structStmtFmt: "$1 $2",
-    packedPragma: "", # XXX: not supported yet
     props: {hasCpp})
 
 # Tiny C Compiler
@@ -254,7 +275,7 @@ compiler tcc:
     compilerExe: "tcc",
     cppCompiler: "",
     compileTmpl: "-c $options $include -o $objfile $file",
-    buildGui: "UNAVAILABLE!",
+    buildGui: "-Wl,-subsystem=gui",
     buildDll: " -shared",
     buildLib: "", # XXX: not supported yet
     linkerExe: "tcc",
@@ -266,7 +287,6 @@ compiler tcc:
     pic: "",
     asmStmtFrmt: "__asm{$n$1$n}$n",
     structStmtFmt: "$1 $2",
-    packedPragma: "", # XXX: not supported yet
     props: {hasSwitchRange, hasComputedGoto})
 
 # Pelles C Compiler
@@ -292,7 +312,6 @@ compiler pcc:
     pic: "",
     asmStmtFrmt: "__asm{$n$1$n}$n",
     structStmtFmt: "$1 $2",
-    packedPragma: "", # XXX: not supported yet
     props: {})
 
 # Your C Compiler
@@ -317,12 +336,12 @@ compiler ucc:
     pic: "",
     asmStmtFrmt: "__asm{$n$1$n}$n",
     structStmtFmt: "$1 $2",
-    packedPragma: "", # XXX: not supported yet
     props: {})
 
 const
   CC*: array[succ(low(TSystemCC))..high(TSystemCC), TInfoCC] = [
     gcc(),
+    nintendoSwitchGCC(),
     llvmGcc(),
     clang(),
     lcc(),
@@ -333,41 +352,13 @@ const
     tcc(),
     pcc(),
     ucc(),
-    icl()]
+    icl(),
+    icc()]
 
   hExt* = ".h"
 
-var
-  cCompiler* = ccGcc # the used compiler
-  gMixedMode*: bool  # true if some module triggered C++ codegen
-  cIncludes*: seq[string] = @[]   # directories to search for included files
-  cLibs*: seq[string] = @[]       # directories to search for lib files
-  cLinkedLibs*: seq[string] = @[] # libraries to link
-
-# implementation
-
-proc libNameTmpl(): string {.inline.} =
-  result = if targetOS == osWindows: "$1.lib" else: "lib$1.a"
-
-type
-  CfileFlag* {.pure.} = enum
-    Cached,    ## no need to recompile this time
-    External   ## file was introduced via .compile pragma
-
-  Cfile* = object
-    cname*, obj*: string
-    flags*: set[CFileFlag]
-  CfileList = seq[Cfile]
-
-var
-  externalToLink: seq[string] = @[] # files to link in addition to the file
-                                    # we compiled
-  linkOptionsCmd: string = ""
-  compileOptionsCmd: seq[string] = @[]
-  linkOptions: string = ""
-  compileOptions: string = ""
-  ccompilerpath: string = ""
-  toCompile: CfileList = @[]
+proc libNameTmpl(conf: ConfigRef): string {.inline.} =
+  result = if conf.target.targetOS == osWindows: "$1.lib" else: "lib$1.a"
 
 proc nameToCC*(name: string): TSystemCC =
   ## Returns the kind of compiler referred to by `name`, or ccNone
@@ -377,330 +368,431 @@ proc nameToCC*(name: string): TSystemCC =
       return i
   result = ccNone
 
-proc getConfigVar(c: TSystemCC, suffix: string): string =
+proc getConfigVar(conf: ConfigRef; c: TSystemCC, suffix: string): string =
   # use ``cpu.os.cc`` for cross compilation, unless ``--compileOnly`` is given
   # for niminst support
   let fullSuffix =
-    if gCmd == cmdCompileToCpp:
+    if conf.cmd == cmdCompileToCpp:
       ".cpp" & suffix
-    elif gCmd == cmdCompileToOC:
+    elif conf.cmd == cmdCompileToOC:
       ".objc" & suffix
-    elif gCmd == cmdCompileToJS:
+    elif conf.cmd == cmdCompileToJS:
       ".js" & suffix
     else:
       suffix
 
-  if (platform.hostOS != targetOS or platform.hostCPU != targetCPU) and
-      optCompileOnly notin gGlobalOptions:
-    let fullCCname = platform.CPU[targetCPU].name & '.' &
-                     platform.OS[targetOS].name & '.' &
+  if (conf.target.hostOS != conf.target.targetOS or conf.target.hostCPU != conf.target.targetCPU) and
+      optCompileOnly notin conf.globalOptions:
+    let fullCCname = platform.CPU[conf.target.targetCPU].name & '.' &
+                     platform.OS[conf.target.targetOS].name & '.' &
                      CC[c].name & fullSuffix
-    result = getConfigVar(fullCCname)
+    result = getConfigVar(conf, fullCCname)
     if result.len == 0:
       # not overriden for this cross compilation setting?
-      result = getConfigVar(CC[c].name & fullSuffix)
+      result = getConfigVar(conf, CC[c].name & fullSuffix)
   else:
-    result = getConfigVar(CC[c].name & fullSuffix)
-
-proc setCC*(ccname: string) =
-  cCompiler = nameToCC(ccname)
-  if cCompiler == ccNone: rawMessage(errUnknownCcompiler, ccname)
-  compileOptions = getConfigVar(cCompiler, ".options.always")
-  linkOptions = ""
-  ccompilerpath = getConfigVar(cCompiler, ".path")
-  for i in countup(low(CC), high(CC)): undefSymbol(CC[i].name)
-  defineSymbol(CC[cCompiler].name)
+    result = getConfigVar(conf, CC[c].name & fullSuffix)
+
+proc setCC*(conf: ConfigRef; ccname: string; info: TLineInfo) =
+  conf.cCompiler = nameToCC(ccname)
+  if conf.cCompiler == ccNone:
+    localError(conf, info, "unknown C compiler: '$1'" % ccname)
+  conf.compileOptions = getConfigVar(conf, conf.cCompiler, ".options.always")
+  conf.linkOptions = ""
+  conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path")
+  for i in countup(low(CC), high(CC)): undefSymbol(conf.symbols, CC[i].name)
+  defineSymbol(conf.symbols, CC[conf.cCompiler].name)
 
 proc addOpt(dest: var string, src: string) =
   if len(dest) == 0 or dest[len(dest)-1] != ' ': add(dest, " ")
   add(dest, src)
 
-proc addLinkOption*(option: string) =
-  addOpt(linkOptions, option)
+proc addLinkOption*(conf: ConfigRef; option: string) =
+  addOpt(conf.linkOptions, option)
 
-proc addCompileOption*(option: string) =
-  if strutils.find(compileOptions, option, 0) < 0:
-    addOpt(compileOptions, option)
+proc addCompileOption*(conf: ConfigRef; option: string) =
+  if strutils.find(conf.compileOptions, option, 0) < 0:
+    addOpt(conf.compileOptions, option)
 
-proc addLinkOptionCmd*(option: string) =
-  addOpt(linkOptionsCmd, option)
+proc addLinkOptionCmd*(conf: ConfigRef; option: string) =
+  addOpt(conf.linkOptionsCmd, option)
 
-proc addCompileOptionCmd*(option: string) =
-  compileOptionsCmd.add(option)
+proc addCompileOptionCmd*(conf: ConfigRef; option: string) =
+  conf.compileOptionsCmd.add(option)
 
-proc initVars*() =
+proc initVars*(conf: ConfigRef) =
   # we need to define the symbol here, because ``CC`` may have never been set!
-  for i in countup(low(CC), high(CC)): undefSymbol(CC[i].name)
-  defineSymbol(CC[cCompiler].name)
-  addCompileOption(getConfigVar(cCompiler, ".options.always"))
+  for i in countup(low(CC), high(CC)): undefSymbol(conf.symbols, CC[i].name)
+  defineSymbol(conf.symbols, CC[conf.cCompiler].name)
+  addCompileOption(conf, getConfigVar(conf, conf.cCompiler, ".options.always"))
   #addLinkOption(getConfigVar(cCompiler, ".options.linker"))
-  if len(ccompilerpath) == 0:
-    ccompilerpath = getConfigVar(cCompiler, ".path")
+  if len(conf.ccompilerpath) == 0:
+    conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path")
 
-proc completeCFilePath*(cfile: string, createSubDir: bool = true): string =
-  result = completeGeneratedFilePath(cfile, createSubDir)
+proc completeCFilePath*(conf: ConfigRef; cfile: AbsoluteFile,
+                        createSubDir: bool = true): AbsoluteFile =
+  result = completeGeneratedFilePath(conf, cfile, createSubDir)
 
-proc toObjFile*(filename: string): string =
+proc toObjFile*(conf: ConfigRef; filename: AbsoluteFile): AbsoluteFile =
   # Object file for compilation
-  #if filename.endsWith(".cpp"):
-  #  result = changeFileExt(filename, "cpp." & CC[cCompiler].objExt)
-  #else:
-  result = changeFileExt(filename, CC[cCompiler].objExt)
+  result = AbsoluteFile(filename.string & "." & CC[conf.cCompiler].objExt)
 
-proc addFileToCompile*(cf: Cfile) =
-  toCompile.add(cf)
+proc addFileToCompile*(conf: ConfigRef; cf: Cfile) =
+  conf.toCompile.add(cf)
 
-proc resetCompilationLists* =
-  toCompile.setLen 0
+proc resetCompilationLists*(conf: ConfigRef) =
+  conf.toCompile.setLen 0
   ## XXX: we must associate these with their originating module
   # when the module is loaded/unloaded it adds/removes its items
   # That's because we still need to hash check the external files
   # Maybe we can do that in checkDep on the other hand?
-  externalToLink.setLen 0
+  conf.externalToLink.setLen 0
 
-proc addExternalFileToLink*(filename: string) =
-  externalToLink.insert(filename, 0)
+proc addExternalFileToLink*(conf: ConfigRef; filename: AbsoluteFile) =
+  conf.externalToLink.insert(filename.string, 0)
 
-proc execWithEcho(cmd: string, msg = hintExecuting): int =
-  rawMessage(msg, cmd)
+proc execWithEcho(conf: ConfigRef; cmd: string, msg = hintExecuting): int =
+  rawMessage(conf, msg, cmd)
   result = execCmd(cmd)
 
-proc execExternalProgram*(cmd: string, msg = hintExecuting) =
-  if execWithEcho(cmd, msg) != 0:
-    rawMessage(errExecutionOfProgramFailed, cmd)
-
-proc generateScript(projectFile: string, script: Rope) =
-  let (dir, name, ext) = splitFile(projectFile)
-  writeRope(script, dir / addFileExt("compile_" & name,
-                                     platform.OS[targetOS].scriptExt))
+proc execExternalProgram*(conf: ConfigRef; cmd: string, msg = hintExecuting) =
+  if execWithEcho(conf, cmd, msg) != 0:
+    rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
+      cmd)
+
+proc generateScript(conf: ConfigRef; projectFile: AbsoluteFile, script: Rope) =
+  let (_, name, _) = splitFile(projectFile)
+  let filename = getNimcacheDir(conf) / RelativeFile(addFileExt("compile_" & name,
+                                     platform.OS[conf.target.targetOS].scriptExt))
+  if writeRope(script, filename):
+    copyFile(conf.libpath / RelativeFile"nimbase.h",
+             getNimcacheDir(conf) / RelativeFile"nimbase.h")
+  else:
+    rawMessage(conf, errGenerated, "could not write to file: " & filename.string)
 
-proc getOptSpeed(c: TSystemCC): string =
-  result = getConfigVar(c, ".options.speed")
+proc getOptSpeed(conf: ConfigRef; c: TSystemCC): string =
+  result = getConfigVar(conf, c, ".options.speed")
   if result == "":
     result = CC[c].optSpeed   # use default settings from this file
 
-proc getDebug(c: TSystemCC): string =
-  result = getConfigVar(c, ".options.debug")
+proc getDebug(conf: ConfigRef; c: TSystemCC): string =
+  result = getConfigVar(conf, c, ".options.debug")
   if result == "":
     result = CC[c].debug      # use default settings from this file
 
-proc getOptSize(c: TSystemCC): string =
-  result = getConfigVar(c, ".options.size")
+proc getOptSize(conf: ConfigRef; c: TSystemCC): string =
+  result = getConfigVar(conf, c, ".options.size")
   if result == "":
     result = CC[c].optSize    # use default settings from this file
 
-proc noAbsolutePaths: bool {.inline.} =
+proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} =
   # We used to check current OS != specified OS, but this makes no sense
   # really: Cross compilation from Linux to Linux for example is entirely
   # reasonable.
   # `optGenMapping` is included here for niminst.
-  result = gGlobalOptions * {optGenScript, optGenMapping} != {}
+  result = conf.globalOptions * {optGenScript, optGenMapping} != {}
 
-proc cFileSpecificOptions(cfilename: string): string =
-  result = compileOptions
-  for option in compileOptionsCmd:
+proc cFileSpecificOptions(conf: ConfigRef; cfilename: AbsoluteFile): string =
+  result = conf.compileOptions
+  for option in conf.compileOptionsCmd:
     if strutils.find(result, option, 0) < 0:
       addOpt(result, option)
 
-  var trunk = splitFile(cfilename).name
-  if optCDebug in gGlobalOptions:
-    var key = trunk & ".debug"
-    if existsConfigVar(key): addOpt(result, getConfigVar(key))
-    else: addOpt(result, getDebug(cCompiler))
-  if optOptimizeSpeed in gOptions:
-    var key = trunk & ".speed"
-    if existsConfigVar(key): addOpt(result, getConfigVar(key))
-    else: addOpt(result, getOptSpeed(cCompiler))
-  elif optOptimizeSize in gOptions:
-    var key = trunk & ".size"
-    if existsConfigVar(key): addOpt(result, getConfigVar(key))
-    else: addOpt(result, getOptSize(cCompiler))
-  var key = trunk & ".always"
-  if existsConfigVar(key): addOpt(result, getConfigVar(key))
-
-proc getCompileOptions: string =
-  result = cFileSpecificOptions("__dummy__")
-
-proc getLinkOptions: string =
-  result = linkOptions & " " & linkOptionsCmd & " "
-  for linkedLib in items(cLinkedLibs):
-    result.add(CC[cCompiler].linkLibCmd % linkedLib.quoteShell)
-  for libDir in items(cLibs):
-    result.add(join([CC[cCompiler].linkDirCmd, libDir.quoteShell]))
-
-proc needsExeExt(): bool {.inline.} =
-  result = (optGenScript in gGlobalOptions and targetOS == osWindows) or
-           (platform.hostOS == osWindows)
-
-proc getCompilerExe(compiler: TSystemCC; cfile: string): string =
-  result = if gCmd == cmdCompileToCpp and not cfile.endsWith(".c"):
+  let trunk = splitFile(cfilename).name
+  if optCDebug in conf.globalOptions:
+    let key = trunk & ".debug"
+    if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
+    else: addOpt(result, getDebug(conf, conf.cCompiler))
+  if optOptimizeSpeed in conf.options:
+    let key = trunk & ".speed"
+    if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
+    else: addOpt(result, getOptSpeed(conf, conf.cCompiler))
+  elif optOptimizeSize in conf.options:
+    let key = trunk & ".size"
+    if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
+    else: addOpt(result, getOptSize(conf, conf.cCompiler))
+  let key = trunk & ".always"
+  if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
+
+proc getCompileOptions(conf: ConfigRef): string =
+  result = cFileSpecificOptions(conf, AbsoluteFile"__dummy__")
+
+proc getLinkOptions(conf: ConfigRef): string =
+  result = conf.linkOptions & " " & conf.linkOptionsCmd & " "
+  for linkedLib in items(conf.cLinkedLibs):
+    result.add(CC[conf.cCompiler].linkLibCmd % linkedLib.quoteShell)
+  for libDir in items(conf.cLibs):
+    result.add(join([CC[conf.cCompiler].linkDirCmd, libDir.quoteShell]))
+
+proc needsExeExt(conf: ConfigRef): bool {.inline.} =
+  result = (optGenScript in conf.globalOptions and conf.target.targetOS == osWindows) or
+           (conf.target.hostOS == osWindows)
+
+proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: AbsoluteFile): string =
+  result = if conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c"):
              CC[compiler].cppCompiler
            else:
              CC[compiler].compilerExe
   if result.len == 0:
-    rawMessage(errCompilerDoesntSupportTarget, CC[compiler].name)
+    rawMessage(conf, errGenerated,
+      "Compiler '$1' doesn't support the requested target" %
+      CC[compiler].name)
 
-proc getLinkerExe(compiler: TSystemCC): string =
+proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string =
   result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe
-           elif gMixedMode and gCmd != cmdCompileToCpp: CC[compiler].cppCompiler
-           else: compiler.getCompilerExe("")
-
-proc getCompileCFileCmd*(cfile: Cfile): string =
-  var c = cCompiler
-  var options = cFileSpecificOptions(cfile.cname)
-  var exe = getConfigVar(c, ".exe")
-  if exe.len == 0: exe = c.getCompilerExe(cfile.cname)
-
-  if needsExeExt(): exe = addFileExt(exe, "exe")
-  if optGenDynLib in gGlobalOptions and
-      ospNeedsPIC in platform.OS[targetOS].props:
+           elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler
+           else: getCompilerExe(conf, compiler, AbsoluteFile"")
+
+proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string =
+  var c = conf.cCompiler
+  var options = cFileSpecificOptions(conf, cfile.cname)
+  var exe = getConfigVar(conf, c, ".exe")
+  if exe.len == 0: exe = getCompilerExe(conf, c, cfile.cname)
+
+  if needsExeExt(conf): exe = addFileExt(exe, "exe")
+  if optGenDynLib in conf.globalOptions and
+      ospNeedsPIC in platform.OS[conf.target.targetOS].props:
     add(options, ' ' & CC[c].pic)
 
   var includeCmd, compilePattern: string
-  if not noAbsolutePaths():
+  if not noAbsolutePaths(conf):
     # compute include paths:
-    includeCmd = CC[c].includeCmd & quoteShell(libpath)
+    includeCmd = CC[c].includeCmd & quoteShell(conf.libpath)
 
-    for includeDir in items(cIncludes):
+    for includeDir in items(conf.cIncludes):
       includeCmd.add(join([CC[c].includeCmd, includeDir.quoteShell]))
 
-    compilePattern = joinPath(ccompilerpath, exe)
+    compilePattern = joinPath(conf.ccompilerpath, exe)
   else:
     includeCmd = ""
-    compilePattern = c.getCompilerExe(cfile.cname)
+    compilePattern = getCompilerExe(conf, c, cfile.cname)
 
-  var cf = if noAbsolutePaths(): extractFilename(cfile.cname)
+  var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string)
            else: cfile.cname
 
   var objfile =
-    if cfile.obj.len == 0:
-      if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths():
-        toObjFile(cf)
+    if cfile.obj.isEmpty:
+      if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths(conf):
+        toObjFile(conf, cf).string
       else:
-        completeCFilePath(toObjFile(cf))
-    elif noAbsolutePaths():
-      extractFilename(cfile.obj)
+        completeCFilePath(conf, toObjFile(conf, cf)).string
+    elif noAbsolutePaths(conf):
+      extractFilename(cfile.obj.string)
     else:
-      cfile.obj
+      cfile.obj.string
+
+  # D files are required by nintendo switch libs for
+  # compilation. They are basically a list of all includes.
+  let dfile = objfile.changeFileExt(".d").quoteShell()
 
   objfile = quoteShell(objfile)
-  cf = quoteShell(cf)
+  let cfsh = quoteShell(cf)
   result = quoteShell(compilePattern % [
-    "file", cf, "objfile", objfile, "options", options,
-    "include", includeCmd, "nim", getPrefixDir(),
-    "nim", getPrefixDir(), "lib", libpath])
+    "dfile", dfile,
+    "file", cfsh, "objfile", objfile, "options", options,
+    "include", includeCmd, "nim", getPrefixDir(conf).string,
+    "lib", conf.libpath.string])
   add(result, ' ')
   addf(result, CC[c].compileTmpl, [
-    "file", cf, "objfile", objfile,
+    "dfile", dfile,
+    "file", cfsh, "objfile", objfile,
     "options", options, "include", includeCmd,
-    "nim", quoteShell(getPrefixDir()),
-    "nim", quoteShell(getPrefixDir()),
-    "lib", quoteShell(libpath)])
+    "nim", quoteShell(getPrefixDir(conf)),
+    "lib", quoteShell(conf.libpath)])
 
-proc footprint(cfile: Cfile): SecureHash =
+proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash =
   result = secureHash(
-    $secureHashFile(cfile.cname) &
-    platform.OS[targetOS].name &
-    platform.CPU[targetCPU].name &
-    extccomp.CC[extccomp.cCompiler].name &
-    getCompileCFileCmd(cfile))
-
-proc externalFileChanged(cfile: Cfile): bool =
-  if gCmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM}:
+    $secureHashFile(cfile.cname.string) &
+    platform.OS[conf.target.targetOS].name &
+    platform.CPU[conf.target.targetCPU].name &
+    extccomp.CC[conf.cCompiler].name &
+    getCompileCFileCmd(conf, cfile))
+
+proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
+  if conf.cmd notin {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC, cmdCompileToLLVM}:
     return false
 
-  var hashFile = toGeneratedFile(cfile.cname.withPackageName, "sha1")
-  var currentHash = footprint(cfile)
+  var hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1")
+  var currentHash = footprint(conf, cfile)
   var f: File
-  if open(f, hashFile, fmRead):
+  if open(f, hashFile.string, fmRead):
     let oldHash = parseSecureHash(f.readLine())
     close(f)
     result = oldHash != currentHash
   else:
     result = true
   if result:
-    if open(f, hashFile, fmWrite):
+    if open(f, hashFile.string, fmWrite):
       f.writeLine($currentHash)
       close(f)
 
-proc addExternalFileToCompile*(c: var Cfile) =
-  if optForceFullMake notin gGlobalOptions and not externalFileChanged(c):
+proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) =
+  if optForceFullMake notin conf.globalOptions and fileExists(c.obj) and
+      not externalFileChanged(conf, c):
     c.flags.incl CfileFlag.Cached
-  toCompile.add(c)
+  conf.toCompile.add(c)
 
-proc addExternalFileToCompile*(filename: string) =
+proc addExternalFileToCompile*(conf: ConfigRef; filename: AbsoluteFile) =
   var c = Cfile(cname: filename,
-    obj: toObjFile(completeCFilePath(changeFileExt(filename, ""), false)),
+    obj: toObjFile(conf, completeCFilePath(conf, filename, false)),
     flags: {CfileFlag.External})
-  addExternalFileToCompile(c)
+  addExternalFileToCompile(conf, c)
 
-proc compileCFile(list: CFileList, script: var Rope, cmds: var TStringSeq,
+proc compileCFile(conf: ConfigRef; list: CFileList, script: var Rope, cmds: var TStringSeq,
                   prettyCmds: var TStringSeq) =
   for it in list:
     # call the C compiler for the .c file:
     if it.flags.contains(CfileFlag.Cached): continue
-    var compileCmd = getCompileCFileCmd(it)
-    if optCompileOnly notin gGlobalOptions:
+    var compileCmd = getCompileCFileCmd(conf, it)
+    if optCompileOnly notin conf.globalOptions:
       add(cmds, compileCmd)
       let (_, name, _) = splitFile(it.cname)
-      add(prettyCmds, "CC: " & name)
-    if optGenScript in gGlobalOptions:
+      add(prettyCmds, if hintCC in conf.notes: "CC: " & name else: "")
+    if optGenScript in conf.globalOptions:
       add(script, compileCmd)
-      add(script, tnl)
+      add(script, "\n")
 
-proc getLinkCmd(projectfile, objfiles: string): string =
-  if optGenStaticLib in gGlobalOptions:
+proc getLinkCmd(conf: ConfigRef; projectfile: AbsoluteFile, objfiles: string): string =
+  if optGenStaticLib in conf.globalOptions:
     var libname: string
-    if options.outFile.len > 0:
-      libname = options.outFile.expandTilde
+    if not conf.outFile.isEmpty:
+      libname = conf.outFile.string.expandTilde
       if not libname.isAbsolute():
         libname = getCurrentDir() / libname
     else:
-      libname = (libNameTmpl() % splitFile(gProjectName).name)
-    result = CC[cCompiler].buildLib % ["libfile", libname,
+      libname = (libNameTmpl(conf) % splitFile(conf.projectName).name)
+    result = CC[conf.cCompiler].buildLib % ["libfile", quoteShell(libname),
                                        "objfiles", objfiles]
   else:
-    var linkerExe = getConfigVar(cCompiler, ".linkerexe")
-    if len(linkerExe) == 0: linkerExe = cCompiler.getLinkerExe
-    if needsExeExt(): linkerExe = addFileExt(linkerExe, "exe")
-    if noAbsolutePaths(): result = quoteShell(linkerExe)
-    else: result = quoteShell(joinPath(ccompilerpath, linkerExe))
-    let buildgui = if optGenGuiApp in gGlobalOptions: CC[cCompiler].buildGui
-                   else: ""
+    var linkerExe = getConfigVar(conf, conf.cCompiler, ".linkerexe")
+    if len(linkerExe) == 0: linkerExe = getLinkerExe(conf, conf.cCompiler)
+    # bug #6452: We must not use ``quoteShell`` here for ``linkerExe``
+    if needsExeExt(conf): linkerExe = addFileExt(linkerExe, "exe")
+    if noAbsolutePaths(conf): result = linkerExe
+    else: result = joinPath(conf.cCompilerpath, linkerExe)
+    let buildgui = if optGenGuiApp in conf.globalOptions and conf.target.targetOS == osWindows:
+                     CC[conf.cCompiler].buildGui
+                   else:
+                     ""
     var exefile, builddll: string
-    if optGenDynLib in gGlobalOptions:
-      exefile = platform.OS[targetOS].dllFrmt % splitFile(projectfile).name
-      builddll = CC[cCompiler].buildDll
+    if optGenDynLib in conf.globalOptions:
+      exefile = platform.OS[conf.target.targetOS].dllFrmt % splitFile(projectfile).name
+      builddll = CC[conf.cCompiler].buildDll
     else:
-      exefile = splitFile(projectfile).name & platform.OS[targetOS].exeExt
+      exefile = splitFile(projectfile).name & platform.OS[conf.target.targetOS].exeExt
       builddll = ""
-    if options.outFile.len > 0:
-      exefile = options.outFile.expandTilde
+    if not conf.outFile.isEmpty:
+      exefile = conf.outFile.string.expandTilde
       if not exefile.isAbsolute():
         exefile = getCurrentDir() / exefile
-    if not noAbsolutePaths():
+    if not noAbsolutePaths(conf):
       if not exefile.isAbsolute():
-        exefile = joinPath(splitFile(projectfile).dir, exefile)
+        exefile = string(splitFile(projectfile).dir / RelativeFile(exefile))
     when false:
-      if optCDebug in gGlobalOptions:
+      if optCDebug in conf.globalOptions:
         writeDebugInfo(exefile.changeFileExt("ndb"))
     exefile = quoteShell(exefile)
-    let linkOptions = getLinkOptions() & " " &
-                      getConfigVar(cCompiler, ".options.linker")
+
+    # Map files are required by Nintendo Switch compilation. They are a list
+    # of all function calls in the library and where they come from.
+    let mapfile = quoteShell(getNimcacheDir(conf) / RelativeFile(splitFile(projectFile).name & ".map"))
+
+    let linkOptions = getLinkOptions(conf) & " " &
+                      getConfigVar(conf, conf.cCompiler, ".options.linker")
+    var linkTmpl = getConfigVar(conf, conf.cCompiler, ".linkTmpl")
+    if linkTmpl.len == 0:
+      linkTmpl = CC[conf.cCompiler].linkTmpl
     result = quoteShell(result % ["builddll", builddll,
+        "mapfile", mapfile,
         "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles,
-        "exefile", exefile, "nim", getPrefixDir(), "lib", libpath])
+        "exefile", exefile, "nim", getPrefixDir(conf).string, "lib", conf.libpath.string])
     result.add ' '
-    addf(result, CC[cCompiler].linkTmpl, ["builddll", builddll,
+    addf(result, linkTmpl, ["builddll", builddll,
+        "mapfile", mapfile,
         "buildgui", buildgui, "options", linkOptions,
         "objfiles", objfiles, "exefile", exefile,
-        "nim", quoteShell(getPrefixDir()),
-        "lib", quoteShell(libpath)])
+        "nim", quoteShell(getPrefixDir(conf)),
+        "lib", quoteShell(conf.libpath)])
+
+template tryExceptOSErrorMessage(conf: ConfigRef; errorPrefix: string = "", body: untyped): typed =
+  try:
+    body
+  except OSError:
+    let ose = (ref OSError)(getCurrentException())
+    if errorPrefix.len > 0:
+      rawMessage(conf, errGenerated, errorPrefix & " " & ose.msg & " " & $ose.errorCode)
+    else:
+      rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
+        (ose.msg & " " & $ose.errorCode))
+    raise
+
+proc execLinkCmd(conf: ConfigRef; linkCmd: string) =
+  tryExceptOSErrorMessage(conf, "invocation of external linker program failed."):
+    execExternalProgram(conf, linkCmd,
+      if optListCmd in conf.globalOptions or conf.verbosity > 1: hintExecuting else: hintLinking)
+
+proc execCmdsInParallel(conf: ConfigRef; cmds: seq[string]; prettyCb: proc (idx: int)) =
+  let runCb = proc (idx: int, p: Process) =
+    let exitCode = p.peekExitCode
+    if exitCode != 0:
+      rawMessage(conf, errGenerated, "execution of an external compiler program '" &
+        cmds[idx] & "' failed with exit code: " & $exitCode & "\n\n" &
+        p.outputStream.readAll.strip)
+  if conf.numberOfProcessors == 0: conf.numberOfProcessors = countProcessors()
+  var res = 0
+  if conf.numberOfProcessors <= 1:
+    for i in countup(0, high(cmds)):
+      tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."):
+        res = execWithEcho(conf, cmds[i])
+      if res != 0:
+        rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
+          cmds[i])
+  else:
+    tryExceptOSErrorMessage(conf, "invocation of external compiler program failed."):
+      if optListCmd in conf.globalOptions or conf.verbosity > 1:
+        res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath},
+                            conf.numberOfProcessors, afterRunEvent=runCb)
+      elif conf.verbosity == 1:
+        res = execProcesses(cmds, {poStdErrToStdOut, poUsePath},
+                            conf.numberOfProcessors, prettyCb, afterRunEvent=runCb)
+      else:
+        res = execProcesses(cmds, {poStdErrToStdOut, poUsePath},
+                            conf.numberOfProcessors, afterRunEvent=runCb)
+  if res != 0:
+    if conf.numberOfProcessors <= 1:
+      rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
+        cmds.join())
+
+proc linkViaResponseFile(conf: ConfigRef; cmd: string) =
+  # Extracting the linker.exe here is a bit hacky but the best solution
+  # given ``buildLib``'s design.
+  var i = 0
+  var last = 0
+  if cmd.len > 0 and cmd[0] == '"':
+    inc i
+    while i < cmd.len and cmd[i] != '"': inc i
+    last = i
+    inc i
+  else:
+    while i < cmd.len and cmd[i] != ' ': inc i
+    last = i
+  while i < cmd.len and cmd[i] == ' ': inc i
+  let linkerArgs = conf.projectName & "_" & "linkerArgs.txt"
+  let args = cmd.substr(i)
+  # GCC's response files don't support backslashes. Junk.
+  if conf.cCompiler == ccGcc:
+    writeFile(linkerArgs, args.replace('\\', '/'))
+  else:
+    writeFile(linkerArgs, args)
+  try:
+    execLinkCmd(conf, cmd.substr(0, last) & " @" & linkerArgs)
+  finally:
+    removeFile(linkerArgs)
 
-proc callCCompiler*(projectfile: string) =
+proc callCCompiler*(conf: ConfigRef; projectfile: AbsoluteFile) =
   var
     linkCmd: string
-  if gGlobalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}:
+  if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}:
     return # speed up that call if only compiling and no script shall be
            # generated
   #var c = cCompiler
@@ -708,59 +800,45 @@ proc callCCompiler*(projectfile: string) =
   var cmds: TStringSeq = @[]
   var prettyCmds: TStringSeq = @[]
   let prettyCb = proc (idx: int) =
-    echo prettyCmds[idx]
-  let runCb = proc (idx: int, p: Process) =
-    let exitCode = p.peekExitCode
-    if exitCode != 0:
-      rawMessage(errGenerated, "execution of an external compiler program '" &
-        cmds[idx] & "' failed with exit code: " & $exitCode & "\n\n" &
-        p.outputStream.readAll.strip)
-  compileCFile(toCompile, script, cmds, prettyCmds)
-  if optCompileOnly notin gGlobalOptions:
-    if gNumberOfProcessors == 0: gNumberOfProcessors = countProcessors()
-    var res = 0
-    if gNumberOfProcessors <= 1:
-      for i in countup(0, high(cmds)):
-        res = execWithEcho(cmds[i])
-        if res != 0: rawMessage(errExecutionOfProgramFailed, cmds[i])
-    elif optListCmd in gGlobalOptions or gVerbosity > 1:
-      res = execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams},
-                          gNumberOfProcessors, afterRunEvent=runCb)
-    elif gVerbosity == 1:
-      res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams},
-                          gNumberOfProcessors, prettyCb, afterRunEvent=runCb)
-    else:
-      res = execProcesses(cmds, {poStdErrToStdOut, poUsePath, poParentStreams},
-                          gNumberOfProcessors, afterRunEvent=runCb)
-    if res != 0:
-      if gNumberOfProcessors <= 1:
-        rawMessage(errExecutionOfProgramFailed, cmds.join())
-  if optNoLinking notin gGlobalOptions:
+    when declared(echo):
+      let cmd = prettyCmds[idx]
+      if cmd != "": echo cmd
+  compileCFile(conf, conf.toCompile, script, cmds, prettyCmds)
+  if optCompileOnly notin conf.globalOptions:
+    execCmdsInParallel(conf, cmds, prettyCb)
+  if optNoLinking notin conf.globalOptions:
     # call the linker:
     var objfiles = ""
-    for it in externalToLink:
-      let objFile = if noAbsolutePaths(): it.extractFilename else: it
+    for it in conf.externalToLink:
+      let objFile = if noAbsolutePaths(conf): it.extractFilename else: it
       add(objfiles, ' ')
       add(objfiles, quoteShell(
-          addFileExt(objFile, CC[cCompiler].objExt)))
-    for x in toCompile:
+          addFileExt(objFile, CC[conf.cCompiler].objExt)))
+    for x in conf.toCompile:
+      let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj.string
       add(objfiles, ' ')
-      add(objfiles, quoteShell(x.obj))
-
-    linkCmd = getLinkCmd(projectfile, objfiles)
-    if optCompileOnly notin gGlobalOptions:
-      execExternalProgram(linkCmd,
-        if optListCmd in gGlobalOptions or gVerbosity > 1: hintExecuting else: hintLinking)
+      add(objfiles, quoteShell(objFile))
+
+    linkCmd = getLinkCmd(conf, projectfile, objfiles)
+    if optCompileOnly notin conf.globalOptions:
+      if defined(windows) and linkCmd.len > 8_000:
+        # Windows's command line limit is about 8K (don't laugh...) so C compilers on
+        # Windows support a feature where the command line can be passed via ``@linkcmd``
+        # to them.
+        linkViaResponseFile(conf, linkCmd)
+      else:
+        execLinkCmd(conf, linkCmd)
   else:
     linkCmd = ""
-  if optGenScript in gGlobalOptions:
+  if optGenScript in conf.globalOptions:
     add(script, linkCmd)
-    add(script, tnl)
-    generateScript(projectfile, script)
+    add(script, "\n")
+    generateScript(conf, projectfile, script)
 
-from json import escapeJson
+#from json import escapeJson
+import json
 
-proc writeJsonBuildInstructions*(projectfile: string) =
+proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
   template lit(x: untyped) = f.write x
   template str(x: untyped) =
     when compiles(escapeJson(x, buf)):
@@ -770,69 +848,107 @@ proc writeJsonBuildInstructions*(projectfile: string) =
     else:
       f.write escapeJson(x)
 
-  proc cfiles(f: File; buf: var string; list: CfileList, isExternal: bool) =
-    var i = 0
-    for it in list:
+  proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) =
+    var pastStart = false
+    for it in clist:
       if CfileFlag.Cached in it.flags: continue
-      let compileCmd = getCompileCFileCmd(it)
+      let compileCmd = getCompileCFileCmd(conf, it)
+      if pastStart: lit "],\L"
       lit "["
-      str it.cname
+      str it.cname.string
       lit ", "
       str compileCmd
-      inc i
-      if i == list.len:
-        lit "]\L"
-      else:
-        lit "],\L"
+      pastStart = true
+    lit "]\L"
+
+  proc linkfiles(conf: ConfigRef; f: File; buf, objfiles: var string; clist: CfileList;
+                 llist: seq[string]) =
+    var pastStart = false
+    for it in llist:
+      let objfile = if noAbsolutePaths(conf): it.extractFilename
+                    else: it
+      let objstr = addFileExt(objfile, CC[conf.cCompiler].objExt)
+      add(objfiles, ' ')
+      add(objfiles, objstr)
+      if pastStart: lit ",\L"
+      str objstr
+      pastStart = true
 
-  proc linkfiles(f: File; buf, objfiles: var string; toLink: seq[string]) =
-    for i, it in toLink:
-      let objfile = addFileExt(it, CC[cCompiler].objExt)
-      str(objfile)
+    for it in clist:
+      let objstr = quoteShell(it.obj)
       add(objfiles, ' ')
-      add(objfiles, quoteShell(objfile))
-      
-      if i == toLink.high:
-        lit "\L"
-      else:
-        lit ",\L"
+      add(objfiles, objstr)
+      if pastStart: lit ",\L"
+      str objstr
+      pastStart = true
+    lit "\L"
 
   var buf = newStringOfCap(50)
 
-  let file = projectfile.splitFile.name
-  let jsonFile = toGeneratedFile(file, "json")
+  let jsonFile = toGeneratedFile(conf, projectfile, "json")
 
   var f: File
-  if open(f, jsonFile, fmWrite):
+  if open(f, jsonFile.string, fmWrite):
     lit "{\"compile\":[\L"
-    cfiles(f, buf, toCompile, false)
+    cfiles(conf, f, buf, conf.toCompile, false)
     lit "],\L\"link\":[\L"
     var objfiles = ""
     # XXX add every file here that is to link
-    linkfiles(f, buf, objfiles, externalToLink)
+    linkfiles(conf, f, buf, objfiles, conf.toCompile, conf.externalToLink)
 
     lit "],\L\"linkcmd\": "
-    str getLinkCmd(projectfile, objfiles)
+    str getLinkCmd(conf, projectfile, objfiles)
     lit "\L}\L"
     close(f)
 
-proc genMappingFiles(list: CFileList): Rope =
+proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
+  let jsonFile = toGeneratedFile(conf, projectfile, "json")
+  try:
+    let data = json.parseFile(jsonFile.string)
+    let toCompile = data["compile"]
+    doAssert toCompile.kind == JArray
+    var cmds: TStringSeq = @[]
+    var prettyCmds: TStringSeq = @[]
+    for c in toCompile:
+      doAssert c.kind == JArray
+      doAssert c.len >= 2
+
+      add(cmds, c[1].getStr)
+      let (_, name, _) = splitFile(c[0].getStr)
+      add(prettyCmds, "CC: " & name)
+
+    let prettyCb = proc (idx: int) =
+      when declared(echo):
+        echo prettyCmds[idx]
+    execCmdsInParallel(conf, cmds, prettyCb)
+
+    let linkCmd = data["linkcmd"]
+    doAssert linkCmd.kind == JString
+    execLinkCmd(conf, linkCmd.getStr)
+  except:
+    when declared(echo):
+      echo getCurrentException().getStackTrace()
+    quit "error evaluating JSON file: " & jsonFile.string
+
+proc genMappingFiles(conf: ConfigRef; list: CFileList): Rope =
   for it in list:
-    addf(result, "--file:r\"$1\"$N", [rope(it.cname)])
+    addf(result, "--file:r\"$1\"$N", [rope(it.cname.string)])
 
-proc writeMapping*(gSymbolMapping: Rope) =
-  if optGenMapping notin gGlobalOptions: return
+proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) =
+  if optGenMapping notin conf.globalOptions: return
   var code = rope("[C_Files]\n")
-  add(code, genMappingFiles(toCompile))
+  add(code, genMappingFiles(conf, conf.toCompile))
   add(code, "\n[C_Compiler]\nFlags=")
-  add(code, strutils.escape(getCompileOptions()))
+  add(code, strutils.escape(getCompileOptions(conf)))
 
   add(code, "\n[Linker]\nFlags=")
-  add(code, strutils.escape(getLinkOptions() & " " &
-                            getConfigVar(cCompiler, ".options.linker")))
+  add(code, strutils.escape(getLinkOptions(conf) & " " &
+                            getConfigVar(conf, conf.cCompiler, ".options.linker")))
 
   add(code, "\n[Environment]\nlibpath=")
-  add(code, strutils.escape(libpath))
+  add(code, strutils.escape(conf.libpath.string))
 
-  addf(code, "\n[Symbols]$n$1", [gSymbolMapping])
-  writeRope(code, joinPath(gProjectPath, "mapping.txt"))
+  addf(code, "\n[Symbols]$n$1", [symbolMapping])
+  let filename = conf.projectPath / RelativeFile"mapping.txt"
+  if not writeRope(code, filename):
+    rawMessage(conf, errGenerated, "could not write to file: " & filename.string)
diff --git a/compiler/filter_tmpl.nim b/compiler/filter_tmpl.nim
index 361b3d276..b884b1ec3 100644
--- a/compiler/filter_tmpl.nim
+++ b/compiler/filter_tmpl.nim
@@ -11,16 +11,12 @@
 
 import
   llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options,
-  renderer, filters
-
-proc filterTmpl*(stdin: PLLStream, filename: string, call: PNode): PLLStream
-  # #! template(subsChar='$', metaChar='#') | standard(version="0.7.2")
-# implementation
+  renderer, filters, lineinfos, pathutils
 
 type
   TParseState = enum
     psDirective, psTempl
-  TTmplParser{.final.} = object
+  TTmplParser = object
     inp: PLLStream
     state: TParseState
     info: TLineInfo
@@ -31,7 +27,7 @@ type
     emit, conc, toStr: string
     curly, bracket, par: int
     pendingExprLine: bool
-
+    config: ConfigRef
 
 const
   PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '.', '_'}
@@ -39,16 +35,15 @@ const
 proc newLine(p: var TTmplParser) =
   llStreamWrite(p.outp, repeat(')', p.emitPar))
   p.emitPar = 0
-  if p.info.line > int16(1): llStreamWrite(p.outp, "\n")
+  if p.info.line > uint16(1): llStreamWrite(p.outp, "\n")
   if p.pendingExprLine:
     llStreamWrite(p.outp, spaces(2))
     p.pendingExprLine = false
 
 proc scanPar(p: var TTmplParser, d: int) =
   var i = d
-  while true:
+  while i < p.x.len:
     case p.x[i]
-    of '\0': break
     of '(': inc(p.par)
     of ')': dec(p.par)
     of '[': inc(p.bracket)
@@ -61,41 +56,48 @@ proc scanPar(p: var TTmplParser, d: int) =
 proc withInExpr(p: TTmplParser): bool {.inline.} =
   result = p.par > 0 or p.bracket > 0 or p.curly > 0
 
+const
+  LineContinuationOprs = {'+', '-', '*', '/', '\\', '<', '>', '^',
+                          '|', '%', '&', '$', '@', '~', ','}
+
 proc parseLine(p: var TTmplParser) =
   var j = 0
-  while p.x[j] == ' ': inc(j)
-  if p.x[0] == p.nimDirective and p.x[1] == '?':
+  let len = p.x.len
+
+  while j < len and p.x[j] == ' ': inc(j)
+
+  if len >= 2 and p.x[0] == p.nimDirective and p.x[1] == '?':
     newLine(p)
-  elif p.x[j] == p.nimDirective:
+  elif j < len and p.x[j] == p.nimDirective:
     newLine(p)
     inc(j)
-    while p.x[j] == ' ': inc(j)
+    while j < len and p.x[j] == ' ': inc(j)
     let d = j
     var keyw = ""
-    while p.x[j] in PatternChars:
+    while j < len and p.x[j] in PatternChars:
       add(keyw, p.x[j])
       inc(j)
 
     scanPar(p, j)
-    p.pendingExprLine = withInExpr(p) or llstream.endsWithOpr(p.x)
+    p.pendingExprLine = withInExpr(p) or p.x.endsWith(LineContinuationOprs)
     case keyw
     of "end":
       if p.indent >= 2:
         dec(p.indent, 2)
       else:
         p.info.col = int16(j)
-        localError(p.info, errXNotAllowedHere, "end")
+        localError(p.config, p.info, "'end' does not close a control flow construct")
       llStreamWrite(p.outp, spaces(p.indent))
       llStreamWrite(p.outp, "#end")
     of "if", "when", "try", "while", "for", "block", "case", "proc", "iterator",
-       "converter", "macro", "template", "method":
+       "converter", "macro", "template", "method", "func":
       llStreamWrite(p.outp, spaces(p.indent))
       llStreamWrite(p.outp, substr(p.x, d))
       inc(p.indent, 2)
     of "elif", "of", "else", "except", "finally":
       llStreamWrite(p.outp, spaces(p.indent - 2))
       llStreamWrite(p.outp, substr(p.x, d))
-    of "wLet", "wVar", "wConst", "wType":
+    of "let", "var", "const", "type":
       llStreamWrite(p.outp, spaces(p.indent))
       llStreamWrite(p.outp, substr(p.x, d))
       if not p.x.contains({':', '='}):
@@ -126,10 +128,8 @@ proc parseLine(p: var TTmplParser) =
       llStreamWrite(p.outp, "(\"")
       inc(p.emitPar)
     p.state = psTempl
-    while true:
+    while j < len:
       case p.x[j]
-      of '\0':
-        break
       of '\x01'..'\x1F', '\x80'..'\xFF':
         llStreamWrite(p.outp, "\\x")
         llStreamWrite(p.outp, toHex(ord(p.x[j]), 2))
@@ -156,11 +156,8 @@ proc parseLine(p: var TTmplParser) =
             llStreamWrite(p.outp, '(')
             inc(j)
             var curly = 0
-            while true:
+            while j < len:
               case p.x[j]
-              of '\0':
-                localError(p.info, errXExpected, "}")
-                break
               of '{':
                 inc(j)
                 inc(curly)
@@ -173,6 +170,9 @@ proc parseLine(p: var TTmplParser) =
               else:
                 llStreamWrite(p.outp, p.x[j])
                 inc(j)
+            if curly > 0:
+              localError(p.config, p.info, "expected closing '}'")
+              break
             llStreamWrite(p.outp, ')')
             llStreamWrite(p.outp, p.conc)
             llStreamWrite(p.outp, '\"')
@@ -181,7 +181,7 @@ proc parseLine(p: var TTmplParser) =
             llStreamWrite(p.outp, p.conc)
             llStreamWrite(p.outp, p.toStr)
             llStreamWrite(p.outp, '(')
-            while p.x[j] in PatternChars:
+            while j < len and p.x[j] in PatternChars:
               llStreamWrite(p.outp, p.x[j])
               inc(j)
             llStreamWrite(p.outp, ')')
@@ -193,28 +193,30 @@ proc parseLine(p: var TTmplParser) =
               inc(j)
             else:
               p.info.col = int16(j)
-              localError(p.info, errInvalidExpression, "$")
+              localError(p.config, p.info, "invalid expression")
         else:
           llStreamWrite(p.outp, p.x[j])
           inc(j)
     llStreamWrite(p.outp, "\\n\"")
 
-proc filterTmpl(stdin: PLLStream, filename: string, call: PNode): PLLStream =
+proc filterTmpl*(stdin: PLLStream, filename: AbsoluteFile,
+                 call: PNode; conf: ConfigRef): PLLStream =
   var p: TTmplParser
-  p.info = newLineInfo(filename, 0, 0)
+  p.config = conf
+  p.info = newLineInfo(conf, filename, 0, 0)
   p.outp = llStreamOpen("")
   p.inp = stdin
-  p.subsChar = charArg(call, "subschar", 1, '$')
-  p.nimDirective = charArg(call, "metachar", 2, '#')
-  p.emit = strArg(call, "emit", 3, "result.add")
-  p.conc = strArg(call, "conc", 4, " & ")
-  p.toStr = strArg(call, "tostring", 5, "$")
+  p.subsChar = charArg(conf, call, "subschar", 1, '$')
+  p.nimDirective = charArg(conf, call, "metachar", 2, '#')
+  p.emit = strArg(conf, call, "emit", 3, "result.add")
+  p.conc = strArg(conf, call, "conc", 4, " & ")
+  p.toStr = strArg(conf, call, "tostring", 5, "$")
   p.x = newStringOfCap(120)
   # do not process the first line which contains the directive:
   if llStreamReadLine(p.inp, p.x):
-    p.info.line = p.info.line + int16(1)
+    p.info.line = p.info.line + 1'u16
   while llStreamReadLine(p.inp, p.x):
-    p.info.line = p.info.line + int16(1)
+    p.info.line = p.info.line + 1'u16
     parseLine(p)
   newLine(p)
   result = p.outp
diff --git a/compiler/filters.nim b/compiler/filters.nim
index d1a6409ff..d9e8e41da 100644
--- a/compiler/filters.nim
+++ b/compiler/filters.nim
@@ -11,53 +11,46 @@
 
 import
   llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options,
-  renderer
+  renderer, pathutils
 
-proc filterReplace*(stdin: PLLStream, filename: string, call: PNode): PLLStream
-proc filterStrip*(stdin: PLLStream, filename: string, call: PNode): PLLStream
-  # helpers to retrieve arguments:
-proc charArg*(n: PNode, name: string, pos: int, default: char): char
-proc strArg*(n: PNode, name: string, pos: int, default: string): string
-proc boolArg*(n: PNode, name: string, pos: int, default: bool): bool
-# implementation
+proc invalidPragma(conf: ConfigRef; n: PNode) =
+  localError(conf, n.info,
+      "'$1' not allowed here" % renderTree(n, {renderNoComments}))
 
-proc invalidPragma(n: PNode) =
-  localError(n.info, errXNotAllowedHere, renderTree(n, {renderNoComments}))
-
-proc getArg(n: PNode, name: string, pos: int): PNode =
+proc getArg(conf: ConfigRef; n: PNode, name: string, pos: int): PNode =
   result = nil
   if n.kind in {nkEmpty..nkNilLit}: return
   for i in countup(1, sonsLen(n) - 1):
     if n.sons[i].kind == nkExprEqExpr:
-      if n.sons[i].sons[0].kind != nkIdent: invalidPragma(n)
+      if n.sons[i].sons[0].kind != nkIdent: invalidPragma(conf, n)
       if cmpIgnoreStyle(n.sons[i].sons[0].ident.s, name) == 0:
         return n.sons[i].sons[1]
     elif i == pos:
       return n.sons[i]
 
-proc charArg(n: PNode, name: string, pos: int, default: char): char =
-  var x = getArg(n, name, pos)
+proc charArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: char): char =
+  var x = getArg(conf, n, name, pos)
   if x == nil: result = default
   elif x.kind == nkCharLit: result = chr(int(x.intVal))
-  else: invalidPragma(n)
+  else: invalidPragma(conf, n)
 
-proc strArg(n: PNode, name: string, pos: int, default: string): string =
-  var x = getArg(n, name, pos)
+proc strArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: string): string =
+  var x = getArg(conf, n, name, pos)
   if x == nil: result = default
   elif x.kind in {nkStrLit..nkTripleStrLit}: result = x.strVal
-  else: invalidPragma(n)
+  else: invalidPragma(conf, n)
 
-proc boolArg(n: PNode, name: string, pos: int, default: bool): bool =
-  var x = getArg(n, name, pos)
+proc boolArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: bool): bool =
+  var x = getArg(conf, n, name, pos)
   if x == nil: result = default
   elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "true") == 0: result = true
   elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "false") == 0: result = false
-  else: invalidPragma(n)
+  else: invalidPragma(conf, n)
 
-proc filterStrip(stdin: PLLStream, filename: string, call: PNode): PLLStream =
-  var pattern = strArg(call, "startswith", 1, "")
-  var leading = boolArg(call, "leading", 2, true)
-  var trailing = boolArg(call, "trailing", 3, true)
+proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream =
+  var pattern = strArg(conf, call, "startswith", 1, "")
+  var leading = boolArg(conf, call, "leading", 2, true)
+  var trailing = boolArg(conf, call, "trailing", 3, true)
   result = llStreamOpen("")
   var line = newStringOfCap(80)
   while llStreamReadLine(stdin, line):
@@ -68,10 +61,10 @@ proc filterStrip(stdin: PLLStream, filename: string, call: PNode): PLLStream =
       llStreamWriteln(result, line)
   llStreamClose(stdin)
 
-proc filterReplace(stdin: PLLStream, filename: string, call: PNode): PLLStream =
-  var sub = strArg(call, "sub", 1, "")
-  if len(sub) == 0: invalidPragma(call)
-  var by = strArg(call, "by", 2, "")
+proc filterReplace*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream =
+  var sub = strArg(conf, call, "sub", 1, "")
+  if len(sub) == 0: invalidPragma(conf, call)
+  var by = strArg(conf, call, "by", 2, "")
   result = llStreamOpen("")
   var line = newStringOfCap(80)
   while llStreamReadLine(stdin, line):
diff --git a/compiler/forloops.nim b/compiler/forloops.nim
index 5074d79d5..2cd1db7f7 100644
--- a/compiler/forloops.nim
+++ b/compiler/forloops.nim
@@ -45,13 +45,13 @@ proc counterInTree(n, loop: PNode; counter: PSym): bool =
     for it in n:
       if counterInTree(it.lastSon): return true
   else:
-    for i in 0 .. <safeLen(n):
+    for i in 0 ..< safeLen(n):
       if counterInTree(n[i], loop, counter): return true
 
 proc copyExcept(n: PNode, x, dest: PNode) =
   if x == n: return
   if n.kind in {nkStmtList, nkStmtListExpr}:
-    for i in 0 .. <n.len: copyExcept(n[i], x, dest)
+    for i in 0 ..< n.len: copyExcept(n[i], x, dest)
   else:
     dest.add n
 
diff --git a/compiler/gorgeimpl.nim b/compiler/gorgeimpl.nim
new file mode 100644
index 000000000..44636f382
--- /dev/null
+++ b/compiler/gorgeimpl.nim
@@ -0,0 +1,58 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2017 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Module that implements ``gorge`` for the compiler.
+
+import msgs, std / sha1, os, osproc, streams, strutils, options,
+  lineinfos, pathutils
+
+proc readOutput(p: Process): (string, int) =
+  result[0] = ""
+  var output = p.outputStream
+  while not output.atEnd:
+    result[0].add(output.readLine)
+    result[0].add("\n")
+  if result[0].len > 0:
+    result[0].setLen(result[0].len - "\n".len)
+  result[1] = p.waitForExit
+
+proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (string, int) =
+  let workingDir = parentDir(toFullPath(conf, info))
+  if cache.len > 0:# and optForceFullMake notin gGlobalOptions:
+    let h = secureHash(cmd & "\t" & input & "\t" & cache)
+    let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string
+    var f: File
+    if open(f, filename):
+      result = (f.readAll, 0)
+      f.close
+      return
+    var readSuccessful = false
+    try:
+      var p = startProcess(cmd, workingDir,
+                           options={poEvalCommand, poStderrToStdout})
+      if input.len != 0:
+        p.inputStream.write(input)
+        p.inputStream.close()
+      result = p.readOutput
+      readSuccessful = true
+      # only cache successful runs:
+      if result[1] == 0:
+        writeFile(filename, result[0])
+    except IOError, OSError:
+      if not readSuccessful: result = ("", -1)
+  else:
+    try:
+      var p = startProcess(cmd, workingDir,
+                           options={poEvalCommand, poStderrToStdout})
+      if input.len != 0:
+        p.inputStream.write(input)
+        p.inputStream.close()
+      result = p.readOutput
+    except IOError, OSError:
+      result = ("", -1)
diff --git a/compiler/guards.nim b/compiler/guards.nim
index df32cf98c..a01c023e4 100644
--- a/compiler/guards.nim
+++ b/compiler/guards.nim
@@ -10,7 +10,7 @@
 ## This module implements the 'implies' relation for guards.
 
 import ast, astalgo, msgs, magicsys, nimsets, trees, types, renderer, idents,
-  saturate
+  saturate, modulegraphs, options, lineinfos
 
 const
   someEq = {mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
@@ -52,7 +52,7 @@ proc isLet(n: PNode): bool =
 
 proc isVar(n: PNode): bool =
   n.kind == nkSym and n.sym.kind in {skResult, skVar} and
-      {sfGlobal, sfAddrTaken} * n.sym.flags == {}
+      {sfAddrTaken} * n.sym.flags == {}
 
 proc isLetLocation(m: PNode, isApprox: bool): bool =
   # consider: 'n[].kind' --> we really need to support 1 deref op even if this
@@ -70,7 +70,7 @@ proc isLetLocation(m: PNode, isApprox: bool): bool =
       n = n.sons[0]
       inc derefs
     of nkBracketExpr:
-      if isConstExpr(n.sons[1]) or isLet(n.sons[1]):
+      if isConstExpr(n.sons[1]) or isLet(n.sons[1]) or isConstExpr(n.sons[1].skipConv):
         n = n.sons[0]
       else: return
     of nkHiddenStdConv, nkHiddenSubConv, nkConv:
@@ -83,18 +83,25 @@ proc isLetLocation(m: PNode, isApprox: bool): bool =
 
 proc interestingCaseExpr*(m: PNode): bool = isLetLocation(m, true)
 
-let
-  opLe = createMagic("<=", mLeI)
-  opLt = createMagic("<", mLtI)
-  opAnd = createMagic("and", mAnd)
-  opOr = createMagic("or", mOr)
-  opIsNil = createMagic("isnil", mIsNil)
-  opEq = createMagic("==", mEqI)
-  opAdd = createMagic("+", mAddI)
-  opSub = createMagic("-", mSubI)
-  opMul = createMagic("*", mMulI)
-  opDiv = createMagic("div", mDivI)
-  opLen = createMagic("len", mLengthSeq)
+type
+  Operators* = object
+    opNot, opContains, opLe, opLt, opAnd, opOr, opIsNil, opEq: PSym
+    opAdd, opSub, opMul, opDiv, opLen: PSym
+
+proc initOperators*(g: ModuleGraph): Operators =
+  result.opLe = createMagic(g, "<=", mLeI)
+  result.opLt = createMagic(g, "<", mLtI)
+  result.opAnd = createMagic(g, "and", mAnd)
+  result.opOr = createMagic(g, "or", mOr)
+  result.opIsNil = createMagic(g, "isnil", mIsNil)
+  result.opEq = createMagic(g, "==", mEqI)
+  result.opAdd = createMagic(g, "+", mAddI)
+  result.opSub = createMagic(g, "-", mSubI)
+  result.opMul = createMagic(g, "*", mMulI)
+  result.opDiv = createMagic(g, "div", mDivI)
+  result.opLen = createMagic(g, "len", mLengthSeq)
+  result.opNot = createMagic(g, "not", mNot)
+  result.opContains = createMagic(g, "contains", mInSet)
 
 proc swapArgs(fact: PNode, newOp: PSym): PNode =
   result = newNodeI(nkCall, fact.info, 3)
@@ -102,16 +109,16 @@ proc swapArgs(fact: PNode, newOp: PSym): PNode =
   result.sons[1] = fact.sons[2]
   result.sons[2] = fact.sons[1]
 
-proc neg(n: PNode): PNode =
+proc neg(n: PNode; o: Operators): PNode =
   if n == nil: return nil
   case n.getMagic
   of mNot:
     result = n.sons[1]
   of someLt:
     # not (a < b)  ==  a >= b  ==  b <= a
-    result = swapArgs(n, opLe)
+    result = swapArgs(n, o.opLe)
   of someLe:
-    result = swapArgs(n, opLt)
+    result = swapArgs(n, o.opLt)
   of mInSet:
     if n.sons[1].kind != nkCurly: return nil
     let t = n.sons[2].typ.skipTypes(abstractInst)
@@ -124,8 +131,8 @@ proc neg(n: PNode): PNode =
         let eAsNode = newIntNode(nkIntLit, e.sym.position)
         if not inSet(n.sons[1], eAsNode): s.add eAsNode
       result.sons[1] = s
-    elif t.kind notin {tyString, tySequence} and lengthOrd(t) < 1000:
-      result.sons[1] = complement(n.sons[1])
+    #elif t.kind notin {tyString, tySequence} and lengthOrd(t) < 1000:
+    #  result.sons[1] = complement(n.sons[1])
     else:
       # not ({2, 3, 4}.contains(x))   x != 2 and x != 3 and x != 4
       # XXX todo
@@ -133,11 +140,11 @@ proc neg(n: PNode): PNode =
   of mOr:
     # not (a or b) --> not a and not b
     let
-      a = n.sons[1].neg
-      b = n.sons[2].neg
+      a = n.sons[1].neg(o)
+      b = n.sons[2].neg(o)
     if a != nil and b != nil:
       result = newNodeI(nkCall, n.info, 3)
-      result.sons[0] = newSymNode(opAnd)
+      result.sons[0] = newSymNode(o.opAnd)
       result.sons[1] = a
       result.sons[2] = b
     elif a != nil:
@@ -147,7 +154,7 @@ proc neg(n: PNode): PNode =
   else:
     # leave  not (a == 4)  as it is
     result = newNodeI(nkCall, n.info, 2)
-    result.sons[0] = newSymNode(opNot)
+    result.sons[0] = newSymNode(o.opNot)
     result.sons[1] = n
 
 proc buildCall(op: PSym; a: PNode): PNode =
@@ -181,7 +188,7 @@ proc `|div|`(a, b: PNode): PNode =
   if a.kind in {nkCharLit..nkUInt64Lit}: result.intVal = a.intVal div b.intVal
   else: result.floatVal = a.floatVal / b.floatVal
 
-proc negate(a, b, res: PNode): PNode =
+proc negate(a, b, res: PNode; o: Operators): PNode =
   if b.kind in {nkCharLit..nkUInt64Lit} and b.intVal != low(BiggestInt):
     var b = copyNode(b)
     b.intVal = -b.intVal
@@ -189,11 +196,11 @@ proc negate(a, b, res: PNode): PNode =
       b.intVal = b.intVal |+| a.intVal
       result = b
     else:
-      result = buildCall(opAdd, a, b)
+      result = buildCall(o.opAdd, a, b)
   elif b.kind in {nkFloatLit..nkFloat64Lit}:
     var b = copyNode(b)
     b.floatVal = -b.floatVal
-    result = buildCall(opAdd, a, b)
+    result = buildCall(o.opAdd, a, b)
   else:
     result = res
 
@@ -201,35 +208,35 @@ proc zero(): PNode = nkIntLit.newIntNode(0)
 proc one(): PNode = nkIntLit.newIntNode(1)
 proc minusOne(): PNode = nkIntLit.newIntNode(-1)
 
-proc lowBound*(x: PNode): PNode =
-  result = nkIntLit.newIntNode(firstOrd(x.typ))
+proc lowBound*(conf: ConfigRef; x: PNode): PNode =
+  result = nkIntLit.newIntNode(firstOrd(conf, x.typ))
   result.info = x.info
 
-proc highBound*(x: PNode): PNode =
+proc highBound*(conf: ConfigRef; x: PNode; o: Operators): PNode =
   let typ = x.typ.skipTypes(abstractInst)
   result = if typ.kind == tyArray:
-             nkIntLit.newIntNode(lastOrd(typ))
+             nkIntLit.newIntNode(lastOrd(conf, typ))
            elif typ.kind == tySequence and x.kind == nkSym and
                x.sym.kind == skConst:
              nkIntLit.newIntNode(x.sym.ast.len-1)
            else:
-             opAdd.buildCall(opLen.buildCall(x), minusOne())
+             o.opAdd.buildCall(o.opLen.buildCall(x), minusOne())
   result.info = x.info
 
-proc reassociation(n: PNode): PNode =
+proc reassociation(n: PNode; o: Operators): PNode =
   result = n
   # (foo+5)+5 --> foo+10;  same for '*'
   case result.getMagic
   of someAdd:
     if result[2].isValue and
         result[1].getMagic in someAdd and result[1][2].isValue:
-      result = opAdd.buildCall(result[1][1], result[1][2] |+| result[2])
+      result = o.opAdd.buildCall(result[1][1], result[1][2] |+| result[2])
       if result[2].intVal == 0:
         result = result[1]
   of someMul:
     if result[2].isValue and
         result[1].getMagic in someMul and result[1][2].isValue:
-      result = opMul.buildCall(result[1][1], result[1][2] |*| result[2])
+      result = o.opMul.buildCall(result[1][1], result[1][2] |*| result[2])
       if result[2].intVal == 1:
         result = result[1]
       elif result[2].intVal == 0:
@@ -243,12 +250,12 @@ proc pred(n: PNode): PNode =
   else:
     result = n
 
-proc canon*(n: PNode): PNode =
+proc canon*(n: PNode; o: Operators): PNode =
   # XXX for now only the new code in 'semparallel' uses this
   if n.safeLen >= 1:
     result = shallowCopy(n)
-    for i in 0 .. < n.len:
-      result.sons[i] = canon(n.sons[i])
+    for i in 0 ..< n.len:
+      result.sons[i] = canon(n.sons[i], o)
   elif n.kind == nkSym and n.sym.kind == skLet and
       n.sym.ast.getMagic in (someEq + someAdd + someMul + someMin +
       someMax + someHigh + {mUnaryLt} + someSub + someLen + someDiv):
@@ -263,24 +270,24 @@ proc canon*(n: PNode): PNode =
       # (4 + foo) + 2 --> (foo + 4) + 2
   of someHigh:
     # high == len+(-1)
-    result = opAdd.buildCall(opLen.buildCall(result[1]), minusOne())
+    result = o.opAdd.buildCall(o.opLen.buildCall(result[1]), minusOne())
   of mUnaryLt:
-    result = buildCall(opAdd, result[1], minusOne())
+    result = buildCall(o.opAdd, result[1], minusOne())
   of someSub:
     # x - 4  -->  x + (-4)
-    result = negate(result[1], result[2], result)
+    result = negate(result[1], result[2], result, o)
   of someLen:
-    result.sons[0] = opLen.newSymNode
+    result.sons[0] = o.opLen.newSymNode
   of someLt:
     # x < y  same as x <= y-1:
-    let y = n[2].canon
+    let y = n[2].canon(o)
     let p = pred(y)
-    let minus = if p != y: p else: opAdd.buildCall(y, minusOne()).canon
-    result = opLe.buildCall(n[1].canon, minus)
+    let minus = if p != y: p else: o.opAdd.buildCall(y, minusOne()).canon(o)
+    result = o.opLe.buildCall(n[1].canon(o), minus)
   else: discard
 
   result = skipConv(result)
-  result = reassociation(result)
+  result = reassociation(result, o)
   # most important rule: (x-4) <= a.len -->  x <= a.len+4
   case result.getMagic
   of someLe:
@@ -291,10 +298,10 @@ proc canon*(n: PNode): PNode =
       case x.getMagic
       of someSub:
         result = buildCall(result[0].sym, x[1],
-                           reassociation(opAdd.buildCall(y, x[2])))
+                           reassociation(o.opAdd.buildCall(y, x[2]), o))
       of someAdd:
         # Rule A:
-        let plus = negate(y, x[2], nil).reassociation
+        let plus = negate(y, x[2], nil, o).reassociation(o)
         if plus != nil: result = buildCall(result[0].sym, x[1], plus)
       else: discard
     elif y.kind in nkCallKinds and y.len == 3 and y[2].isValue and
@@ -303,9 +310,9 @@ proc canon*(n: PNode): PNode =
       case y.getMagic
       of someSub:
         result = buildCall(result[0].sym, y[1],
-                           reassociation(opAdd.buildCall(x, y[2])))
+                           reassociation(o.opAdd.buildCall(x, y[2]), o))
       of someAdd:
-        let plus = negate(x, y[2], nil).reassociation
+        let plus = negate(x, y[2], nil, o).reassociation(o)
         # ensure that Rule A will not trigger afterwards with the
         # additional 'not isLetLocation' constraint:
         if plus != nil and not isLetLocation(x, true):
@@ -323,15 +330,15 @@ proc canon*(n: PNode): PNode =
       result.sons[2] = y[1]
   else: discard
 
-proc `+@`*(a: PNode; b: BiggestInt): PNode =
-  canon(if b != 0: opAdd.buildCall(a, nkIntLit.newIntNode(b)) else: a)
+proc buildAdd*(a: PNode; b: BiggestInt; o: Operators): PNode =
+  canon(if b != 0: o.opAdd.buildCall(a, nkIntLit.newIntNode(b)) else: a, o)
 
-proc usefulFact(n: PNode): PNode =
+proc usefulFact(n: PNode; o: Operators): PNode =
   case n.getMagic
   of someEq:
     if skipConv(n.sons[2]).kind == nkNilLit and (
         isLetLocation(n.sons[1], false) or isVar(n.sons[1])):
-      result = opIsNil.buildCall(n.sons[1])
+      result = o.opIsNil.buildCall(n.sons[1])
     else:
       if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true):
         # XXX algebraic simplifications!  'i-1 < a.len' --> 'i < a.len+1'
@@ -351,11 +358,11 @@ proc usefulFact(n: PNode): PNode =
       result = n
   of mAnd:
     let
-      a = usefulFact(n.sons[1])
-      b = usefulFact(n.sons[2])
+      a = usefulFact(n.sons[1], o)
+      b = usefulFact(n.sons[2], o)
     if a != nil and b != nil:
       result = newNodeI(nkCall, n.info, 3)
-      result.sons[0] = newSymNode(opAnd)
+      result.sons[0] = newSymNode(o.opAnd)
       result.sons[1] = a
       result.sons[2] = b
     elif a != nil:
@@ -363,9 +370,9 @@ proc usefulFact(n: PNode): PNode =
     elif b != nil:
       result = b
   of mNot:
-    let a = usefulFact(n.sons[1])
+    let a = usefulFact(n.sons[1], o)
     if a != nil:
-      result = a.neg
+      result = a.neg(o)
   of mOr:
     # 'or' sucks! (p.isNil or q.isNil) --> hard to do anything
     # with that knowledge...
@@ -374,14 +381,14 @@ proc usefulFact(n: PNode): PNode =
     #  (x == 3) or (y == 2)  ---> not ( not (x==3) and not (y == 2))
     #  not (x != 3 and y != 2)
     let
-      a = usefulFact(n.sons[1]).neg
-      b = usefulFact(n.sons[2]).neg
+      a = usefulFact(n.sons[1], o).neg(o)
+      b = usefulFact(n.sons[2], o).neg(o)
     if a != nil and b != nil:
       result = newNodeI(nkCall, n.info, 3)
-      result.sons[0] = newSymNode(opAnd)
+      result.sons[0] = newSymNode(o.opAnd)
       result.sons[1] = a
       result.sons[2] = b
-      result = result.neg
+      result = result.neg(o)
   elif n.kind == nkSym and n.sym.kind == skLet:
     # consider:
     #   let a = 2 < x
@@ -389,32 +396,34 @@ proc usefulFact(n: PNode): PNode =
     #     ...
     # We make can easily replace 'a' by '2 < x' here:
     if n.sym.ast != nil:
-      result = usefulFact(n.sym.ast)
+      result = usefulFact(n.sym.ast, o)
   elif n.kind == nkStmtListExpr:
-    result = usefulFact(n.lastSon)
+    result = usefulFact(n.lastSon, o)
 
 type
-  TModel* = seq[PNode] # the "knowledge base"
+  TModel* = object
+    s*: seq[PNode] # the "knowledge base"
+    o*: Operators
 
 proc addFact*(m: var TModel, nn: PNode) =
-  let n = usefulFact(nn)
-  if n != nil: m.add n
+  let n = usefulFact(nn, m.o)
+  if n != nil: m.s.add n
 
 proc addFactNeg*(m: var TModel, n: PNode) =
-  let n = n.neg
+  let n = n.neg(m.o)
   if n != nil: addFact(m, n)
 
-proc canonOpr(opr: PSym): PSym =
-  case opr.magic
-  of someEq: result = opEq
-  of someLe: result = opLe
-  of someLt: result = opLt
-  of someLen: result = opLen
-  of someAdd: result = opAdd
-  of someSub: result = opSub
-  of someMul: result = opMul
-  of someDiv: result = opDiv
-  else: result = opr
+proc sameOpr(a, b: PSym): bool =
+  case a.magic
+  of someEq: result = b.magic in someEq
+  of someLe: result = b.magic in someLe
+  of someLt: result = b.magic in someLt
+  of someLen: result = b.magic in someLen
+  of someAdd: result = b.magic in someAdd
+  of someSub: result = b.magic in someSub
+  of someMul: result = b.magic in someMul
+  of someDiv: result = b.magic in someDiv
+  else: result = a == b
 
 proc sameTree*(a, b: PNode): bool =
   result = false
@@ -425,7 +434,7 @@ proc sameTree*(a, b: PNode): bool =
     of nkSym:
       result = a.sym == b.sym
       if not result and a.sym.magic != mNone:
-        result = a.sym.magic == b.sym.magic or canonOpr(a.sym) == canonOpr(b.sym)
+        result = a.sym.magic == b.sym.magic or sameOpr(a.sym, b.sym)
     of nkIdent: result = a.ident.id == b.ident.id
     of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal
     of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal
@@ -462,8 +471,8 @@ proc invalidateFacts*(m: var TModel, n: PNode) =
   # The same mechanism could be used for more complex data stored on the heap;
   # procs that 'write: []' cannot invalidate 'n.kind' for instance. In fact, we
   # could CSE these expressions then and help C's optimizer.
-  for i in 0..high(m):
-    if m[i] != nil and m[i].hasSubTree(n): m[i] = nil
+  for i in 0..high(m.s):
+    if m.s[i] != nil and m.s[i].hasSubTree(n): m.s[i] = nil
 
 proc valuesUnequal(a, b: PNode): bool =
   if a.isValue and b.isValue:
@@ -486,7 +495,7 @@ proc impliesEq(fact, eq: PNode): TImplication =
     if sameTree(fact.sons[2], eq.sons[loc]) and isValue(eq.sons[val]):
       if inSet(fact.sons[1], eq.sons[val]): result = impYes
       else: result = impNo
-  of mNot, mOr, mAnd: internalError(eq.info, "impliesEq")
+  of mNot, mOr, mAnd: assert(false, "impliesEq")
   else: discard
 
 proc leImpliesIn(x, c, aSet: PNode): TImplication =
@@ -494,7 +503,7 @@ proc leImpliesIn(x, c, aSet: PNode): TImplication =
     # fact:  x <= 4;  question x in {56}?
     # --> true if every value <= 4 is in the set {56}
     #
-    var value = newIntNode(c.kind, firstOrd(x.typ))
+    var value = newIntNode(c.kind, firstOrd(nil, x.typ))
     # don't iterate too often:
     if c.intVal - value.intVal < 1000:
       var i, pos, neg: int
@@ -511,7 +520,7 @@ proc geImpliesIn(x, c, aSet: PNode): TImplication =
     # --> true iff every value >= 4 is in the set {56}
     #
     var value = newIntNode(c.kind, c.intVal)
-    let max = lastOrd(x.typ)
+    let max = lastOrd(nil, x.typ)
     # don't iterate too often:
     if max - value.intVal < 1000:
       var i, pos, neg: int
@@ -523,8 +532,8 @@ proc geImpliesIn(x, c, aSet: PNode): TImplication =
       elif neg == i: result = impNo
 
 proc compareSets(a, b: PNode): TImplication =
-  if equalSets(a, b): result = impYes
-  elif intersectSets(a, b).len == 0: result = impNo
+  if equalSets(nil, a, b): result = impYes
+  elif intersectSets(nil, a, b).len == 0: result = impNo
 
 proc impliesIn(fact, loc, aSet: PNode): TImplication =
   case fact.sons[0].sym.magic
@@ -549,7 +558,7 @@ proc impliesIn(fact, loc, aSet: PNode): TImplication =
     elif sameTree(fact.sons[2], loc):
       # 4 < x  -->  3 <= x
       result = geImpliesIn(fact.sons[2], fact.sons[1].pred, aSet)
-  of mNot, mOr, mAnd: internalError(loc.info, "impliesIn")
+  of mNot, mOr, mAnd: assert(false, "impliesIn")
   else: discard
 
 proc valueIsNil(n: PNode): TImplication =
@@ -567,11 +576,11 @@ proc impliesIsNil(fact, eq: PNode): TImplication =
       result = valueIsNil(fact.sons[2].skipConv)
     elif sameTree(fact.sons[2], eq.sons[1]):
       result = valueIsNil(fact.sons[1].skipConv)
-  of mNot, mOr, mAnd: internalError(eq.info, "impliesIsNil")
+  of mNot, mOr, mAnd: assert(false, "impliesIsNil")
   else: discard
 
 proc impliesGe(fact, x, c: PNode): TImplication =
-  internalAssert isLocation(x)
+  assert isLocation(x)
   case fact.sons[0].sym.magic
   of someEq:
     if sameTree(fact.sons[1], x):
@@ -603,7 +612,7 @@ proc impliesGe(fact, x, c: PNode): TImplication =
       # fact: 3 <= x; question: x >= 2 ?  --> true iff 2 <= 3
       if isValue(fact.sons[1]) and isValue(c):
         if leValue(c, fact.sons[1]): result = impYes
-  of mNot, mOr, mAnd: internalError(x.info, "impliesGe")
+  of mNot, mOr, mAnd: assert(false, "impliesGe")
   else: discard
 
 proc impliesLe(fact, x, c: PNode): TImplication =
@@ -643,7 +652,7 @@ proc impliesLe(fact, x, c: PNode): TImplication =
       if isValue(fact.sons[1]) and isValue(c):
         if leValue(c, fact.sons[1].pred): result = impNo
 
-  of mNot, mOr, mAnd: internalError(x.info, "impliesLe")
+  of mNot, mOr, mAnd: assert(false, "impliesLe")
   else: discard
 
 proc impliesLt(fact, x, c: PNode): TImplication =
@@ -707,14 +716,14 @@ proc factImplies(fact, prop: PNode): TImplication =
 
 proc doesImply*(facts: TModel, prop: PNode): TImplication =
   assert prop.kind in nkCallKinds
-  for f in facts:
+  for f in facts.s:
     # facts can be invalidated, in which case they are 'nil':
     if not f.isNil:
       result = f.factImplies(prop)
       if result != impUnknown: return
 
-proc impliesNotNil*(facts: TModel, arg: PNode): TImplication =
-  result = doesImply(facts, opIsNil.buildCall(arg).neg)
+proc impliesNotNil*(m: TModel, arg: PNode): TImplication =
+  result = doesImply(m, m.o.opIsNil.buildCall(arg).neg(m.o))
 
 proc simpleSlice*(a, b: PNode): BiggestInt =
   # returns 'c' if a..b matches (i+c)..(i+c), -1 otherwise. (i)..(i) is matched
@@ -768,8 +777,12 @@ macro `=~`(x: PNode, pat: untyped): bool =
 
   var conds = newTree(nnkBracket)
   m(x, pat, conds)
-  result = nestList(!"and", conds)
-
+  when compiles(nestList(ident"and", conds)):
+    result = nestList(ident"and", conds)
+  #elif declared(macros.toNimIdent):
+  #  result = nestList(toNimIdent"and", conds)
+  else:
+    result = nestList(!"and", conds)
 
 proc isMinusOne(n: PNode): bool =
   n.kind in {nkCharLit..nkUInt64Lit} and n.intVal == -1
@@ -786,10 +799,10 @@ proc ple(m: TModel; a, b: PNode): TImplication =
 
   # use type information too:  x <= 4  iff  high(x) <= 4
   if b.isValue and a.typ != nil and a.typ.isOrdinalType:
-    if lastOrd(a.typ) <= b.intVal: return impYes
+    if lastOrd(nil, a.typ) <= b.intVal: return impYes
   # 3 <= x   iff  low(x) <= 3
   if a.isValue and b.typ != nil and b.typ.isOrdinalType:
-    if firstOrd(b.typ) <= a.intVal: return impYes
+    if firstOrd(nil, b.typ) <= a.intVal: return impYes
 
   # x <= x
   if sameTree(a, b): return impYes
@@ -813,20 +826,20 @@ proc ple(m: TModel; a, b: PNode): TImplication =
   if a.getMagic in someMul and a[2].isValue and a[1].getMagic in someDiv and
       a[1][2].isValue:
     # simplify   (x div 4) * 2 <= y   to  x div (c div d)  <= y
-    if ple(m, buildCall(opDiv, a[1][1], `|div|`(a[1][2], a[2])), b) == impYes:
+    if ple(m, buildCall(m.o.opDiv, a[1][1], `|div|`(a[1][2], a[2])), b) == impYes:
       return impYes
 
   # x*3 + x == x*4. It follows that:
   # x*3 + y <= x*4  if  y <= x  and 3 <= 4
   if a =~ x*dc + y and b =~ x2*ec:
     if sameTree(x, x2):
-      let ec1 = opAdd.buildCall(ec, minusOne())
+      let ec1 = m.o.opAdd.buildCall(ec, minusOne())
       if x >=? 1 and ec >=? 1 and dc >=? 1 and dc <=? ec1 and y <=? x:
         return impYes
   elif a =~ x*dc and b =~ x2*ec + y:
     #echo "BUG cam ehrer e ", a, " <=? ", b
     if sameTree(x, x2):
-      let ec1 = opAdd.buildCall(ec, minusOne())
+      let ec1 = m.o.opAdd.buildCall(ec, minusOne())
       if x >=? 1 and ec >=? 1 and dc >=? 1 and dc <=? ec1 and y <=? zero():
         return impYes
 
@@ -859,9 +872,9 @@ proc ple(m: TModel; a, b: PNode): TImplication =
 
   # use the knowledge base:
   return pleViaModel(m, a, b)
-  #return doesImply(m, opLe.buildCall(a, b))
+  #return doesImply(m, o.opLe.buildCall(a, b))
 
-type TReplacements = seq[tuple[a,b: PNode]]
+type TReplacements = seq[tuple[a, b: PNode]]
 
 proc replaceSubTree(n, x, by: PNode): PNode =
   if sameTree(n, x):
@@ -879,11 +892,11 @@ proc applyReplacements(n: PNode; rep: TReplacements): PNode =
 
 proc pleViaModelRec(m: var TModel; a, b: PNode): TImplication =
   # now check for inferrable facts: a <= b and b <= c  implies a <= c
-  for i in 0..m.high:
-    let fact = m[i]
+  for i in 0..m.s.high:
+    let fact = m.s[i]
     if fact != nil and fact.getMagic in someLe:
       # mark as used:
-      m[i] = nil
+      m.s[i] = nil
       # i <= len-100
       # i <=? len-1
       # --> true  if  (len-100) <= (len-1)
@@ -915,7 +928,7 @@ proc pleViaModelRec(m: var TModel; a, b: PNode): TImplication =
 proc pleViaModel(model: TModel; aa, bb: PNode): TImplication =
   # compute replacements:
   var replacements: TReplacements = @[]
-  for fact in model:
+  for fact in model.s:
     if fact != nil and fact.getMagic in someEq:
       let a = fact[1]
       let b = fact[2]
@@ -925,12 +938,13 @@ proc pleViaModel(model: TModel; aa, bb: PNode): TImplication =
   var a = aa
   var b = bb
   if replacements.len > 0:
-    m = @[]
+    m.s = @[]
+    m.o = model.o
     # make the other facts consistent:
-    for fact in model:
+    for fact in model.s:
       if fact != nil and fact.getMagic notin someEq:
         # XXX 'canon' should not be necessary here, but it is
-        m.add applyReplacements(fact, replacements).canon
+        m.s.add applyReplacements(fact, replacements).canon(m.o)
     a = applyReplacements(aa, replacements)
     b = applyReplacements(bb, replacements)
   else:
@@ -939,31 +953,31 @@ proc pleViaModel(model: TModel; aa, bb: PNode): TImplication =
   result = pleViaModelRec(m, a, b)
 
 proc proveLe*(m: TModel; a, b: PNode): TImplication =
-  let x = canon(opLe.buildCall(a, b))
+  let x = canon(m.o.opLe.buildCall(a, b), m.o)
   #echo "ROOT ", renderTree(x[1]), " <=? ", renderTree(x[2])
   result = ple(m, x[1], x[2])
   if result == impUnknown:
     # try an alternative:  a <= b  iff  not (b < a)  iff  not (b+1 <= a):
-    let y = canon(opLe.buildCall(opAdd.buildCall(b, one()), a))
+    let y = canon(m.o.opLe.buildCall(m.o.opAdd.buildCall(b, one()), a), m.o)
     result = ~ple(m, y[1], y[2])
 
 proc addFactLe*(m: var TModel; a, b: PNode) =
-  m.add canon(opLe.buildCall(a, b))
+  m.s.add canon(m.o.opLe.buildCall(a, b), m.o)
 
 proc settype(n: PNode): PType =
   result = newType(tySet, n.typ.owner)
   addSonSkipIntLit(result, n.typ)
 
-proc buildOf(it, loc: PNode): PNode =
+proc buildOf(it, loc: PNode; o: Operators): PNode =
   var s = newNodeI(nkCurly, it.info, it.len-1)
   s.typ = settype(loc)
   for i in 0..it.len-2: s.sons[i] = it.sons[i]
   result = newNodeI(nkCall, it.info, 3)
-  result.sons[0] = newSymNode(opContains)
+  result.sons[0] = newSymNode(o.opContains)
   result.sons[1] = s
   result.sons[2] = loc
 
-proc buildElse(n: PNode): PNode =
+proc buildElse(n: PNode; o: Operators): PNode =
   var s = newNodeIT(nkCurly, n.info, settype(n.sons[0]))
   for i in 1..n.len-2:
     let branch = n.sons[i]
@@ -971,23 +985,23 @@ proc buildElse(n: PNode): PNode =
     for j in 0..branch.len-2:
       s.add(branch.sons[j])
   result = newNodeI(nkCall, n.info, 3)
-  result.sons[0] = newSymNode(opContains)
+  result.sons[0] = newSymNode(o.opContains)
   result.sons[1] = s
   result.sons[2] = n.sons[0]
 
 proc addDiscriminantFact*(m: var TModel, n: PNode) =
   var fact = newNodeI(nkCall, n.info, 3)
-  fact.sons[0] = newSymNode(opEq)
+  fact.sons[0] = newSymNode(m.o.opEq)
   fact.sons[1] = n.sons[0]
   fact.sons[2] = n.sons[1]
-  m.add fact
+  m.s.add fact
 
 proc addAsgnFact*(m: var TModel, key, value: PNode) =
   var fact = newNodeI(nkCall, key.info, 3)
-  fact.sons[0] = newSymNode(opEq)
+  fact.sons[0] = newSymNode(m.o.opEq)
   fact.sons[1] = key
   fact.sons[2] = value
-  m.add fact
+  m.s.add fact
 
 proc sameSubexprs*(m: TModel; a, b: PNode): bool =
   # This should be used to check whether two *path expressions* refer to the
@@ -1000,7 +1014,7 @@ proc sameSubexprs*(m: TModel; a, b: PNode): bool =
   # However, nil checking requires exactly the same mechanism! But for now
   # we simply use sameTree and live with the unsoundness of the analysis.
   var check = newNodeI(nkCall, a.info, 3)
-  check.sons[0] = newSymNode(opEq)
+  check.sons[0] = newSymNode(m.o.opEq)
   check.sons[1] = a
   check.sons[2] = b
   result = m.doesImply(check) == impYes
@@ -1008,11 +1022,11 @@ proc sameSubexprs*(m: TModel; a, b: PNode): bool =
 proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) =
   let branch = n.sons[i]
   if branch.kind == nkOfBranch:
-    m.add buildOf(branch, n.sons[0])
+    m.s.add buildOf(branch, n.sons[0], m.o)
   else:
-    m.add n.buildElse.neg
+    m.s.add n.buildElse(m.o).neg(m.o)
 
-proc buildProperFieldCheck(access, check: PNode): PNode =
+proc buildProperFieldCheck(access, check: PNode; o: Operators): PNode =
   if check.sons[1].kind == nkCurly:
     result = copyTree(check)
     if access.kind == nkDotExpr:
@@ -1024,10 +1038,10 @@ proc buildProperFieldCheck(access, check: PNode): PNode =
   else:
     # it is some 'not'
     assert check.getMagic == mNot
-    result = buildProperFieldCheck(access, check.sons[1]).neg
+    result = buildProperFieldCheck(access, check.sons[1], o).neg(o)
 
-proc checkFieldAccess*(m: TModel, n: PNode) =
+proc checkFieldAccess*(m: TModel, n: PNode; conf: ConfigRef) =
   for i in 1..n.len-1:
-    let check = buildProperFieldCheck(n.sons[0], n.sons[i])
+    let check = buildProperFieldCheck(n.sons[0], n.sons[i], m.o)
     if check != nil and m.doesImply(check) != impYes:
-      message(n.info, warnProveField, renderTree(n.sons[0])); break
+      message(conf, n.info, warnProveField, renderTree(n.sons[0])); break
diff --git a/compiler/hlo.nim b/compiler/hlo.nim
index 9491eef83..bbbcb4e56 100644
--- a/compiler/hlo.nim
+++ b/compiler/hlo.nim
@@ -12,12 +12,12 @@
 proc hlo(c: PContext, n: PNode): PNode
 
 proc evalPattern(c: PContext, n, orig: PNode): PNode =
-  internalAssert n.kind == nkCall and n.sons[0].kind == nkSym
+  internalAssert c.config, n.kind == nkCall and n.sons[0].kind == nkSym
   # we need to ensure that the resulting AST is semchecked. However, it's
   # aweful to semcheck before macro invocation, so we don't and treat
   # templates and macros as immediate in this context.
   var rule: string
-  if optHints in gOptions and hintPattern in gNotes:
+  if optHints in c.config.options and hintPattern in c.config.notes:
     rule = renderTree(n, {renderNoComments})
   let s = n.sons[0].sym
   case s.kind
@@ -27,8 +27,8 @@ proc evalPattern(c: PContext, n, orig: PNode): PNode =
     result = semTemplateExpr(c, n, s, {efFromHlo})
   else:
     result = semDirectOp(c, n, {})
-  if optHints in gOptions and hintPattern in gNotes:
-    message(orig.info, hintPattern, rule & " --> '" &
+  if optHints in c.config.options and hintPattern in c.config.notes:
+    message(c.config, orig.info, hintPattern, rule & " --> '" &
       renderTree(result, {renderNoComments}) & "'")
 
 proc applyPatterns(c: PContext, n: PNode): PNode =
@@ -36,16 +36,16 @@ proc applyPatterns(c: PContext, n: PNode): PNode =
   # we apply the last pattern first, so that pattern overriding is possible;
   # however the resulting AST would better not trigger the old rule then
   # anymore ;-)
-  for i in countdown(<c.patterns.len, 0):
+  for i in countdown(c.patterns.len-1, 0):
     let pattern = c.patterns[i]
     if not isNil(pattern):
       let x = applyRule(c, pattern, result)
       if not isNil(x):
         assert x.kind in {nkStmtList, nkCall}
         # better be safe than sorry, so check evalTemplateCounter too:
-        inc(evalTemplateCounter)
-        if evalTemplateCounter > 100:
-          globalError(n.info, errTemplateInstantiationTooNested)
+        inc(c.config.evalTemplateCounter)
+        if c.config.evalTemplateCounter > evalTemplateLimit:
+          globalError(c.config, n.info, "template instantiation too nested")
         # deactivate this pattern:
         c.patterns[i] = nil
         if x.kind == nkStmtList:
@@ -54,7 +54,7 @@ proc applyPatterns(c: PContext, n: PNode): PNode =
           result = flattenStmts(x)
         else:
           result = evalPattern(c, x, result)
-        dec(evalTemplateCounter)
+        dec(c.config.evalTemplateCounter)
         # activate this pattern again:
         c.patterns[i] = pattern
 
@@ -75,7 +75,7 @@ proc hlo(c: PContext, n: PNode): PNode =
     result = applyPatterns(c, n)
     if result == n:
       # no optimization applied, try subtrees:
-      for i in 0 .. < safeLen(result):
+      for i in 0 ..< safeLen(result):
         let a = result.sons[i]
         let h = hlo(c, a)
         if h != a: result.sons[i] = h
@@ -86,18 +86,18 @@ proc hlo(c: PContext, n: PNode): PNode =
       else:
         result = fitNode(c, n.typ, result, n.info)
       # optimization has been applied so check again:
-      result = commonOptimizations(c.module, result)
+      result = commonOptimizations(c.graph, c.module, result)
       result = hlo(c, result)
-      result = commonOptimizations(c.module, result)
+      result = commonOptimizations(c.graph, c.module, result)
 
 proc hloBody(c: PContext, n: PNode): PNode =
   # fast exit:
-  if c.patterns.len == 0 or optPatterns notin gOptions: return n
+  if c.patterns.len == 0 or optPatterns notin c.config.options: return n
   c.hloLoopDetector = 0
   result = hlo(c, n)
 
 proc hloStmt(c: PContext, n: PNode): PNode =
   # fast exit:
-  if c.patterns.len == 0 or optPatterns notin gOptions: return n
+  if c.patterns.len == 0 or optPatterns notin c.config.options: return n
   c.hloLoopDetector = 0
   result = hlo(c, n)
diff --git a/compiler/idents.nim b/compiler/idents.nim
index eecfa60a1..58800b73d 100644
--- a/compiler/idents.nim
+++ b/compiler/idents.nim
@@ -12,7 +12,7 @@
 # id. This module is essential for the compiler's performance.
 
 import
-  hashes, strutils, etcpriv, wordrecg
+  hashes, strutils, wordrecg
 
 type
   TIdObj* = object of RootObj
@@ -30,14 +30,9 @@ type
     wordCounter: int
     idAnon*, idDelegator*, emptyIdent*: PIdent
 
-var
-  legacy: IdentCache
+proc resetIdentCache*() = discard
 
-proc resetIdentCache*() =
-  for i in low(legacy.buckets)..high(legacy.buckets):
-    legacy.buckets[i] = nil
-
-proc cmpIgnoreStyle(a, b: cstring, blen: int): int =
+proc cmpIgnoreStyle*(a, b: cstring, blen: int): int =
   if a[0] != b[0]: return 1
   var i = 0
   var j = 0
@@ -45,8 +40,6 @@ proc cmpIgnoreStyle(a, b: cstring, blen: int): int =
   while j < blen:
     while a[i] == '_': inc(i)
     while b[j] == '_': inc(j)
-    while isMagicIdentSeparatorRune(a, i): inc(i, magicIdentSeparatorRuneByteWidth)
-    while isMagicIdentSeparatorRune(b, j): inc(j, magicIdentSeparatorRuneByteWidth)
     # tolower inlined:
     var aa = a[i]
     var bb = b[j]
@@ -73,11 +66,9 @@ proc cmpExact(a, b: cstring, blen: int): int =
   if result == 0:
     if a[i] != '\0': result = 1
 
-{.this: self.}
-
-proc getIdent*(self: IdentCache; identifier: cstring, length: int, h: Hash): PIdent =
-  var idx = h and high(buckets)
-  result = buckets[idx]
+proc getIdent*(ic: IdentCache; identifier: cstring, length: int, h: Hash): PIdent =
+  var idx = h and high(ic.buckets)
+  result = ic.buckets[idx]
   var last: PIdent = nil
   var id = 0
   while result != nil:
@@ -85,8 +76,8 @@ proc getIdent*(self: IdentCache; identifier: cstring, length: int, h: Hash): PId
       if last != nil:
         # make access to last looked up identifier faster:
         last.next = result.next
-        result.next = buckets[idx]
-        buckets[idx] = result
+        result.next = ic.buckets[idx]
+        ic.buckets[idx] = result
       return
     elif cmpIgnoreStyle(cstring(result.s), identifier, length) == 0:
       assert((id == 0) or (id == result.id))
@@ -97,41 +88,31 @@ proc getIdent*(self: IdentCache; identifier: cstring, length: int, h: Hash): PId
   result.h = h
   result.s = newString(length)
   for i in countup(0, length - 1): result.s[i] = identifier[i]
-  result.next = buckets[idx]
-  buckets[idx] = result
+  result.next = ic.buckets[idx]
+  ic.buckets[idx] = result
   if id == 0:
-    inc(wordCounter)
-    result.id = -wordCounter
+    inc(ic.wordCounter)
+    result.id = -ic.wordCounter
   else:
     result.id = id
 
-proc getIdent*(self: IdentCache; identifier: string): PIdent =
-  result = getIdent(cstring(identifier), len(identifier),
+proc getIdent*(ic: IdentCache; identifier: string): PIdent =
+  result = getIdent(ic, cstring(identifier), len(identifier),
                     hashIgnoreStyle(identifier))
 
-proc getIdent*(self: IdentCache; identifier: string, h: Hash): PIdent =
-  result = getIdent(cstring(identifier), len(identifier), h)
+proc getIdent*(ic: IdentCache; identifier: string, h: Hash): PIdent =
+  result = getIdent(ic, cstring(identifier), len(identifier), h)
 
 proc newIdentCache*(): IdentCache =
-  if legacy.isNil:
-    result = IdentCache()
-    result.idAnon = result.getIdent":anonymous"
-    result.wordCounter = 1
-    result.idDelegator = result.getIdent":delegator"
-    result.emptyIdent = result.getIdent("")
-    # initialize the keywords:
-    for s in countup(succ(low(specialWords)), high(specialWords)):
-      result.getIdent(specialWords[s], hashIgnoreStyle(specialWords[s])).id = ord(s)
-    legacy = result
-  else:
-    result = legacy
+  result = IdentCache()
+  result.idAnon = result.getIdent":anonymous"
+  result.wordCounter = 1
+  result.idDelegator = result.getIdent":delegator"
+  result.emptyIdent = result.getIdent("")
+  # initialize the keywords:
+  for s in countup(succ(low(specialWords)), high(specialWords)):
+    result.getIdent(specialWords[s], hashIgnoreStyle(specialWords[s])).id = ord(s)
 
 proc whichKeyword*(id: PIdent): TSpecialWord =
   if id.id < 0: result = wInvalid
   else: result = TSpecialWord(id.id)
-
-proc getIdent*(identifier: string): PIdent =
-  ## for backwards compatibility.
-  if legacy.isNil:
-    discard newIdentCache()
-  legacy.getIdent identifier
diff --git a/compiler/idgen.nim b/compiler/idgen.nim
index c6b1a4d07..239df0c57 100644
--- a/compiler/idgen.nim
+++ b/compiler/idgen.nim
@@ -9,7 +9,7 @@
 
 ## This module contains a simple persistent id generator.
 
-import idents, strutils, os, options
+import idents, strutils, os, options, pathutils
 
 var gFrontEndId*: int
 
@@ -36,20 +36,20 @@ proc setId*(id: int) {.inline.} =
 proc idSynchronizationPoint*(idRange: int) =
   gFrontEndId = (gFrontEndId div idRange + 1) * idRange + 1
 
-proc toGid(f: string): string =
+proc toGid(conf: ConfigRef; f: AbsoluteFile): string =
   # we used to use ``f.addFileExt("gid")`` (aka ``$project.gid``), but this
   # will cause strange bugs if multiple projects are in the same folder, so
   # we simply use a project independent name:
-  result = options.completeGeneratedFilePath("nim.gid")
+  result = options.completeGeneratedFilePath(conf, AbsoluteFile"nim.gid").string
 
-proc saveMaxIds*(project: string) =
-  var f = open(project.toGid, fmWrite)
+proc saveMaxIds*(conf: ConfigRef; project: AbsoluteFile) =
+  var f = open(toGid(conf, project), fmWrite)
   f.writeLine($gFrontEndId)
   f.close()
 
-proc loadMaxIds*(project: string) =
+proc loadMaxIds*(conf: ConfigRef; project: AbsoluteFile) =
   var f: File
-  if open(f, project.toGid, fmRead):
+  if open(f, toGid(conf, project), fmRead):
     var line = newStringOfCap(20)
     if f.readLine(line):
       var frontEndId = parseInt(line)
diff --git a/compiler/importer.nim b/compiler/importer.nim
index c4861df7f..131b1ad8a 100644
--- a/compiler/importer.nim
+++ b/compiler/importer.nim
@@ -7,58 +7,29 @@
 #    distribution, for details about the copyright.
 #
 
-# This module implements the symbol importing mechanism.
+## This module implements the symbol importing mechanism.
 
 import
-  intsets, strutils, os, ast, astalgo, msgs, options, idents, rodread, lookups,
-  semdata, passes, renderer
-
-proc evalImport*(c: PContext, n: PNode): PNode
-proc evalFrom*(c: PContext, n: PNode): PNode
-
-proc getModuleName*(n: PNode): string =
-  # This returns a short relative module name without the nim extension
-  # e.g. like "system", "importer" or "somepath/module"
-  # The proc won't perform any checks that the path is actually valid
-  case n.kind
-  of nkStrLit, nkRStrLit, nkTripleStrLit:
-    try:
-      result = pathSubs(n.strVal, n.info.toFullPath().splitFile().dir)
-    except ValueError:
-      localError(n.info, "invalid path: " & n.strVal)
-      result = n.strVal
-  of nkIdent:
-    result = n.ident.s
-  of nkSym:
-    result = n.sym.name.s
-  of nkInfix, nkPrefix:
-    if n.sons[0].kind == nkIdent and n.sons[0].ident.id == getIdent("as").id:
-      # XXX hack ahead:
-      n.kind = nkImportAs
-      n.sons[0] = n.sons[1]
-      n.sons[1] = n.sons[2]
-      n.sons.setLen(2)
-      return getModuleName(n.sons[0])
-    # hacky way to implement 'x / y /../ z':
-    result = renderTree(n, {renderNoComments}).replace(" ")
-  of nkDotExpr:
-    result = renderTree(n, {renderNoComments}).replace(".", "/")
-  of nkImportAs:
-    result = getModuleName(n.sons[0])
-  else:
-    localError(n.info, errGenerated, "invalid module name: '$1'" % n.renderTree)
-    result = ""
-
-proc checkModuleName*(n: PNode; doLocalError=true): int32 =
-  # This returns the full canonical path for a given module import
-  let modulename = n.getModuleName
-  let fullPath = findModule(modulename, n.info.toFullPath)
-  if fullPath.len == 0:
-    if doLocalError:
-      localError(n.info, errCannotOpenFile, modulename)
-    result = InvalidFileIDX
-  else:
-    result = fullPath.fileInfoIdx
+  intsets, strutils, os, ast, astalgo, msgs, options, idents, lookups,
+  semdata, passes, renderer, modulepaths, sigmatch, lineinfos
+
+proc readExceptSet*(c: PContext, n: PNode): IntSet =
+  assert n.kind in {nkImportExceptStmt, nkExportExceptStmt}
+  result = initIntSet()
+  for i in 1 ..< n.len:
+    let ident = lookups.considerQuotedIdent(c, n[i])
+    result.incl(ident.id)
+
+proc importPureEnumField*(c: PContext; s: PSym) =
+  let check = strTableGet(c.importTable.symbols, s.name)
+  if check == nil:
+    let checkB = strTableGet(c.pureEnumFields, s.name)
+    if checkB == nil:
+      strTableAdd(c.pureEnumFields, s)
+    else:
+      # mark as ambigous:
+      incl(c.ambiguousSymbols, checkB.id)
+      incl(c.ambiguousSymbols, s.id)
 
 proc rawImportSymbol(c: PContext, s: PSym) =
   # This does not handle stubs, because otherwise loading on demand would be
@@ -66,7 +37,7 @@ proc rawImportSymbol(c: PContext, s: PSym) =
   # check if we have already a symbol of the same name:
   var check = strTableGet(c.importTable.symbols, s.name)
   if check != nil and check.id != s.id:
-    if s.kind notin OverloadableSyms:
+    if s.kind notin OverloadableSyms or check.kind notin OverloadableSyms:
       # s and check need to be qualified:
       incl(c.ambiguousSymbols, s.id)
       incl(c.ambiguousSymbols, check.id)
@@ -75,11 +46,11 @@ proc rawImportSymbol(c: PContext, s: PSym) =
   strTableAdd(c.importTable.symbols, s)
   if s.kind == skType:
     var etyp = s.typ
-    if etyp.kind in {tyBool, tyEnum} and sfPure notin s.flags:
+    if etyp.kind in {tyBool, tyEnum}:
       for j in countup(0, sonsLen(etyp.n) - 1):
         var e = etyp.n.sons[j].sym
         if e.kind != skEnumField:
-          internalError(s.info, "rawImportSymbol")
+          internalError(c.config, s.info, "rawImportSymbol")
           # BUGFIX: because of aliases for enums the symbol may already
           # have been put into the symbol table
           # BUGFIX: but only iff they are the same symbols!
@@ -91,21 +62,25 @@ proc rawImportSymbol(c: PContext, s: PSym) =
             break
           check = nextIdentIter(it, c.importTable.symbols)
         if e != nil:
-          rawImportSymbol(c, e)
+          if sfPure notin s.flags:
+            rawImportSymbol(c, e)
+          else:
+            importPureEnumField(c, e)
   else:
     # rodgen assures that converters and patterns are no stubs
     if s.kind == skConverter: addConverter(c, s)
     if hasPattern(s): addPattern(c, s)
 
 proc importSymbol(c: PContext, n: PNode, fromMod: PSym) =
-  let ident = lookups.considerQuotedIdent(n)
+  let ident = lookups.considerQuotedIdent(c, n)
   let s = strTableGet(fromMod.tab, ident)
   if s == nil:
     errorUndeclaredIdentifier(c, n.info, ident.s)
   else:
-    if s.kind == skStub: loadStub(s)
+    when false:
+      if s.kind == skStub: loadStub(s)
     if s.kind notin ExportableSymKinds:
-      internalError(n.info, "importSymbol: 2")
+      internalError(c.config, n.info, "importSymbol: 2")
     # for an enumeration we have to add all identifiers
     case s.kind
     of skProcKinds:
@@ -113,7 +88,7 @@ proc importSymbol(c: PContext, n: PNode, fromMod: PSym) =
       var it: TIdentIter
       var e = initIdentIter(it, fromMod.tab, s.name)
       while e != nil:
-        if e.name.id != s.name.id: internalError(n.info, "importSymbol: 3")
+        if e.name.id != s.name.id: internalError(c.config, n.info, "importSymbol: 3")
         rawImportSymbol(c, e)
         e = nextIdentIter(it, fromMod.tab)
     else: rawImportSymbol(c, s)
@@ -125,7 +100,7 @@ proc importAllSymbolsExcept(c: PContext, fromMod: PSym, exceptSet: IntSet) =
     if s.kind != skModule:
       if s.kind != skEnumField:
         if s.kind notin ExportableSymKinds:
-          internalError(s.info, "importAllSymbols: " & $s.kind)
+          internalError(c.config, s.info, "importAllSymbols: " & $s.kind & " " & s.name.s)
         if exceptSet.isNil or s.name.id notin exceptSet:
           rawImportSymbol(c, s)
     s = nextIter(i, fromMod.tab)
@@ -146,22 +121,23 @@ proc importForwarded(c: PContext, n: PNode, exceptSet: IntSet) =
       elif exceptSet.isNil or s.name.id notin exceptSet:
         rawImportSymbol(c, s)
   of nkExportExceptStmt:
-    localError(n.info, errGenerated, "'export except' not implemented")
+    localError(c.config, n.info, "'export except' not implemented")
   else:
     for i in 0..safeLen(n)-1:
       importForwarded(c, n.sons[i], exceptSet)
 
-proc importModuleAs(n: PNode, realModule: PSym): PSym =
+proc importModuleAs(c: PContext; n: PNode, realModule: PSym): PSym =
   result = realModule
   if n.kind != nkImportAs: discard
   elif n.len != 2 or n.sons[1].kind != nkIdent:
-    localError(n.info, errGenerated, "module alias must be an identifier")
+    localError(c.config, n.info, "module alias must be an identifier")
   elif n.sons[1].ident.id != realModule.name.id:
     # some misguided guy will write 'import abc.foo as foo' ...
-    result = createModuleAlias(realModule, n.sons[1].ident, realModule.info)
+    result = createModuleAlias(realModule, n.sons[1].ident, realModule.info,
+                               c.config.options)
 
-proc myImportModule(c: PContext, n: PNode): PSym =
-  var f = checkModuleName(n)
+proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym =
+  let f = checkModuleName(c.config, n)
   if f != InvalidFileIDX:
     let L = c.graph.importStack.len
     let recursion = c.graph.importStack.find(f)
@@ -171,10 +147,10 @@ proc myImportModule(c: PContext, n: PNode): PSym =
       var err = ""
       for i in countup(recursion, L-1):
         if i > recursion: err.add "\n"
-        err.add toFullPath(c.graph.importStack[i]) & " imports " &
-                toFullPath(c.graph.importStack[i+1])
+        err.add toFullPath(c.config, c.graph.importStack[i]) & " imports " &
+                toFullPath(c.config, c.graph.importStack[i+1])
       c.recursiveDep = err
-    result = importModuleAs(n, gImportModule(c.graph, c.module, f, c.cache))
+    result = importModuleAs(c, n, c.graph.importModuleCallback(c.graph, c.module, f))
     #echo "set back to ", L
     c.graph.importStack.setLen(L)
     # we cannot perform this check reliably because of
@@ -182,13 +158,27 @@ proc myImportModule(c: PContext, n: PNode): PSym =
     when true:
       if result.info.fileIndex == c.module.info.fileIndex and
           result.info.fileIndex == n.info.fileIndex:
-        localError(n.info, errGenerated, "A module cannot import itself")
+        localError(c.config, n.info, "A module cannot import itself")
     if sfDeprecated in result.flags:
-      message(n.info, warnDeprecated, result.name.s)
-    #suggestSym(n.info, result, false)
+      if result.constraint != nil:
+        message(c.config, n.info, warnDeprecated, result.constraint.strVal & "; " & result.name.s)
+      else:
+        message(c.config, n.info, warnDeprecated, result.name.s)
+    suggestSym(c.config, n.info, result, c.graph.usageSym, false)
+    importStmtResult.add newSymNode(result, n.info)
+    #newStrNode(toFullPath(c.config, f), n.info)
+
+proc transformImportAs(c: PContext; n: PNode): PNode =
+  if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as":
+    result = newNodeI(nkImportAs, n.info)
+    result.add n.sons[1]
+    result.add n.sons[2]
+  else:
+    result = n
 
-proc impMod(c: PContext; it: PNode) =
-  let m = myImportModule(c, it)
+proc impMod(c: PContext; it: PNode; importStmtResult: PNode) =
+  let it = transformImportAs(c, it)
+  let m = myImportModule(c, it, importStmtResult)
   if m != nil:
     var emptySet: IntSet
     # ``addDecl`` needs to be done before ``importAllSymbols``!
@@ -196,25 +186,35 @@ proc impMod(c: PContext; it: PNode) =
     importAllSymbolsExcept(c, m, emptySet)
     #importForwarded(c, m.ast, emptySet)
 
-proc evalImport(c: PContext, n: PNode): PNode =
-  result = n
+proc evalImport*(c: PContext, n: PNode): PNode =
+  result = newNodeI(nkImportStmt, n.info)
   for i in countup(0, sonsLen(n) - 1):
     let it = n.sons[i]
     if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
-      let sep = renderTree(it.sons[0], {renderNoComments})
-      let dir = renderTree(it.sons[1], {renderNoComments})
+      let sep = it[0]
+      let dir = it[1]
+      var imp = newNodeI(nkInfix, it.info)
+      imp.add sep
+      imp.add dir
+      imp.add sep # dummy entry, replaced in the loop
       for x in it[2]:
-        let f = renderTree(x, {renderNoComments})
-        let a = newStrNode(nkStrLit, (dir & sep & f).replace(" "))
-        a.info = it.info
-        impMod(c, a)
+        # transform `a/b/[c as d]` to `/a/b/c as d`
+        if x.kind == nkInfix and x.sons[0].ident.s == "as":
+          let impAs = copyTree(x)
+          imp.sons[2] = x.sons[1]
+          impAs.sons[1] = imp
+          impMod(c, imp, result)
+        else:
+          imp.sons[2] = x
+          impMod(c, imp, result)
     else:
-      impMod(c, it)
+      impMod(c, it, result)
 
-proc evalFrom(c: PContext, n: PNode): PNode =
-  result = n
-  checkMinSonsLen(n, 2)
-  var m = myImportModule(c, n.sons[0])
+proc evalFrom*(c: PContext, n: PNode): PNode =
+  result = newNodeI(nkImportStmt, n.info)
+  checkMinSonsLen(n, 2, c.config)
+  n.sons[0] = transformImportAs(c, n.sons[0])
+  var m = myImportModule(c, n.sons[0], result)
   if m != nil:
     n.sons[0] = newSymNode(m)
     addDecl(c, m, n.info)               # add symbol to symbol table of module
@@ -223,15 +223,12 @@ proc evalFrom(c: PContext, n: PNode): PNode =
         importSymbol(c, n.sons[i], m)
 
 proc evalImportExcept*(c: PContext, n: PNode): PNode =
-  result = n
-  checkMinSonsLen(n, 2)
-  var m = myImportModule(c, n.sons[0])
+  result = newNodeI(nkImportStmt, n.info)
+  checkMinSonsLen(n, 2, c.config)
+  n.sons[0] = transformImportAs(c, n.sons[0])
+  var m = myImportModule(c, n.sons[0], result)
   if m != nil:
     n.sons[0] = newSymNode(m)
     addDecl(c, m, n.info)               # add symbol to symbol table of module
-    var exceptSet = initIntSet()
-    for i in countup(1, sonsLen(n) - 1):
-      let ident = lookups.considerQuotedIdent(n.sons[i])
-      exceptSet.incl(ident.id)
-    importAllSymbolsExcept(c, m, exceptSet)
+    importAllSymbolsExcept(c, m, readExceptSet(c, n))
     #importForwarded(c, m.ast, exceptSet)
diff --git a/compiler/incremental.nim b/compiler/incremental.nim
new file mode 100644
index 000000000..f66a75efd
--- /dev/null
+++ b/compiler/incremental.nim
@@ -0,0 +1,197 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Basic type definitions the module graph needs in order to support
+## incremental compilations.
+
+const nimIncremental* = defined(nimIncremental)
+
+import options, lineinfos, pathutils
+
+when nimIncremental:
+  import ast, msgs, intsets, btrees, db_sqlite, std / sha1
+  from strutils import parseInt
+
+  type
+    Writer* = object
+      sstack*: seq[PSym]          # a stack of symbols to process
+      tstack*: seq[PType]         # a stack of types to process
+      tmarks*, smarks*: IntSet
+      forwardedSyms*: seq[PSym]
+
+    Reader* = object
+      syms*: BTree[int, PSym]
+      types*: BTree[int, PType]
+
+    IncrementalCtx* = object
+      db*: DbConn
+      w*: Writer
+      r*: Reader
+      configChanged*: bool
+
+  proc init*(incr: var IncrementalCtx) =
+    incr.w.sstack = @[]
+    incr.w.tstack = @[]
+    incr.w.tmarks = initIntSet()
+    incr.w.smarks = initIntSet()
+    incr.w.forwardedSyms = @[]
+    incr.r.syms = initBTree[int, PSym]()
+    incr.r.types = initBTree[int, PType]()
+
+
+  proc hashFileCached*(conf: ConfigRef; fileIdx: FileIndex; fullpath: AbsoluteFile): string =
+    result = msgs.getHash(conf, fileIdx)
+    if result.len == 0:
+      result = $secureHashFile(string fullpath)
+      msgs.setHash(conf, fileIdx, result)
+
+  proc toDbFileId*(incr: var IncrementalCtx; conf: ConfigRef; fileIdx: FileIndex): int =
+    if fileIdx == FileIndex(-1): return -1
+    let fullpath = toFullPath(conf, fileIdx)
+    let row = incr.db.getRow(sql"select id, fullhash from filenames where fullpath = ?",
+      fullpath)
+    let id = row[0]
+    let fullhash = hashFileCached(conf, fileIdx, AbsoluteFile fullpath)
+    if id.len == 0:
+      result = int incr.db.insertID(sql"insert into filenames(nimid, fullpath, fullhash) values (?, ?, ?)",
+        int(fileIdx), fullpath, fullhash)
+    else:
+      if row[1] != fullhash:
+        incr.db.exec(sql"update filenames set fullhash = ? where fullpath = ?", fullhash, fullpath)
+      result = parseInt(id)
+
+  proc fromDbFileId*(incr: var IncrementalCtx; conf: ConfigRef; dbId: int): FileIndex =
+    if dbId == -1: return FileIndex(-1)
+    let fullpath = incr.db.getValue(sql"select fullpath from filenames where id = ?", dbId)
+    doAssert fullpath.len > 0, "cannot find file name for DB ID " & $dbId
+    result = fileInfoIdx(conf, AbsoluteFile fullpath)
+
+
+  proc addModuleDep*(incr: var IncrementalCtx; conf: ConfigRef;
+                     module, fileIdx: FileIndex;
+                     isIncludeFile: bool) =
+    if conf.symbolFiles != v2Sf: return
+
+    let a = toDbFileId(incr, conf, module)
+    let b = toDbFileId(incr, conf, fileIdx)
+
+    incr.db.exec(sql"insert into deps(module, dependency, isIncludeFile) values (?, ?, ?)",
+      a, b, ord(isIncludeFile))
+
+  # --------------- Database model ---------------------------------------------
+
+  proc createDb*(db: DbConn) =
+    db.exec(sql"""
+      create table if not exists controlblock(
+        idgen integer not null
+      );
+    """)
+
+    db.exec(sql"""
+      create table if not exists config(
+        config varchar(8000) not null
+      );
+    """)
+
+    db.exec(sql"""
+      create table if not exists filenames(
+        id integer primary key,
+        nimid integer not null,
+        fullpath varchar(8000) not null,
+        fullHash varchar(256) not null
+      );
+    """)
+    db.exec sql"create index if not exists FilenameIx on filenames(fullpath);"
+
+    db.exec(sql"""
+      create table if not exists modules(
+        id integer primary key,
+        nimid integer not null,
+        fullpath varchar(8000) not null,
+        interfHash varchar(256) not null,
+        fullHash varchar(256) not null,
+
+        created timestamp not null default (DATETIME('now'))
+      );""")
+    db.exec(sql"""create unique index if not exists SymNameIx on modules(fullpath);""")
+
+    db.exec(sql"""
+      create table if not exists deps(
+        id integer primary key,
+        module integer not null,
+        dependency integer not null,
+        isIncludeFile integer not null,
+        foreign key (module) references filenames(id),
+        foreign key (dependency) references filenames(id)
+      );""")
+    db.exec(sql"""create index if not exists DepsIx on deps(module);""")
+
+    db.exec(sql"""
+      create table if not exists types(
+        id integer primary key,
+        nimid integer not null,
+        module integer not null,
+        data blob not null,
+        foreign key (module) references module(id)
+      );
+    """)
+    db.exec sql"create index TypeByModuleIdx on types(module);"
+    db.exec sql"create index TypeByNimIdIdx on types(nimid);"
+
+    db.exec(sql"""
+      create table if not exists syms(
+        id integer primary key,
+        nimid integer not null,
+        module integer not null,
+        name varchar(256) not null,
+        data blob not null,
+        exported int not null,
+        foreign key (module) references module(id)
+      );
+    """)
+    db.exec sql"create index if not exists SymNameIx on syms(name);"
+    db.exec sql"create index SymByNameAndModuleIdx on syms(name, module);"
+    db.exec sql"create index SymByModuleIdx on syms(module);"
+    db.exec sql"create index SymByNimIdIdx on syms(nimid);"
+
+
+    db.exec(sql"""
+      create table if not exists toplevelstmts(
+        id integer primary key,
+        position integer not null,
+        module integer not null,
+        data blob not null,
+        foreign key (module) references module(id)
+      );
+    """)
+    db.exec sql"create index TopLevelStmtByModuleIdx on toplevelstmts(module);"
+    db.exec sql"create index TopLevelStmtByPositionIdx on toplevelstmts(position);"
+
+    db.exec(sql"""
+      create table if not exists statics(
+        id integer primary key,
+        module integer not null,
+        data blob not null,
+        foreign key (module) references module(id)
+      );
+    """)
+    db.exec sql"create index StaticsByModuleIdx on toplevelstmts(module);"
+    db.exec sql"insert into controlblock(idgen) values (0)"
+
+
+else:
+  type
+    IncrementalCtx* = object
+
+  template init*(incr: IncrementalCtx) = discard
+
+  template addModuleDep*(incr: var IncrementalCtx; conf: ConfigRef;
+                     module, fileIdx: FileIndex;
+                     isIncludeFile: bool) =
+    discard
diff --git a/compiler/installer.ini b/compiler/installer.ini
index 0987aa6be..63790d90f 100644
--- a/compiler/installer.ini
+++ b/compiler/installer.ini
@@ -6,7 +6,7 @@ Name: "Nim"
 Version: "$version"
 Platforms: """
   windows: i386;amd64
-  linux: i386;amd64;powerpc64;arm;sparc;mips;mipsel;powerpc;powerpc64el;arm64
+  linux: i386;amd64;powerpc64;arm;sparc;mips;mipsel;mips64;mips64el;powerpc;powerpc64el;arm64;riscv64
   macosx: i386;amd64;powerpc64
   solaris: i386;amd64;sparc;sparc64
   freebsd: i386;amd64
@@ -14,6 +14,8 @@ Platforms: """
   openbsd: i386;amd64
   dragonfly: i386;amd64
   haiku: i386;amd64
+  android: i386;arm;arm64
+  nintendoswitch: arm64
 """
 
 Authors: "Andreas Rumpf"
@@ -47,11 +49,8 @@ Start: "doc/html/overview.html"
 
 
 [Other]
-Files: "readme.txt;copying.txt"
-Files: "makefile"
+Files: "copying.txt"
 Files: "koch.nim"
-Files: "install_nimble.nims"
-Files: "install_tools.nims"
 
 Files: "icons/nim.ico"
 Files: "icons/nim.rc"
@@ -66,14 +65,10 @@ Files: "compiler"
 Files: "doc"
 Files: "doc/html"
 Files: "tools"
-Files: "web/website.ini"
-Files: "web/ticker.html"
-Files: "web/*.nim"
-Files: "web/*.rst"
-Files: "web/*.csv"
-Files: "web/news/*.rst"
-Files: "bin/nimblepkg/*.nim"
-Files: "bin/nimblepkg/*.cfg"
+Files: "nimpretty"
+Files: "testament"
+Files: "nimsuggest"
+Files: "nimsuggest/tests/*.nim"
 
 [Lib]
 Files: "lib"
@@ -81,13 +76,11 @@ Files: "lib"
 [Other]
 Files: "examples"
 Files: "dist/nimble"
-Files: "dist/nimsuggest"
 
 Files: "tests"
 
 [Windows]
 Files: "bin/nim.exe"
-Files: "bin/c2nim.exe"
 Files: "bin/nimgrep.exe"
 Files: "bin/nimsuggest.exe"
 Files: "bin/nimble.exe"
@@ -110,9 +103,9 @@ Download: r"Aporia Text Editor|dist|aporia.zip|97997|https://nim-lang.org/downlo
 ; for now only NSIS supports optional downloads
 
 [WinBin]
-Files: "$NIMINSTDEPS/makelink.exe"
-Files: "$NIMINSTDEPS/7zG.exe"
-Files: "$NIMINSTDEPS/*.dll"
+Files: "bin/makelink.exe"
+Files: "bin/7zG.exe"
+Files: "bin/*.dll"
 
 [UnixBin]
 Files: "bin/nim"
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index f5e8027b3..4d22c4224 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -8,7 +8,6 @@
 #
 
 # This is the JavaScript code generator.
-# Also a PHP code generator. ;-)
 
 discard """
 The JS code generator contains only 2 tricks:
@@ -31,18 +30,18 @@ implements the required case distinction.
 
 import
   ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options,
-  nversion, nimsets, msgs, securehash, bitsets, idents, types, os,
-  times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils,
-  intsets, cgmeth, lowerings
+  nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables,
+  times, ropes, math, passes, ccgutils, wordrecg, renderer,
+  intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils, pathutils, transf
 
-from modulegraphs import ModuleGraph
+from modulegraphs import ModuleGraph, PPassContext
 
 type
-  TTarget = enum
-    targetJS, targetPHP
-  TJSGen = object of TPassContext
+  TJSGen = object of PPassContext
     module: PSym
-    target: TTarget
+    graph: ModuleGraph
+    config: ConfigRef
+    sigConflicts: CountTable[SigHash]
 
   BModule = ref TJSGen
   TJSTypeKind = enum       # necessary JS "types"
@@ -67,13 +66,19 @@ type
     res: Rope               # result part; index if this is an
                              # (address, index)-tuple
     address: Rope           # address of an (address, index)-tuple
+    tmpLoc: Rope            # tmp var which stores the (address, index)
+                            # pair to prevent multiple evals.
+                            # the tmp is initialized upon evaling the
+                            # address.
+                            # might be nil.
+                            # (see `maybeMakeTemp`)
 
   TBlock = object
     id: int                  # the ID of the label; positive means that it
                              # has been used (i.e. the label should be emitted)
     isLoop: bool             # whether it's a 'block' or 'while'
 
-  TGlobals = object
+  PGlobals = ref object of RootObj
     typeInfo, constants, code: Rope
     forwarded: seq[PSym]
     generatedSyms: IntSet
@@ -81,7 +86,6 @@ type
     classes: seq[(PType, Rope)]
     unique: int    # for temp identifier generation
 
-  PGlobals = ref TGlobals
   PProc = ref TProc
   TProc = object
     procDef: PNode
@@ -91,14 +95,37 @@ type
     module: BModule
     g: PGlobals
     beforeRetNeeded: bool
-    target: TTarget # duplicated here for faster dispatching
     unique: int    # for temp identifier generation
     blocks: seq[TBlock]
+    extraIndent: int
     up: PProc     # up the call chain; required for closure support
     declaredGlobals: IntSet
 
-template `|`(a, b: untyped): untyped {.dirty.} =
-  (if p.target == targetJS: a else: b)
+template config*(p: PProc): ConfigRef = p.module.config
+
+proc indentLine(p: PProc, r: Rope): Rope =
+  result = r
+  var p = p
+  while true:
+    for i in countup(0, p.blocks.len - 1 + p.extraIndent):
+      prepend(result, "\t".rope)
+    if p.up == nil or p.up.prc != p.prc.owner:
+      break
+    p = p.up
+
+template line(p: PProc, added: string) =
+  add(p.body, indentLine(p, rope(added)))
+
+template line(p: PProc, added: Rope) =
+  add(p.body, indentLine(p, added))
+
+template lineF(p: PProc, frmt: FormatStr, args: varargs[Rope]) =
+  add(p.body, indentLine(p, ropes.`%`(frmt, args)))
+
+template nested(p, body) =
+  inc p.extraIndent
+  body
+  dec p.extraIndent
 
 proc newGlobals(): PGlobals =
   new(result)
@@ -110,16 +137,15 @@ proc newGlobals(): PGlobals =
 proc initCompRes(r: var TCompRes) =
   r.address = nil
   r.res = nil
+  r.tmpLoc = nil
   r.typ = etyNone
   r.kind = resNone
 
 proc rdLoc(a: TCompRes): Rope {.inline.} =
-  result = a.res
-  when false:
-    if a.typ != etyBaseIndex:
-      result = a.res
-    else:
-      result = "$1[$2]" % [a.address, a.res]
+  if a.typ != etyBaseIndex:
+    result = a.res
+  else:
+    result = "$1[$2]" % [a.address, a.res]
 
 proc newProc(globals: PGlobals, module: BModule, procDef: PNode,
              options: TOptions): PProc =
@@ -129,10 +155,8 @@ proc newProc(globals: PGlobals, module: BModule, procDef: PNode,
     module: module,
     procDef: procDef,
     g: globals,
-    target: module.target)
+    extraIndent: int(procDef != nil))
   if procDef != nil: result.prc = procDef.sons[namePos].sym
-  if result.target == targetPHP:
-    result.declaredGlobals = initIntSet()
 
 proc declareGlobal(p: PProc; id: int; r: Rope) =
   if p.prc != nil and not p.declaredGlobals.containsOrIncl(id):
@@ -145,7 +169,7 @@ const
 proc mapType(typ: PType): TJSTypeKind =
   let t = skipTypes(typ, abstractInst)
   case t.kind
-  of tyVar, tyRef, tyPtr:
+  of tyVar, tyRef, tyPtr, tyLent:
     if skipTypes(t.lastSon, abstractInst).kind in MappedToObject:
       result = etyObject
     else:
@@ -159,26 +183,29 @@ proc mapType(typ: PType): TJSTypeKind =
   of tyBool: result = etyBool
   of tyFloat..tyFloat128: result = etyFloat
   of tySet: result = etyObject # map a set to a table
-  of tyString, tySequence: result = etySeq
-  of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs:
+  of tyString, tySequence, tyOpt: result = etySeq
+  of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs, tyUncheckedArray:
     result = etyObject
   of tyNil: result = etyNull
-  of tyGenericInst, tyGenericParam, tyGenericBody, tyGenericInvocation,
-     tyNone, tyFromExpr, tyForward, tyEmpty, tyFieldAccessor,
-     tyExpr, tyStmt, tyTypeDesc, tyTypeClasses, tyVoid, tyAlias:
+  of tyGenericParam, tyGenericBody, tyGenericInvocation,
+     tyNone, tyFromExpr, tyForward, tyEmpty,
+     tyExpr, tyStmt, tyTypeDesc, tyBuiltInTypeClass, tyCompositeTypeClass,
+     tyAnd, tyOr, tyNot, tyAnything, tyVoid:
     result = etyNone
+  of tyGenericInst, tyInferred, tyAlias, tyUserTypeClass, tyUserTypeClassInst,
+     tySink:
+    result = mapType(typ.lastSon)
   of tyStatic:
     if t.n != nil: result = mapType(lastSon t)
     else: result = etyNone
   of tyProc: result = etyProc
   of tyCString: result = etyString
-  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("mapType")
+  of tyOptAsRef: doAssert(false, "mapType")
 
 proc mapType(p: PProc; typ: PType): TJSTypeKind =
-  if p.target == targetPHP: result = etyObject
-  else: result = mapType(typ)
+  result = mapType(typ)
 
-proc mangleName(s: PSym; target: TTarget): Rope =
+proc mangleName(m: BModule, s: PSym): Rope =
   proc validJsName(name: string): bool =
     result = true
     const reservedWords = ["abstract", "await", "boolean", "break", "byte",
@@ -203,7 +230,7 @@ proc mangleName(s: PSym; target: TTarget): Rope =
   if result == nil:
     if s.kind == skField and s.name.s.validJsName:
       result = rope(s.name.s)
-    elif target == targetJS or s.kind == skTemp:
+    elif s.kind == skTemp:
       result = rope(mangle(s.name.s))
     else:
       var x = newStringOfCap(s.name.s.len)
@@ -221,9 +248,15 @@ proc mangleName(s: PSym; target: TTarget): Rope =
           x.add("HEX" & toHex(ord(c), 2))
         inc i
       result = rope(x)
-    if s.name.s != "this" and s.kind != skField:
-      add(result, "_")
-      add(result, rope(s.id))
+    # From ES5 on reserved words can be used as object field names
+    if s.kind != skField:
+      if optHotCodeReloading in m.config.options:
+        # When hot reloading is enabled, we must ensure that the names
+        # of functions and types will be preserved across rebuilds:
+        add(result, idOrSig(s, m.module.name.s, m.sigConflicts))
+      else:
+        add(result, "_")
+        add(result, rope(s.id))
     s.loc.r = result
 
 proc escapeJSString(s: string): string =
@@ -244,9 +277,7 @@ proc escapeJSString(s: string): string =
   result.add("\"")
 
 proc makeJSString(s: string, escapeNonAscii = true): Rope =
-  if s.isNil:
-    result = "null".rope
-  elif escapeNonAscii:
+  if escapeNonAscii:
     result = strutils.escape(s).rope
   else:
     result = escapeJSString(s).rope
@@ -260,23 +291,22 @@ proc genConstant(p: PProc, c: PSym)
 
 proc useMagic(p: PProc, name: string) =
   if name.len == 0: return
-  var s = magicsys.getCompilerProc(name)
+  var s = magicsys.getCompilerProc(p.module.graph, name)
   if s != nil:
-    internalAssert s.kind in {skProc, skMethod, skConverter}
+    internalAssert p.config, s.kind in {skProc, skFunc, skMethod, skConverter}
     if not p.g.generatedSyms.containsOrIncl(s.id):
       let code = genProc(p, s)
       add(p.g.constants, code)
   else:
-    # we used to exclude the system module from this check, but for DLL
-    # generation support this sloppyness leads to hard to detect bugs, so
-    # we're picky here for the system module too:
-    if p.prc != nil: globalError(p.prc.info, errSystemNeeds, name)
-    else: rawMessage(errSystemNeeds, name)
+    if p.prc != nil:
+      globalError(p.config, p.prc.info, "system module needs: " & name)
+    else:
+      rawMessage(p.config, errGenerated, "system module needs: " & name)
 
 proc isSimpleExpr(p: PProc; n: PNode): bool =
   # calls all the way down --> can stay expression based
-  if n.kind in nkCallKinds+{nkBracketExpr, nkDotExpr, nkPar} or
-      (p.target == targetJS and n.kind in {nkObjConstr, nkBracket, nkCurly}):
+  if n.kind in nkCallKinds+{nkBracketExpr, nkDotExpr, nkPar, nkTupleConstr} or
+      (n.kind in {nkObjConstr, nkBracket, nkCurly}):
     for c in n:
       if not p.isSimpleExpr(c): return false
     result = true
@@ -285,12 +315,9 @@ proc isSimpleExpr(p: PProc; n: PNode): bool =
 
 proc getTemp(p: PProc, defineInLocals: bool = true): Rope =
   inc(p.unique)
-  if p.target == targetJS:
-    result = "Tmp$1" % [rope(p.unique)]
-    if defineInLocals:
-      addf(p.locals, "var $1;$n", [result])
-  else:
-    result = "$$Tmp$1" % [rope(p.unique)]
+  result = "Tmp$1" % [rope(p.unique)]
+  if defineInLocals:
+    add(p.locals, p.indentLine("var $1;$n" % [result]))
 
 proc genAnd(p: PProc, a, b: PNode, r: var TCompRes) =
   assert r.kind == resNone
@@ -313,9 +340,11 @@ proc genAnd(p: PProc, a, b: PNode, r: var TCompRes) =
     #     tmp = b
     # tmp
     gen(p, a, x)
-    p.body.addf("if (!$1) $2 = false; else {", [x.rdLoc, r.rdLoc])
-    gen(p, b, y)
-    p.body.addf("$2 = $1; }", [y.rdLoc, r.rdLoc])
+    lineF(p, "if (!$1) $2 = false; else {", [x.rdLoc, r.rdLoc])
+    p.nested:
+      gen(p, b, y)
+      lineF(p, "$2 = $1;", [y.rdLoc, r.rdLoc])
+    line(p, "}")
 
 proc genOr(p: PProc, a, b: PNode, r: var TCompRes) =
   assert r.kind == resNone
@@ -329,9 +358,11 @@ proc genOr(p: PProc, a, b: PNode, r: var TCompRes) =
     r.res = p.getTemp
     r.kind = resVal
     gen(p, a, x)
-    p.body.addf("if ($1) $2 = true; else {", [x.rdLoc, r.rdLoc])
-    gen(p, b, y)
-    p.body.addf("$2 = $1; }", [y.rdLoc, r.rdLoc])
+    lineF(p, "if ($1) $2 = true; else {", [x.rdLoc, r.rdLoc])
+    p.nested:
+      gen(p, b, y)
+      lineF(p, "$2 = $1;", [y.rdLoc, r.rdLoc])
+    line(p, "}")
 
 type
   TMagicFrmt = array[0..3, string]
@@ -342,8 +373,8 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
     ["addInt", "", "addInt($1, $2)", "($1 + $2)"], # AddI
     ["subInt", "", "subInt($1, $2)", "($1 - $2)"], # SubI
     ["mulInt", "", "mulInt($1, $2)", "($1 * $2)"], # MulI
-    ["divInt", "", "divInt($1, $2)", "Math.floor($1 / $2)"], # DivI
-    ["modInt", "", "modInt($1, $2)", "Math.floor($1 % $2)"], # ModI
+    ["divInt", "", "divInt($1, $2)", "Math.trunc($1 / $2)"], # DivI
+    ["modInt", "", "modInt($1, $2)", "Math.trunc($1 % $2)"], # ModI
     ["addInt", "", "addInt($1, $2)", "($1 + $2)"], # Succ
     ["subInt", "", "subInt($1, $2)", "($1 - $2)"], # Pred
     ["", "", "($1 + $2)", "($1 + $2)"], # AddF64
@@ -352,6 +383,7 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
     ["", "", "($1 / $2)", "($1 / $2)"], # DivF64
     ["", "", "", ""], # ShrI
     ["", "", "($1 << $2)", "($1 << $2)"], # ShlI
+    ["", "", "($1 >> $2)", "($1 >> $2)"], # AshrI
     ["", "", "($1 & $2)", "($1 & $2)"], # BitandI
     ["", "", "($1 | $2)", "($1 | $2)"], # BitorI
     ["", "", "($1 ^ $2)", "($1 ^ $2)"], # BitxorI
@@ -410,50 +442,80 @@ const # magic checked op; magic unchecked op; checked op; unchecked op
     ["toU32", "toU32", "toU32($1)", "toU32($1)"], # toU32
     ["", "", "$1", "$1"],     # ToFloat
     ["", "", "$1", "$1"],     # ToBiggestFloat
-    ["", "", "Math.floor($1)", "Math.floor($1)"], # ToInt
-    ["", "", "Math.floor($1)", "Math.floor($1)"], # ToBiggestInt
+    ["", "", "Math.trunc($1)", "Math.trunc($1)"], # ToInt
+    ["", "", "Math.trunc($1)", "Math.trunc($1)"], # ToBiggestInt
     ["nimCharToStr", "nimCharToStr", "nimCharToStr($1)", "nimCharToStr($1)"],
-    ["nimBoolToStr", "nimBoolToStr", "nimBoolToStr($1)", "nimBoolToStr($1)"], [
-      "cstrToNimstr", "cstrToNimstr", "cstrToNimstr(($1)+\"\")",
-      "cstrToNimstr(($1)+\"\")"], ["cstrToNimstr", "cstrToNimstr",
-                                   "cstrToNimstr(($1)+\"\")",
-                                   "cstrToNimstr(($1)+\"\")"], ["cstrToNimstr",
-      "cstrToNimstr", "cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")"],
+    ["nimBoolToStr", "nimBoolToStr", "nimBoolToStr($1)", "nimBoolToStr($1)"],
+    ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")"],
+    ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")"],
+    ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr(($1)+\"\")", "cstrToNimstr(($1)+\"\")"],
     ["cstrToNimstr", "cstrToNimstr", "cstrToNimstr($1)", "cstrToNimstr($1)"],
     ["", "", "$1", "$1"]]
 
+proc needsTemp(p: PProc; n: PNode): bool =
+  # check if n contains a call to determine
+  # if a temp should be made to prevent multiple evals
+  if n.kind in nkCallKinds + {nkTupleConstr, nkObjConstr, nkBracket, nkCurly}:
+    return true
+  for c in n:
+    if needsTemp(p, c):
+      return true
+
+proc maybeMakeTemp(p: PProc, n: PNode; x: TCompRes): tuple[a, tmp: Rope] =
+  var
+    a = x.rdLoc
+    b = a
+  if needsTemp(p, n):
+    # if we have tmp just use it
+    if x.tmpLoc != nil and (mapType(n.typ) == etyBaseIndex or n.kind in {nkHiddenDeref, nkDerefExpr}):
+      b = "$1[0][$1[1]]" % [x.tmpLoc]
+      (a: a, tmp: b)
+    else:
+      let tmp = p.getTemp
+      b = tmp
+      a = "($1 = $2, $1)" % [tmp, a]
+      (a: a, tmp: b)
+  else:
+    (a: a, tmp: b)
+
 proc binaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
+  # $1 and $2 in the `frmt` string bind to lhs and rhs of the expr,
+  # if $3 or $4 are present they will be substituted with temps for
+  # lhs and rhs respectively
   var x, y: TCompRes
   useMagic(p, magic)
   gen(p, n.sons[1], x)
   gen(p, n.sons[2], y)
-  r.res = frmt % [x.rdLoc, y.rdLoc]
+
+  var
+    a, tmp = x.rdLoc
+    b, tmp2 = y.rdLoc
+  if "$3" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x)
+  if "$4" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x)
+
+  r.res = frmt % [a, b, tmp, tmp2]
   r.kind = resExpr
 
 proc unsignedTrimmerJS(size: BiggestInt): Rope =
   case size
-    of 1: rope"& 0xff"
-    of 2: rope"& 0xffff"
-    of 4: rope">>> 0"
-    else: rope""
+  of 1: rope"& 0xff"
+  of 2: rope"& 0xffff"
+  of 4: rope">>> 0"
+  else: rope""
 
-proc unsignedTrimmerPHP(size: BiggestInt): Rope =
-  case size
-    of 1: rope"& 0xff"
-    of 2: rope"& 0xffff"
-    of 4: rope"& 0xffffffff"
-    else: rope""
 
 template unsignedTrimmer(size: BiggestInt): Rope =
-  size.unsignedTrimmerJS | size.unsignedTrimmerPHP
+  size.unsignedTrimmerJS
 
-proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, reassign: bool = false) =
+proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string,
+                    reassign = false) =
   var x, y: TCompRes
   gen(p, n.sons[1], x)
   gen(p, n.sons[2], y)
   let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size)
   if reassign:
-    r.res = "$1 = (($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer]
+    let (a, tmp) = maybeMakeTemp(p, n[1], x)
+    r.res = "$1 = (($5 $2 $3) $4)" % [a, rope op, y.rdLoc, trimmer, tmp]
   else:
     r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer]
 
@@ -467,23 +529,26 @@ proc ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
   r.kind = resExpr
 
 proc unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
+  # $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1
   useMagic(p, magic)
   gen(p, n.sons[1], r)
-  r.res = frmt % [r.rdLoc]
+  var a, tmp = r.rdLoc
+  if "$2" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], r)
+  r.res = frmt % [a, tmp]
   r.kind = resExpr
 
-proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic, ops: TMagicOps) =
+proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
   var
     x, y: TCompRes
   let i = ord(optOverflowCheck notin p.options)
-  useMagic(p, ops[op][i])
+  useMagic(p, jsOps[op][i])
   if sonsLen(n) > 2:
     gen(p, n.sons[1], x)
     gen(p, n.sons[2], y)
-    r.res = ops[op][i + 2] % [x.rdLoc, y.rdLoc]
+    r.res = jsOps[op][i + 2] % [x.rdLoc, y.rdLoc]
   else:
     gen(p, n.sons[1], r)
-    r.res = ops[op][i + 2] % [r.rdLoc]
+    r.res = jsOps[op][i + 2] % [r.rdLoc]
 
 proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
   case op
@@ -492,48 +557,28 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
   of mMulU: binaryUintExpr(p, n, r, "*")
   of mDivU: binaryUintExpr(p, n, r, "/")
   of mDivI:
-    if p.target == targetPHP:
-      var x, y: TCompRes
-      gen(p, n.sons[1], x)
-      gen(p, n.sons[2], y)
-      r.res = "intval($1 / $2)" % [x.rdLoc, y.rdLoc]
-    else:
-      arithAux(p, n, r, op, jsOps)
+    arithAux(p, n, r, op)
   of mModI:
-    if p.target == targetPHP:
-      var x, y: TCompRes
-      gen(p, n.sons[1], x)
-      gen(p, n.sons[2], y)
-      r.res = "($1 % $2)" % [x.rdLoc, y.rdLoc]
-    else:
-      arithAux(p, n, r, op, jsOps)
+    arithAux(p, n, r, op)
   of mShrI:
     var x, y: TCompRes
     gen(p, n.sons[1], x)
     gen(p, n.sons[2], y)
     let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size)
-    if p.target == targetPHP:
-      # XXX prevent multi evaluations
-      r.res = "(($1 $2) >= 0) ? (($1 $2) >> $3) : ((($1 $2) & 0x7fffffff) >> $3) | (0x40000000 >> ($3 - 1))" % [x.rdLoc, trimmer, y.rdLoc]
-    else:
-      r.res = "(($1 $2) >>> $3)" % [x.rdLoc, trimmer, y.rdLoc]
+    r.res = "(($1 $2) >>> $3)" % [x.rdLoc, trimmer, y.rdLoc]
   of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mFloatToStr,
       mCStrToStr, mStrToStr, mEnumToStr:
-    if p.target == targetPHP:
-      if op == mEnumToStr:
-        var x: TCompRes
-        gen(p, n.sons[1], x)
-        r.res = "$#[$#]" % [genEnumInfoPHP(p, n.sons[1].typ), x.rdLoc]
-      elif op == mCharToStr:
-        var x: TCompRes
-        gen(p, n.sons[1], x)
-        r.res = "chr($#)" % [x.rdLoc]
-      else:
-        gen(p, n.sons[1], r)
+    arithAux(p, n, r, op)
+  of mEqRef, mEqUntracedRef:
+    if mapType(n[1].typ) != etyBaseIndex:
+      arithAux(p, n, r, op)
     else:
-      arithAux(p, n, r, op, jsOps)
+      var x, y: TCompRes
+      gen(p, n[1], x)
+      gen(p, n[2], y)
+      r.res = "($# == $# && $# == $#)" % [x.address, y.address, x.res, y.res]
   else:
-    arithAux(p, n, r, op, jsOps)
+    arithAux(p, n, r, op)
   r.kind = resExpr
 
 proc hasFrameInfo(p: PProc): bool =
@@ -543,19 +588,19 @@ proc hasFrameInfo(p: PProc): bool =
 proc genLineDir(p: PProc, n: PNode) =
   let line = toLinenumber(n.info)
   if optLineDir in p.options:
-    addf(p.body, "// line $2 \"$1\"$n",
-         [rope(toFilename(n.info)), rope(line)])
+    lineF(p, "// line $2 \"$1\"$n",
+         [rope(toFilename(p.config, n.info)), rope(line)])
   if {optStackTrace, optEndb} * p.options == {optStackTrace, optEndb} and
       ((p.prc == nil) or sfPure notin p.prc.flags):
     useMagic(p, "endb")
-    addf(p.body, "endb($1);$n", [rope(line)])
+    lineF(p, "endb($1);$n", [rope(line)])
   elif hasFrameInfo(p):
-    addf(p.body, "F.line = $1;$n" | "$$F['line'] = $1;$n", [rope(line)])
+    lineF(p, "F.line = $1;$n", [rope(line)])
 
 proc genWhileStmt(p: PProc, n: PNode) =
   var
     cond: TCompRes
-  internalAssert isEmptyType(n.typ)
+  internalAssert p.config, isEmptyType(n.typ)
   genLineDir(p, n)
   inc(p.unique)
   var length = len(p.blocks)
@@ -563,20 +608,20 @@ proc genWhileStmt(p: PProc, n: PNode) =
   p.blocks[length].id = -p.unique
   p.blocks[length].isLoop = true
   let labl = p.unique.rope
-  addf(p.body, "L$1: while (true) {$n" | "while (true) {$n", [labl])
-  gen(p, n.sons[0], cond)
-  addf(p.body, "if (!$1) break L$2;$n" | "if (!$1) goto L$2;$n",
+  lineF(p, "L$1: while (true) {$n", [labl])
+  p.nested: gen(p, n.sons[0], cond)
+  lineF(p, "if (!$1) break L$2;$n",
        [cond.res, labl])
-  genStmt(p, n.sons[1])
-  addf(p.body, "}$n" | "}L$#:;$n", [labl])
+  p.nested: genStmt(p, n.sons[1])
+  lineF(p, "}$n", [labl])
   setLen(p.blocks, length)
 
 proc moveInto(p: PProc, src: var TCompRes, dest: TCompRes) =
   if src.kind != resNone:
     if dest.kind != resNone:
-      p.body.addf("$1 = $2;$n", [dest.rdLoc, src.rdLoc])
+      lineF(p, "$1 = $2;$n", [dest.rdLoc, src.rdLoc])
     else:
-      p.body.addf("$1;$n", [src.rdLoc])
+      lineF(p, "$1;$n", [src.rdLoc])
     src.kind = resNone
     src.res = nil
 
@@ -612,81 +657,98 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
   var i = 1
   var length = sonsLen(n)
   var catchBranchesExist = length > 1 and n.sons[i].kind == nkExceptBranch
-  if catchBranchesExist and p.target == targetJS:
-    add(p.body, "++excHandler;" & tnl)
+  if catchBranchesExist:
+    add(p.body, "++excHandler;\L")
   var tmpFramePtr = rope"F"
   if optStackTrace notin p.options:
     tmpFramePtr = p.getTemp(true)
-    add(p.body, tmpFramePtr & " = framePtr;" & tnl)
-  addf(p.body, "try {$n", [])
-  if p.target == targetPHP and p.globals == nil:
-      p.globals = "global $lastJSError; global $prevJSError;".rope
+    line(p, tmpFramePtr & " = framePtr;\L")
+  lineF(p, "try {$n", [])
   var a: TCompRes
   gen(p, n.sons[0], a)
   moveInto(p, a, r)
   var generalCatchBranchExists = false
-  let dollar = rope(if p.target == targetJS: "" else: "$")
-  if p.target == targetJS and catchBranchesExist:
+  if catchBranchesExist:
     addf(p.body, "--excHandler;$n} catch (EXC) {$n var prevJSError = lastJSError;$n" &
         " lastJSError = EXC;$n --excHandler;$n", [])
-    add(p.body, "framePtr = $1;$n" % [tmpFramePtr])
-  elif p.target == targetPHP:
-    addf(p.body, "} catch (Exception $$EXC) {$n $$prevJSError = $$lastJSError;$n $$lastJSError = $$EXC;$n", [])
+    line(p, "framePtr = $1;$n" % [tmpFramePtr])
   while i < length and n.sons[i].kind == nkExceptBranch:
     let blen = sonsLen(n.sons[i])
     if blen == 1:
       # general except section:
       generalCatchBranchExists = true
-      if i > 1: addf(p.body, "else {$n", [])
+      if i > 1: lineF(p, "else {$n", [])
       gen(p, n.sons[i].sons[0], a)
       moveInto(p, a, r)
-      if i > 1: addf(p.body, "}$n", [])
+      if i > 1: lineF(p, "}$n", [])
     else:
       var orExpr: Rope = nil
+      var excAlias: PNode = nil
+
       useMagic(p, "isObj")
       for j in countup(0, blen - 2):
-        if n.sons[i].sons[j].kind != nkType:
-          internalError(n.info, "genTryStmt")
+        var throwObj: PNode
+        let it = n.sons[i].sons[j]
+
+        if it.isInfixAs():
+          throwObj = it[1]
+          excAlias = it[2]
+          # If this is a ``except exc as sym`` branch there must be no following
+          # nodes
+          doAssert orExpr == nil
+        elif it.kind == nkType:
+          throwObj = it
+        else:
+          internalError(p.config, n.info, "genTryStmt")
+
         if orExpr != nil: add(orExpr, "||")
-        addf(orExpr, "isObj($2lastJSError.m_type, $1)",
-             [genTypeInfo(p, n.sons[i].sons[j].typ), dollar])
-      if i > 1: add(p.body, "else ")
-      addf(p.body, "if ($1lastJSError && ($2)) {$n", [dollar, orExpr])
+        # Generate the correct type checking code depending on whether this is a
+        # NIM-native or a JS-native exception
+        # if isJsObject(throwObj.typ):
+        if isImportedException(throwObj.typ, p.config):
+          addf(orExpr, "lastJSError instanceof $1",
+            [throwObj.typ.sym.loc.r])
+        else:
+          addf(orExpr, "isObj(lastJSError.m_type, $1)",
+               [genTypeInfo(p, throwObj.typ)])
+
+      if i > 1: line(p, "else ")
+      lineF(p, "if (lastJSError && ($1)) {$n", [orExpr])
+      # If some branch requires a local alias introduce it here. This is needed
+      # since JS cannot do ``catch x as y``.
+      if excAlias != nil:
+        excAlias.sym.loc.r = mangleName(p.module, excAlias.sym)
+        lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.r)
       gen(p, n.sons[i].sons[blen - 1], a)
       moveInto(p, a, r)
-      addf(p.body, "}$n", [])
+      lineF(p, "}$n", [])
     inc(i)
   if catchBranchesExist:
     if not generalCatchBranchExists:
       useMagic(p, "reraiseException")
-      add(p.body, "else {" & tnl & "reraiseException();" & tnl & "}" & tnl)
-    addf(p.body, "$1lastJSError = $1prevJSError;$n", [dollar])
-  if p.target == targetJS:
-    add(p.body, "} finally {" & tnl)
-    add(p.body, "framePtr = $1;$n" % [tmpFramePtr])
-  if p.target == targetPHP:
-    # XXX ugly hack for PHP codegen
-    add(p.body, "}" & tnl)
+      line(p, "else {\L")
+      line(p, "\treraiseException();\L")
+      line(p, "}\L")
+    lineF(p, "lastJSError = prevJSError;$n")
+  line(p, "} finally {\L")
+  line(p, "framePtr = $1;$n" % [tmpFramePtr])
   if i < length and n.sons[i].kind == nkFinally:
     genStmt(p, n.sons[i].sons[0])
-  if p.target == targetPHP:
-    # XXX ugly hack for PHP codegen
-    add(p.body, "if($lastJSError) throw($lastJSError);" & tnl)
-  if p.target == targetJS:
-    add(p.body, "}" & tnl)
+  line(p, "}\L")
 
 proc genRaiseStmt(p: PProc, n: PNode) =
-  genLineDir(p, n)
   if n.sons[0].kind != nkEmpty:
     var a: TCompRes
     gen(p, n.sons[0], a)
     let typ = skipTypes(n.sons[0].typ, abstractPtrs)
+    genLineDir(p, n)
     useMagic(p, "raiseException")
-    addf(p.body, "raiseException($1, $2);$n",
-         [a.rdLoc, makeJSString(typ.sym.name.s)])
+    lineF(p, "raiseException($1, $2);$n",
+             [a.rdLoc, makeJSString(typ.sym.name.s)])
   else:
+    genLineDir(p, n)
     useMagic(p, "reraiseException")
-    add(p.body, "reraiseException();" & tnl)
+    line(p, "reraiseException();\L")
 
 proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) =
   var
@@ -694,11 +756,11 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) =
   genLineDir(p, n)
   gen(p, n.sons[0], cond)
   let stringSwitch = skipTypes(n.sons[0].typ, abstractVar).kind == tyString
-  if stringSwitch and p.target == targetJS:
+  if stringSwitch:
     useMagic(p, "toJSStr")
-    addf(p.body, "switch (toJSStr($1)) {$n", [cond.rdLoc])
+    lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc])
   else:
-    addf(p.body, "switch ($1) {$n", [cond.rdLoc])
+    lineF(p, "switch ($1) {$n", [cond.rdLoc])
   if not isEmptyType(n.typ):
     r.kind = resVal
     r.res = getTemp(p)
@@ -712,44 +774,46 @@ proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) =
           var v = copyNode(e.sons[0])
           while v.intVal <= e.sons[1].intVal:
             gen(p, v, cond)
-            addf(p.body, "case $1: ", [cond.rdLoc])
+            lineF(p, "case $1:$n", [cond.rdLoc])
             inc(v.intVal)
         else:
           if stringSwitch:
             case e.kind
-            of nkStrLit..nkTripleStrLit: addf(p.body, "case $1: ",
+            of nkStrLit..nkTripleStrLit: lineF(p, "case $1:$n",
                 [makeJSString(e.strVal, false)])
-            else: internalError(e.info, "jsgen.genCaseStmt: 2")
+            else: internalError(p.config, e.info, "jsgen.genCaseStmt: 2")
           else:
             gen(p, e, cond)
-            addf(p.body, "case $1: ", [cond.rdLoc])
-      gen(p, lastSon(it), stmt)
-      moveInto(p, stmt, r)
-      addf(p.body, "$nbreak;$n", [])
+            lineF(p, "case $1:$n", [cond.rdLoc])
+      p.nested:
+        gen(p, lastSon(it), stmt)
+        moveInto(p, stmt, r)
+        lineF(p, "break;$n", [])
     of nkElse:
-      addf(p.body, "default: $n", [])
-      gen(p, it.sons[0], stmt)
-      moveInto(p, stmt, r)
-      addf(p.body, "break;$n", [])
-    else: internalError(it.info, "jsgen.genCaseStmt")
-  addf(p.body, "}$n", [])
+      lineF(p, "default: $n", [])
+      p.nested:
+        gen(p, it.sons[0], stmt)
+        moveInto(p, stmt, r)
+        lineF(p, "break;$n", [])
+    else: internalError(p.config, it.info, "jsgen.genCaseStmt")
+  lineF(p, "}$n", [])
 
 proc genBlock(p: PProc, n: PNode, r: var TCompRes) =
   inc(p.unique)
   let idx = len(p.blocks)
   if n.sons[0].kind != nkEmpty:
     # named block?
-    if (n.sons[0].kind != nkSym): internalError(n.info, "genBlock")
+    if (n.sons[0].kind != nkSym): internalError(p.config, n.info, "genBlock")
     var sym = n.sons[0].sym
     sym.loc.k = locOther
     sym.position = idx+1
+  let labl = p.unique
+  lineF(p, "L$1: do {$n", [labl.rope])
   setLen(p.blocks, idx + 1)
   p.blocks[idx].id = - p.unique # negative because it isn't used yet
-  let labl = p.unique
-  addf(p.body, "L$1: do {$n" | "", [labl.rope])
   gen(p, n.sons[1], r)
-  addf(p.body, "} while(false);$n" | "$nL$#:;$n", [labl.rope])
   setLen(p.blocks, idx)
+  lineF(p, "} while(false);$n", [labl.rope])
 
 proc genBreakStmt(p: PProc, n: PNode) =
   var idx: int
@@ -765,21 +829,44 @@ proc genBreakStmt(p: PProc, n: PNode) =
     idx = len(p.blocks) - 1
     while idx >= 0 and not p.blocks[idx].isLoop: dec idx
     if idx < 0 or not p.blocks[idx].isLoop:
-      internalError(n.info, "no loop to break")
+      internalError(p.config, n.info, "no loop to break")
   p.blocks[idx].id = abs(p.blocks[idx].id) # label is used
-  addf(p.body, "break L$1;$n" | "goto L$1;$n", [rope(p.blocks[idx].id)])
+  lineF(p, "break L$1;$n", [rope(p.blocks[idx].id)])
 
 proc genAsmOrEmitStmt(p: PProc, n: PNode) =
   genLineDir(p, n)
+  p.body.add p.indentLine(nil)
   for i in countup(0, sonsLen(n) - 1):
-    case n.sons[i].kind
-    of nkStrLit..nkTripleStrLit: add(p.body, n.sons[i].strVal)
+    let it = n[i]
+    case it.kind
+    of nkStrLit..nkTripleStrLit:
+      p.body.add(it.strVal)
     of nkSym:
-      let v = n.sons[i].sym
-      if p.target == targetPHP and v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}:
-        add(p.body, "$")
-      add(p.body, mangleName(v, p.target))
-    else: internalError(n.sons[i].info, "jsgen: genAsmOrEmitStmt()")
+      let v = it.sym
+      # for backwards compatibility we don't deref syms here :-(
+      if false:
+        discard
+      else:
+        var r: TCompRes
+        gen(p, it, r)
+
+        if it.typ.kind == tyPointer:
+          # A fat pointer is disguised as an array
+          r.res = r.address
+          r.address = nil
+          r.typ = etyNone
+        elif r.typ == etyBaseIndex:
+          # Deference first
+          r.res = "$1[$2]" % [r.address, r.res]
+          r.address = nil
+          r.typ = etyNone
+
+        p.body.add(r.rdLoc)
+    else:
+      var r: TCompRes
+      gen(p, it, r)
+      p.body.add(r.rdLoc)
+  p.body.add "\L"
 
 proc genIf(p: PProc, n: PNode, r: var TCompRes) =
   var cond, stmt: TCompRes
@@ -791,18 +878,18 @@ proc genIf(p: PProc, n: PNode, r: var TCompRes) =
     let it = n.sons[i]
     if sonsLen(it) != 1:
       if i > 0:
-        addf(p.body, "else {$n", [])
+        lineF(p, "else {$n", [])
         inc(toClose)
-      gen(p, it.sons[0], cond)
-      addf(p.body, "if ($1) {$n", [cond.rdLoc])
+      p.nested: gen(p, it.sons[0], cond)
+      lineF(p, "if ($1) {$n", [cond.rdLoc])
       gen(p, it.sons[1], stmt)
     else:
       # else part:
-      addf(p.body, "else {$n", [])
-      gen(p, it.sons[0], stmt)
+      lineF(p, "else {$n", [])
+      p.nested: gen(p, it.sons[0], stmt)
     moveInto(p, stmt, r)
-    addf(p.body, "}$n", [])
-  add(p.body, repeat('}', toClose) & tnl)
+    lineF(p, "}$n", [])
+  line(p, repeat('}', toClose) & "\L")
 
 proc generateHeader(p: PProc, typ: PType): Rope =
   result = nil
@@ -811,99 +898,110 @@ proc generateHeader(p: PProc, typ: PType): Rope =
     var param = typ.n.sons[i].sym
     if isCompileTimeOnly(param.typ): continue
     if result != nil: add(result, ", ")
-    var name = mangleName(param, p.target)
-    if p.target == targetJS:
-      add(result, name)
-      if mapType(param.typ) == etyBaseIndex:
-        add(result, ", ")
-        add(result, name)
-        add(result, "_Idx")
-    elif not (i == 1 and param.name.s == "this"):
-      let k = param.typ.skipTypes({tyGenericInst, tyAlias}).kind
-      if k in {tyVar, tyRef, tyPtr, tyPointer}:
-        add(result, "&")
-      add(result, "$")
+    var name = mangleName(p.module, param)
+    add(result, name)
+    if mapType(param.typ) == etyBaseIndex:
+      add(result, ", ")
       add(result, name)
-      # XXX I think something like this is needed for PHP to really support
-      # ptr "inside" strings and seq
-      #if mapType(param.typ) == etyBaseIndex:
-      #  add(result, ", $")
-      #  add(result, name)
-      #  add(result, "_Idx")
+      add(result, "_Idx")
+
+proc countJsParams(typ: PType): int =
+  for i in countup(1, sonsLen(typ.n) - 1):
+    assert(typ.n.sons[i].kind == nkSym)
+    var param = typ.n.sons[i].sym
+    if isCompileTimeOnly(param.typ): continue
+    if mapType(param.typ) == etyBaseIndex:
+      inc result, 2
+    else:
+      inc result
 
 const
   nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
-    nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkObjConstr, nkStringToCString,
+    nkFloatLit..nkFloat64Lit, nkCurly, nkPar, nkStringToCString,
     nkCStringToString, nkCall, nkPrefix, nkPostfix, nkInfix,
     nkCommand, nkHiddenCallConv, nkCallStrLit}
 
 proc needsNoCopy(p: PProc; y: PNode): bool =
-  result = (y.kind in nodeKindsNeedNoCopy) or
-      (skipTypes(y.typ, abstractInst).kind in {tyRef, tyPtr, tyVar}) or
-      p.target == targetPHP
+  # if the node is a literal object constructor we have to recursively
+  # check the expressions passed into it
+  case y.kind
+  of nkObjConstr:
+    for arg in y.sons[1..^1]:
+      if not needsNoCopy(p, arg[1]):
+        return false
+  of nkTupleConstr:
+    for arg in y.sons:
+      var arg = arg
+      if arg.kind == nkExprColonExpr:
+        arg = arg[1]
+      if not needsNoCopy(p, arg):
+        return false
+  of nkBracket:
+    for arg in y.sons:
+      if not needsNoCopy(p, arg):
+        return false
+  of nodeKindsNeedNoCopy:
+    return true
+  else:
+    return (mapType(y.typ) != etyBaseIndex and
+            (skipTypes(y.typ, abstractInst).kind in
+             {tyRef, tyPtr, tyLent, tyVar, tyCString, tyProc} + IntegralTypes))
+  return true
 
 proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
   var a, b: TCompRes
-
-  if p.target == targetPHP and x.kind == nkBracketExpr and
-      x[0].typ.skipTypes(abstractVar).kind in {tyString, tyCString}:
-    var c: TCompRes
-    gen(p, x[0], a)
-    gen(p, x[1], b)
-    gen(p, y, c)
-    addf(p.body, "$#[$#] = chr($#);$n", [a.rdLoc, b.rdLoc, c.rdLoc])
-    return
-
   var xtyp = mapType(p, x.typ)
 
-  if x.kind == nkHiddenDeref and x.sons[0].kind == nkCall and xtyp != etyObject:
-    gen(p, x.sons[0], a)
-    let tmp = p.getTemp(false)
-    addf(p.body, "var $1 = $2;$n", [tmp, a.rdLoc])
-    a.res = "$1[0][$1[1]]" % [tmp]
-  else:
-    gen(p, x, a)
-
+  gen(p, x, a)
+  genLineDir(p, y)
   gen(p, y, b)
 
   # we don't care if it's an etyBaseIndex (global) of a string, it's
   # still a string that needs to be copied properly:
-  if x.typ.skipTypes(abstractInst).kind in {tySequence, tyString}:
+  if x.typ.skipTypes(abstractInst).kind in {tySequence, tyOpt, tyString}:
     xtyp = etySeq
   case xtyp
   of etySeq:
     if (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
-      addf(p.body, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
+      lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
     else:
       useMagic(p, "nimCopy")
-      addf(p.body, "$1 = nimCopy(null, $2, $3);$n",
-           [a.rdLoc, b.res, genTypeInfo(p, y.typ)])
+      lineF(p, "$1 = nimCopy(null, $2, $3);$n",
+               [a.rdLoc, b.res, genTypeInfo(p, y.typ)])
   of etyObject:
     if (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded:
-      addf(p.body, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
+      lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
     else:
       useMagic(p, "nimCopy")
-      addf(p.body, "nimCopy($1, $2, $3);$n",
-           [a.res, b.res, genTypeInfo(p, y.typ)])
+      lineF(p, "nimCopy($1, $2, $3);$n",
+               [a.res, b.res, genTypeInfo(p, y.typ)])
   of etyBaseIndex:
     if a.typ != etyBaseIndex or b.typ != etyBaseIndex:
       if y.kind == nkCall:
         let tmp = p.getTemp(false)
-        addf(p.body, "var $1 = $4; $2 = $1[0]; $3 = $1[1];$n", [tmp, a.address, a.res, b.rdLoc])
+        lineF(p, "var $1 = $4; $2 = $1[0]; $3 = $1[1];$n", [tmp, a.address, a.res, b.rdLoc])
+      elif b.typ == etyBaseIndex:
+        lineF(p, "$# = [$#, $#];$n", [a.res, b.address, b.res])
       else:
-        internalError(x.info, "genAsgn")
+        internalError(p.config, x.info, "genAsgn")
     else:
-      addf(p.body, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res])
+      lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res])
   else:
-    addf(p.body, "$1 = $2;$n", [a.res, b.res])
+    lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc])
 
 proc genAsgn(p: PProc, n: PNode) =
-  genLineDir(p, n)
-  genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=p.target == targetPHP)
+  genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false)
 
 proc genFastAsgn(p: PProc, n: PNode) =
-  genLineDir(p, n)
-  genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=true)
+  # 'shallowCopy' always produced 'noCopyNeeded = true' here but this is wrong
+  # for code like
+  #  while j >= pos:
+  #    dest[i].shallowCopy(dest[j])
+  # See bug #5933. So we try to be more compatible with the C backend semantics
+  # here for 'shallowCopy'. This is an educated guess and might require further
+  # changes later:
+  let noCopy = n[0].typ.skipTypes(abstractInst).kind in {tySequence, tyOpt, tyString}
+  genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=noCopy)
 
 proc genSwap(p: PProc, n: PNode) =
   var a, b: TCompRes
@@ -913,19 +1011,18 @@ proc genSwap(p: PProc, n: PNode) =
   if mapType(p, skipTypes(n.sons[1].typ, abstractVar)) == etyBaseIndex:
     let tmp2 = p.getTemp(false)
     if a.typ != etyBaseIndex or b.typ != etyBaseIndex:
-      internalError(n.info, "genSwap")
-    addf(p.body, "var $1 = $2; $2 = $3; $3 = $1;$n" |
-                 "$1 = $2; $2 = $3; $3 = $1;$n", [
-                 tmp, a.address, b.address])
+      internalError(p.config, n.info, "genSwap")
+    lineF(p, "var $1 = $2; $2 = $3; $3 = $1;$n",
+             [tmp, a.address, b.address])
     tmp = tmp2
-  addf(p.body, "var $1 = $2; $2 = $3; $3 = $1;" |
-               "$1 = $2; $2 = $3; $3 = $1;", [tmp, a.res, b.res])
+  lineF(p, "var $1 = $2; $2 = $3; $3 = $1;",
+           [tmp, a.res, b.res])
 
-proc getFieldPosition(f: PNode): int =
+proc getFieldPosition(p: PProc; f: PNode): int =
   case f.kind
   of nkIntLit..nkUInt64Lit: result = int(f.intVal)
   of nkSym: result = f.sym.position
-  else: internalError(f.info, "genFieldPosition")
+  else: internalError(p.config, f.info, "genFieldPosition")
 
 proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
@@ -933,48 +1030,89 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
   let b = if n.kind == nkHiddenAddr: n.sons[0] else: n
   gen(p, b.sons[0], a)
   if skipTypes(b.sons[0].typ, abstractVarRange).kind == tyTuple:
-    if p.target == targetJS:
-      r.res = makeJSString( "Field" & $getFieldPosition(b.sons[1]) )
-    else:
-      r.res = getFieldPosition(b.sons[1]).rope
+    r.res = makeJSString("Field" & $getFieldPosition(p, b.sons[1]))
   else:
-    if b.sons[1].kind != nkSym: internalError(b.sons[1].info, "genFieldAddr")
+    if b.sons[1].kind != nkSym: internalError(p.config, b.sons[1].info, "genFieldAddr")
     var f = b.sons[1].sym
-    if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
+    if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
     r.res = makeJSString($f.loc.r)
-  internalAssert a.typ != etyBaseIndex
+  internalAssert p.config, a.typ != etyBaseIndex
   r.address = a.res
   r.kind = resExpr
 
 proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) =
-  r.typ = etyNone
   gen(p, n.sons[0], r)
+  r.typ = mapType(n.typ)
   let otyp = skipTypes(n.sons[0].typ, abstractVarRange)
+
+  template mkTemp(i: int) =
+    if r.typ == etyBaseIndex:
+      if needsTemp(p, n[i]):
+        let tmp = p.getTemp
+        r.address = "($1 = $2, $1)[0]" % [tmp, r.res]
+        r.res = "$1[1]" % [tmp]
+        r.tmpLoc = tmp
+      else:
+        r.address = "$1[0]" % [r.res]
+        r.res = "$1[1]" % [r.res]
   if otyp.kind == tyTuple:
-    r.res = ("$1.Field$2" | "$1[$2]") %
-        [r.res, getFieldPosition(n.sons[1]).rope]
+    r.res = ("$1.Field$2") %
+        [r.res, getFieldPosition(p, n.sons[1]).rope]
+    mkTemp(0)
   else:
-    if n.sons[1].kind != nkSym: internalError(n.sons[1].info, "genFieldAccess")
+    if n.sons[1].kind != nkSym: internalError(p.config, n.sons[1].info, "genFieldAccess")
     var f = n.sons[1].sym
-    if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
-    if p.target == targetJS:
-      r.res = "$1.$2" % [r.res, f.loc.r]
-    else:
-      if {sfImportc, sfExportc} * f.flags != {}:
-        r.res = "$1->$2" % [r.res, f.loc.r]
-      else:
-        r.res = "$1['$2']" % [r.res, f.loc.r]
+    if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
+    r.res = "$1.$2" % [r.res, f.loc.r]
+    mkTemp(1)
   r.kind = resExpr
 
 proc genAddr(p: PProc, n: PNode, r: var TCompRes)
 
-proc genCheckedFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
-  let m = if n.kind == nkHiddenAddr: n.sons[0] else: n
-  internalAssert m.kind == nkCheckedFieldExpr
-  genAddr(p, m, r) # XXX
-
-proc genCheckedFieldAccess(p: PProc, n: PNode, r: var TCompRes) =
-  genFieldAccess(p, n.sons[0], r) # XXX
+proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) =
+  internalAssert p.config, n.kind == nkCheckedFieldExpr
+  # nkDotExpr to access the requested field
+  let accessExpr = n[0]
+  # nkCall to check if the discriminant is valid
+  var checkExpr = n[1]
+
+  let negCheck = checkExpr[0].sym.magic == mNot
+  if negCheck:
+    checkExpr = checkExpr[^1]
+
+  # Field symbol
+  var field = accessExpr[1].sym
+  internalAssert p.config, field.kind == skField
+  if field.loc.r == nil: field.loc.r = mangleName(p.module, field)
+  # Discriminant symbol
+  let disc = checkExpr[2].sym
+  internalAssert p.config, disc.kind == skField
+  if disc.loc.r == nil: disc.loc.r = mangleName(p.module, disc)
+
+  var setx: TCompRes
+  gen(p, checkExpr[1], setx)
+
+  var obj: TCompRes
+  gen(p, accessExpr[0], obj)
+  # Avoid evaluating the LHS twice (one to read the discriminant and one to read
+  # the field)
+  let tmp = p.getTemp()
+  lineF(p, "var $1 = $2;$n", tmp, obj.res)
+
+  useMagic(p, "raiseFieldError")
+  useMagic(p, "makeNimstrLit")
+  lineF(p, "if ($1[$2.$3]$4undefined) { raiseFieldError(makeNimstrLit($5)); }$n",
+    setx.res, tmp, disc.loc.r, if negCheck: ~"!==" else: ~"===",
+    makeJSString(field.name.s))
+
+  if addrTyp != nil and mapType(p, addrTyp) == etyBaseIndex:
+    r.typ = etyBaseIndex
+    r.res = makeJSString($field.loc.r)
+    r.address = tmp
+  else:
+    r.typ = etyNone
+    r.res = "$1.$2" % [tmp, field.loc.r]
+  r.kind = resExpr
 
 proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
   var
@@ -984,20 +1122,15 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
   let m = if n.kind == nkHiddenAddr: n.sons[0] else: n
   gen(p, m.sons[0], a)
   gen(p, m.sons[1], b)
-  internalAssert a.typ != etyBaseIndex and b.typ != etyBaseIndex
-  r.address = a.res
+  #internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex
+  let (x, tmp) = maybeMakeTemp(p, m[0], a)
+  r.address = x
   var typ = skipTypes(m.sons[0].typ, abstractPtrs)
-  if typ.kind == tyArray: first = firstOrd(typ.sons[0])
+  if typ.kind == tyArray: first = firstOrd(p.config, typ.sons[0])
   else: first = 0
-  if optBoundsCheck in p.options and not isConstExpr(m.sons[1]):
+  if optBoundsCheck in p.options:
     useMagic(p, "chckIndx")
-    if p.target == targetPHP:
-      if typ.kind != tyString:
-        r.res = "chckIndx($1, $2, count($3))-$2" % [b.res, rope(first), a.res]
-      else:
-        r.res = "chckIndx($1, $2, strlen($3))-$2" % [b.res, rope(first), a.res]
-    else:
-      r.res = "chckIndx($1, $2, $3.length)-$2" % [b.res, rope(first), a.res]
+    r.res = "chckIndx($1, $2, $3.length+$2-1)-$2" % [b.res, rope(first), tmp]
   elif first != 0:
     r.res = "($1)-$2" % [b.res, rope(first)]
   else:
@@ -1006,34 +1139,29 @@ proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
 
 proc genArrayAccess(p: PProc, n: PNode, r: var TCompRes) =
   var ty = skipTypes(n.sons[0].typ, abstractVarRange)
-  if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.lastSon, abstractVarRange)
+  if ty.kind in {tyRef, tyPtr, tyLent}: ty = skipTypes(ty.lastSon, abstractVarRange)
   case ty.kind
   of tyArray, tyOpenArray, tySequence, tyString, tyCString, tyVarargs:
     genArrayAddr(p, n, r)
   of tyTuple:
-    if p.target == targetPHP:
-      genFieldAccess(p, n, r)
-      return
     genFieldAddr(p, n, r)
-  else: internalError(n.info, "expr(nkBracketExpr, " & $ty.kind & ')')
-  r.typ = etyNone
-  if r.res == nil: internalError(n.info, "genArrayAccess")
-  if p.target == targetPHP:
-    if n.sons[0].kind in nkCallKinds+{nkStrLit..nkTripleStrLit}:
-      useMagic(p, "nimAt")
-      if ty.kind in {tyString, tyCString}:
-        # XXX this needs to be more like substr($1,$2)
-        r.res = "ord(nimAt($1, $2))" % [r.address, r.res]
-      else:
-        r.res = "nimAt($1, $2)" % [r.address, r.res]
-    elif ty.kind in {tyString, tyCString}:
-      # XXX this needs to be more like substr($1,$2)
-      r.res = "ord(@$1[$2])" % [r.address, r.res]
+  else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')')
+  r.typ = mapType(n.typ)
+  if r.res == nil: internalError(p.config, n.info, "genArrayAccess")
+  if ty.kind == tyCString:
+    r.res = "$1.charCodeAt($2)" % [r.address, r.res]
+  elif r.typ == etyBaseIndex:
+    if needsTemp(p, n[0]):
+      let tmp = p.getTemp
+      r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc]
+      r.res = "$1[1]" % [tmp]
+      r.tmpLoc = tmp
     else:
-      r.res = "$1[$2]" % [r.address, r.res]
+      let x = r.rdLoc
+      r.address = "$1[0]" % [x]
+      r.res = "$1[1]" % [x]
   else:
     r.res = "$1[$2]" % [r.address, r.res]
-  r.address = nil
   r.kind = resExpr
 
 template isIndirect(x: PSym): bool =
@@ -1041,14 +1169,14 @@ template isIndirect(x: PSym): bool =
   ({sfAddrTaken, sfGlobal} * v.flags != {} and
     #(mapType(v.typ) != etyObject) and
     {sfImportc, sfVolatile, sfExportc} * v.flags == {} and
-    v.kind notin {skProc, skConverter, skMethod, skIterator,
-                  skConst, skTemp, skLet} and p.target == targetJS)
+    v.kind notin {skProc, skFunc, skConverter, skMethod, skIterator,
+                  skConst, skTemp, skLet})
 
 proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
   case n.sons[0].kind
   of nkSym:
     let s = n.sons[0].sym
-    if s.loc.r == nil: internalError(n.info, "genAddr: 3")
+    if s.loc.r == nil: internalError(p.config, n.info, "genAddr: 3")
     case s.kind
     of skVar, skLet, skResult:
       r.kind = resExpr
@@ -1058,8 +1186,6 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
         r.typ = etyNone
         if isIndirect(s):
           r.res = s.loc.r & "[0]"
-        elif p.target == targetPHP:
-          r.res = "&" & s.loc.r
         else:
           r.res = s.loc.r
         r.address = nil
@@ -1072,10 +1198,10 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
       else:
         # 'var openArray' for instance produces an 'addr' but this is harmless:
         gen(p, n.sons[0], r)
-        #internalError(n.info, "genAddr: 4 " & renderTree(n))
-    else: internalError(n.info, "genAddr: 2")
+        #internalError(p.config, n.info, "genAddr: 4 " & renderTree(n))
+    else: internalError(p.config, n.info, "genAddr: 2")
   of nkCheckedFieldExpr:
-    genCheckedFieldAddr(p, n, r)
+    genCheckedFieldOp(p, n[0], n.typ, r)
   of nkDotExpr:
     if mapType(p, n.typ) == etyBaseIndex:
       genFieldAddr(p, n.sons[0], r)
@@ -1092,23 +1218,15 @@ proc genAddr(p: PProc, n: PNode, r: var TCompRes) =
         genArrayAddr(p, n.sons[0], r)
       of tyTuple:
         genFieldAddr(p, n.sons[0], r)
-      else: internalError(n.sons[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')')
+      else: internalError(p.config, n.sons[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')')
   of nkObjDownConv:
     gen(p, n.sons[0], r)
   of nkHiddenDeref:
     gen(p, n.sons[0].sons[0], r)
-  else: internalError(n.sons[0].info, "genAddr: " & $n.sons[0].kind)
+  else: internalError(p.config, n.sons[0].info, "genAddr: " & $n.sons[0].kind)
 
 proc thisParam(p: PProc; typ: PType): PType =
-  if p.target == targetPHP:
-    # XXX Might be very useful for the JS backend too?
-    let typ = skipTypes(typ, abstractInst)
-    assert(typ.kind == tyProc)
-    if 1 < sonsLen(typ.n):
-      assert(typ.n.sons[1].kind == nkSym)
-      let param = typ.n.sons[1].sym
-      if param.name.s == "this":
-        result = param.typ.skipTypes(abstractVar)
+  discard
 
 proc attachProc(p: PProc; content: Rope; s: PSym) =
   let otyp = thisParam(p, s.typ)
@@ -1139,40 +1257,32 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) =
   case s.kind
   of skVar, skLet, skParam, skTemp, skResult, skForVar:
     if s.loc.r == nil:
-      internalError(n.info, "symbol has no generated name: " & s.name.s)
-    if p.target == targetJS:
-      let k = mapType(p, s.typ)
-      if k == etyBaseIndex:
-        r.typ = etyBaseIndex
-        if {sfAddrTaken, sfGlobal} * s.flags != {}:
+      internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
+    let k = mapType(p, s.typ)
+    if k == etyBaseIndex:
+      r.typ = etyBaseIndex
+      if {sfAddrTaken, sfGlobal} * s.flags != {}:
+        if isIndirect(s):
+          r.address = "$1[0][0]" % [s.loc.r]
+          r.res = "$1[0][1]" % [s.loc.r]
+        else:
           r.address = "$1[0]" % [s.loc.r]
           r.res = "$1[1]" % [s.loc.r]
-        else:
-          r.address = s.loc.r
-          r.res = s.loc.r & "_Idx"
-      elif isIndirect(s):
-        r.res = "$1[0]" % [s.loc.r]
       else:
-        r.res = s.loc.r
+        r.address = s.loc.r
+        r.res = s.loc.r & "_Idx"
+    elif isIndirect(s):
+      r.res = "$1[0]" % [s.loc.r]
     else:
-      r.res = "$" & s.loc.r
-      if sfGlobal in s.flags:
-        p.declareGlobal(s.id, r.res)
+      r.res = s.loc.r
   of skConst:
     genConstant(p, s)
     if s.loc.r == nil:
-      internalError(n.info, "symbol has no generated name: " & s.name.s)
-    if p.target == targetJS:
-      r.res = s.loc.r
-    else:
-      r.res = "$" & s.loc.r
-      p.declareGlobal(s.id, r.res)
-  of skProc, skConverter, skMethod:
-    discard mangleName(s, p.target)
-    if p.target == targetPHP and r.kind != resCallee:
-      r.res = makeJsString($s.loc.r)
-    else:
-      r.res = s.loc.r
+      internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
+    r.res = s.loc.r
+  of skProc, skFunc, skConverter, skMethod:
+    discard mangleName(p.module, s)
+    r.res = s.loc.r
     if lfNoDecl in s.loc.flags or s.magic != mNone or
        {sfImportc, sfInfixCall} * s.flags != {}:
       discard
@@ -1185,24 +1295,31 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) =
       genProcForSymIfNeeded(p, s)
   else:
     if s.loc.r == nil:
-      internalError(n.info, "symbol has no generated name: " & s.name.s)
+      internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
     r.res = s.loc.r
   r.kind = resVal
 
 proc genDeref(p: PProc, n: PNode, r: var TCompRes) =
-  if mapType(p, n.sons[0].typ) == etyObject:
-    gen(p, n.sons[0], r)
+  let it = n.sons[0]
+  let t = mapType(p, it.typ)
+  if t == etyObject:
+    gen(p, it, r)
   else:
     var a: TCompRes
-    gen(p, n.sons[0], a)
-    r.kind = resExpr
-    if a.typ == etyBaseIndex:
-      r.res = "$1[$2]" % [a.address, a.res]
-    elif n.sons[0].kind == nkCall:
+    gen(p, it, a)
+    r.kind = a.kind
+    r.typ = mapType(p, n.typ)
+    if r.typ == etyBaseIndex:
       let tmp = p.getTemp
-      r.res = "($1 = $2, $1[0])[$1[1]]" % [tmp, a.res]
+      r.address = "($1 = $2, $1)[0]" % [tmp, a.rdLoc]
+      r.res = "$1[1]" % [tmp]
+      r.tmpLoc = tmp
+    elif a.typ == etyBaseIndex:
+      if a.tmpLoc != nil:
+        r.tmpLoc = a.tmpLoc
+      r.res = a.rdLoc
     else:
-      internalError(n.info, "genDeref")
+      internalError(p.config, n.info, "genDeref")
 
 proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
@@ -1214,7 +1331,7 @@ proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) =
   else:
     add(r.res, a.res)
 
-proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes) =
+proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) =
   var a: TCompRes
   gen(p, n, a)
   if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and
@@ -1224,6 +1341,12 @@ proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes) =
     add(r.res, a.address)
     add(r.res, ", ")
     add(r.res, a.res)
+    if emitted != nil: inc emitted[]
+  elif n.typ.kind in {tyVar, tyPtr, tyRef, tyLent} and n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex:
+    # this fixes bug #5608:
+    let tmp = getTemp(p)
+    add(r.res, "($1 = $2, $1[0]), $1[1]" % [tmp, a.rdLoc])
+    if emitted != nil: inc emitted[]
   else:
     add(r.res, a.res)
 
@@ -1234,6 +1357,7 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
   var typ = skipTypes(n.sons[0].typ, abstractInst)
   assert(typ.kind == tyProc)
   assert(sonsLen(typ) == sonsLen(typ.n))
+  var emitted = start-1
 
   for i in countup(start, sonsLen(n) - 1):
     let it = n.sons[i]
@@ -1247,13 +1371,23 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
     if paramType.isNil:
       genArgNoParam(p, it, r)
     else:
-      genArg(p, it, paramType.sym, r)
+      genArg(p, it, paramType.sym, r, addr emitted)
+    inc emitted
     hasArgs = true
   add(r.res, ")")
+  when false:
+    # XXX look into this:
+    let jsp = countJsParams(typ)
+    if emitted != jsp and tfVarargs notin typ.flags:
+      localError(p.config, n.info, "wrong number of parameters emitted; expected: " & $jsp &
+        " but got: " & $emitted)
   r.kind = resExpr
 
 proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
                  generated: var int; r: var TCompRes) =
+  if i >= n.len:
+    globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i &
+        " but got only: " & $(n.len-1))
   let it = n[i]
   var paramType: PNode = nil
   if i < sonsLen(typ):
@@ -1275,7 +1409,7 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
     case pat[i]
     of '@':
       var generated = 0
-      for k in j .. < n.len:
+      for k in j ..< n.len:
         if generated > 0: add(r.res, ", ")
         genOtherArg(p, n, k, typ, generated, r)
       inc i
@@ -1303,10 +1437,10 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
 proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
   # don't call '$' here for efficiency:
   let f = n[0].sym
-  if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
+  if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
   if sfInfixCall in f.flags:
     let pat = n.sons[0].sym.loc.r.data
-    internalAssert pat != nil
+    internalAssert p.config, pat.len > 0
     if pat.contains({'#', '(', '@'}):
       var typ = skipTypes(n.sons[0].typ, abstractInst)
       assert(typ.kind == tyProc)
@@ -1316,14 +1450,12 @@ proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
     gen(p, n.sons[1], r)
     if r.typ == etyBaseIndex:
       if r.address == nil:
-        globalError(n.info, "cannot invoke with infix syntax")
+        globalError(p.config, n.info, "cannot invoke with infix syntax")
       r.res = "$1[$2]" % [r.address, r.res]
       r.address = nil
       r.typ = etyNone
-    add(r.res, "." | "->")
+    add(r.res, ".")
   var op: TCompRes
-  if p.target == targetPHP:
-    op.kind = resCallee
   gen(p, n.sons[0], op)
   add(r.res, op.res)
   genArgs(p, n, r, 2)
@@ -1332,28 +1464,29 @@ proc genCall(p: PProc, n: PNode, r: var TCompRes) =
   if n.sons[0].kind == nkSym and thisParam(p, n.sons[0].typ) != nil:
     genInfixCall(p, n, r)
     return
-  if p.target == targetPHP:
-    r.kind = resCallee
   gen(p, n.sons[0], r)
   genArgs(p, n, r)
+  if n.typ != nil:
+    let t = mapType(n.typ)
+    if t == etyBaseIndex:
+      let tmp = p.getTemp
+      r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc]
+      r.res = "$1[1]" % [tmp]
+      r.tmpLoc = tmp
+      r.typ = t
 
 proc genEcho(p: PProc, n: PNode, r: var TCompRes) =
   let n = n[1].skipConv
-  internalAssert n.kind == nkBracket
-  if p.target == targetJS:
-    useMagic(p, "toJSStr") # Used in rawEcho
-    useMagic(p, "rawEcho")
-  elif n.len == 0:
-    r.kind = resExpr
-    add(r.res, """print("\n")""")
-    return
-  add(r.res, "rawEcho(" | "print(")
+  internalAssert p.config, n.kind == nkBracket
+  useMagic(p, "toJSStr") # Used in rawEcho
+  useMagic(p, "rawEcho")
+  add(r.res, "rawEcho(")
   for i in countup(0, sonsLen(n) - 1):
     let it = n.sons[i]
     if it.typ.isCompileTimeOnly: continue
-    if i > 0: add(r.res, ", " | ".")
+    if i > 0: add(r.res, ", ")
     genArgNoParam(p, it, r)
-  add(r.res, ")" | """."\n")""")
+  add(r.res, ")")
   r.kind = resExpr
 
 proc putToSeq(s: string, indirect: bool): Rope =
@@ -1373,24 +1506,22 @@ proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output:
   of nkSym:
     if rec.sym.id notin excludedFieldIDs:
       if output.len > 0: output.add(", ")
-      if p.target == targetJS:
-        output.addf("$#: ", [mangleName(rec.sym, p.target)])
-      else:
-        output.addf("'$#' => ", [mangleName(rec.sym, p.target)])
+      output.addf("$#: ", [mangleName(p.module, rec.sym)])
       output.add(createVar(p, rec.sym.typ, false))
-  else: internalError(rec.info, "createRecordVarAux")
+  else: internalError(p.config, rec.info, "createRecordVarAux")
 
 proc createObjInitList(p: PProc, typ: PType, excludedFieldIDs: IntSet, output: var Rope) =
   var t = typ
-  if tfFinal notin t.flags or t.sons[0] != nil:
+  if objHasTypeField(t):
     if output.len > 0: output.add(", ")
-    addf(output, "m_type: $1" | "'m_type' => $#", [genTypeInfo(p, t)])
+    addf(output, "m_type: $1", [genTypeInfo(p, t)])
   while t != nil:
     t = t.skipTypes(skipPtrs)
     createRecordVarAux(p, t.n, excludedFieldIDs, output)
     t = t.sons[0]
 
 proc arrayTypeForElemType(typ: PType): string =
+  # XXX This should also support tyEnum and tyBool
   case typ.kind
   of tyInt, tyInt32: "Int32Array"
   of tyInt16: "Int16Array"
@@ -1400,7 +1531,7 @@ proc arrayTypeForElemType(typ: PType): string =
   of tyUint8: "Uint8Array"
   of tyFloat32: "Float32Array"
   of tyFloat64, tyFloat: "Float64Array"
-  else: nil
+  else: ""
 
 proc createVar(p: PProc, typ: PType, indirect: bool): Rope =
   var t = skipTypes(typ, abstractInst)
@@ -1409,82 +1540,93 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope =
     result = putToSeq("0", indirect)
   of tyFloat..tyFloat128:
     result = putToSeq("0.0", indirect)
-  of tyRange, tyGenericInst, tyAlias:
+  of tyRange, tyGenericInst, tyAlias, tySink:
     result = createVar(p, lastSon(typ), indirect)
   of tySet:
-    result = putToSeq("{}" | "array()", indirect)
+    result = putToSeq("{}", indirect)
   of tyBool:
     result = putToSeq("false", indirect)
   of tyArray:
-    let length = int(lengthOrd(t))
+    let length = int(lengthOrd(p.config, t))
     let e = elemType(t)
     let jsTyp = arrayTypeForElemType(e)
-    if not jsTyp.isNil and p.target == targetJS:
+    if jsTyp.len > 0:
       result = "new $1($2)" % [rope(jsTyp), rope(length)]
     elif length > 32:
       useMagic(p, "arrayConstr")
       # XXX: arrayConstr depends on nimCopy. This line shouldn't be necessary.
-      if p.target == targetJS: useMagic(p, "nimCopy")
+      useMagic(p, "nimCopy")
       result = "arrayConstr($1, $2, $3)" % [rope(length),
           createVar(p, e, false), genTypeInfo(p, e)]
     else:
-      result = rope("[" | "array(")
+      result = rope("[")
       var i = 0
       while i < length:
         if i > 0: add(result, ", ")
         add(result, createVar(p, e, false))
         inc(i)
-      add(result, "]" | ")")
+      add(result, "]")
     if indirect: result = "[$1]" % [result]
   of tyTuple:
-    if p.target == targetJS:
-      result = rope("{")
-      for i in 0.. <t.sonsLen:
-        if i > 0: add(result, ", ")
-        addf(result, "Field$1: $2", [i.rope,
-             createVar(p, t.sons[i], false)])
-      add(result, "}")
-      if indirect: result = "[$1]" % [result]
-    else:
-      result = rope("array(")
-      for i in 0.. <t.sonsLen:
-        if i > 0: add(result, ", ")
-        add(result, createVar(p, t.sons[i], false))
-      add(result, ")")
+    result = rope("{")
+    for i in 0..<t.sonsLen:
+      if i > 0: add(result, ", ")
+      addf(result, "Field$1: $2", [i.rope,
+            createVar(p, t.sons[i], false)])
+    add(result, "}")
+    if indirect: result = "[$1]" % [result]
   of tyObject:
     var initList: Rope
     createObjInitList(p, t, initIntSet(), initList)
-    result = ("{$1}" | "array($#)") % [initList]
+    result = ("{$1}") % [initList]
     if indirect: result = "[$1]" % [result]
-  of tyVar, tyPtr, tyRef:
+  of tyVar, tyPtr, tyLent, tyRef, tyPointer:
     if mapType(p, t) == etyBaseIndex:
       result = putToSeq("[null, 0]", indirect)
     else:
       result = putToSeq("null", indirect)
-  of tySequence, tyString, tyCString, tyPointer, tyProc:
+  of tySequence, tyOpt, tyString, tyCString, tyProc:
     result = putToSeq("null", indirect)
   of tyStatic:
     if t.n != nil:
       result = createVar(p, lastSon t, indirect)
     else:
-      internalError("createVar: " & $t.kind)
+      internalError(p.config, "createVar: " & $t.kind)
       result = nil
   else:
-    internalError("createVar: " & $t.kind)
+    internalError(p.config, "createVar: " & $t.kind)
     result = nil
 
+template returnType: untyped =
+  ~""
+
 proc genVarInit(p: PProc, v: PSym, n: PNode) =
   var
     a: TCompRes
     s: Rope
+    varCode: string
+    varName = mangleName(p.module, v)
+    useReloadingGuard = sfGlobal in v.flags and optHotCodeReloading in p.config.options
+
+  if v.constraint.isNil:
+    if useReloadingGuard:
+      lineF(p, "var $1;$n", varName)
+      lineF(p, "if ($1 === undefined) {$n", varName)
+      varCode = $varName
+    else:
+      varCode = "var $2"
+  else:
+    varCode = v.constraint.strVal
+
   if n.kind == nkEmpty:
-    let mname = mangleName(v, p.target)
-    addf(p.body, "var $1 = $2;$n" | "$$$1 = $2;$n",
-         [mname, createVar(p, v.typ, isIndirect(v))])
-    if v.typ.kind in { tyVar, tyPtr, tyRef } and mapType(p, v.typ) == etyBaseIndex:
-      addf(p.body, "var $1_Idx = 0;$n", [ mname ])
+    if not isIndirect(v) and
+      v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex:
+      lineF(p, "var $1 = null;$n", [varName])
+      lineF(p, "var $1_Idx = 0;$n", [varName])
+    else:
+      lineF(p, varCode & " = $3;$n",
+                [returnType, varName, createVar(p, v.typ, isIndirect(v))])
   else:
-    discard mangleName(v, p.target)
     gen(p, n, a)
     case mapType(p, v.typ)
     of etyObject, etySeq:
@@ -1497,32 +1639,39 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
       let targetBaseIndex = {sfAddrTaken, sfGlobal} * v.flags == {}
       if a.typ == etyBaseIndex:
         if targetBaseIndex:
-          addf(p.body, "var $1 = $2, $1_Idx = $3;$n", [
-              v.loc.r, a.address, a.res])
+          lineF(p, varCode & " = $3, $2_Idx = $4;$n",
+                   [returnType, v.loc.r, a.address, a.res])
         else:
-          addf(p.body, "var $1 = [$2, $3];$n",
-              [v.loc.r, a.address, a.res])
+          if isIndirect(v):
+            lineF(p, varCode & " = [[$3, $4]];$n",
+                     [returnType, v.loc.r, a.address, a.res])
+          else:
+            lineF(p, varCode & " = [$3, $4];$n",
+                     [returnType, v.loc.r, a.address, a.res])
       else:
         if targetBaseIndex:
           let tmp = p.getTemp
-          addf(p.body, "var $1 = $2, $3 = $1[0], $3_Idx = $1[1];$n",
-              [tmp, a.res, v.loc.r])
+          lineF(p, "var $1 = $2, $3 = $1[0], $3_Idx = $1[1];$n",
+                   [tmp, a.res, v.loc.r])
         else:
-          addf(p.body, "var $1 = $2;$n", [v.loc.r, a.res])
+          lineF(p, varCode & " = $3;$n", [returnType, v.loc.r, a.res])
       return
     else:
       s = a.res
     if isIndirect(v):
-      addf(p.body, "var $1 = /**/[$2];$n", [v.loc.r, s])
+      lineF(p, varCode & " = [$3];$n", [returnType, v.loc.r, s])
     else:
-      addf(p.body, "var $1 = $2;$n" | "$$$1 = $2;$n", [v.loc.r, s])
+      lineF(p, varCode & " = $3;$n", [returnType, v.loc.r, s])
+
+  if useReloadingGuard:
+    lineF(p, "}$n")
 
 proc genVarStmt(p: PProc, n: PNode) =
   for i in countup(0, sonsLen(n) - 1):
     var a = n.sons[i]
     if a.kind != nkCommentStmt:
       if a.kind == nkVarTuple:
-        let unpacked = lowerTupleUnpacking(a, p.prc)
+        let unpacked = lowerTupleUnpacking(p.module.graph, a, p.prc)
         genStmt(p, unpacked)
       else:
         assert(a.kind == nkIdentDefs)
@@ -1545,25 +1694,26 @@ proc genNew(p: PProc, n: PNode) =
   var a: TCompRes
   gen(p, n.sons[1], a)
   var t = skipTypes(n.sons[1].typ, abstractVar).sons[0]
-  if p.target == targetJS:
-    addf(p.body, "$1 = $2;$n", [a.res, createVar(p, t, false)])
+  if mapType(t) == etyObject:
+    lineF(p, "$1 = $2;$n", [a.rdLoc, createVar(p, t, false)])
+  elif a.typ == etyBaseIndex:
+    lineF(p, "$1 = [$3]; $2 = 0;$n", [a.address, a.res, createVar(p, t, false)])
   else:
-    addf(p.body, "$3 = $2; $1 = &$3;$n", [a.res, createVar(p, t, false), getTemp(p)])
+    lineF(p, "$1 = [[$2], 0];$n", [a.rdLoc, createVar(p, t, false)])
 
 proc genNewSeq(p: PProc, n: PNode) =
   var x, y: TCompRes
   gen(p, n.sons[1], x)
   gen(p, n.sons[2], y)
   let t = skipTypes(n.sons[1].typ, abstractVar).sons[0]
-  addf(p.body, "$1 = new Array($2); for (var i=0;i<$2;++i) {$1[i]=$3;}" |
-               "$1 = array(); for ($$i=0;$$i<$2;++$$i) {$1[]=$3;}", [
+  lineF(p, "$1 = new Array($2); for (var i=0;i<$2;++i) {$1[i]=$3;}", [
     x.rdLoc, y.rdLoc, createVar(p, t, false)])
 
 proc genOrd(p: PProc, n: PNode, r: var TCompRes) =
   case skipTypes(n.sons[1].typ, abstractVar).kind
-  of tyEnum, tyInt..tyInt64, tyChar: gen(p, n.sons[1], r)
+  of tyEnum, tyInt..tyUInt64, tyChar: gen(p, n.sons[1], r)
   of tyBool: unaryExpr(p, n, r, "", "($1 ? 1:0)")
-  else: internalError(n.info, "genOrd")
+  else: internalError(p.config, n.info, "genOrd")
 
 proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
@@ -1573,35 +1723,20 @@ proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) =
   if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar:
     r.res.add("[$1].concat(" % [a.res])
   else:
-    r.res.add("($1.slice(0,-1)).concat(" % [a.res])
+    r.res.add("($1 || []).concat(" % [a.res])
 
   for i in countup(2, sonsLen(n) - 2):
     gen(p, n.sons[i], a)
     if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar:
       r.res.add("[$1]," % [a.res])
     else:
-      r.res.add("$1.slice(0,-1)," % [a.res])
+      r.res.add("$1 || []," % [a.res])
 
   gen(p, n.sons[sonsLen(n) - 1], a)
   if skipTypes(n.sons[sonsLen(n) - 1].typ, abstractVarRange).kind == tyChar:
-    r.res.add("[$1, 0])" % [a.res])
-  else:
-    r.res.add("$1)" % [a.res])
-
-proc genConStrStrPHP(p: PProc, n: PNode, r: var TCompRes) =
-  var a: TCompRes
-  gen(p, n.sons[1], a)
-  r.kind = resExpr
-  if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar:
-    r.res.add("chr($1)" % [a.res])
+    r.res.add("[$1])" % [a.res])
   else:
-    r.res.add(a.res)
-  for i in countup(2, sonsLen(n) - 1):
-    gen(p, n.sons[i], a)
-    if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar:
-      r.res.add(".chr($1)" % [a.res])
-    else:
-      r.res.add(".$1" % [a.res])
+    r.res.add("$1 || [])" % [a.res])
 
 proc genToArray(p: PProc; n: PNode; r: var TCompRes) =
   # we map mArray to PHP's array constructor, a mild hack:
@@ -1612,37 +1747,68 @@ proc genToArray(p: PProc; n: PNode; r: var TCompRes) =
   if x.kind == nkBracket:
     for i in countup(0, x.len - 1):
       let it = x[i]
-      if it.kind == nkPar and it.len == 2:
+      if it.kind in {nkPar, nkTupleConstr} and it.len == 2:
         if i > 0: r.res.add(", ")
         gen(p, it[0], a)
         gen(p, it[1], b)
         r.res.add("$# => $#" % [a.rdLoc, b.rdLoc])
       else:
-        localError(it.info, "'toArray' needs tuple constructors")
+        localError(p.config, it.info, "'toArray' needs tuple constructors")
   else:
-    localError(x.info, "'toArray' needs an array literal")
+    localError(p.config, x.info, "'toArray' needs an array literal")
   r.res.add(")")
 
+proc genReprAux(p: PProc, n: PNode, r: var TCompRes, magic: string, typ: Rope = nil) =
+  useMagic(p, magic)
+  add(r.res, magic & "(")
+  var a: TCompRes
+
+  gen(p, n.sons[1], a)
+  if magic == "reprAny":
+    # the pointer argument in reprAny is expandend to
+    # (pointedto, pointer), so we need to fill it
+    if a.address.isNil:
+      add(r.res, a.res)
+      add(r.res, ", null")
+    else:
+      add(r.res, "$1, $2" % [a.address, a.res])
+  else:
+    add(r.res, a.res)
+
+  if not typ.isNil:
+    add(r.res, ", ")
+    add(r.res, typ)
+  add(r.res, ")")
+
 proc genRepr(p: PProc, n: PNode, r: var TCompRes) =
-  if p.target == targetPHP:
-    localError(n.info, "'repr' not available for PHP backend")
-    return
   let t = skipTypes(n.sons[1].typ, abstractVarRange)
-  case t.kind
-  of tyInt..tyUInt64:
-    unaryExpr(p, n, r, "", "(\"\"+ ($1))")
+  case t.kind:
+  of tyInt..tyInt64, tyUInt..tyUInt64:
+    genReprAux(p, n, r, "reprInt")
+  of tyChar:
+    genReprAux(p, n, r, "reprChar")
+  of tyBool:
+    genReprAux(p, n, r, "reprBool")
+  of tyFloat..tyFloat128:
+    genReprAux(p, n, r, "reprFloat")
+  of tyString:
+    genReprAux(p, n, r, "reprStr")
   of tyEnum, tyOrdinal:
-    gen(p, n.sons[1], r)
-    useMagic(p, "cstrToNimstr")
-    r.kind = resExpr
-    r.res = "cstrToNimstr($1.node.sons[$2].name)" % [genTypeInfo(p, t), r.res]
+    genReprAux(p, n, r, "reprEnum", genTypeInfo(p, t))
+  of tySet:
+    genReprAux(p, n, r, "reprSet", genTypeInfo(p, t))
+  of tyEmpty, tyVoid:
+    localError(p.config, n.info, "'repr' doesn't support 'void' type")
+  of tyPointer:
+    genReprAux(p, n, r, "reprPointer")
+  of tyOpenArray, tyVarargs:
+    genReprAux(p, n, r, "reprJSONStringify")
   else:
-    # XXX:
-    internalError(n.info, "genRepr: Not implemented")
+    genReprAux(p, n, r, "reprAny", genTypeInfo(p, t))
 
 proc genOf(p: PProc, n: PNode, r: var TCompRes) =
   var x: TCompRes
-  let t = skipTypes(n.sons[2].typ, abstractVarRange+{tyRef, tyPtr, tyTypeDesc})
+  let t = skipTypes(n.sons[2].typ, abstractVarRange+{tyRef, tyPtr, tyLent, tyTypeDesc})
   gen(p, n.sons[1], x)
   if tfFinal in t.flags:
     r.res = "($1.m_type == $2)" % [x.res, genTypeInfo(p, t)]
@@ -1655,8 +1821,12 @@ proc genReset(p: PProc, n: PNode) =
   var x: TCompRes
   useMagic(p, "genericReset")
   gen(p, n.sons[1], x)
-  addf(p.body, "$1 = genericReset($1, $2);$n", [x.res,
-                genTypeInfo(p, n.sons[1].typ)])
+  if x.typ == etyBaseIndex:
+    lineF(p, "$1 = null, $2 = 0;$n", [x.address, x.res])
+  else:
+    let (a, tmp) = maybeMakeTemp(p, n[1], x)
+    lineF(p, "$1 = genericReset($3, $2);$n", [a,
+                  genTypeInfo(p, n.sons[1].typ), tmp])
 
 proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
   var
@@ -1674,90 +1844,98 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
     if not (optOverflowCheck in p.options): unaryExpr(p, n, r, "", "$1 - 1")
     else: unaryExpr(p, n, r, "subInt", "subInt($1, 1)")
   of mAppendStrCh:
-    if p.target == targetJS:
-      binaryExpr(p, n, r, "addChar",
-          "if ($1 != null) { addChar($1, $2); } else { $1 = [$2, 0]; }")
-    else:
-      binaryExpr(p, n, r, "",
-          "$1 .= chr($2)")
+    binaryExpr(p, n, r, "addChar",
+        "if ($1 != null) { addChar($3, $2); } else { $3 = [$2]; }")
   of mAppendStrStr:
-    if p.target == targetJS:
-      if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString:
-          binaryExpr(p, n, r, "", "if ($1 != null) { $1 += $2; } else { $1 = $2; }")
-      else:
-        binaryExpr(p, n, r, "",
-          "if ($1 != null) { $1 = ($1.slice(0, -1)).concat($2); } else { $1 = $2;}")
-      # XXX: make a copy of $2, because of Javascript's sucking semantics
+    var lhs, rhs: TCompRes
+    gen(p, n[1], lhs)
+    gen(p, n[2], rhs)
+
+    let rhsIsLit = n[2].kind in nkStrKinds
+    let (a, tmp) = maybeMakeTemp(p, n[1], lhs)
+    if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString:
+      r.res = "if ($1 != null) { $4 += $2; } else { $4 = $2$3; }" % [
+        a, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()", tmp]
     else:
-      binaryExpr(p, n, r, "",
-          "$1 .= $2;")
+      r.res = "if ($1 != null) { $4 = ($4).concat($2); } else { $4 = $2$3; }" % [
+          lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()", tmp]
+    r.kind = resExpr
   of mAppendSeqElem:
-    if p.target == targetJS:
-      binaryExpr(p, n, r, "",
-          "if ($1 != null) { $1.push($2); } else { $1 = [$2]; }")
+    var x, y: TCompRes
+    gen(p, n.sons[1], x)
+    gen(p, n.sons[2], y)
+    let (a, tmp) = maybeMakeTemp(p, n[1], x)
+    if mapType(n[2].typ) == etyBaseIndex:
+      let c = "[$1, $2]" % [y.address, y.res]
+      r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, c, tmp]
+    elif needsNoCopy(p, n[2]):
+      r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, y.rdLoc, tmp]
     else:
-      binaryExpr(p, n, r, "",
-          "$1[] = $2")
+      useMagic(p, "nimCopy")
+      let c = getTemp(p, defineInLocals=false)
+      lineF(p, "var $1 = nimCopy(null, $2, $3);$n",
+            [c, y.rdLoc, genTypeInfo(p, n[2].typ)])
+      r.res = "if ($1 != null) { $3.push($2); } else { $3 = [$2]; }" % [a, c, tmp]
+    r.kind = resExpr
   of mConStrStr:
-    if p.target == targetJS:
-      genConStrStr(p, n, r)
-    else:
-      genConStrStrPHP(p, n, r)
+    genConStrStr(p, n, r)
   of mEqStr:
-    if p.target == targetJS:
-      binaryExpr(p, n, r, "eqStrings", "eqStrings($1, $2)")
-    else:
-      binaryExpr(p, n, r, "", "($1 == $2)")
+    binaryExpr(p, n, r, "eqStrings", "eqStrings($1, $2)")
   of mLeStr:
-    if p.target == targetJS:
-      binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) <= 0)")
-    else:
-      binaryExpr(p, n, r, "", "($1 <= $2)")
+    binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) <= 0)")
   of mLtStr:
-    if p.target == targetJS:
-      binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)")
+    binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)")
+  of mIsNil:
+    if mapType(n[1].typ) != etyBaseIndex:
+      unaryExpr(p, n, r, "", "($1 === null)")
     else:
-      binaryExpr(p, n, r, "", "($1 < $2)")
-  of mIsNil: unaryExpr(p, n, r, "", "($1 === null)")
+      var x: TCompRes
+      gen(p, n[1], x)
+      r.res = "($# === null && $# === 0)" % [x.address, x.res]
   of mEnumToStr: genRepr(p, n, r)
   of mNew, mNewFinalize: genNew(p, n)
-  of mSizeOf: r.res = rope(getSize(n.sons[1].typ))
-  of mChr, mArrToSeq: gen(p, n.sons[1], r)      # nothing to do
-  of mOrd: genOrd(p, n, r)
-  of mLengthStr:
-    if p.target == targetJS and n.sons[1].typ.skipTypes(abstractInst).kind == tyCString:
-      unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)")
+  of mChr: gen(p, n.sons[1], r)
+  of mArrToSeq:
+    if needsNoCopy(p, n.sons[1]):
+      gen(p, n.sons[1], r)
     else:
-      unaryExpr(p, n, r, "", "($1 != null ? $1.length-1 : 0)" |
-                             "strlen($1)")
-  of mXLenStr: unaryExpr(p, n, r, "", "$1.length-1" | "strlen($1)")
-  of mLengthSeq, mLengthOpenArray, mLengthArray:
-    unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)" |
-                           "count($1)")
-  of mXLenSeq:
-    unaryExpr(p, n, r, "", "$1.length" | "count($1)")
+      var x: TCompRes
+      gen(p, n.sons[1], x)
+      useMagic(p, "nimCopy")
+      r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)]
+  of mDestroy: discard "ignore calls to the default destructor"
+  of mOrd: genOrd(p, n, r)
+  of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray:
+    unaryExpr(p, n, r, "", "($1 != null ? $2.length : 0)")
+  of mXLenStr, mXLenSeq:
+    unaryExpr(p, n, r, "", "$1.length")
   of mHigh:
-    if skipTypes(n.sons[1].typ, abstractVar).kind == tyString:
-      unaryExpr(p, n, r, "", "($1 != null ? ($1.length-2) : -1)" |
-                             "(strlen($1)-1)")
-    else:
-      unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)" |
-                             "(count($1)-1)")
+    unaryExpr(p, n, r, "", "($1 != null ? ($2.length-1) : -1)")
   of mInc:
     if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64:
       binaryUintExpr(p, n, r, "+", true)
     else:
       if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 += $2")
-      else: binaryExpr(p, n, r, "addInt", "$1 = addInt($1, $2)")
+      else: binaryExpr(p, n, r, "addInt", "$1 = addInt($3, $2)")
   of ast.mDec:
     if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64:
       binaryUintExpr(p, n, r, "-", true)
     else:
       if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2")
-      else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)")
+      else: binaryExpr(p, n, r, "subInt", "$1 = subInt($3, $2)")
   of mSetLengthStr:
-    binaryExpr(p, n, r, "", "$1.length = $2+1; $1[$1.length-1] = 0" | "$1 = substr($1, 0, $2)")
-  of mSetLengthSeq: binaryExpr(p, n, r, "", "$1.length = $2" | "$1 = array_slice($1, 0, $2)")
+    binaryExpr(p, n, r, "mnewString", "($1 === null ? $3 = mnewString($2) : $3.length = $2)")
+  of mSetLengthSeq:
+    var x, y: TCompRes
+    gen(p, n.sons[1], x)
+    gen(p, n.sons[2], y)
+    let t = skipTypes(n.sons[1].typ, abstractVar).sons[0]
+    let (a, tmp) = maybeMakeTemp(p, n[1], x)
+    let (b, tmp2) = maybeMakeTemp(p, n[2], y)
+    r.res = """if ($1 === null) $4 = [];
+               if ($4.length < $2) { for (var i=$4.length;i<$5;++i) $4.push($3); }
+               else { $4.length = $5; }""" % [a, b, createVar(p, t, false), tmp, tmp2]
+    r.kind = resExpr
   of mCard: unaryExpr(p, n, r, "SetCard", "SetCard($1)")
   of mLtSet: binaryExpr(p, n, r, "SetLt", "SetLt($1, $2)")
   of mLeSet: binaryExpr(p, n, r, "SetLe", "SetLe($1, $2)")
@@ -1766,50 +1944,21 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
   of mPlusSet: binaryExpr(p, n, r, "SetPlus", "SetPlus($1, $2)")
   of mMinusSet: binaryExpr(p, n, r, "SetMinus", "SetMinus($1, $2)")
   of mIncl: binaryExpr(p, n, r, "", "$1[$2] = true")
-  of mExcl: binaryExpr(p, n, r, "", "delete $1[$2]" | "unset $1[$2]")
+  of mExcl: binaryExpr(p, n, r, "", "delete $1[$2]")
   of mInSet:
-    if p.target == targetJS:
-      binaryExpr(p, n, r, "", "($1[$2] != undefined)")
-    else:
-      let s = n.sons[1]
-      if s.kind == nkCurly:
-        var a, b, x: TCompRes
-        gen(p, n.sons[2], x)
-        r.res = rope("(")
-        r.kind = resExpr
-        for i in countup(0, sonsLen(s) - 1):
-          if i > 0: add(r.res, " || ")
-          var it = s.sons[i]
-          if it.kind == nkRange:
-            gen(p, it.sons[0], a)
-            gen(p, it.sons[1], b)
-            addf(r.res, "($1 >= $2 && $1 <= $3)", [x.res, a.res, b.res,])
-          else:
-            gen(p, it, a)
-            addf(r.res, "($1 == $2)", [x.res, a.res])
-        add(r.res, ")")
-      else:
-        binaryExpr(p, n, r, "", "isset($1[$2])")
+    binaryExpr(p, n, r, "", "($1[$2] != undefined)")
   of mNewSeq: genNewSeq(p, n)
-  of mNewSeqOfCap: unaryExpr(p, n, r, "", "[]" | "array()")
+  of mNewSeqOfCap: unaryExpr(p, n, r, "", "[]")
   of mOf: genOf(p, n, r)
   of mReset: genReset(p, n)
   of mEcho: genEcho(p, n, r)
   of mNLen..mNError, mSlurp, mStaticExec:
-    localError(n.info, errXMustBeCompileTime, n.sons[0].sym.name.s)
+    localError(p.config, n.info, errXMustBeCompileTime % n.sons[0].sym.name.s)
   of mCopyStr:
-    binaryExpr(p, n, r, "", "($1.slice($2))" | "substr($1, $2)")
-  of mCopyStrLast:
-    if p.target == targetJS:
-      ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))")
-    else:
-      ternaryExpr(p, n, r, "nimSubstr", "nimSubstr($#, $#, $#)")
+    binaryExpr(p, n, r, "", "($1.slice($2))")
   of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)")
   of mNewStringOfCap:
-    if p.target == targetJS:
-      unaryExpr(p, n, r, "mnewString", "mnewString(0)")
-    else:
-      unaryExpr(p, n, r, "", "''")
+    unaryExpr(p, n, r, "mnewString", "mnewString(0)")
   of mDotDot:
     genProcForSymIfNeeded(p, n.sons[0].sym)
     genCall(p, n, r)
@@ -1817,17 +1966,16 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
     useMagic(p, "nimParseBiggestFloat")
     genCall(p, n, r)
   of mArray:
-    if p.target == targetPHP: genToArray(p, n, r)
-    else: genCall(p, n, r)
+    genCall(p, n, r)
   else:
     genCall(p, n, r)
-    #else internalError(e.info, 'genMagic: ' + magicToStr[op]);
+    #else internalError(p.config, e.info, 'genMagic: ' + magicToStr[op]);
 
 proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) =
   var
     a, b: TCompRes
-  useMagic(p, "SetConstr")
-  r.res = rope("SetConstr(")
+  useMagic(p, "setConstr")
+  r.res = rope("setConstr(")
   r.kind = resExpr
   for i in countup(0, sonsLen(n) - 1):
     if i > 0: add(r.res, ", ")
@@ -1835,33 +1983,45 @@ proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) =
     if it.kind == nkRange:
       gen(p, it.sons[0], a)
       gen(p, it.sons[1], b)
-      addf(r.res, "[$1, $2]" | "array($#,$#)", [a.res, b.res])
+      addf(r.res, "[$1, $2]", [a.res, b.res])
     else:
       gen(p, it, a)
       add(r.res, a.res)
   add(r.res, ")")
+  # emit better code for constant sets:
+  if isDeepConstExpr(n):
+    inc(p.g.unique)
+    let tmp = rope("ConstSet") & rope(p.g.unique)
+    addf(p.g.constants, "var $1 = $2;$n", [tmp, r.res])
+    r.res = tmp
 
 proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
-  r.res = rope("[" | "array(")
+  r.res = rope("[")
   r.kind = resExpr
   for i in countup(0, sonsLen(n) - 1):
     if i > 0: add(r.res, ", ")
     gen(p, n.sons[i], a)
-    add(r.res, a.res)
-  add(r.res, "]" | ")")
+    if a.typ == etyBaseIndex:
+      addf(r.res, "[$1, $2]", [a.address, a.res])
+    else:
+      add(r.res, a.res)
+  add(r.res, "]")
 
 proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
-  r.res = rope("{" | "array(")
+  r.res = rope("{")
   r.kind = resExpr
   for i in countup(0, sonsLen(n) - 1):
     if i > 0: add(r.res, ", ")
     var it = n.sons[i]
     if it.kind == nkExprColonExpr: it = it.sons[1]
     gen(p, it, a)
-    addf(r.res, "Field$#: $#" | "$2", [i.rope, a.res])
-  r.res.add("}" | ")")
+    if a.typ == etyBaseIndex:
+      addf(r.res, "Field$#: [$#, $#]", [i.rope, a.address, a.res])
+    else:
+      addf(r.res, "Field$#: $#", [i.rope, a.res])
+  r.res.add("}")
 
 proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
@@ -1871,15 +2031,29 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
   for i in countup(1, sonsLen(n) - 1):
     if i > 1: add(initList, ", ")
     var it = n.sons[i]
-    internalAssert it.kind == nkExprColonExpr
-    gen(p, it.sons[1], a)
+    internalAssert p.config, it.kind == nkExprColonExpr
+    let val = it.sons[1]
+    gen(p, val, a)
     var f = it.sons[0].sym
-    if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
+    if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
     fieldIDs.incl(f.id)
-    addf(initList, "$#: $#" | "'$#' => $#" , [f.loc.r, a.res])
+
+    let typ = val.typ.skipTypes(abstractInst)
+    if (typ.kind in IntegralTypes+{tyCstring, tyRef, tyPtr} and
+          mapType(p, typ) != etyBaseIndex) or
+          a.typ == etyBaseIndex or
+          needsNoCopy(p, it.sons[1]):
+      discard
+    else:
+      useMagic(p, "nimCopy")
+      a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)]
+    if a.typ == etyBaseIndex:
+      addf(initList, "$#: [$#, $#]", [f.loc.r, a.address, a.res])
+    else:
+      addf(initList, "$#: $#", [f.loc.r, a.res])
   let t = skipTypes(n.typ, abstractInst + skipPtrs)
   createObjInitList(p, t, fieldIDs, initList)
-  r.res = ("{$1}" | "array($#)") % [initList]
+  r.res = ("{$1}") % [initList]
 
 proc genConv(p: PProc, n: PNode, r: var TCompRes) =
   var dest = skipTypes(n.typ, abstractVarRange)
@@ -1890,10 +2064,10 @@ proc genConv(p: PProc, n: PNode, r: var TCompRes) =
     return
   case dest.kind:
   of tyBool:
-    r.res = "(($1)? 1:0)" % [r.res]
+    r.res = "(!!($1))" % [r.res]
     r.kind = resExpr
   of tyInt:
-    r.res = "($1|0)" % [r.res]
+    r.res = "(($1)|0)" % [r.res]
   else:
     # TODO: What types must we handle here?
     discard
@@ -1918,7 +2092,7 @@ proc convStrToCStr(p: PProc, n: PNode, r: var TCompRes) =
     gen(p, n.sons[0].sons[0], r)
   else:
     gen(p, n.sons[0], r)
-    if r.res == nil: internalError(n.info, "convStrToCStr")
+    if r.res == nil: internalError(p.config, n.info, "convStrToCStr")
     useMagic(p, "toJSStr")
     r.res = "toJSStr($1)" % [r.res]
     r.kind = resExpr
@@ -1930,46 +2104,55 @@ proc convCStrToStr(p: PProc, n: PNode, r: var TCompRes) =
     gen(p, n.sons[0].sons[0], r)
   else:
     gen(p, n.sons[0], r)
-    if r.res == nil: internalError(n.info, "convCStrToStr")
+    if r.res == nil: internalError(p.config, n.info, "convCStrToStr")
     useMagic(p, "cstrToNimstr")
     r.res = "cstrToNimstr($1)" % [r.res]
     r.kind = resExpr
 
 proc genReturnStmt(p: PProc, n: PNode) =
-  if p.procDef == nil: internalError(n.info, "genReturnStmt")
+  if p.procDef == nil: internalError(p.config, n.info, "genReturnStmt")
   p.beforeRetNeeded = true
   if n.sons[0].kind != nkEmpty:
     genStmt(p, n.sons[0])
   else:
     genLineDir(p, n)
-  addf(p.body, "break BeforeRet;$n" | "goto BeforeRet;$n", [])
+  lineF(p, "break BeforeRet;$n", [])
 
 proc frameCreate(p: PProc; procname, filename: Rope): Rope =
-  result = (("var F={procname:$1,prev:framePtr,filename:$2,line:0};$nframePtr = F;$n" |
-             "global $$framePtr; $$F=array('procname'=>$#,'prev'=>$$framePtr,'filename'=>$#,'line'=>0);$n$$framePtr = &$$F;$n")) % [
-            procname, filename]
+  let frameFmt =
+    "var F={procname:$1,prev:framePtr,filename:$2,line:0};$n"
+
+  result = p.indentLine(frameFmt % [procname, filename])
+  result.add p.indentLine(ropes.`%`("framePtr = F;$n", []))
 
 proc frameDestroy(p: PProc): Rope =
-  result = rope(("framePtr = F.prev;" | "$framePtr = $F['prev'];") & tnl)
+  result = p.indentLine rope(("framePtr = F.prev;") & "\L")
 
 proc genProcBody(p: PProc, prc: PSym): Rope =
   if hasFrameInfo(p):
     result = frameCreate(p,
               makeJSString(prc.owner.name.s & '.' & prc.name.s),
-              makeJSString(toFilename(prc.info)))
+              makeJSString(toFilename(p.config, prc.info)))
   else:
     result = nil
   if p.beforeRetNeeded:
-    addf(result, "BeforeRet: do {$n$1} while (false); $n" |
-                 "$# BeforeRet:;$n", [p.body])
+    result.add p.indentLine(~"BeforeRet: do {$n")
+    result.add p.body
+    result.add p.indentLine(~"} while (false);$n")
   else:
     add(result, p.body)
-  if prc.typ.callConv == ccSysCall and p.target == targetJS:
+  if prc.typ.callConv == ccSysCall:
     result = ("try {$n$1} catch (e) {$n" &
       " alert(\"Unhandled exception:\\n\" + e.message + \"\\n\"$n}") % [result]
   if hasFrameInfo(p):
     add(result, frameDestroy(p))
 
+proc optionaLine(p: Rope): Rope =
+  if p == nil:
+    return nil
+  else:
+    return p & "\L"
+
 proc genProc(oldProc: PProc, prc: PSym): Rope =
   var
     resultSym: PSym
@@ -1980,34 +2163,72 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
   p.up = oldProc
   var returnStmt: Rope = nil
   var resultAsgn: Rope = nil
-  let name = mangleName(prc, p.target)
+  var name = mangleName(p.module, prc)
   let header = generateHeader(p, prc.typ)
   if prc.typ.sons[0] != nil and sfPure notin prc.flags:
     resultSym = prc.ast.sons[resultPos].sym
-    let mname = mangleName(resultSym, p.target)
-    resultAsgn = ("var $# = $#;$n" | "$$$# = $#;$n") % [
-        mname,
-        createVar(p, resultSym.typ, isIndirect(resultSym))]
-    if resultSym.typ.kind in { tyVar, tyPtr, tyRef } and mapType(p, resultSym.typ) == etyBaseIndex:
-      resultAsgn.add "var $#_Idx = 0;$n" % [
-        mname ];
+    let mname = mangleName(p.module, resultSym)
+    if not isindirect(resultSym) and
+      resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and
+        mapType(p, resultSym.typ) == etyBaseIndex:
+      resultAsgn = p.indentLine(("var $# = null;$n") % [mname])
+      resultAsgn.add p.indentLine("var $#_Idx = 0;$n" % [mname])
+    else:
+      let resVar = createVar(p, resultSym.typ, isIndirect(resultSym))
+      resultAsgn = p.indentLine(("var $# = $#;$n") % [mname, resVar])
     gen(p, prc.ast.sons[resultPos], a)
     if mapType(p, resultSym.typ) == etyBaseIndex:
       returnStmt = "return [$#, $#];$n" % [a.address, a.res]
     else:
       returnStmt = "return $#;$n" % [a.res]
-  genStmt(p, prc.getBody)
 
-  result = "function $#($#) {$n$#$n$#$#$#$#}$n" %
-            [name, header, p.globals, p.locals, resultAsgn,
-             genProcBody(p, prc), returnStmt]
+  let transformed_body = transformBody(oldProc.module.graph, prc, cache = false)
+  p.nested: genStmt(p, transformed_body)
+
+  var def: Rope
+  if not prc.constraint.isNil:
+    def = (prc.constraint.strVal & " {$n$#$#$#$#$#") %
+            [ returnType,
+              name,
+              header,
+              optionaLine(p.globals),
+              optionaLine(p.locals),
+              optionaLine(resultAsgn),
+              optionaLine(genProcBody(p, prc)),
+              optionaLine(p.indentLine(returnStmt))]
+  else:
+    result = ~"\L"
+
+    if optHotCodeReloading in p.config.options:
+      # Here, we introduce thunks that create the equivalent of a jump table
+      # for all global functions, because references to them may be stored
+      # in JavaScript variables. The added indirection ensures that such
+      # references will end up calling the reloaded code.
+      var thunkName = name
+      name = name & "IMLP"
+      result.add("function $#() { return $#.apply(this, arguments); }$n" %
+                 [thunkName, name])
+
+    def = "function $#($#) {$n$#$#$#$#$#" %
+            [ name,
+              header,
+              optionaLine(p.globals),
+              optionaLine(p.locals),
+              optionaLine(resultAsgn),
+              optionaLine(genProcBody(p, prc)),
+              optionaLine(p.indentLine(returnStmt))]
+
+  dec p.extraIndent
+  result.add p.indentLine(def)
+  result.add p.indentLine(~"}$n")
+
   #if gVerbosity >= 3:
   #  echo "END   generated code for: " & prc.name.s
 
 proc genStmt(p: PProc, n: PNode) =
   var r: TCompRes
   gen(p, n, r)
-  if r.res != nil: addf(p.body, "$#;$n", [r.res])
+  if r.res != nil: lineF(p, "$#;$n", [r.res])
 
 proc genPragma(p: PProc, n: PNode) =
   for it in n.sons:
@@ -2022,10 +2243,10 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) =
   if dest.kind == src.kind:
     # no-op conversion
     return
-  let toInt = (dest.kind in tyInt .. tyInt32)
-  let toUint = (dest.kind in tyUInt .. tyUInt32)
-  let fromInt = (src.kind in tyInt .. tyInt32)
-  let fromUint = (src.kind in tyUInt .. tyUInt32)
+  let toInt = (dest.kind in tyInt..tyInt32)
+  let toUint = (dest.kind in tyUInt..tyUInt32)
+  let fromInt = (src.kind in tyInt..tyInt32)
+  let fromUint = (src.kind in tyUInt..tyUInt32)
 
   if toUint and (fromInt or fromUint):
     let trimmer = unsignedTrimmer(dest.size)
@@ -2037,8 +2258,7 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) =
     elif fromUint:
       if src.size == 4 and dest.size == 4:
         # XXX prevent multi evaluations
-        r.res = "($1|0)" % [r.res] |
-          "($1>(float)2147483647?(int)$1-4294967296:$1)" % [r.res]
+        r.res = "($1|0)" % [r.res]
       else:
         let trimmer = unsignedTrimmer(dest.size)
         let minuend = case dest.size
@@ -2047,6 +2267,13 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) =
           of 4: "0xfffffffe"
           else: ""
         r.res = "($1 - ($2 $3))" % [rope minuend, r.res, trimmer]
+  elif (src.kind == tyPtr and mapType(p, src) == etyObject) and dest.kind == tyPointer:
+    r.address = r.res
+    r.res = ~"null"
+    r.typ = etyBaseIndex
+  elif (dest.kind == tyPtr and mapType(p, dest) == etyObject) and src.kind == tyPointer:
+    r.res = r.address
+    r.typ = etyObject
 
 proc gen(p: PProc, n: PNode, r: var TCompRes) =
   r.typ = etyNone
@@ -2056,7 +2283,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   case n.kind
   of nkSym:
     genSym(p, n, r)
-  of nkCharLit..nkUInt32Lit:
+  of nkCharLit..nkUInt64Lit:
     if n.typ.kind == tyBool:
       r.res = if n.intVal == 0: rope"false" else: rope"true"
     else:
@@ -2074,23 +2301,32 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
       r.res = rope"null"
       r.kind = resExpr
   of nkStrLit..nkTripleStrLit:
-    if skipTypes(n.typ, abstractVarRange).kind == tyString and
-       p.target == targetJS:
-      useMagic(p, "makeNimstrLit")
-      r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)]
+    if skipTypes(n.typ, abstractVarRange).kind == tyString:
+      if n.strVal.len != 0:
+        useMagic(p, "makeNimstrLit")
+        r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)]
+      else:
+        r.res = rope"[]"
     else:
       r.res = makeJSString(n.strVal, false)
     r.kind = resExpr
   of nkFloatLit..nkFloat64Lit:
     let f = n.floatVal
-    if f != f: r.res = rope"NaN"
-    elif f == 0.0: r.res = rope"0.0"
-    elif f == 0.5 * f:
-      if f > 0.0: r.res = rope"Infinity"
-      else: r.res = rope"-Infinity"
+    case classify(f)
+    of fcNaN:
+      r.res = rope"NaN"
+    of fcNegZero:
+      r.res = rope"-0.0"
+    of fcZero:
+      r.res = rope"0.0"
+    of fcInf:
+      r.res = rope"Infinity"
+    of fcNegInf:
+      r.res = rope"-Infinity"
     else: r.res = rope(f.toStrMaxPrecision)
     r.kind = resExpr
   of nkCallKinds:
+    if isEmptyType(n.typ): genLineDir(p, n)
     if (n.sons[0].kind == nkSym) and (n.sons[0].sym.magic != mNone):
       genMagic(p, n, r)
     elif n.sons[0].kind == nkSym and sfInfixCall in n.sons[0].sym.flags and
@@ -2101,18 +2337,15 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkClosure: gen(p, n[0], r)
   of nkCurly: genSetConstr(p, n, r)
   of nkBracket: genArrayConstr(p, n, r)
-  of nkPar: genTupleConstr(p, n, r)
+  of nkPar, nkTupleConstr: genTupleConstr(p, n, r)
   of nkObjConstr: genObjConstr(p, n, r)
   of nkHiddenStdConv, nkHiddenSubConv, nkConv: genConv(p, n, r)
   of nkAddr, nkHiddenAddr:
-    if p.target == targetJS:
-      genAddr(p, n, r)
-    else:
-      gen(p, n.sons[0], r)
+    genAddr(p, n, r)
   of nkDerefExpr, nkHiddenDeref: genDeref(p, n, r)
   of nkBracketExpr: genArrayAccess(p, n, r)
   of nkDotExpr: genFieldAccess(p, n, r)
-  of nkCheckedFieldExpr: genCheckedFieldAccess(p, n, r)
+  of nkCheckedFieldExpr: genCheckedFieldOp(p, n, nil, r)
   of nkObjDownConv: gen(p, n.sons[0], r)
   of nkObjUpConv: upConv(p, n, r)
   of nkCast: genCast(p, n, r)
@@ -2124,7 +2357,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkEmpty: discard
   of nkLambdaKinds:
     let s = n.sons[namePos].sym
-    discard mangleName(s, p.target)
+    discard mangleName(p.module, s)
     r.res = s.loc.r
     if lfNoDecl in s.loc.flags or s.magic != mNone: discard
     elif not p.g.generatedSyms.containsOrIncl(s.id):
@@ -2147,7 +2380,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkVarSection, nkLetSection: genVarStmt(p, n)
   of nkConstSection: discard
   of nkForStmt, nkParForStmt:
-    internalError(n.info, "for statement not eliminated")
+    internalError(p.config, n.info, "for statement not eliminated")
   of nkCaseStmt: genCaseJS(p, n, r)
   of nkReturnStmt: genReturnStmt(p, n)
   of nkBreakStmt: genBreakStmt(p, n)
@@ -2162,74 +2395,70 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkRaiseStmt: genRaiseStmt(p, n)
   of nkTypeSection, nkCommentStmt, nkIteratorDef, nkIncludeStmt,
      nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
-     nkFromStmt, nkTemplateDef, nkMacroDef: discard
+     nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt: discard
   of nkPragma: genPragma(p, n)
-  of nkProcDef, nkMethodDef, nkConverterDef:
+  of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef:
     var s = n.sons[namePos].sym
     if {sfExportc, sfCompilerProc} * s.flags == {sfExportc}:
       genSym(p, n.sons[namePos], r)
       r.res = nil
   of nkGotoState, nkState:
-    internalError(n.info, "first class iterators not implemented")
+    internalError(p.config, n.info, "first class iterators not implemented")
   of nkPragmaBlock: gen(p, n.lastSon, r)
-  else: internalError(n.info, "gen: unknown node type: " & $n.kind)
+  of nkComesFrom:
+    discard "XXX to implement for better stack traces"
+  else: internalError(p.config, n.info, "gen: unknown node type: " & $n.kind)
 
-var globals: PGlobals
-
-proc newModule(module: PSym): BModule =
+proc newModule(g: ModuleGraph; module: PSym): BModule =
   new(result)
   result.module = module
-  if globals == nil:
-    globals = newGlobals()
-
-proc genHeader(target: TTarget): Rope =
-  if target == targetJS:
-    result = (
-      "/* Generated by the Nim Compiler v$1 */$n" &
-      "/*   (c) 2017 Andreas Rumpf */$n$n" &
-      "var framePtr = null;$n" &
-      "var excHandler = 0;$n" &
-      "var lastJSError = null;$n" &
-      "if (typeof Int8Array === 'undefined') Int8Array = Array;$n" &
-      "if (typeof Int16Array === 'undefined') Int16Array = Array;$n" &
-      "if (typeof Int32Array === 'undefined') Int32Array = Array;$n" &
-      "if (typeof Uint8Array === 'undefined') Uint8Array = Array;$n" &
-      "if (typeof Uint16Array === 'undefined') Uint16Array = Array;$n" &
-      "if (typeof Uint32Array === 'undefined') Uint32Array = Array;$n" &
-      "if (typeof Float32Array === 'undefined') Float32Array = Array;$n" &
-      "if (typeof Float64Array === 'undefined') Float64Array = Array;$n") %
-      [rope(VersionAsString)]
-  else:
-    result = ("<?php$n" &
-              "/* Generated by the Nim Compiler v$1 */$n" &
-              "/*   (c) 2017 Andreas Rumpf */$n$n" &
-              "$$framePtr = null;$n" &
-              "$$excHandler = 0;$n" &
-              "$$lastJSError = null;$n") %
-             [rope(VersionAsString)]
+  result.sigConflicts = initCountTable[SigHash]()
+  if g.backend == nil:
+    g.backend = newGlobals()
+  result.graph = g
+  result.config = g.config
+
+proc genHeader(): Rope =
+  result = (
+    "/* Generated by the Nim Compiler v$1 */$n" &
+    "/*   (c) " & copyrightYear & " Andreas Rumpf */$n$n" &
+    "var framePtr = null;$n" &
+    "var excHandler = 0;$n" &
+    "var lastJSError = null;$n" &
+    "if (typeof Int8Array === 'undefined') Int8Array = Array;$n" &
+    "if (typeof Int16Array === 'undefined') Int16Array = Array;$n" &
+    "if (typeof Int32Array === 'undefined') Int32Array = Array;$n" &
+    "if (typeof Uint8Array === 'undefined') Uint8Array = Array;$n" &
+    "if (typeof Uint16Array === 'undefined') Uint16Array = Array;$n" &
+    "if (typeof Uint32Array === 'undefined') Uint32Array = Array;$n" &
+    "if (typeof Float32Array === 'undefined') Float32Array = Array;$n" &
+    "if (typeof Float64Array === 'undefined') Float64Array = Array;$n") %
+    [rope(VersionAsString)]
 
 proc genModule(p: PProc, n: PNode) =
   if optStackTrace in p.options:
     add(p.body, frameCreate(p,
         makeJSString("module " & p.module.module.name.s),
-        makeJSString(toFilename(p.module.module.info))))
-  genStmt(p, n)
+        makeJSString(toFilename(p.config, p.module.module.info))))
+  let n_transformed = transformStmt(p.module.graph, p.module.module, n)
+  genStmt(p, n_transformed)
   if optStackTrace in p.options:
     add(p.body, frameDestroy(p))
 
 proc myProcess(b: PPassContext, n: PNode): PNode =
-  if passes.skipCodegen(n): return n
   result = n
-  var m = BModule(b)
-  if m.module == nil: internalError(n.info, "myProcess")
+  let m = BModule(b)
+  if passes.skipCodegen(m.config, n): return n
+  if m.module == nil: internalError(m.config, n.info, "myProcess")
+  let globals = PGlobals(m.graph.backend)
   var p = newProc(globals, m, nil, m.module.options)
   p.unique = globals.unique
   genModule(p, n)
   add(p.g.code, p.locals)
   add(p.g.code, p.body)
-  globals.unique = p.unique
 
 proc wholeCode(graph: ModuleGraph; m: BModule): Rope =
+  let globals = PGlobals(graph.backend)
   for prc in globals.forwarded:
     if not globals.generatedSyms.containsOrIncl(prc.id):
       var p = newProc(globals, m, nil, m.module.options)
@@ -2249,11 +2478,11 @@ proc getClassName(t: PType): Rope =
   if s.isNil or sfAnon in s.flags:
     s = skipTypes(t, abstractPtrs).sym
   if s.isNil or sfAnon in s.flags:
-    internalError("cannot retrieve class name")
+    doAssert(false, "cannot retrieve class name")
   if s.loc.r != nil: result = s.loc.r
   else: result = rope(s.name.s)
 
-proc genClass(obj: PType; content: Rope; ext: string) =
+proc genClass(conf: ConfigRef; obj: PType; content: Rope; ext: string) =
   let cls = getClassName(obj)
   let t = skipTypes(obj, abstractPtrs)
   let extends = if t.kind == tyObject and t.sons[0] != nil:
@@ -2261,40 +2490,38 @@ proc genClass(obj: PType; content: Rope; ext: string) =
     else: nil
   let result = ("<?php$n" &
             "/* Generated by the Nim Compiler v$# */$n" &
-            "/*   (c) 2017 Andreas Rumpf */$n$n" &
+            "/*   (c) " & copyrightYear & " Andreas Rumpf */$n$n" &
             "require_once \"nimsystem.php\";$n" &
             "class $#$# {$n$#$n}$n") %
            [rope(VersionAsString), cls, extends, content]
 
-  let outfile = changeFileExt(completeCFilePath($cls), ext)
+  let outfile = changeFileExt(completeCFilePath(conf, AbsoluteFile($cls)), ext)
   discard writeRopeIfNotEqual(result, outfile)
 
 proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
-  if passes.skipCodegen(n): return n
   result = myProcess(b, n)
   var m = BModule(b)
+  if passes.skipCodegen(m.config, n): return n
   if sfMainModule in m.module.flags:
-    let ext = if m.target == targetJS: "js" else: "php"
-    let f = if globals.classes.len == 0: m.module.filename
+    let globals = PGlobals(graph.backend)
+    let ext = "js"
+    let f = if globals.classes.len == 0: toFilename(m.config, FileIndex m.module.position)
             else: "nimsystem"
     let code = wholeCode(graph, m)
     let outfile =
-      if options.outFile.len > 0:
-        if options.outFile.isAbsolute: options.outFile
-        else: getCurrentDir() / options.outFile
+      if not m.config.outFile.isEmpty:
+        if m.config.outFile.string.isAbsolute: m.config.outFile
+        else: AbsoluteFile(getCurrentDir() / m.config.outFile.string)
       else:
-        changeFileExt(completeCFilePath(f), ext)
-    discard writeRopeIfNotEqual(genHeader(m.target) & code, outfile)
+        changeFileExt(completeCFilePath(m.config, AbsoluteFile f), ext)
+    let (outDir, _, _) = splitFile(outfile)
+    if not outDir.isEmpty:
+      createDir(outDir)
+    discard writeRopeIfNotEqual(genHeader() & code, outfile)
     for obj, content in items(globals.classes):
-      genClass(obj, content, ext)
+      genClass(m.config, obj, content, ext)
 
-proc myOpenCached(graph: ModuleGraph; s: PSym, rd: PRodReader): PPassContext =
-  internalError("symbol files are not possible with the JS code generator")
-  result = nil
-
-proc myOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext =
-  var r = newModule(s)
-  r.target = if gCmd == cmdCompileToPHP: targetPHP else: targetJS
-  result = r
+proc myOpen(graph: ModuleGraph; s: PSym): PPassContext =
+  result = newModule(graph, s)
 
-const JSgenPass* = makePass(myOpen, myOpenCached, myProcess, myClose)
+const JSgenPass* = makePass(myOpen, myProcess, myClose)
diff --git a/compiler/jstypes.nim b/compiler/jstypes.nim
index f49bd7668..d86b09a03 100644
--- a/compiler/jstypes.nim
+++ b/compiler/jstypes.nim
@@ -25,7 +25,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
     else:
       s = nil
       for i in countup(0, length - 1):
-        if i > 0: add(s, ", " & tnl)
+        if i > 0: add(s, ", \L")
         add(s, genObjectFields(p, typ, n.sons[i]))
       result = ("{kind: 2, len: $1, offset: 0, " &
           "typ: null, name: null, sons: [$2]}") % [rope(length), s]
@@ -34,11 +34,11 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
     s = genTypeInfo(p, field.typ)
     result = ("{kind: 1, offset: \"$1\", len: 0, " &
         "typ: $2, name: $3, sons: null}") %
-                   [mangleName(field, p.target), s,
+                   [mangleName(p.module, field), s,
                     makeJSString(field.name.s)]
   of nkRecCase:
     length = sonsLen(n)
-    if (n.sons[0].kind != nkSym): internalError(n.info, "genObjectFields")
+    if (n.sons[0].kind != nkSym): internalError(p.config, n.info, "genObjectFields")
     field = n.sons[0].sym
     s = genTypeInfo(p, field.typ)
     for i in countup(1, length - 1):
@@ -47,7 +47,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
       case b.kind
       of nkOfBranch:
         if sonsLen(b) < 2:
-          internalError(b.info, "genObjectFields; nkOfBranch broken")
+          internalError(p.config, b.info, "genObjectFields; nkOfBranch broken")
         for j in countup(0, sonsLen(b) - 2):
           if u != nil: add(u, ", ")
           if b.sons[j].kind == nkRange:
@@ -56,20 +56,24 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
           else:
             add(u, rope(getOrdValue(b.sons[j])))
       of nkElse:
-        u = rope(lengthOrd(field.typ))
-      else: internalError(n.info, "genObjectFields(nkRecCase)")
-      if result != nil: add(result, ", " & tnl)
-      addf(result, "[SetConstr($1), $2]",
+        u = rope(lengthOrd(p.config, field.typ))
+      else: internalError(p.config, n.info, "genObjectFields(nkRecCase)")
+      if result != nil: add(result, ", \L")
+      addf(result, "[setConstr($1), $2]",
            [u, genObjectFields(p, typ, lastSon(b))])
     result = ("{kind: 3, offset: \"$1\", len: $3, " &
         "typ: $2, name: $4, sons: [$5]}") % [
-        mangleName(field, p.target), s,
-        rope(lengthOrd(field.typ)), makeJSString(field.name.s), result]
-  else: internalError(n.info, "genObjectFields")
+        mangleName(p.module, field), s,
+        rope(lengthOrd(p.config, field.typ)), makeJSString(field.name.s), result]
+  else: internalError(p.config, n.info, "genObjectFields")
+
+proc objHasTypeField(t: PType): bool {.inline.} =
+  tfInheritable in t.flags or t.sons[0] != nil
 
 proc genObjectInfo(p: PProc, typ: PType, name: Rope) =
+  let kind = if objHasTypeField(typ): tyObject else: tyTuple
   var s = ("var $1 = {size: 0, kind: $2, base: null, node: null, " &
-           "finalizer: null};$n") % [name, rope(ord(typ.kind))]
+           "finalizer: null};$n") % [name, rope(ord(kind))]
   prepend(p.g.typeInfo, s)
   addf(p.g.typeInfo, "var NNI$1 = $2;$n",
        [rope(typ.id), genObjectFields(p, typ, typ.n)])
@@ -80,8 +84,8 @@ proc genObjectInfo(p: PProc, typ: PType, name: Rope) =
 
 proc genTupleFields(p: PProc, typ: PType): Rope =
   var s: Rope = nil
-  for i in 0 .. <typ.len:
-    if i > 0: add(s, ", " & tnl)
+  for i in 0 ..< typ.len:
+    if i > 0: add(s, ", \L")
     s.addf("{kind: 1, offset: \"Field$1\", len: 0, " &
            "typ: $2, name: \"Field$1\", sons: null}",
            [i.rope, genTypeInfo(p, typ.sons[i])])
@@ -100,9 +104,9 @@ proc genEnumInfo(p: PProc, typ: PType, name: Rope) =
   let length = sonsLen(typ.n)
   var s: Rope = nil
   for i in countup(0, length - 1):
-    if (typ.n.sons[i].kind != nkSym): internalError(typ.n.info, "genEnumInfo")
+    if (typ.n.sons[i].kind != nkSym): internalError(p.config, typ.n.info, "genEnumInfo")
     let field = typ.n.sons[i].sym
-    if i > 0: add(s, ", " & tnl)
+    if i > 0: add(s, ", \L")
     let extName = if field.ast == nil: field.name.s else: field.ast.strVal
     addf(s, "\"$1\": {kind: 1, offset: $1, typ: $2, name: $3, len: 0, sons: null}",
          [rope(field.position), name, makeJSString(extName)])
@@ -117,27 +121,8 @@ proc genEnumInfo(p: PProc, typ: PType, name: Rope) =
     addf(p.g.typeInfo, "$1.base = $2;$n",
          [name, genTypeInfo(p, typ.sons[0])])
 
-proc genEnumInfoPHP(p: PProc; t: PType): Rope =
-  let t = t.skipTypes({tyGenericInst, tyDistinct, tyAlias})
-  result = "$$NTI$1" % [rope(t.id)]
-  p.declareGlobal(t.id, result)
-  if containsOrIncl(p.g.typeInfoGenerated, t.id): return
-
-  let length = sonsLen(t.n)
-  var s: Rope = nil
-  for i in countup(0, length - 1):
-    if (t.n.sons[i].kind != nkSym): internalError(t.n.info, "genEnumInfo")
-    let field = t.n.sons[i].sym
-    if i > 0: add(s, ", " & tnl)
-    let extName = if field.ast == nil: field.name.s else: field.ast.strVal
-    addf(s, "$# => $#$n",
-         [rope(field.position), makeJSString(extName)])
-  prepend(p.g.typeInfo, "$$$# = $#;$n" % [result, s])
-
 proc genTypeInfo(p: PProc, typ: PType): Rope =
-  if p.target == targetPHP:
-    return makeJSString(typeToString(typ, preferModuleInfo))
-  let t = typ.skipTypes({tyGenericInst, tyDistinct, tyAlias})
+  let t = typ.skipTypes({tyGenericInst, tyDistinct, tyAlias, tySink})
   result = "NTI$1" % [rope(t.id)]
   if containsOrIncl(p.g.typeInfoGenerated, t.id): return
   case t.kind
@@ -148,7 +133,7 @@ proc genTypeInfo(p: PProc, typ: PType): Rope =
       "var $1 = {size: 0,kind: $2,base: null,node: null,finalizer: null};$n" %
       [result, rope(ord(t.kind))]
     prepend(p.g.typeInfo, s)
-  of tyVar, tyRef, tyPtr, tySequence, tyRange, tySet:
+  of tyVar, tyLent, tyRef, tyPtr, tySequence, tyRange, tySet:
     var s =
       "var $1 = {size: 0,kind: $2,base: null,node: null,finalizer: null};$n" %
               [result, rope(ord(t.kind))]
@@ -167,5 +152,5 @@ proc genTypeInfo(p: PProc, typ: PType): Rope =
   of tyTuple: genTupleInfo(p, t, result)
   of tyStatic:
     if t.n != nil: result = genTypeInfo(p, lastSon t)
-    else: internalError("genTypeInfo(" & $t.kind & ')')
-  else: internalError("genTypeInfo(" & $t.kind & ')')
+    else: internalError(p.config, "genTypeInfo(" & $t.kind & ')')
+  else: internalError(p.config, "genTypeInfo(" & $t.kind & ')')
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index cd2ccfe53..ddde1be31 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -7,11 +7,12 @@
 #    distribution, for details about the copyright.
 #
 
-# This include file implements lambda lifting for the transformator.
+# This file implements lambda lifting for the transformator.
 
 import
-  intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os,
-  idents, renderer, types, magicsys, rodread, lowerings, tables
+  intsets, strutils, options, ast, astalgo, trees, treetab, msgs,
+  idents, renderer, types, magicsys, lowerings, tables, modulegraphs, lineinfos,
+  transf
 
 discard """
   The basic approach is that captured vars need to be put on the heap and
@@ -125,32 +126,32 @@ proc newCall(a: PSym, b: PNode): PNode =
   result.add newSymNode(a)
   result.add b
 
-proc createStateType(iter: PSym): PType =
+proc createClosureIterStateType*(g: ModuleGraph; iter: PSym): PType =
   var n = newNodeI(nkRange, iter.info)
   addSon(n, newIntNode(nkIntLit, -1))
   addSon(n, newIntNode(nkIntLit, 0))
   result = newType(tyRange, iter)
   result.n = n
-  var intType = nilOrSysInt()
+  var intType = nilOrSysInt(g)
   if intType.isNil: intType = newType(tyInt, iter)
   rawAddSon(result, intType)
 
-proc createStateField(iter: PSym): PSym =
-  result = newSym(skField, getIdent(":state"), iter, iter.info)
-  result.typ = createStateType(iter)
+proc createStateField(g: ModuleGraph; iter: PSym): PSym =
+  result = newSym(skField, getIdent(g.cache, ":state"), iter, iter.info)
+  result.typ = createClosureIterStateType(g, iter)
 
-proc createEnvObj(owner: PSym; info: TLineInfo): PType =
+proc createEnvObj(g: ModuleGraph; owner: PSym; info: TLineInfo): PType =
   # YYY meh, just add the state field for every closure for now, it's too
   # hard to figure out if it comes from a closure iterator:
-  result = createObj(owner, info)
-  rawAddField(result, createStateField(owner))
+  result = createObj(g, owner, info, final=false)
+  rawAddField(result, createStateField(g, owner))
 
-proc getIterResult(iter: PSym): PSym =
+proc getClosureIterResult*(g: ModuleGraph; iter: PSym): PSym =
   if resultPos < iter.ast.len:
     result = iter.ast.sons[resultPos].sym
   else:
     # XXX a bit hacky:
-    result = newSym(skResult, getIdent":result", iter, iter.info)
+    result = newSym(skResult, getIdent(g.cache, ":result"), iter, iter.info, {})
     result.typ = iter.typ.sons[0]
     incl(result.flags, sfUsed)
     iter.ast.add newSymNode(result)
@@ -166,7 +167,7 @@ proc addHiddenParam(routine: PSym, param: PSym) =
   assert sfFromGeneric in param.flags
   #echo "produced environment: ", param.id, " for ", routine.id
 
-proc getHiddenParam(routine: PSym): PSym =
+proc getHiddenParam(g: ModuleGraph; routine: PSym): PSym =
   let params = routine.ast.sons[paramsPos]
   let hidden = lastSon(params)
   if hidden.kind == nkSym and hidden.sym.kind == skParam and hidden.sym.name.s == paramName:
@@ -174,7 +175,7 @@ proc getHiddenParam(routine: PSym): PSym =
     assert sfFromGeneric in result.flags
   else:
     # writeStackTrace()
-    localError(routine.info, "internal error: could not find env param for " & routine.name.s)
+    localError(g.config, routine.info, "internal error: could not find env param for " & routine.name.s)
     result = routine
 
 proc getEnvParam*(routine: PSym): PSym =
@@ -186,15 +187,16 @@ proc getEnvParam*(routine: PSym): PSym =
 
 proc interestingVar(s: PSym): bool {.inline.} =
   result = s.kind in {skVar, skLet, skTemp, skForVar, skParam, skResult} and
-    sfGlobal notin s.flags
+    sfGlobal notin s.flags and
+    s.typ.kind notin {tyStatic, tyTypeDesc}
 
 proc illegalCapture(s: PSym): bool {.inline.} =
   result = skipTypes(s.typ, abstractInst).kind in
-                   {tyVar, tyOpenArray, tyVarargs} or
+                   {tyVar, tyOpenArray, tyVarargs, tyLent} or
       s.kind == skResult
 
 proc isInnerProc(s: PSym): bool =
-  if s.kind in {skProc, skMethod, skConverter, skIterator} and s.magic == mNone:
+  if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator} and s.magic == mNone:
     result = s.skipGenericOwner.kind in routineKinds
 
 proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
@@ -207,43 +209,43 @@ proc newAsgnStmt(le, ri: PNode, info: TLineInfo): PNode =
   result.sons[0] = le
   result.sons[1] = ri
 
-proc makeClosure*(prc: PSym; env: PNode; info: TLineInfo): PNode =
+proc makeClosure*(g: ModuleGraph; prc: PSym; env: PNode; info: TLineInfo): PNode =
   result = newNodeIT(nkClosure, info, prc.typ)
   result.add(newSymNode(prc))
   if env == nil:
-    result.add(newNodeIT(nkNilLit, info, getSysType(tyNil)))
+    result.add(newNodeIT(nkNilLit, info, getSysType(g, info, tyNil)))
   else:
     if env.skipConv.kind == nkClosure:
-      localError(info, "internal error: taking closure of closure")
+      localError(g.config, info, "internal error: taking closure of closure")
     result.add(env)
 
 proc interestingIterVar(s: PSym): bool {.inline.} =
   # XXX optimization: Only lift the variable if it lives across
   # yield/return boundaries! This can potentially speed up
   # closure iterators quite a bit.
-  result = s.kind in {skVar, skLet, skTemp, skForVar} and sfGlobal notin s.flags
+  result = s.kind in {skResult, skVar, skLet, skTemp, skForVar} and sfGlobal notin s.flags
 
 template isIterator*(owner: PSym): bool =
   owner.kind == skIterator and owner.typ.callConv == ccClosure
 
-proc liftingHarmful(owner: PSym): bool {.inline.} =
+proc liftingHarmful(conf: ConfigRef; owner: PSym): bool {.inline.} =
   ## lambda lifting can be harmful for JS-like code generators.
   let isCompileTime = sfCompileTime in owner.flags or owner.kind == skMacro
-  result = gCmd in {cmdCompileToPHP, cmdCompileToJS} and not isCompileTime
+  result = conf.cmd == cmdCompileToJS and not isCompileTime
 
-proc liftIterSym*(n: PNode; owner: PSym): PNode =
+proc liftIterSym*(g: ModuleGraph; n: PNode; owner: PSym): PNode =
   # transforms  (iter)  to  (let env = newClosure[iter](); (iter, env))
-  if liftingHarmful(owner): return n
+  if liftingHarmful(g.config, owner): return n
   let iter = n.sym
   assert iter.isIterator
 
   result = newNodeIT(nkStmtListExpr, n.info, n.typ)
 
-  let hp = getHiddenParam(iter)
+  let hp = getHiddenParam(g, iter)
   var env: PNode
   if owner.isIterator:
-    let it = getHiddenParam(owner)
-    addUniqueField(it.typ.sons[0], hp)
+    let it = getHiddenParam(g, owner)
+    addUniqueField(it.typ.sons[0], hp, g.cache)
     env = indirectAccess(newSymNode(it), hp, hp.info)
   else:
     let e = newSym(skLet, iter.name, owner, n.info)
@@ -254,13 +256,13 @@ proc liftIterSym*(n: PNode; owner: PSym): PNode =
     addVar(v, env)
     result.add(v)
   # add 'new' statement:
-  result.add newCall(getSysSym"internalNew", env)
-  result.add makeClosure(iter, env, n.info)
+  result.add newCall(getSysSym(g, n.info, "internalNew"), env)
+  result.add makeClosure(g, iter, env, n.info)
 
-proc freshVarForClosureIter*(s, owner: PSym): PNode =
-  let envParam = getHiddenParam(owner)
+proc freshVarForClosureIter*(g: ModuleGraph; s, owner: PSym): PNode =
+  let envParam = getHiddenParam(g, owner)
   let obj = envParam.typ.lastSon
-  addField(obj, s)
+  addField(obj, s, g.cache)
 
   var access = newSymNode(envParam)
   assert obj.kind == tyObject
@@ -268,15 +270,20 @@ proc freshVarForClosureIter*(s, owner: PSym): PNode =
   if field != nil:
     result = rawIndirectAccess(access, field, s.info)
   else:
-    localError(s.info, "internal error: cannot generate fresh variable")
+    localError(g.config, s.info, "internal error: cannot generate fresh variable")
     result = access
 
 # ------------------ new stuff -------------------------------------------
 
-proc markAsClosure(owner: PSym; n: PNode) =
+proc markAsClosure(g: ModuleGraph; owner: PSym; n: PNode) =
   let s = n.sym
-  if illegalCapture(s) or owner.typ.callConv notin {ccClosure, ccDefault}:
-    localError(n.info, errIllegalCaptureX, s.name.s)
+  if illegalCapture(s):
+    localError(g.config, n.info,
+      ("'$1' is of type <$2> which cannot be captured as it would violate memory" &
+       " safety, declared here: $3") % [s.name.s, typeToString(s.typ), g.config$s.info])
+  elif owner.typ.callConv notin {ccClosure, ccDefault}:
+    localError(g.config, n.info, "illegal capture '$1' because '$2' has the calling convention: <$3>" %
+      [s.name.s, owner.name.s, CallingConvToStr[owner.typ.callConv]])
   incl(owner.typ.flags, tfCapturesEnv)
   owner.typ.callConv = ccClosure
 
@@ -285,12 +292,14 @@ type
     processed, capturedVars: IntSet
     ownerToType: Table[int, PType]
     somethingToDo: bool
+    graph: ModuleGraph
 
-proc initDetectionPass(fn: PSym): DetectionPass =
+proc initDetectionPass(g: ModuleGraph; fn: PSym): DetectionPass =
   result.processed = initIntSet()
   result.capturedVars = initIntSet()
   result.ownerToType = initTable[int, PType]()
   result.processed.incl(fn.id)
+  result.graph = g
 
 discard """
 proc outer =
@@ -307,7 +316,7 @@ proc getEnvTypeForOwner(c: var DetectionPass; owner: PSym;
   result = c.ownerToType.getOrDefault(owner.id)
   if result.isNil:
     result = newType(tyRef, owner)
-    let obj = createEnvObj(owner, info)
+    let obj = createEnvObj(c.graph, owner, info)
     rawAddSon(result, obj)
     c.ownerToType[owner.id] = result
 
@@ -316,13 +325,13 @@ proc createUpField(c: var DetectionPass; dest, dep: PSym; info: TLineInfo) =
   let obj = refObj.lastSon
   let fieldType = c.getEnvTypeForOwner(dep, info) #getHiddenParam(dep).typ
   if refObj == fieldType:
-    localError(dep.info, "internal error: invalid up reference computed")
+    localError(c.graph.config, dep.info, "internal error: invalid up reference computed")
 
-  let upIdent = getIdent(upName)
+  let upIdent = getIdent(c.graph.cache, upName)
   let upField = lookupInRecord(obj.n, upIdent)
   if upField != nil:
     if upField.typ != fieldType:
-      localError(dep.info, "internal error: up references do not agree")
+      localError(c.graph.config, dep.info, "internal error: up references do not agree")
   else:
     let result = newSym(skField, upIdent, obj.owner, obj.owner.info)
     result.typ = fieldType
@@ -359,19 +368,20 @@ proc addClosureParam(c: var DetectionPass; fn: PSym; info: TLineInfo) =
   let owner = if fn.kind == skIterator: fn else: fn.skipGenericOwner
   let t = c.getEnvTypeForOwner(owner, info)
   if cp == nil:
-    cp = newSym(skParam, getIdent(paramName), fn, fn.info)
+    cp = newSym(skParam, getIdent(c.graph.cache, paramName), fn, fn.info)
     incl(cp.flags, sfFromGeneric)
     cp.typ = t
     addHiddenParam(fn, cp)
   elif cp.typ != t and fn.kind != skIterator:
-    localError(fn.info, "internal error: inconsistent environment type")
+    localError(c.graph.config, fn.info, "internal error: inconsistent environment type")
   #echo "adding closure to ", fn.name.s
 
 proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
   case n.kind
   of nkSym:
     let s = n.sym
-    if s.kind in {skProc, skMethod, skConverter, skIterator} and s.typ != nil and s.typ.callConv == ccClosure:
+    if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator} and
+        s.typ != nil and s.typ.callConv == ccClosure:
       # this handles the case that the inner proc was declared as
       # .closure but does not actually capture anything:
       addClosureParam(c, s, n.info)
@@ -381,7 +391,8 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
     if innerProc:
       if s.isIterator: c.somethingToDo = true
       if not c.processed.containsOrIncl(s.id):
-        detectCapturedVars(s.getBody, s, c)
+        let body = transformBody(c.graph, s)
+        detectCapturedVars(body, s, c)
     let ow = s.skipGenericOwner
     if ow == owner:
       if owner.isIterator:
@@ -389,14 +400,15 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
         addClosureParam(c, owner, n.info)
         if interestingIterVar(s):
           if not c.capturedVars.containsOrIncl(s.id):
-            let obj = getHiddenParam(owner).typ.lastSon
+            let obj = getHiddenParam(c.graph, owner).typ.lastSon
             #let obj = c.getEnvTypeForOwner(s.owner).lastSon
-            addField(obj, s)
-      # but always return because the rest of the proc is only relevant when
-      # ow != owner:
-      return
+
+            if s.name.id == getIdent(c.graph.cache, ":state").id:
+              obj.n[0].sym.id = -s.id
+            else:
+              addField(obj, s, c.graph.cache)
     # direct or indirect dependency:
-    if (innerProc and s.typ.callConv == ccClosure) or interestingVar(s):
+    elif (innerProc and s.typ.callConv == ccClosure) or interestingVar(s):
       discard """
         proc outer() =
           var x: int
@@ -409,14 +421,14 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
       """
       # mark 'owner' as taking a closure:
       c.somethingToDo = true
-      markAsClosure(owner, n)
+      markAsClosure(c.graph, owner, n)
       addClosureParam(c, owner, n.info)
       #echo "capturing ", n.info
       # variable 's' is actually captured:
       if interestingVar(s) and not c.capturedVars.containsOrIncl(s.id):
         let obj = c.getEnvTypeForOwner(ow, n.info).lastSon
         #getHiddenParam(owner).typ.lastSon
-        addField(obj, s)
+        addField(obj, s, c.graph.cache)
       # create required upFields:
       var w = owner.skipGenericOwner
       if isInnerProc(w) or owner.isIterator:
@@ -434,18 +446,21 @@ proc detectCapturedVars(n: PNode; owner: PSym; c: var DetectionPass) =
           """
           let up = w.skipGenericOwner
           #echo "up for ", w.name.s, " up ", up.name.s
-          markAsClosure(w, n)
+          markAsClosure(c.graph, w, n)
           addClosureParam(c, w, n.info) # , ow
           createUpField(c, w, up, n.info)
           w = up
   of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit,
-     nkTemplateDef, nkTypeSection:
-    discard
-  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef:
+     nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef,
+     nkConverterDef, nkMacroDef, nkFuncDef:
     discard
   of nkLambdaKinds, nkIteratorDef:
     if n.typ != nil:
       detectCapturedVars(n[namePos], owner, c)
+  of nkReturnStmt:
+    if n[0].kind in {nkAsgn, nkFastAsgn}:
+      detectCapturedVars(n[0].sons[1], owner, c)
+    else: assert n[0].kind == nkEmpty
   else:
     for i in 0..<n.len:
       detectCapturedVars(n[i], owner, c)
@@ -454,16 +469,17 @@ type
   LiftingPass = object
     processed: IntSet
     envVars: Table[int, PNode]
+    inContainer: int
 
 proc initLiftingPass(fn: PSym): LiftingPass =
   result.processed = initIntSet()
   result.processed.incl(fn.id)
   result.envVars = initTable[int, PNode]()
 
-proc accessViaEnvParam(n: PNode; owner: PSym): PNode =
+proc accessViaEnvParam(g: ModuleGraph; n: PNode; owner: PSym): PNode =
   let s = n.sym
   # Type based expression construction for simplicity:
-  let envParam = getHiddenParam(owner)
+  let envParam = getHiddenParam(g, owner)
   if not envParam.isNil:
     var access = newSymNode(envParam)
     while true:
@@ -472,14 +488,14 @@ proc accessViaEnvParam(n: PNode; owner: PSym): PNode =
       let field = getFieldFromObj(obj, s)
       if field != nil:
         return rawIndirectAccess(access, field, n.info)
-      let upField = lookupInRecord(obj.n, getIdent(upName))
+      let upField = lookupInRecord(obj.n, getIdent(g.cache, upName))
       if upField == nil: break
       access = rawIndirectAccess(access, upField, n.info)
-  localError(n.info, "internal error: environment misses: " & s.name.s)
+  localError(g.config, n.info, "internal error: environment misses: " & s.name.s)
   result = n
 
-proc newEnvVar(owner: PSym; typ: PType): PNode =
-  var v = newSym(skVar, getIdent(envName), owner, owner.info)
+proc newEnvVar(cache: IdentCache; owner: PSym; typ: PType): PNode =
+  var v = newSym(skVar, getIdent(cache, envName), owner, owner.info)
   incl(v.flags, sfShadowed)
   v.typ = typ
   result = newSymNode(v)
@@ -494,22 +510,22 @@ proc newEnvVar(owner: PSym; typ: PType): PNode =
 proc setupEnvVar(owner: PSym; d: DetectionPass;
                  c: var LiftingPass): PNode =
   if owner.isIterator:
-    return getHiddenParam(owner).newSymNode
+    return getHiddenParam(d.graph, owner).newSymNode
   result = c.envvars.getOrDefault(owner.id)
   if result.isNil:
     let envVarType = d.ownerToType.getOrDefault(owner.id)
     if envVarType.isNil:
-      localError owner.info, "internal error: could not determine closure type"
-    result = newEnvVar(owner, envVarType)
+      localError d.graph.config, owner.info, "internal error: could not determine closure type"
+    result = newEnvVar(d.graph.cache, owner, envVarType)
     c.envVars[owner.id] = result
 
-proc getUpViaParam(owner: PSym): PNode =
-  let p = getHiddenParam(owner)
+proc getUpViaParam(g: ModuleGraph; owner: PSym): PNode =
+  let p = getHiddenParam(g, owner)
   result = p.newSymNode
   if owner.isIterator:
-    let upField = lookupInRecord(p.typ.lastSon.n, getIdent(upName))
+    let upField = lookupInRecord(p.typ.lastSon.n, getIdent(g.cache, upName))
     if upField == nil:
-      localError(owner.info, "could not find up reference for closure iter")
+      localError(g.config, owner.info, "could not find up reference for closure iter")
     else:
       result = rawIndirectAccess(result, upField, p.info)
 
@@ -519,7 +535,7 @@ proc rawClosureCreation(owner: PSym;
 
   var env: PNode
   if owner.isIterator:
-    env = getHiddenParam(owner).newSymNode
+    env = getHiddenParam(d.graph, owner).newSymNode
   else:
     env = setupEnvVar(owner, d, c)
     if env.kind == nkSym:
@@ -527,7 +543,7 @@ proc rawClosureCreation(owner: PSym;
       addVar(v, env)
       result.add(v)
     # add 'new' statement:
-    result.add(newCall(getSysSym"internalNew", env))
+    result.add(newCall(getSysSym(d.graph, env.info, "internalNew"), env))
     # add assignment statements for captured parameters:
     for i in 1..<owner.typ.n.len:
       let local = owner.typ.n[i].sym
@@ -536,9 +552,9 @@ proc rawClosureCreation(owner: PSym;
         # add ``env.param = param``
         result.add(newAsgnStmt(fieldAccess, newSymNode(local), env.info))
 
-  let upField = lookupInRecord(env.typ.lastSon.n, getIdent(upName))
+  let upField = lookupInRecord(env.typ.lastSon.n, getIdent(d.graph.cache, upName))
   if upField != nil:
-    let up = getUpViaParam(owner)
+    let up = getUpViaParam(d.graph, owner)
     if up != nil and upField.typ == up.typ:
       result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info),
                  up, env.info))
@@ -546,36 +562,36 @@ proc rawClosureCreation(owner: PSym;
     #  result.add(newAsgnStmt(rawIndirectAccess(env, upField, env.info),
     #             oldenv, env.info))
     else:
-      localError(env.info, "internal error: cannot create up reference")
+      localError(d.graph.config, env.info, "internal error: cannot create up reference")
 
 proc closureCreationForIter(iter: PNode;
                             d: DetectionPass; c: var LiftingPass): PNode =
   result = newNodeIT(nkStmtListExpr, iter.info, iter.sym.typ)
   let owner = iter.sym.skipGenericOwner
-  var v = newSym(skVar, getIdent(envName), owner, iter.info)
+  var v = newSym(skVar, getIdent(d.graph.cache, envName), owner, iter.info)
   incl(v.flags, sfShadowed)
-  v.typ = getHiddenParam(iter.sym).typ
+  v.typ = getHiddenParam(d.graph, iter.sym).typ
   var vnode: PNode
   if owner.isIterator:
-    let it = getHiddenParam(owner)
-    addUniqueField(it.typ.sons[0], v)
+    let it = getHiddenParam(d.graph, owner)
+    addUniqueField(it.typ.sons[0], v, d.graph.cache)
     vnode = indirectAccess(newSymNode(it), v, v.info)
   else:
     vnode = v.newSymNode
     var vs = newNodeI(nkVarSection, iter.info)
     addVar(vs, vnode)
     result.add(vs)
-  result.add(newCall(getSysSym"internalNew", vnode))
+  result.add(newCall(getSysSym(d.graph, iter.info, "internalNew"), vnode))
 
-  let upField = lookupInRecord(v.typ.lastSon.n, getIdent(upName))
+  let upField = lookupInRecord(v.typ.lastSon.n, getIdent(d.graph.cache, upName))
   if upField != nil:
     let u = setupEnvVar(owner, d, c)
     if u.typ == upField.typ:
       result.add(newAsgnStmt(rawIndirectAccess(vnode, upField, iter.info),
                  u, iter.info))
     else:
-      localError(iter.info, "internal error: cannot create up reference for iter")
-  result.add makeClosure(iter.sym, vnode, iter.info)
+      localError(d.graph.config, iter.info, "internal error: cannot create up reference for iter")
+  result.add makeClosure(d.graph, iter.sym, vnode, iter.info)
 
 proc accessViaEnvVar(n: PNode; owner: PSym; d: DetectionPass;
                      c: var LiftingPass): PNode =
@@ -585,110 +601,39 @@ proc accessViaEnvVar(n: PNode; owner: PSym; d: DetectionPass;
   if field != nil:
     result = rawIndirectAccess(access, field, n.info)
   else:
-    localError(n.info, "internal error: not part of closure object type")
+    localError(d.graph.config, n.info, "internal error: not part of closure object type")
     result = n
 
-proc getStateField(owner: PSym): PSym =
-  getHiddenParam(owner).typ.sons[0].n.sons[0].sym
+proc getStateField*(g: ModuleGraph; owner: PSym): PSym =
+  getHiddenParam(g, owner).typ.sons[0].n.sons[0].sym
 
 proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
                       c: var LiftingPass): PNode
 
-proc transformYield(n: PNode; owner: PSym; d: DetectionPass;
-                    c: var LiftingPass): PNode =
-  let state = getStateField(owner)
-  assert state != nil
-  assert state.typ != nil
-  assert state.typ.n != nil
-  inc state.typ.n.sons[1].intVal
-  let stateNo = state.typ.n.sons[1].intVal
-
-  var stateAsgnStmt = newNodeI(nkAsgn, n.info)
-  stateAsgnStmt.add(rawIndirectAccess(newSymNode(getEnvParam(owner)),
-                    state, n.info))
-  stateAsgnStmt.add(newIntTypeNode(nkIntLit, stateNo, getSysType(tyInt)))
-
-  var retStmt = newNodeI(nkReturnStmt, n.info)
-  if n.sons[0].kind != nkEmpty:
-    var a = newNodeI(nkAsgn, n.sons[0].info)
-    var retVal = liftCapturedVars(n.sons[0], owner, d, c)
-    addSon(a, newSymNode(getIterResult(owner)))
-    addSon(a, retVal)
-    retStmt.add(a)
-  else:
-    retStmt.add(emptyNode)
-
-  var stateLabelStmt = newNodeI(nkState, n.info)
-  stateLabelStmt.add(newIntTypeNode(nkIntLit, stateNo, getSysType(tyInt)))
-
-  result = newNodeI(nkStmtList, n.info)
-  result.add(stateAsgnStmt)
-  result.add(retStmt)
-  result.add(stateLabelStmt)
-
-proc transformReturn(n: PNode; owner: PSym; d: DetectionPass;
-                     c: var LiftingPass): PNode =
-  let state = getStateField(owner)
-  result = newNodeI(nkStmtList, n.info)
-  var stateAsgnStmt = newNodeI(nkAsgn, n.info)
-  stateAsgnStmt.add(rawIndirectAccess(newSymNode(getEnvParam(owner)),
-                    state, n.info))
-  stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt)))
-  result.add(stateAsgnStmt)
-  result.add(n)
-
-proc wrapIterBody(n: PNode; owner: PSym): PNode =
-  if not owner.isIterator: return n
-  when false:
-    # unfortunately control flow is still convoluted and we can end up
-    # multiple times here for the very same iterator. We shield against this
-    # with some rather primitive check for now:
-    if n.kind == nkStmtList and n.len > 0:
-      if n.sons[0].kind == nkGotoState: return n
-      if n.len > 1 and n[1].kind == nkStmtList and n[1].len > 0 and
-          n[1][0].kind == nkGotoState:
-        return n
-  let info = n.info
-  result = newNodeI(nkStmtList, info)
-  var gs = newNodeI(nkGotoState, info)
-  gs.add(rawIndirectAccess(newSymNode(owner.getHiddenParam), getStateField(owner), info))
-  result.add(gs)
-  var state0 = newNodeI(nkState, info)
-  state0.add(newIntNode(nkIntLit, 0))
-  result.add(state0)
-
-  result.add(n)
-
-  var stateAsgnStmt = newNodeI(nkAsgn, info)
-  stateAsgnStmt.add(rawIndirectAccess(newSymNode(owner.getHiddenParam),
-                    getStateField(owner), info))
-  stateAsgnStmt.add(newIntTypeNode(nkIntLit, -1, getSysType(tyInt)))
-  result.add(stateAsgnStmt)
-
 proc symToClosure(n: PNode; owner: PSym; d: DetectionPass;
                   c: var LiftingPass): PNode =
   let s = n.sym
   if s == owner:
     # recursive calls go through (lambda, hiddenParam):
-    let available = getHiddenParam(owner)
-    result = makeClosure(s, available.newSymNode, n.info)
+    let available = getHiddenParam(d.graph, owner)
+    result = makeClosure(d.graph, s, available.newSymNode, n.info)
   elif s.isIterator:
     result = closureCreationForIter(n, d, c)
   elif s.skipGenericOwner == owner:
     # direct dependency, so use the outer's env variable:
-    result = makeClosure(s, setupEnvVar(owner, d, c), n.info)
+    result = makeClosure(d.graph, s, setupEnvVar(owner, d, c), n.info)
   else:
-    let available = getHiddenParam(owner)
-    let wanted = getHiddenParam(s).typ
+    let available = getHiddenParam(d.graph, owner)
+    let wanted = getHiddenParam(d.graph, s).typ
     # ugh: call through some other inner proc;
     var access = newSymNode(available)
     while true:
       if access.typ == wanted:
-        return makeClosure(s, access, n.info)
+        return makeClosure(d.graph, s, access, n.info)
       let obj = access.typ.sons[0]
-      let upField = lookupInRecord(obj.n, getIdent(upName))
+      let upField = lookupInRecord(obj.n, getIdent(d.graph.cache, upName))
       if upField == nil:
-        localError(n.info, "internal error: no environment found")
+        localError(d.graph.config, n.info, "internal error: no environment found")
         return n
       access = rawIndirectAccess(access, upField, n.info)
 
@@ -702,24 +647,29 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
       if not c.processed.containsOrIncl(s.id):
         #if s.name.s == "temp":
         #  echo renderTree(s.getBody, {renderIds})
-        let body = wrapIterBody(liftCapturedVars(s.getBody, s, d, c), s)
+        let oldInContainer = c.inContainer
+        c.inContainer = 0
+        var body = transformBody(d.graph, s)
+        body = liftCapturedVars(body, s, d, c)
         if c.envvars.getOrDefault(s.id).isNil:
-          s.ast.sons[bodyPos] = body
+          s.transformedBody = body
         else:
-          s.ast.sons[bodyPos] = newTree(nkStmtList, rawClosureCreation(s, d, c), body)
+          s.transformedBody = newTree(nkStmtList, rawClosureCreation(s, d, c), body)
+        c.inContainer = oldInContainer
+
       if s.typ.callConv == ccClosure:
         result = symToClosure(n, owner, d, c)
+
     elif s.id in d.capturedVars:
       if s.owner != owner:
-        result = accessViaEnvParam(n, owner)
+        result = accessViaEnvParam(d.graph, n, owner)
       elif owner.isIterator and interestingIterVar(s):
-        result = accessViaEnvParam(n, owner)
+        result = accessViaEnvParam(d.graph, n, owner)
       else:
         result = accessViaEnvVar(n, owner, d, c)
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit,
-     nkTemplateDef, nkTypeSection:
-    discard
-  of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef:
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkComesFrom,
+     nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef, nkConverterDef,
+     nkMacroDef, nkFuncDef:
     discard
   of nkClosure:
     if n[1].kind == nkNilLit:
@@ -732,45 +682,76 @@ proc liftCapturedVars(n: PNode; owner: PSym; d: DetectionPass;
         n.sons[1] = x.sons[1]
   of nkLambdaKinds, nkIteratorDef:
     if n.typ != nil and n[namePos].kind == nkSym:
+      let oldInContainer = c.inContainer
+      c.inContainer = 0
       let m = newSymNode(n[namePos].sym)
       m.typ = n.typ
       result = liftCapturedVars(m, owner, d, c)
+      c.inContainer = oldInContainer
   of nkHiddenStdConv:
     if n.len == 2:
       n.sons[1] = liftCapturedVars(n[1], owner, d, c)
       if n[1].kind == nkClosure: result = n[1]
+  of nkReturnStmt:
+    if n[0].kind in {nkAsgn, nkFastAsgn}:
+      # we have a `result = result` expression produced by the closure
+      # transform, let's not touch the LHS in order to make the lifting pass
+      # correct when `result` is lifted
+      n[0].sons[1] = liftCapturedVars(n[0].sons[1], owner, d, c)
+    else: assert n[0].kind == nkEmpty
   else:
     if owner.isIterator:
-      if n.kind == nkYieldStmt:
-        return transformYield(n, owner, d, c)
-      elif n.kind == nkReturnStmt:
-        return transformReturn(n, owner, d, c)
-      elif nfLL in n.flags:
+      if nfLL in n.flags:
         # special case 'when nimVm' due to bug #3636:
         n.sons[1] = liftCapturedVars(n[1], owner, d, c)
         return
+
+    let inContainer = n.kind in {nkObjConstr, nkBracket}
+    if inContainer: inc c.inContainer
     for i in 0..<n.len:
       n.sons[i] = liftCapturedVars(n[i], owner, d, c)
+    if inContainer: dec c.inContainer
 
 # ------------------ old stuff -------------------------------------------
 
 proc semCaptureSym*(s, owner: PSym) =
+  discard """
+    proc outer() =
+      var x: int
+      proc inner() =
+        proc innerInner() =
+          echo x
+        innerInner()
+      inner()
+    # inner() takes a closure too!
+  """
+  proc propagateClosure(start, last: PSym) =
+    var o = start
+    while o != nil and o.kind != skModule:
+      if o == last: break
+      o.typ.callConv = ccClosure
+      o = o.skipGenericOwner
+
   if interestingVar(s) and s.kind != skResult:
     if owner.typ != nil and not isGenericRoutine(owner):
       # XXX: is this really safe?
       # if we capture a var from another generic routine,
       # it won't be consider captured.
       var o = owner.skipGenericOwner
-      while o.kind != skModule and o != nil:
+      while o != nil and o.kind != skModule:
         if s.owner == o:
-          owner.typ.callConv = ccClosure
-          #echo "computing .closure for ", owner.name.s, " ", owner.info, " because of ", s.name.s
+          if owner.typ.callConv in {ccClosure, ccDefault} or owner.kind == skIterator:
+            owner.typ.callConv = ccClosure
+            propagateClosure(owner.skipGenericOwner, s.owner)
+          else:
+            discard "do not produce an error here, but later"
+          #echo "computing .closure for ", owner.name.s, " because of ", s.name.s
         o = o.skipGenericOwner
     # since the analysis is not entirely correct, we don't set 'tfCapturesEnv'
     # here
 
-proc liftIterToProc*(fn: PSym; body: PNode; ptrType: PType): PNode =
-  var d = initDetectionPass(fn)
+proc liftIterToProc*(g: ModuleGraph; fn: PSym; body: PNode; ptrType: PType): PNode =
+  var d = initDetectionPass(g, fn)
   var c = initLiftingPass(fn)
   # pretend 'fn' is a closure iterator for the analysis:
   let oldKind = fn.kind
@@ -779,11 +760,11 @@ proc liftIterToProc*(fn: PSym; body: PNode; ptrType: PType): PNode =
   fn.typ.callConv = ccClosure
   d.ownerToType[fn.id] = ptrType
   detectCapturedVars(body, fn, d)
-  result = wrapIterBody(liftCapturedVars(body, fn, d, c), fn)
+  result = liftCapturedVars(body, fn, d, c)
   fn.kind = oldKind
   fn.typ.callConv = oldCC
 
-proc liftLambdas*(fn: PSym, body: PNode; tooEarly: var bool): PNode =
+proc liftLambdas*(g: ModuleGraph; fn: PSym, body: PNode; tooEarly: var bool): PNode =
   # XXX gCmd == cmdCompileToJS does not suffice! The compiletime stuff needs
   # the transformation even when compiling to JS ...
 
@@ -791,23 +772,24 @@ proc liftLambdas*(fn: PSym, body: PNode; tooEarly: var bool): PNode =
   let isCompileTime = sfCompileTime in fn.flags or fn.kind == skMacro
 
   if body.kind == nkEmpty or (
-      gCmd in {cmdCompileToPHP, cmdCompileToJS} and not isCompileTime) or
+      g.config.cmd == cmdCompileToJS and not isCompileTime) or
       fn.skipGenericOwner.kind != skModule:
+
     # ignore forward declaration:
     result = body
     tooEarly = true
   else:
-    var d = initDetectionPass(fn)
+    var d = initDetectionPass(g, fn)
     detectCapturedVars(body, fn, d)
     if not d.somethingToDo and fn.isIterator:
       addClosureParam(d, fn, body.info)
       d.somethingToDo = true
     if d.somethingToDo:
       var c = initLiftingPass(fn)
-      var newBody = liftCapturedVars(body, fn, d, c)
+      result = liftCapturedVars(body, fn, d, c)
+      # echo renderTree(result, {renderIds})
       if c.envvars.getOrDefault(fn.id) != nil:
-        newBody = newTree(nkStmtList, rawClosureCreation(fn, d, c), newBody)
-      result = wrapIterBody(newBody, fn)
+        result = newTree(nkStmtList, rawClosureCreation(fn, d, c), result)
     else:
       result = body
     #if fn.name.s == "get2":
@@ -815,15 +797,12 @@ proc liftLambdas*(fn: PSym, body: PNode; tooEarly: var bool): PNode =
     #  echo renderTree(result, {renderIds})
 
 proc liftLambdasForTopLevel*(module: PSym, body: PNode): PNode =
-  if body.kind == nkEmpty or gCmd == cmdCompileToJS:
-    result = body
-  else:
-    # XXX implement it properly
-    result = body
+  # XXX implement it properly
+  result = body
 
 # ------------------- iterator transformation --------------------------------
 
-proc liftForLoop*(body: PNode; owner: PSym): PNode =
+proc liftForLoop*(g: ModuleGraph; body: PNode; owner: PSym): PNode =
   # problem ahead: the iterator could be invoked indirectly, but then
   # we don't know what environment to create here:
   #
@@ -848,13 +827,14 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode =
       cl = createClosure()
       while true:
         let i = foo(cl)
-        nkBreakState(cl.state)
+        if (nkBreakState(cl.state)):
+          break
         ...
     """
-  if liftingHarmful(owner): return body
+  if liftingHarmful(g.config, owner): return body
   var L = body.len
   if not (body.kind == nkForStmt and body[L-2].kind in nkCallKinds):
-    localError(body.info, "ignored invalid for loop")
+    localError(g.config, body.info, "ignored invalid for loop")
     return body
   var call = body[L-2]
 
@@ -867,7 +847,7 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode =
     # createClosure()
     let iter = op.sym
 
-    let hp = getHiddenParam(iter)
+    let hp = getHiddenParam(g, iter)
     env = newSym(skLet, iter.name, owner, body.info)
     env.typ = hp.typ
     env.flags = hp.flags
@@ -876,7 +856,7 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode =
     addVar(v, newSymNode(env))
     result.add(v)
     # add 'new' statement:
-    result.add(newCall(getSysSym"internalNew", env.newSymNode))
+    result.add(newCall(getSysSym(g, env.info, "internalNew"), env.newSymNode))
   elif op.kind == nkStmtListExpr:
     let closure = op.lastSon
     if closure.kind == nkClosure:
@@ -886,7 +866,7 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode =
 
   var loopBody = newNodeI(nkStmtList, body.info, 3)
   var whileLoop = newNodeI(nkWhileStmt, body.info, 2)
-  whileLoop.sons[0] = newIntTypeNode(nkIntLit, 1, getSysType(tyBool))
+  whileLoop.sons[0] = newIntTypeNode(nkIntLit, 1, getSysType(g, body.info, tyBool))
   whileLoop.sons[1] = loopBody
   result.add whileLoop
 
@@ -899,14 +879,25 @@ proc liftForLoop*(body: PNode; owner: PSym): PNode =
       body[i].sym.kind = skLet
     addSon(vpart, body[i])
 
-  addSon(vpart, ast.emptyNode) # no explicit type
+  addSon(vpart, newNodeI(nkEmpty, body.info)) # no explicit type
   if not env.isNil:
-    call.sons[0] = makeClosure(call.sons[0].sym, env.newSymNode, body.info)
+    call.sons[0] = makeClosure(g, call.sons[0].sym, env.newSymNode, body.info)
   addSon(vpart, call)
   addSon(v2, vpart)
 
   loopBody.sons[0] = v2
   var bs = newNodeI(nkBreakState, body.info)
   bs.addSon(call.sons[0])
-  loopBody.sons[1] = bs
+
+  let ibs = newNodeI(nkIfStmt, body.info)
+  let elifBranch = newNodeI(nkElifBranch, body.info)
+  elifBranch.add(bs)
+
+  let br = newNodeI(nkBreakStmt, body.info)
+  br.add(g.emptyNode)
+
+  elifBranch.add(br)
+  ibs.add(elifBranch)
+
+  loopBody.sons[1] = ibs
   loopBody.sons[2] = body[L-1]
diff --git a/compiler/layouter.nim b/compiler/layouter.nim
new file mode 100644
index 000000000..8605ade45
--- /dev/null
+++ b/compiler/layouter.nim
@@ -0,0 +1,305 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Layouter for nimpretty.
+
+import idents, lexer, lineinfos, llstream, options, msgs, strutils,
+  pathutils
+from os import changeFileExt
+
+const
+  MaxLineLen = 80
+  LineCommentColumn = 30
+
+type
+  SplitKind = enum
+    splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary
+
+  SemicolonKind = enum
+    detectSemicolonKind, useSemicolon, dontTouch
+
+  Emitter* = object
+    config: ConfigRef
+    fid: FileIndex
+    lastTok: TTokType
+    inquote, lastTokWasTerse: bool
+    semicolons: SemicolonKind
+    col, lastLineNumber, lineSpan, indentLevel, indWidth*: int
+    keepIndents*: int
+    doIndentMore*: int
+    content: string
+    indentStack: seq[int]
+    fixedUntil: int # marks where we must not go in the content
+    altSplitPos: array[SplitKind, int] # alternative split positions
+
+proc openEmitter*(em: var Emitter, cache: IdentCache;
+                  config: ConfigRef, fileIdx: FileIndex) =
+  let fullPath = Absolutefile config.toFullPath(fileIdx)
+  if em.indWidth == 0:
+    em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead),
+                                cache, config)
+    if em.indWidth == 0: em.indWidth = 2
+  em.config = config
+  em.fid = fileIdx
+  em.lastTok = tkInvalid
+  em.inquote = false
+  em.col = 0
+  em.content = newStringOfCap(16_000)
+  em.indentStack = newSeqOfCap[int](30)
+  em.indentStack.add 0
+  em.lastLineNumber = 1
+
+proc closeEmitter*(em: var Emitter) =
+  if fileExists(em.config.outFile) and readFile(em.config.outFile.string) == em.content:
+    discard "do nothing, see #9499"
+    return
+  var f = llStreamOpen(em.config.outFile, fmWrite)
+  if f == nil:
+    rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile.string)
+    return
+  f.llStreamWrite em.content
+  llStreamClose(f)
+
+proc countNewlines(s: string): int =
+  result = 0
+  for i in 0..<s.len:
+    if s[i] == '\L': inc result
+
+proc calcCol(em: var Emitter; s: string) =
+  var i = s.len-1
+  em.col = 0
+  while i >= 0 and s[i] != '\L':
+    dec i
+    inc em.col
+
+template wr(x) =
+  em.content.add x
+  inc em.col, x.len
+
+template goodCol(col): bool = col in 40..MaxLineLen
+
+const
+  openPars = {tkParLe, tkParDotLe,
+              tkBracketLe, tkBracketLeColon, tkCurlyDotLe,
+              tkCurlyLe}
+  splitters = openPars + {tkComma, tkSemicolon}
+  oprSet = {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
+            tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor}
+
+template rememberSplit(kind) =
+  if goodCol(em.col):
+    em.altSplitPos[kind] = em.content.len
+
+template moreIndent(em): int =
+  (if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth)
+
+proc softLinebreak(em: var Emitter, lit: string) =
+  # XXX Use an algorithm that is outlined here:
+  # https://llvm.org/devmtg/2013-04/jasper-slides.pdf
+  # +2 because we blindly assume a comma or ' &' might follow
+  if not em.inquote and em.col+lit.len+2 >= MaxLineLen:
+    if em.lastTok in splitters:
+      while em.content.len > 0 and em.content[em.content.high] == ' ':
+        setLen(em.content, em.content.len-1)
+      wr("\L")
+      em.col = 0
+      for i in 1..em.indentLevel+moreIndent(em): wr(" ")
+    else:
+      # search backwards for a good split position:
+      for a in mitems(em.altSplitPos):
+        if a > em.fixedUntil:
+          var spaces = 0
+          while a+spaces < em.content.len and em.content[a+spaces] == ' ':
+            inc spaces
+          if spaces > 0: delete(em.content, a, a+spaces-1)
+          em.col = em.content.len - a
+          let ws = "\L" & repeat(' ', em.indentLevel+moreIndent(em))
+          em.content.insert(ws, a)
+          a = -1
+          break
+
+proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
+
+  template endsInWhite(em): bool =
+    em.content.len == 0 or em.content[em.content.high] in {' ', '\L'}
+  template endsInAlpha(em): bool =
+    em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'}
+
+  proc emitComment(em: var Emitter; tok: TToken) =
+    let lit = strip fileSection(em.config, em.fid, tok.commentOffsetA, tok.commentOffsetB)
+    em.lineSpan = countNewlines(lit)
+    if em.lineSpan > 0: calcCol(em, lit)
+    if not endsInWhite(em):
+      wr(" ")
+      if em.lineSpan == 0 and max(em.col, LineCommentColumn) + lit.len <= MaxLineLen:
+        for i in 1 .. LineCommentColumn - em.col: wr(" ")
+    wr lit
+
+  if tok.tokType == tkComment and tok.literal.startsWith("#!nimpretty"):
+    case tok.literal
+    of "#!nimpretty off":
+      inc em.keepIndents
+      wr("\L")
+      em.lastLineNumber = tok.line + 1
+    of "#!nimpretty on":
+      dec em.keepIndents
+      em.lastLineNumber = tok.line
+    wr("\L")
+    #for i in 1 .. tok.indent: wr " "
+    wr tok.literal
+    em.col = 0
+    em.lineSpan = 0
+    return
+
+  var preventComment = false
+  if tok.tokType == tkComment and tok.line == em.lastLineNumber and tok.indent >= 0:
+    # we have an inline comment so handle it before the indentation token:
+    emitComment(em, tok)
+    preventComment = true
+    em.fixedUntil = em.content.high
+
+  elif tok.indent >= 0:
+    if em.lastTok in (splitters + oprSet) or em.keepIndents > 0:
+      em.indentLevel = tok.indent
+    else:
+      if tok.indent > em.indentStack[^1]:
+        em.indentStack.add tok.indent
+      else:
+        # dedent?
+        while em.indentStack.len > 1 and em.indentStack[^1] > tok.indent:
+          discard em.indentStack.pop()
+      em.indentLevel = em.indentStack.high * em.indWidth
+    #[ we only correct the indentation if it is not in an expression context,
+       so that code like
+
+        const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe,
+                          tkBracketLe, tkBracketLeColon, tkCurlyDotLe,
+                          tkCurlyLe}
+
+       is not touched.
+    ]#
+    # remove trailing whitespace:
+    while em.content.len > 0 and em.content[em.content.high] == ' ':
+      setLen(em.content, em.content.len-1)
+    wr("\L")
+    for i in 2..tok.line - em.lastLineNumber: wr("\L")
+    em.col = 0
+    for i in 1..em.indentLevel:
+      wr(" ")
+    em.fixedUntil = em.content.high
+
+  var lastTokWasTerse = false
+  case tok.tokType
+  of tokKeywordLow..tokKeywordHigh:
+    if endsInAlpha(em):
+      wr(" ")
+    elif not em.inquote and not endsInWhite(em) and
+        em.lastTok notin openPars and not em.lastTokWasTerse:
+      #and tok.tokType in oprSet
+      wr(" ")
+
+    if not em.inquote:
+      wr(TokTypeToStr[tok.tokType])
+
+      case tok.tokType
+      of tkAnd: rememberSplit(splitAnd)
+      of tkOr: rememberSplit(splitOr)
+      of tkIn, tkNotin:
+        rememberSplit(splitIn)
+        wr(" ")
+      else: discard
+    else:
+      # keywords in backticks are not normalized:
+      wr(tok.ident.s)
+
+  of tkColon:
+    wr(TokTypeToStr[tok.tokType])
+    wr(" ")
+  of tkSemicolon, tkComma:
+    wr(TokTypeToStr[tok.tokType])
+    rememberSplit(splitComma)
+    wr(" ")
+  of tkParDotLe, tkParLe, tkBracketDotLe, tkBracketLe,
+     tkCurlyLe, tkCurlyDotLe, tkBracketLeColon:
+    if tok.strongSpaceA > 0 and not em.endsInWhite:
+      wr(" ")
+    wr(TokTypeToStr[tok.tokType])
+    rememberSplit(splitParLe)
+  of tkParRi,
+     tkBracketRi, tkCurlyRi,
+     tkBracketDotRi,
+     tkCurlyDotRi,
+     tkParDotRi,
+     tkColonColon:
+    wr(TokTypeToStr[tok.tokType])
+  of tkDot:
+    lastTokWasTerse = true
+    wr(TokTypeToStr[tok.tokType])
+  of tkEquals:
+    if not em.inquote and not em.endsInWhite: wr(" ")
+    wr(TokTypeToStr[tok.tokType])
+    if not em.inquote: wr(" ")
+  of tkOpr, tkDotDot:
+    if (tok.strongSpaceA == 0 and tok.strongSpaceB == 0) or em.inquote:
+      # bug #9504: remember to not spacify a keyword:
+      lastTokWasTerse = true
+      # if not surrounded by whitespace, don't produce any whitespace either:
+      wr(tok.ident.s)
+    else:
+      if not em.endsInWhite: wr(" ")
+      wr(tok.ident.s)
+      template isUnary(tok): bool =
+        tok.strongSpaceB == 0 and tok.strongSpaceA > 0
+
+      if not isUnary(tok):
+        rememberSplit(splitBinary)
+        wr(" ")
+  of tkAccent:
+    if not em.inquote and endsInAlpha(em): wr(" ")
+    wr(TokTypeToStr[tok.tokType])
+    em.inquote = not em.inquote
+  of tkComment:
+    if not preventComment:
+      emitComment(em, tok)
+  of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit:
+    let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB)
+    softLinebreak(em, lit)
+    if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wr(" ")
+    em.lineSpan = countNewlines(lit)
+    if em.lineSpan > 0: calcCol(em, lit)
+    wr lit
+  of tkEof: discard
+  else:
+    let lit = if tok.ident != nil: tok.ident.s else: tok.literal
+    softLinebreak(em, lit)
+    if endsInAlpha(em): wr(" ")
+    wr lit
+
+  em.lastTok = tok.tokType
+  em.lastTokWasTerse = lastTokWasTerse
+  em.lastLineNumber = tok.line + em.lineSpan
+  em.lineSpan = 0
+
+proc starWasExportMarker*(em: var Emitter) =
+  if em.content.endsWith(" * "):
+    setLen(em.content, em.content.len-3)
+    em.content.add("*")
+    dec em.col, 2
+
+proc commaWasSemicolon*(em: var Emitter) =
+  if em.semicolons == detectSemicolonKind:
+    em.semicolons = if em.content.endsWith(", "): dontTouch else: useSemicolon
+  if em.semicolons == useSemicolon and em.content.endsWith(", "):
+    setLen(em.content, em.content.len-2)
+    em.content.add("; ")
+
+proc curlyRiWasPragma*(em: var Emitter) =
+  if em.content.endsWith("}"):
+    setLen(em.content, em.content.len-1)
+    em.content.add(".}")
diff --git a/compiler/lexer.nim b/compiler/lexer.nim
index 2bb228f41..635e6f08d 100644
--- a/compiler/lexer.nim
+++ b/compiler/lexer.nim
@@ -17,7 +17,7 @@
 
 import
   hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream,
-  wordrecg, etcpriv
+  wordrecg, lineinfos, pathutils
 
 const
   MaxLineLength* = 80         # lines longer than this lead to a warning
@@ -25,7 +25,7 @@ const
   SymChars*: set[char] = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF'}
   SymStartChars*: set[char] = {'a'..'z', 'A'..'Z', '\x80'..'\xFF'}
   OpChars*: set[char] = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '.',
-    '|', '=', '%', '&', '$', '@', '~', ':', '\x80'..'\xFF'}
+    '|', '=', '%', '&', '$', '@', '~', ':'}
 
 # don't forget to update the 'highlite' module if these charsets should change
 
@@ -33,13 +33,13 @@ type
   TTokType* = enum
     tkInvalid, tkEof,         # order is important here!
     tkSymbol, # keywords:
-    tkAddr, tkAnd, tkAs, tkAsm, tkAtomic,
+    tkAddr, tkAnd, tkAs, tkAsm,
     tkBind, tkBlock, tkBreak, tkCase, tkCast,
     tkConcept, tkConst, tkContinue, tkConverter,
     tkDefer, tkDiscard, tkDistinct, tkDiv, tkDo,
     tkElif, tkElse, tkEnd, tkEnum, tkExcept, tkExport,
     tkFinally, tkFor, tkFrom, tkFunc,
-    tkGeneric, tkIf, tkImport, tkIn, tkInclude, tkInterface,
+    tkIf, tkImport, tkIn, tkInclude, tkInterface,
     tkIs, tkIsnot, tkIterator,
     tkLet,
     tkMacro, tkMethod, tkMixin, tkMod, tkNil, tkNot, tkNotin,
@@ -48,7 +48,7 @@ type
     tkShl, tkShr, tkStatic,
     tkTemplate,
     tkTry, tkTuple, tkType, tkUsing,
-    tkVar, tkWhen, tkWhile, tkWith, tkWithout, tkXor,
+    tkVar, tkWhen, tkWhile, tkXor,
     tkYield, # end of keywords
     tkIntLit, tkInt8Lit, tkInt16Lit, tkInt32Lit, tkInt64Lit,
     tkUIntLit, tkUInt8Lit, tkUInt16Lit, tkUInt32Lit, tkUInt64Lit,
@@ -60,7 +60,7 @@ type
     tkCurlyDotLe, tkCurlyDotRi, # {.  and  .}
     tkParDotLe, tkParDotRi,   # (. and .)
     tkComma, tkSemiColon,
-    tkColon, tkColonColon, tkEquals, tkDot, tkDotDot,
+    tkColon, tkColonColon, tkEquals, tkDot, tkDotDot, tkBracketLeColon,
     tkOpr, tkComment, tkAccent,
     tkSpaces, tkInfixOpr, tkPrefixOpr, tkPostfixOpr
 
@@ -75,12 +75,12 @@ const
   tokKeywordHigh* = pred(tkIntLit)
   TokTypeToStr*: array[TTokType, string] = ["tkInvalid", "[EOF]",
     "tkSymbol",
-    "addr", "and", "as", "asm", "atomic",
+    "addr", "and", "as", "asm",
     "bind", "block", "break", "case", "cast",
     "concept", "const", "continue", "converter",
     "defer", "discard", "distinct", "div", "do",
     "elif", "else", "end", "enum", "except", "export",
-    "finally", "for", "from", "func", "generic", "if",
+    "finally", "for", "from", "func", "if",
     "import", "in", "include", "interface", "is", "isnot", "iterator",
     "let",
     "macro", "method", "mixin", "mod",
@@ -89,7 +89,7 @@ const
     "shl", "shr", "static",
     "template",
     "try", "tuple", "type", "using",
-    "var", "when", "while", "with", "without", "xor",
+    "var", "when", "while", "xor",
     "yield",
     "tkIntLit", "tkInt8Lit", "tkInt16Lit", "tkInt32Lit", "tkInt64Lit",
     "tkUIntLit", "tkUInt8Lit", "tkUInt16Lit", "tkUInt32Lit", "tkUInt64Lit",
@@ -98,7 +98,7 @@ const
     "tkTripleStrLit", "tkGStrLit", "tkGTripleStrLit", "tkCharLit", "(",
     ")", "[", "]", "{", "}", "[.", ".]", "{.", ".}", "(.", ".)",
     ",", ";",
-    ":", "::", "=", ".", "..",
+    ":", "::", "=", ".", "..", "[:",
     "tkOpr", "tkComment", "`",
     "tkSpaces", "tkInfixOpr",
     "tkPrefixOpr", "tkPostfixOpr"]
@@ -126,10 +126,14 @@ type
     literal*: string          # the parsed (string) literal; and
                               # documentation comments are here too
     line*, col*: int
+    when defined(nimpretty):
+      offsetA*, offsetB*: int   # used for pretty printing so that literals
+                                # like 0b01 or  r"\L" are unaffected
+      commentOffsetA*, commentOffsetB*: int
 
-  TErrorHandler* = proc (info: TLineInfo; msg: TMsgKind; arg: string)
+  TErrorHandler* = proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string)
   TLexer* = object of TBaseLexer
-    fileIdx*: int32
+    fileIdx*: FileIndex
     indentAhead*: int         # if > 0 an indendation has already been read
                               # this is needed because scanning comments
                               # needs so much look-ahead
@@ -140,11 +144,19 @@ type
     cache*: IdentCache
     when defined(nimsuggest):
       previousToken: TLineInfo
+    config*: ConfigRef
 
-var gLinesCompiled*: int  # all lines that have been compiled
+when defined(nimpretty):
+  var
+    gIndentationWidth*: int
 
 proc getLineInfo*(L: TLexer, tok: TToken): TLineInfo {.inline.} =
-  newLineInfo(L.fileIdx, tok.line, tok.col)
+  result = newLineInfo(L.fileIdx, tok.line, tok.col)
+  when defined(nimpretty):
+    result.offsetA = tok.offsetA
+    result.offsetB = tok.offsetB
+    result.commentOffsetA = tok.commentOffsetA
+    result.commentOffsetB = tok.commentOffsetB
 
 proc isKeyword*(kind: TTokType): bool =
   result = (kind >= tokKeywordLow) and (kind <= tokKeywordHigh)
@@ -152,15 +164,12 @@ proc isKeyword*(kind: TTokType): bool =
 template ones(n): untyped = ((1 shl n)-1) # for utf-8 conversion
 
 proc isNimIdentifier*(s: string): bool =
-  if s[0] in SymStartChars:
+  let sLen = s.len
+  if sLen > 0 and s[0] in SymStartChars:
     var i = 1
-    var sLen = s.len
     while i < sLen:
-      if s[i] == '_':
-        inc(i)
-      elif isMagicIdentSeparatorRune(cstring s, i):
-        inc(i, magicIdentSeparatorRuneByteWidth)
-      if s[i] notin SymChars: return
+      if s[i] == '_': inc(i)
+      if i < sLen and s[i] notin SymChars: return
       inc(i)
     result = true
 
@@ -181,8 +190,8 @@ proc prettyTok*(tok: TToken): string =
   if isKeyword(tok.tokType): result = "keyword " & tok.ident.s
   else: result = tokToStr(tok)
 
-proc printTok*(tok: TToken) =
-  msgWriteln($tok.line & ":" & $tok.col & "\t" &
+proc printTok*(conf: ConfigRef; tok: TToken) =
+  msgWriteln(conf, $tok.line & ":" & $tok.col & "\t" &
       TokTypeToStr[tok.tokType] & " " & tokToStr(tok))
 
 proc initToken*(L: var TToken) =
@@ -194,6 +203,9 @@ proc initToken*(L: var TToken) =
   L.fNumber = 0.0
   L.base = base10
   L.ident = nil
+  when defined(nimpretty):
+    L.commentOffsetA = 0
+    L.commentOffsetB = 0
 
 proc fillToken(L: var TToken) =
   L.tokType = tkInvalid
@@ -204,24 +216,29 @@ proc fillToken(L: var TToken) =
   L.fNumber = 0.0
   L.base = base10
   L.ident = nil
+  when defined(nimpretty):
+    L.commentOffsetA = 0
+    L.commentOffsetB = 0
 
-proc openLexer*(lex: var TLexer, fileIdx: int32, inputstream: PLLStream;
-                 cache: IdentCache) =
+proc openLexer*(lex: var TLexer, fileIdx: FileIndex, inputstream: PLLStream;
+                 cache: IdentCache; config: ConfigRef) =
   openBaseLexer(lex, inputstream)
   lex.fileIdx = fileidx
-  lex.indentAhead = - 1
+  lex.indentAhead = -1
   lex.currLineIndent = 0
   inc(lex.lineNumber, inputstream.lineOffset)
   lex.cache = cache
   when defined(nimsuggest):
     lex.previousToken.fileIndex = fileIdx
+  lex.config = config
 
-proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream;
-                cache: IdentCache) =
-  openLexer(lex, filename.fileInfoIdx, inputstream, cache)
+proc openLexer*(lex: var TLexer, filename: AbsoluteFile, inputstream: PLLStream;
+                cache: IdentCache; config: ConfigRef) =
+  openLexer(lex, fileInfoIdx(config, filename), inputstream, cache, config)
 
 proc closeLexer*(lex: var TLexer) =
-  inc(gLinesCompiled, lex.lineNumber)
+  if lex.config != nil:
+    inc(lex.config.linesCompiled, lex.lineNumber)
   closeBaseLexer(lex)
 
 proc getLineInfo(L: TLexer): TLineInfo =
@@ -229,9 +246,9 @@ proc getLineInfo(L: TLexer): TLineInfo =
 
 proc dispMessage(L: TLexer; info: TLineInfo; msg: TMsgKind; arg: string) =
   if L.errorHandler.isNil:
-    msgs.message(info, msg, arg)
+    msgs.message(L.config, info, msg, arg)
   else:
-    L.errorHandler(info, msg, arg)
+    L.errorHandler(L.config, info, msg, arg)
 
 proc lexMessage*(L: TLexer, msg: TMsgKind, arg = "") =
   L.dispMessage(getLineInfo(L), msg, arg)
@@ -247,51 +264,59 @@ proc lexMessagePos(L: var TLexer, msg: TMsgKind, pos: int, arg = "") =
 proc matchTwoChars(L: TLexer, first: char, second: set[char]): bool =
   result = (L.buf[L.bufpos] == first) and (L.buf[L.bufpos + 1] in second)
 
-template tokenBegin(pos) {.dirty.} =
+template tokenBegin(tok, pos) {.dirty.} =
   when defined(nimsuggest):
     var colA = getColNumber(L, pos)
+  when defined(nimpretty):
+    tok.offsetA = L.offsetBase + pos
 
-template tokenEnd(pos) {.dirty.} =
+template tokenEnd(tok, pos) {.dirty.} =
   when defined(nimsuggest):
     let colB = getColNumber(L, pos)+1
-    if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and
-        L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}:
+    if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
+        L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
       L.cursor = CursorPosition.InToken
-      gTrackPos.col = colA.int16
+      L.config.m.trackPos.col = colA.int16
     colA = 0
+  when defined(nimpretty):
+    tok.offsetB = L.offsetBase + pos
 
-template tokenEndIgnore(pos) =
+template tokenEndIgnore(tok, pos) =
   when defined(nimsuggest):
     let colB = getColNumber(L, pos)
-    if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and
-        L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}:
-      gTrackPos.fileIndex = trackPosInvalidFileIdx
-      gTrackPos.line = -1
+    if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
+        L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
+      L.config.m.trackPos.fileIndex = trackPosInvalidFileIdx
+      L.config.m.trackPos.line = 0'u16
     colA = 0
+  when defined(nimpretty):
+    tok.offsetB = L.offsetBase + pos
 
-template tokenEndPrevious(pos) =
+template tokenEndPrevious(tok, pos) =
   when defined(nimsuggest):
     # when we detect the cursor in whitespace, we attach the track position
     # to the token that came before that, but only if we haven't detected
     # the cursor in a string literal or comment:
     let colB = getColNumber(L, pos)
-    if L.fileIdx == gTrackPos.fileIndex and gTrackPos.col in colA..colB and
-        L.lineNumber == gTrackPos.line and gIdeCmd in {ideSug, ideCon}:
+    if L.fileIdx == L.config.m.trackPos.fileIndex and L.config.m.trackPos.col in colA..colB and
+        L.lineNumber == L.config.m.trackPos.line.int and L.config.ideCmd in {ideSug, ideCon}:
       L.cursor = CursorPosition.BeforeToken
-      gTrackPos = L.previousToken
-      gTrackPosAttached = true
+      L.config.m.trackPos = L.previousToken
+      L.config.m.trackPosAttached = true
     colA = 0
+  when defined(nimpretty):
+    tok.offsetB = L.offsetBase + pos
 
 {.push overflowChecks: off.}
 # We need to parse the largest uint literal without overflow checks
 proc unsafeParseUInt(s: string, b: var BiggestInt, start = 0): int =
   var i = start
-  if s[i] in {'0'..'9'}:
+  if i < s.len and s[i] in {'0'..'9'}:
     b = 0
-    while s[i] in {'0'..'9'}:
+    while i < s.len and s[i] in {'0'..'9'}:
       b = b * 10 + (ord(s[i]) - ord('0'))
       inc(i)
-      while s[i] == '_': inc(i) # underscores are allowed and ignored
+      while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored
     result = i - start
 {.pop.} # overflowChecks
 
@@ -305,18 +330,22 @@ template eatChar(L: var TLexer, t: var TToken) =
   inc(L.bufpos)
 
 proc getNumber(L: var TLexer, result: var TToken) =
-  proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]) =
+  proc matchUnderscoreChars(L: var TLexer, tok: var TToken, chars: set[char]): Natural =
     var pos = L.bufpos              # use registers for pos, buf
     var buf = L.buf
+    result = 0
     while true:
       if buf[pos] in chars:
         add(tok.literal, buf[pos])
         inc(pos)
+        inc(result)
       else:
         break
       if buf[pos] == '_':
         if buf[pos+1] notin chars:
-          lexMessage(L, errInvalidToken, "_")
+          lexMessage(L, errGenerated,
+            "only single underscores may occur in a token and token may not " &
+            "end with an underscore: e.g. '1__1' and '1_' are invalid")
           break
         add(tok.literal, '_')
         inc(pos)
@@ -330,9 +359,8 @@ proc getNumber(L: var TLexer, result: var TToken) =
       inc(pos)
     L.bufpos = pos
 
-  proc lexMessageLitNum(L: var TLexer, msg: TMsgKind, startpos: int) =
+  proc lexMessageLitNum(L: var TLexer, msg: string, startpos: int, msgKind = errGenerated) =
     # Used to get slightly human friendlier err messages.
-    # Note: the erroneous 'O' char in the character set is intentional
     const literalishChars = {'A'..'F', 'a'..'f', '0'..'9', 'X', 'x', 'o', 'O',
       'c', 'C', 'b', 'B', '_', '.', '\'', 'd', 'i', 'u'}
     var msgPos = L.bufpos
@@ -351,52 +379,66 @@ proc getNumber(L: var TLexer, result: var TToken) =
       add(t.literal, L.buf[L.bufpos])
       matchChars(L, t, {'0'..'9'})
     L.bufpos = msgPos
-    lexMessage(L, msg, t.literal)
+    lexMessage(L, msgKind, msg % t.literal)
 
   var
     startpos, endpos: int
     xi: BiggestInt
     isBase10 = true
+    numDigits = 0
   const
-    baseCodeChars = {'X', 'x', 'o', 'c', 'C', 'b', 'B'}
+    # 'c', 'C' is deprecated
+    baseCodeChars = {'X', 'x', 'o', 'b', 'B', 'c', 'C'}
     literalishChars = baseCodeChars + {'A'..'F', 'a'..'f', '0'..'9', '_', '\''}
     floatTypes = {tkFloatLit, tkFloat32Lit, tkFloat64Lit, tkFloat128Lit}
   result.tokType = tkIntLit   # int literal until we know better
   result.literal = ""
   result.base = base10
   startpos = L.bufpos
-  tokenBegin(startPos)
+  tokenBegin(result, startPos)
 
   # First stage: find out base, make verifications, build token literal string
-  if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'O'}:
+  # {'c', 'C'} is added for deprecation reasons to provide a clear error message
+  if L.buf[L.bufpos] == '0' and L.buf[L.bufpos + 1] in baseCodeChars + {'c', 'C', 'O'}:
     isBase10 = false
     eatChar(L, result, '0')
     case L.buf[L.bufpos]
+    of 'c', 'C':
+      lexMessageLitNum(L,
+                       "$1 will soon be invalid for oct literals; Use '0o' " &
+                       "for octals. 'c', 'C' prefix",
+                       startpos,
+                       warnDeprecated)
+      eatChar(L, result, 'c')
+      numDigits = matchUnderscoreChars(L, result, {'0'..'7'})
     of 'O':
-      lexMessageLitNum(L, errInvalidNumberOctalCode, startpos)
+      lexMessageLitNum(L, "$1 is an invalid int literal; For octal literals " &
+                          "use the '0o' prefix.", startpos)
     of 'x', 'X':
       eatChar(L, result, 'x')
-      matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'})
-    of 'o', 'c', 'C':
-      eatChar(L, result, 'c')
-      matchUnderscoreChars(L, result, {'0'..'7'})
+      numDigits = matchUnderscoreChars(L, result, {'0'..'9', 'a'..'f', 'A'..'F'})
+    of 'o':
+      eatChar(L, result, 'o')
+      numDigits = matchUnderscoreChars(L, result, {'0'..'7'})
     of 'b', 'B':
       eatChar(L, result, 'b')
-      matchUnderscoreChars(L, result, {'0'..'1'})
+      numDigits = matchUnderscoreChars(L, result, {'0'..'1'})
     else:
-      internalError(getLineInfo(L), "getNumber")
+      internalError(L.config, getLineInfo(L), "getNumber")
+    if numDigits == 0:
+      lexMessageLitNum(L, "invalid number: '$1'", startpos)
   else:
-    matchUnderscoreChars(L, result, {'0'..'9'})
+    discard matchUnderscoreChars(L, result, {'0'..'9'})
     if (L.buf[L.bufpos] == '.') and (L.buf[L.bufpos + 1] in {'0'..'9'}):
-      result.tokType = tkFloat64Lit
+      result.tokType = tkFloatLit
       eatChar(L, result, '.')
-      matchUnderscoreChars(L, result, {'0'..'9'})
+      discard matchUnderscoreChars(L, result, {'0'..'9'})
     if L.buf[L.bufpos] in {'e', 'E'}:
-      result.tokType = tkFloat64Lit
+      result.tokType = tkFloatLit
       eatChar(L, result, 'e')
       if L.buf[L.bufpos] in {'+', '-'}:
         eatChar(L, result)
-      matchUnderscoreChars(L, result, {'0'..'9'})
+      discard matchUnderscoreChars(L, result, {'0'..'9'})
   endpos = L.bufpos
 
   # Second stage, find out if there's a datatype suffix and handle it
@@ -439,7 +481,7 @@ proc getNumber(L: var TLexer, result: var TToken) =
         result.tokType = tkInt8Lit
         inc(postPos)
       else:
-        lexMessageLitNum(L, errInvalidNumber, startpos)
+        lexMessageLitNum(L, "invalid number: '$1'", startpos)
     of 'u', 'U':
       inc(postPos)
       if (L.buf[postPos] == '6') and (L.buf[postPos + 1] == '4'):
@@ -457,12 +499,12 @@ proc getNumber(L: var TLexer, result: var TToken) =
       else:
         result.tokType = tkUIntLit
     else:
-      lexMessageLitNum(L, errInvalidNumber, startpos)
+      lexMessageLitNum(L, "invalid number: '$1'", startpos)
 
   # Is there still a literalish char awaiting? Then it's an error!
   if  L.buf[postPos] in literalishChars or
      (L.buf[postPos] == '.' and L.buf[postPos + 1] in {'0'..'9'}):
-    lexMessageLitNum(L, errInvalidNumber, startpos)
+    lexMessageLitNum(L, "invalid number: '$1'", startpos)
 
   # Third stage, extract actual number
   L.bufpos = startpos            # restore position
@@ -479,6 +521,7 @@ proc getNumber(L: var TLexer, result: var TToken) =
           if L.buf[pos] != '_':
             xi = `shl`(xi, 1) or (ord(L.buf[pos]) - ord('0'))
           inc(pos)
+      # 'c', 'C' is deprecated
       of 'o', 'c', 'C':
         result.base = base8
         while pos < endpos:
@@ -503,7 +546,7 @@ proc getNumber(L: var TLexer, result: var TToken) =
           else:
             break
       else:
-        internalError(getLineInfo(L), "getNumber")
+        internalError(L.config, getLineInfo(L), "getNumber")
 
       case result.tokType
       of tkIntLit, tkInt64Lit: result.iNumber = xi
@@ -518,8 +561,9 @@ proc getNumber(L: var TLexer, result: var TToken) =
         result.fNumber = (cast[PFloat32](addr(xi)))[]
         # note: this code is endian neutral!
         # XXX: Test this on big endian machine!
-      of tkFloat64Lit: result.fNumber = (cast[PFloat64](addr(xi)))[]
-      else: internalError(getLineInfo(L), "getNumber")
+      of tkFloat64Lit, tkFloatLit:
+        result.fNumber = (cast[PFloat64](addr(xi)))[]
+      else: internalError(L.config, getLineInfo(L), "getNumber")
 
       # Bounds checks. Non decimal literals are allowed to overflow the range of
       # the datatype as long as their pattern don't overflow _bitwise_, hence
@@ -535,7 +579,7 @@ proc getNumber(L: var TLexer, result: var TToken) =
 
         if outOfRange:
           #echo "out of range num: ", result.iNumber, " vs ", xi
-          lexMessageLitNum(L, errNumberOutOfRange, startpos)
+          lexMessageLitNum(L, "number out of range: '$1'", startpos)
 
     else:
       case result.tokType
@@ -551,19 +595,20 @@ proc getNumber(L: var TLexer, result: var TToken) =
         result.iNumber = parseBiggestInt(result.literal)
 
       # Explicit bounds checks
-      let outOfRange = case result.tokType:
-      of tkInt8Lit: (result.iNumber < int8.low or result.iNumber > int8.high)
-      of tkUInt8Lit: (result.iNumber < BiggestInt(uint8.low) or
-                      result.iNumber > BiggestInt(uint8.high))
-      of tkInt16Lit: (result.iNumber < int16.low or result.iNumber > int16.high)
-      of tkUInt16Lit: (result.iNumber < BiggestInt(uint16.low) or
-                      result.iNumber > BiggestInt(uint16.high))
-      of tkInt32Lit: (result.iNumber < int32.low or result.iNumber > int32.high)
-      of tkUInt32Lit: (result.iNumber < BiggestInt(uint32.low) or
-                      result.iNumber > BiggestInt(uint32.high))
-      else: false
-
-      if outOfRange: lexMessageLitNum(L, errNumberOutOfRange, startpos)
+      let outOfRange =
+        case result.tokType
+        of tkInt8Lit: (result.iNumber < int8.low or result.iNumber > int8.high)
+        of tkUInt8Lit: (result.iNumber < BiggestInt(uint8.low) or
+                        result.iNumber > BiggestInt(uint8.high))
+        of tkInt16Lit: (result.iNumber < int16.low or result.iNumber > int16.high)
+        of tkUInt16Lit: (result.iNumber < BiggestInt(uint16.low) or
+                        result.iNumber > BiggestInt(uint16.high))
+        of tkInt32Lit: (result.iNumber < int32.low or result.iNumber > int32.high)
+        of tkUInt32Lit: (result.iNumber < BiggestInt(uint32.low) or
+                        result.iNumber > BiggestInt(uint32.high))
+        else: false
+
+      if outOfRange: lexMessageLitNum(L, "number out of range: '$1'", startpos)
 
     # Promote int literal to int64? Not always necessary, but more consistent
     if result.tokType == tkIntLit:
@@ -571,10 +616,10 @@ proc getNumber(L: var TLexer, result: var TToken) =
         result.tokType = tkInt64Lit
 
   except ValueError:
-    lexMessageLitNum(L, errInvalidNumber, startpos)
+    lexMessageLitNum(L, "invalid number: '$1'", startpos)
   except OverflowError, RangeError:
-    lexMessageLitNum(L, errNumberOutOfRange, startpos)
-  tokenEnd(postPos-1)
+    lexMessageLitNum(L, "number out of range: '$1'", startpos)
+  tokenEnd(result, postPos-1)
   L.bufpos = postPos
 
 proc handleHexChar(L: var TLexer, xi: var int) =
@@ -588,19 +633,70 @@ proc handleHexChar(L: var TLexer, xi: var int) =
   of 'A'..'F':
     xi = (xi shl 4) or (ord(L.buf[L.bufpos]) - ord('A') + 10)
     inc(L.bufpos)
-  else: discard
+  else:
+    lexMessage(L, errGenerated,
+      "expected a hex digit, but found: " & L.buf[L.bufpos] &
+        " ; maybe prepend with 0")
+    # Need to progress for `nim check`
+    inc(L.bufpos)
 
 proc handleDecChars(L: var TLexer, xi: var int) =
   while L.buf[L.bufpos] in {'0'..'9'}:
     xi = (xi * 10) + (ord(L.buf[L.bufpos]) - ord('0'))
     inc(L.bufpos)
 
+proc addUnicodeCodePoint(s: var string, i: int) =
+  # inlined toUTF-8 to avoid unicode and strutils dependencies.
+  let pos = s.len
+  if i <=% 127:
+    s.setLen(pos+1)
+    s[pos+0] = chr(i)
+  elif i <=% 0x07FF:
+    s.setLen(pos+2)
+    s[pos+0] = chr((i shr 6) or 0b110_00000)
+    s[pos+1] = chr((i and ones(6)) or 0b10_0000_00)
+  elif i <=% 0xFFFF:
+    s.setLen(pos+3)
+    s[pos+0] = chr(i shr 12 or 0b1110_0000)
+    s[pos+1] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+    s[pos+2] = chr(i and ones(6) or 0b10_0000_00)
+  elif i <=% 0x001FFFFF:
+    s.setLen(pos+4)
+    s[pos+0] = chr(i shr 18 or 0b1111_0000)
+    s[pos+1] = chr(i shr 12 and ones(6) or 0b10_0000_00)
+    s[pos+2] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+    s[pos+3] = chr(i and ones(6) or 0b10_0000_00)
+  elif i <=% 0x03FFFFFF:
+    s.setLen(pos+5)
+    s[pos+0] = chr(i shr 24 or 0b111110_00)
+    s[pos+1] = chr(i shr 18 and ones(6) or 0b10_0000_00)
+    s[pos+2] = chr(i shr 12 and ones(6) or 0b10_0000_00)
+    s[pos+3] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+    s[pos+4] = chr(i and ones(6) or 0b10_0000_00)
+  elif i <=% 0x7FFFFFFF:
+    s.setLen(pos+6)
+    s[pos+0] = chr(i shr 30 or 0b1111110_0)
+    s[pos+1] = chr(i shr 24 and ones(6) or 0b10_0000_00)
+    s[pos+2] = chr(i shr 18 and ones(6) or 0b10_0000_00)
+    s[pos+3] = chr(i shr 12 and ones(6) or 0b10_0000_00)
+    s[pos+4] = chr(i shr 6 and ones(6) or 0b10_0000_00)
+    s[pos+5] = chr(i and ones(6) or 0b10_0000_00)
+
 proc getEscapedChar(L: var TLexer, tok: var TToken) =
   inc(L.bufpos)               # skip '\'
   case L.buf[L.bufpos]
   of 'n', 'N':
-    if tok.tokType == tkCharLit: lexMessage(L, errNnotAllowedInCharacter)
-    add(tok.literal, tnl)
+    if L.config.oldNewlines:
+      if tok.tokType == tkCharLit:
+        lexMessage(L, errGenerated, "\\n not allowed in character literal")
+      add(tok.literal, L.config.target.tnl)
+    else:
+      add(tok.literal, '\L')
+    inc(L.bufpos)
+  of 'p', 'P':
+    if tok.tokType == tkCharLit:
+      lexMessage(L, errGenerated, "\\p not allowed in character literal")
+    add(tok.literal, L.config.target.tnl)
     inc(L.bufpos)
   of 'r', 'R', 'c', 'C':
     add(tok.literal, CR)
@@ -632,42 +728,49 @@ proc getEscapedChar(L: var TLexer, tok: var TToken) =
   of '\\':
     add(tok.literal, '\\')
     inc(L.bufpos)
-  of 'x', 'X', 'u', 'U':
-    var tp = L.buf[L.bufpos]
+  of 'x', 'X':
     inc(L.bufpos)
     var xi = 0
     handleHexChar(L, xi)
     handleHexChar(L, xi)
-    if tp in {'u', 'U'}:
+    add(tok.literal, chr(xi))
+  of 'u', 'U':
+    if tok.tokType == tkCharLit:
+      lexMessage(L, errGenerated, "\\u not allowed in character literal")
+    inc(L.bufpos)
+    var xi = 0
+    if L.buf[L.bufpos] == '{':
+      inc(L.bufpos)
+      var start = L.bufpos
+      while L.buf[L.bufpos] != '}':
+        handleHexChar(L, xi)
+      if start == L.bufpos:
+        lexMessage(L, errGenerated,
+          "Unicode codepoint cannot be empty")
+      inc(L.bufpos)
+      if xi > 0x10FFFF:
+        let hex = ($L.buf)[start..L.bufpos-2]
+        lexMessage(L, errGenerated,
+          "Unicode codepoint must be lower than 0x10FFFF, but was: " & hex)
+    else:
       handleHexChar(L, xi)
       handleHexChar(L, xi)
-      # inlined toUTF-8 to avoid unicode and strutils dependencies.
-      if xi <=% 127:
-        add(tok.literal, xi.char )
-      elif xi <=% 0x07FF:
-        add(tok.literal, ((xi shr 6) or 0b110_00000).char )
-        add(tok.literal, ((xi and ones(6)) or 0b10_0000_00).char )
-      elif xi <=% 0xFFFF:
-        add(tok.literal, (xi shr 12 or 0b1110_0000).char )
-        add(tok.literal, (xi shr 6 and ones(6) or 0b10_0000_00).char )
-        add(tok.literal, (xi and ones(6) or 0b10_0000_00).char )
-      else: # value is 0xFFFF
-        add(tok.literal, "\xef\xbf\xbf" )
-    else:
-      add(tok.literal, chr(xi))
+      handleHexChar(L, xi)
+      handleHexChar(L, xi)
+    addUnicodeCodePoint(tok.literal, xi)
   of '0'..'9':
     if matchTwoChars(L, '0', {'0'..'9'}):
       lexMessage(L, warnOctalEscape)
     var xi = 0
     handleDecChars(L, xi)
     if (xi <= 255): add(tok.literal, chr(xi))
-    else: lexMessage(L, errInvalidCharacterConstant)
-  else: lexMessage(L, errInvalidCharacterConstant)
+    else: lexMessage(L, errGenerated, "invalid character constant")
+  else: lexMessage(L, errGenerated, "invalid character constant")
 
 proc newString(s: cstring, len: int): string =
   ## XXX, how come there is no support for this?
   result = newString(len)
-  for i in 0 .. <len:
+  for i in 0 ..< len:
     result[i] = s[i]
 
 proc handleCRLF(L: var TLexer, pos: int): int =
@@ -677,11 +780,6 @@ proc handleCRLF(L: var TLexer, pos: int): int =
     if col > MaxLineLength:
       lexMessagePos(L, hintLineTooLong, pos)
 
-    if optEmbedOrigSrc in gGlobalOptions:
-      let lineStart = cast[ByteAddress](L.buf) + L.lineStart
-      let line = newString(cast[cstring](lineStart), col)
-      addSourceLine(L.fileIdx, line)
-
   case L.buf[pos]
   of CR:
     registerLine()
@@ -691,11 +789,18 @@ proc handleCRLF(L: var TLexer, pos: int): int =
     result = nimlexbase.handleLF(L, pos)
   else: result = pos
 
-proc getString(L: var TLexer, tok: var TToken, rawMode: bool) =
-  var pos = L.bufpos + 1          # skip "
+type
+  StringMode = enum
+    normal,
+    raw,
+    generalized
+
+proc getString(L: var TLexer, tok: var TToken, mode: StringMode) =
+  var pos = L.bufpos
   var buf = L.buf                 # put `buf` in a register
   var line = L.lineNumber         # save linenumber for better error message
-  tokenBegin(pos)
+  tokenBegin(tok, pos - ord(mode == raw))
+  inc pos # skip "
   if buf[pos] == '\"' and buf[pos+1] == '\"':
     tok.tokType = tkTripleStrLit # long string literal:
     inc(pos, 2)               # skip ""
@@ -711,21 +816,21 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) =
       of '\"':
         if buf[pos+1] == '\"' and buf[pos+2] == '\"' and
             buf[pos+3] != '\"':
-          tokenEndIgnore(pos+2)
+          tokenEndIgnore(tok, pos+2)
           L.bufpos = pos + 3 # skip the three """
           break
         add(tok.literal, '\"')
         inc(pos)
       of CR, LF:
-        tokenEndIgnore(pos)
+        tokenEndIgnore(tok, pos)
         pos = handleCRLF(L, pos)
         buf = L.buf
-        add(tok.literal, tnl)
+        add(tok.literal, "\n")
       of nimlexbase.EndOfFile:
-        tokenEndIgnore(pos)
+        tokenEndIgnore(tok, pos)
         var line2 = L.lineNumber
         L.lineNumber = line
-        lexMessagePos(L, errClosingTripleQuoteExpected, L.lineStart)
+        lexMessagePos(L, errGenerated, L.lineStart, "closing \"\"\" expected, but end of file reached")
         L.lineNumber = line2
         L.bufpos = pos
         break
@@ -734,23 +839,23 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) =
         inc(pos)
   else:
     # ordinary string literal
-    if rawMode: tok.tokType = tkRStrLit
+    if mode != normal: tok.tokType = tkRStrLit
     else: tok.tokType = tkStrLit
     while true:
       var c = buf[pos]
       if c == '\"':
-        if rawMode and buf[pos+1] == '\"':
+        if mode != normal and buf[pos+1] == '\"':
           inc(pos, 2)
           add(tok.literal, '"')
         else:
-          tokenEndIgnore(pos)
+          tokenEndIgnore(tok, pos)
           inc(pos) # skip '"'
           break
       elif c in {CR, LF, nimlexbase.EndOfFile}:
-        tokenEndIgnore(pos)
-        lexMessage(L, errClosingQuoteExpected)
+        tokenEndIgnore(tok, pos)
+        lexMessage(L, errGenerated, "closing \" expected")
         break
-      elif (c == '\\') and not rawMode:
+      elif (c == '\\') and mode == normal:
         L.bufpos = pos
         getEscapedChar(L, tok)
         pos = L.bufpos
@@ -760,51 +865,42 @@ proc getString(L: var TLexer, tok: var TToken, rawMode: bool) =
     L.bufpos = pos
 
 proc getCharacter(L: var TLexer, tok: var TToken) =
-  tokenBegin(L.bufpos)
+  tokenBegin(tok, L.bufpos)
   inc(L.bufpos)               # skip '
   var c = L.buf[L.bufpos]
   case c
-  of '\0'..pred(' '), '\'': lexMessage(L, errInvalidCharacterConstant)
+  of '\0'..pred(' '), '\'': lexMessage(L, errGenerated, "invalid character literal")
   of '\\': getEscapedChar(L, tok)
   else:
     tok.literal = $c
     inc(L.bufpos)
-  if L.buf[L.bufpos] != '\'': lexMessage(L, errMissingFinalQuote)
-  tokenEndIgnore(L.bufpos)
+  if L.buf[L.bufpos] != '\'':
+    lexMessage(L, errGenerated, "missing closing ' for character literal")
+  tokenEndIgnore(tok, L.bufpos)
   inc(L.bufpos)               # skip '
 
 proc getSymbol(L: var TLexer, tok: var TToken) =
   var h: Hash = 0
   var pos = L.bufpos
   var buf = L.buf
-  tokenBegin(pos)
+  tokenBegin(tok, pos)
   while true:
     var c = buf[pos]
     case c
     of 'a'..'z', '0'..'9', '\x80'..'\xFF':
-      if  c == '\226' and
-          buf[pos+1] == '\128' and
-          buf[pos+2] == '\147':  # It's a 'magic separator' en-dash Unicode
-        if buf[pos + magicIdentSeparatorRuneByteWidth] notin SymChars or
-            isMagicIdentSeparatorRune(buf, pos+magicIdentSeparatorRuneByteWidth) or pos == L.bufpos:
-          lexMessage(L, errInvalidToken, "–")
-          break
-        inc(pos, magicIdentSeparatorRuneByteWidth)
-      else:
-        h = h !& ord(c)
-        inc(pos)
+      h = h !& ord(c)
+      inc(pos)
     of 'A'..'Z':
       c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
       h = h !& ord(c)
       inc(pos)
     of '_':
-      if buf[pos+1] notin SymChars or isMagicIdentSeparatorRune(buf, pos+1):
-        lexMessage(L, errInvalidToken, "_")
+      if buf[pos+1] notin SymChars:
+        lexMessage(L, errGenerated, "invalid token: trailing underscore")
         break
       inc(pos)
-
     else: break
-  tokenEnd(pos-1)
+  tokenEnd(tok, pos-1)
   h = !$h
   tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
   L.bufpos = pos
@@ -825,7 +921,7 @@ proc endOperator(L: var TLexer, tok: var TToken, pos: int,
 proc getOperator(L: var TLexer, tok: var TToken) =
   var pos = L.bufpos
   var buf = L.buf
-  tokenBegin(pos)
+  tokenBegin(tok, pos)
   var h: Hash = 0
   while true:
     var c = buf[pos]
@@ -833,7 +929,7 @@ proc getOperator(L: var TLexer, tok: var TToken) =
     h = h !& ord(c)
     inc(pos)
   endOperator(L, tok, pos, h)
-  tokenEnd(pos-1)
+  tokenEnd(tok, pos-1)
   # advance pos but don't store it in L.bufpos so the next token (which might
   # be an operator too) gets the preceding spaces:
   tok.strongSpaceB = 0
@@ -843,12 +939,65 @@ proc getOperator(L: var TLexer, tok: var TToken) =
   if buf[pos] in {CR, LF, nimlexbase.EndOfFile}:
     tok.strongSpaceB = -1
 
+proc getPrecedence*(tok: TToken, strongSpaces: bool): int =
+  ## Calculates the precedence of the given token.
+  template considerStrongSpaces(x): untyped =
+    x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0)
+
+  case tok.tokType
+  of tkOpr:
+    let L = tok.ident.s.len
+    let relevantChar = tok.ident.s[0]
+
+    # arrow like?
+    if L > 1 and tok.ident.s[L-1] == '>' and
+      tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1)
+
+    template considerAsgn(value: untyped) =
+      result = if tok.ident.s[L-1] == '=': 1 else: value
+
+    case relevantChar
+    of '$', '^': considerAsgn(10)
+    of '*', '%', '/', '\\': considerAsgn(9)
+    of '~': result = 8
+    of '+', '-', '|': considerAsgn(8)
+    of '&': considerAsgn(7)
+    of '=', '<', '>', '!': result = 5
+    of '.': considerAsgn(6)
+    of '?': result = 2
+    else: considerAsgn(2)
+  of tkDiv, tkMod, tkShl, tkShr: result = 9
+  of tkIn, tkNotin, tkIs, tkIsnot, tkOf, tkAs: result = 5
+  of tkDotDot: result = 6
+  of tkAnd: result = 4
+  of tkOr, tkXor, tkPtr, tkRef: result = 3
+  else: return -10
+  result = considerStrongSpaces(result)
+
+
+proc newlineFollows*(L: TLexer): bool =
+  var pos = L.bufpos
+  var buf = L.buf
+  while true:
+    case buf[pos]
+    of ' ', '\t':
+      inc(pos)
+    of CR, LF:
+      result = true
+      break
+    of '#':
+      inc(pos)
+      if buf[pos] == '#': inc(pos)
+      if buf[pos] != '[': return true
+    else:
+      break
+
 proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int;
                           isDoc: bool) =
   var pos = start
   var buf = L.buf
   var toStrip = 0
-  tokenBegin(pos)
+  tokenBegin(tok, pos)
   # detect the amount of indentation:
   if isDoc:
     toStrip = getColNumber(L, pos)
@@ -875,38 +1024,41 @@ proc skipMultiLineComment(L: var TLexer; tok: var TToken; start: int;
       if isDoc:
         if buf[pos+1] == '#' and buf[pos+2] == '#':
           if nesting == 0:
-            tokenEndIgnore(pos+2)
+            tokenEndIgnore(tok, pos+2)
             inc(pos, 3)
             break
           dec nesting
         tok.literal.add ']'
       elif buf[pos+1] == '#':
         if nesting == 0:
-          tokenEndIgnore(pos+1)
+          tokenEndIgnore(tok, pos+1)
           inc(pos, 2)
           break
         dec nesting
       inc pos
     of CR, LF:
-      tokenEndIgnore(pos)
+      tokenEndIgnore(tok, pos)
       pos = handleCRLF(L, pos)
       buf = L.buf
       # strip leading whitespace:
+      when defined(nimpretty): tok.literal.add "\L"
       if isDoc:
-        tok.literal.add "\n"
+        when not defined(nimpretty): tok.literal.add "\n"
         inc tok.iNumber
         var c = toStrip
         while buf[pos] == ' ' and c > 0:
           inc pos
           dec c
     of nimlexbase.EndOfFile:
-      tokenEndIgnore(pos)
+      tokenEndIgnore(tok, pos)
       lexMessagePos(L, errGenerated, pos, "end of multiline comment expected")
       break
     else:
-      if isDoc: tok.literal.add buf[pos]
+      if isDoc or defined(nimpretty): tok.literal.add buf[pos]
       inc(pos)
   L.bufpos = pos
+  when defined(nimpretty):
+    tok.commentOffsetB = L.offsetBase + pos - 1
 
 proc scanComment(L: var TLexer, tok: var TToken) =
   var pos = L.bufpos
@@ -915,10 +1067,13 @@ proc scanComment(L: var TLexer, tok: var TToken) =
   # iNumber contains the number of '\n' in the token
   tok.iNumber = 0
   assert buf[pos+1] == '#'
+  when defined(nimpretty):
+    tok.commentOffsetA = L.offsetBase + pos - 1
+
   if buf[pos+2] == '[':
     skipMultiLineComment(L, tok, pos+3, true)
     return
-  tokenBegin(pos)
+  tokenBegin(tok, pos)
   inc(pos, 2)
 
   var toStrip = 0
@@ -932,7 +1087,7 @@ proc scanComment(L: var TLexer, tok: var TToken) =
       if buf[pos] == '\\': lastBackslash = pos+1
       add(tok.literal, buf[pos])
       inc(pos)
-    tokenEndIgnore(pos)
+    tokenEndIgnore(tok, pos)
     pos = handleCRLF(L, pos)
     buf = L.buf
     var indent = 0
@@ -951,25 +1106,33 @@ proc scanComment(L: var TLexer, tok: var TToken) =
     else:
       if buf[pos] > ' ':
         L.indentAhead = indent
-      tokenEndIgnore(pos)
+      tokenEndIgnore(tok, pos)
       break
   L.bufpos = pos
+  when defined(nimpretty):
+    tok.commentOffsetB = L.offsetBase + pos - 1
 
 proc skip(L: var TLexer, tok: var TToken) =
   var pos = L.bufpos
   var buf = L.buf
-  tokenBegin(pos)
+  tokenBegin(tok, pos)
   tok.strongSpaceA = 0
+  when defined(nimpretty):
+    var hasComment = false
+    var commentIndent = L.currLineIndent
+    tok.commentOffsetA = L.offsetBase + pos
+    tok.commentOffsetB = tok.commentOffsetA
+    tok.line = -1
   while true:
     case buf[pos]
     of ' ':
       inc(pos)
       inc(tok.strongSpaceA)
     of '\t':
-      if not L.allowTabs: lexMessagePos(L, errTabulatorsAreNotAllowed, pos)
+      if not L.allowTabs: lexMessagePos(L, errGenerated, pos, "tabulators are not allowed")
       inc(pos)
     of CR, LF:
-      tokenEndPrevious(pos)
+      tokenEndPrevious(tok, pos)
       pos = handleCRLF(L, pos)
       buf = L.buf
       var indent = 0
@@ -978,12 +1141,19 @@ proc skip(L: var TLexer, tok: var TToken) =
           inc(pos)
           inc(indent)
         elif buf[pos] == '#' and buf[pos+1] == '[':
+          when defined(nimpretty):
+            hasComment = true
+            if tok.line < 0:
+              tok.line = L.lineNumber
+              commentIndent = indent
           skipMultiLineComment(L, tok, pos+2, false)
           pos = L.bufpos
           buf = L.buf
         else:
           break
       tok.strongSpaceA = 0
+      when defined(nimpretty):
+        if buf[pos] == '#' and tok.line < 0: commentIndent = indent
       if buf[pos] > ' ' and (buf[pos] != '#' or buf[pos+1] == '#'):
         tok.indent = indent
         L.currLineIndent = indent
@@ -991,25 +1161,41 @@ proc skip(L: var TLexer, tok: var TToken) =
     of '#':
       # do not skip documentation comment:
       if buf[pos+1] == '#': break
+      when defined(nimpretty):
+        hasComment = true
+        if tok.line < 0:
+          tok.line = L.lineNumber
+
       if buf[pos+1] == '[':
         skipMultiLineComment(L, tok, pos+2, false)
         pos = L.bufpos
         buf = L.buf
       else:
-        tokenBegin(pos)
-        while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}: inc(pos)
-        tokenEndIgnore(pos+1)
+        tokenBegin(tok, pos)
+        while buf[pos] notin {CR, LF, nimlexbase.EndOfFile}:
+          when defined(nimpretty): tok.literal.add buf[pos]
+          inc(pos)
+        tokenEndIgnore(tok, pos+1)
+        when defined(nimpretty):
+          tok.commentOffsetB = L.offsetBase + pos + 1
     else:
       break                   # EndOfFile also leaves the loop
-  tokenEndPrevious(pos-1)
+  tokenEndPrevious(tok, pos-1)
   L.bufpos = pos
+  when defined(nimpretty):
+    if hasComment:
+      tok.commentOffsetB = L.offsetBase + pos - 1
+      tok.tokType = tkComment
+      tok.indent = commentIndent
+    if gIndentationWidth <= 0:
+      gIndentationWidth = tok.indent
 
 proc rawGetTok*(L: var TLexer, tok: var TToken) =
   template atTokenEnd() {.dirty.} =
     when defined(nimsuggest):
       # we attach the cursor to the last *strong* token
       if tok.tokType notin weakTokens:
-        L.previousToken.line = tok.line.int16
+        L.previousToken.line = tok.line.uint16
         L.previousToken.col = tok.col.int16
 
   when defined(nimsuggest):
@@ -1022,10 +1208,14 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
   else:
     tok.indent = -1
   skip(L, tok)
+  when defined(nimpretty):
+    if tok.tokType == tkComment:
+      L.indentAhead = L.currLineIndent
+      return
   var c = L.buf[L.bufpos]
   tok.line = L.lineNumber
   tok.col = getColNumber(L, L.bufpos)
-  if c in SymStartChars - {'r', 'R', 'l'}:
+  if c in SymStartChars - {'r', 'R'}:
     getSymbol(L, tok)
   else:
     case c
@@ -1042,16 +1232,10 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
     of ',':
       tok.tokType = tkComma
       inc(L.bufpos)
-    of 'l':
-      # if we parsed exactly one character and its a small L (l), this
-      # is treated as a warning because it may be confused with the number 1
-      if L.buf[L.bufpos+1] notin (SymChars + {'_'}):
-        lexMessage(L, warnSmallLshouldNotBeUsed)
-      getSymbol(L, tok)
     of 'r', 'R':
       if L.buf[L.bufpos + 1] == '\"':
         inc(L.bufpos)
-        getString(L, tok, true)
+        getString(L, tok, raw)
       else:
         getSymbol(L, tok)
     of '(':
@@ -1062,9 +1246,9 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       else:
         tok.tokType = tkParLe
         when defined(nimsuggest):
-          if L.fileIdx == gTrackPos.fileIndex and tok.col < gTrackPos.col and
-                    tok.line == gTrackPos.line and gIdeCmd == ideCon:
-            gTrackPos.col = tok.col.int16
+          if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col < L.config.m.trackPos.col and
+                    tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideCon:
+            L.config.m.trackPos.col = tok.col.int16
     of ')':
       tok.tokType = tkParRi
       inc(L.bufpos)
@@ -1073,6 +1257,9 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
         tok.tokType = tkBracketDotLe
         inc(L.bufpos)
+      elif L.buf[L.bufpos] == ':':
+        tok.tokType = tkBracketLeColon
+        inc(L.bufpos)
       else:
         tok.tokType = tkBracketLe
     of ']':
@@ -1080,11 +1267,11 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       inc(L.bufpos)
     of '.':
       when defined(nimsuggest):
-        if L.fileIdx == gTrackPos.fileIndex and tok.col+1 == gTrackPos.col and
-            tok.line == gTrackPos.line and gIdeCmd == ideSug:
+        if L.fileIdx == L.config.m.trackPos.fileIndex and tok.col+1 == L.config.m.trackPos.col and
+            tok.line == L.config.m.trackPos.line.int and L.config.ideCmd == ideSug:
           tok.tokType = tkDot
           L.cursor = CursorPosition.InToken
-          gTrackPos.col = tok.col.int16
+          L.config.m.trackPos.col = tok.col.int16
           inc(L.bufpos)
           atTokenEnd()
           return
@@ -1117,19 +1304,18 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       inc(L.bufpos)
     of '_':
       inc(L.bufpos)
-      if L.buf[L.bufpos] notin SymChars+{'_'} and not
-          isMagicIdentSeparatorRune(L.buf, L.bufpos):
+      if L.buf[L.bufpos] notin SymChars+{'_'}:
         tok.tokType = tkSymbol
         tok.ident = L.cache.getIdent("_")
       else:
         tok.literal = $c
         tok.tokType = tkInvalid
-        lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
+        lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')')
     of '\"':
-      # check for extended raw string literal:
-      var rawMode = L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars
-      getString(L, tok, rawMode)
-      if rawMode:
+      # check for generalized raw string literal:
+      let mode = if L.bufpos > 0 and L.buf[L.bufpos-1] in SymChars: generalized else: normal
+      getString(L, tok, mode)
+      if mode == generalized:
         # tkRStrLit -> tkGStrLit
         # tkTripleStrLit -> tkGTripleStrLit
         inc(tok.tokType, 2)
@@ -1141,7 +1327,7 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       getNumber(L, tok)
       let c = L.buf[L.bufpos]
       if c in SymChars+{'_'}:
-        lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
+        lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
     else:
       if c in OpChars:
         getOperator(L, tok)
@@ -1151,6 +1337,29 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
       else:
         tok.literal = $c
         tok.tokType = tkInvalid
-        lexMessage(L, errInvalidToken, c & " (\\" & $(ord(c)) & ')')
+        lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')')
         inc(L.bufpos)
   atTokenEnd()
+
+proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream;
+                     cache: IdentCache; config: ConfigRef): int =
+  var lex: TLexer
+  var tok: TToken
+  initToken(tok)
+  openLexer(lex, fileIdx, inputstream, cache, config)
+  while true:
+    rawGetTok(lex, tok)
+    result = tok.indent
+    if result > 0 or tok.tokType == tkEof: break
+  closeLexer(lex)
+
+proc getPrecedence*(ident: PIdent): int =
+  ## assumes ident is binary operator already
+  var tok: TToken
+  initToken(tok)
+  tok.ident = ident
+  tok.tokType =
+    if tok.ident.id in ord(tokKeywordLow) - ord(tkSymbol) .. ord(tokKeywordHigh) - ord(tkSymbol):
+      TTokType(tok.ident.id + ord(tkSymbol))
+    else: tkOpr
+  getPrecedence(tok, false)
diff --git a/compiler/liftlocals.nim b/compiler/liftlocals.nim
new file mode 100644
index 000000000..ae789cd88
--- /dev/null
+++ b/compiler/liftlocals.nim
@@ -0,0 +1,71 @@
+#
+#
+#           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 the '.liftLocals' pragma.
+
+import
+  intsets, strutils, options, ast, astalgo, msgs,
+  idents, renderer, types, lowerings, lineinfos
+
+from pragmas import getPragmaVal
+from wordrecg import wLiftLocals
+
+type
+  Ctx = object
+    partialParam: PSym
+    objType: PType
+    cache: IdentCache
+
+proc interestingVar(s: PSym): bool {.inline.} =
+  result = s.kind in {skVar, skLet, skTemp, skForVar, skResult} and
+    sfGlobal notin s.flags
+
+proc lookupOrAdd(c: var Ctx; s: PSym; info: TLineInfo): PNode =
+  let field = addUniqueField(c.objType, s, c.cache)
+  var deref = newNodeI(nkHiddenDeref, info)
+  deref.typ = c.objType
+  add(deref, newSymNode(c.partialParam, info))
+  result = newNodeI(nkDotExpr, info)
+  add(result, deref)
+  add(result, newSymNode(field))
+  result.typ = field.typ
+
+proc liftLocals(n: PNode; i: int; c: var Ctx) =
+  let it = n[i]
+  case it.kind
+  of nkSym:
+    if interestingVar(it.sym):
+      n[i] = lookupOrAdd(c, it.sym, it.info)
+  of procDefs, nkTypeSection: discard
+  else:
+    for i in 0 ..< it.safeLen:
+      liftLocals(it, i, c)
+
+proc lookupParam(params, dest: PNode): PSym =
+  if dest.kind != nkIdent: return nil
+  for i in 1 ..< params.len:
+    if params[i].kind == nkSym and params[i].sym.name.id == dest.ident.id:
+      return params[i].sym
+
+proc liftLocalsIfRequested*(prc: PSym; n: PNode; cache: IdentCache; conf: ConfigRef): PNode =
+  let liftDest = getPragmaVal(prc.ast, wLiftLocals)
+  if liftDest == nil: return n
+  let partialParam = lookupParam(prc.typ.n, liftDest)
+  if partialParam.isNil:
+    localError(conf, liftDest.info, "'$1' is not a parameter of '$2'" %
+              [$liftDest, prc.name.s])
+    return n
+  let objType = partialParam.typ.skipTypes(abstractPtrs)
+  if objType.kind != tyObject or tfPartial notin objType.flags:
+    localError(conf, liftDest.info, "parameter '$1' is not a pointer to a partial object" % $liftDest)
+    return n
+  var c = Ctx(partialParam: partialParam, objType: objType, cache: cache)
+  let w = newTree(nkStmtList, n)
+  liftLocals(w, 0, c)
+  result = w[0]
diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim
new file mode 100644
index 000000000..b1ecf779e
--- /dev/null
+++ b/compiler/lineinfos.nim
@@ -0,0 +1,264 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module contains the ``TMsgKind`` enum as well as the
+## ``TLineInfo`` object.
+
+import ropes, tables, pathutils
+
+const
+  explanationsBaseUrl* = "https://nim-lang.org/docs/manual"
+
+type
+  TMsgKind* = enum
+    errUnknown, errInternal, errIllFormedAstX, errCannotOpenFile,
+    errXExpected,
+    errGridTableNotImplemented,
+    errGeneralParseError,
+    errNewSectionExpected,
+    errInvalidDirectiveX,
+    errGenerated,
+    errUser,
+    warnCannotOpenFile,
+    warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
+    warnDeprecated, warnConfigDeprecated,
+    warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel,
+    warnUnknownSubstitutionX, warnLanguageXNotSupported,
+    warnFieldXNotSupported, warnCommentXIgnored,
+    warnTypelessParam,
+    warnUseBase, warnWriteToForeignHeap, warnUnsafeCode,
+    warnEachIdentIsTuple, warnShadowIdent,
+    warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2,
+    warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed,
+    warnInconsistentSpacing, warnUser,
+    hintSuccess, hintSuccessX, hintCC,
+    hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
+    hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
+    hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
+    hintConditionAlwaysTrue, hintConditionAlwaysFalse, hintName, hintPattern,
+    hintExecuting, hintLinking, hintDependency,
+    hintSource, hintPerformance, hintStackTrace, hintGCStats,
+    hintGlobalVar,
+    hintUser, hintUserRaw,
+    hintExtendedContext
+
+const
+  MsgKindToStr*: array[TMsgKind, string] = [
+    errUnknown: "unknown error",
+    errInternal: "internal error: $1",
+    errIllFormedAstX: "illformed AST: $1",
+    errCannotOpenFile: "cannot open '$1'",
+    errXExpected: "'$1' expected",
+    errGridTableNotImplemented: "grid table is not implemented",
+    errGeneralParseError: "general parse error",
+    errNewSectionExpected: "new section expected",
+    errInvalidDirectiveX: "invalid directive: '$1'",
+    errGenerated: "$1",
+    errUser: "$1",
+    warnCannotOpenFile: "cannot open '$1'",
+    warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored",
+    warnXIsNeverRead: "'$1' is never read",
+    warnXmightNotBeenInit: "'$1' might not have been initialized",
+    warnDeprecated: "$1 is deprecated",
+    warnConfigDeprecated: "config file '$1' is deprecated",
+    warnSmallLshouldNotBeUsed: "'l' should not be used as an identifier; may look like '1' (one)",
+    warnUnknownMagic: "unknown magic '$1' might crash the compiler",
+    warnRedefinitionOfLabel: "redefinition of label '$1'",
+    warnUnknownSubstitutionX: "unknown substitution '$1'",
+    warnLanguageXNotSupported: "language '$1' not supported",
+    warnFieldXNotSupported: "field '$1' not supported",
+    warnCommentXIgnored: "comment '$1' ignored",
+    warnTypelessParam: "'$1' has no type. Typeless parameters are deprecated; only allowed for 'template'",
+    warnUseBase: "use {.base.} for base methods; baseless methods are deprecated",
+    warnWriteToForeignHeap: "write to foreign heap",
+    warnUnsafeCode: "unsafe code: '$1'",
+    warnEachIdentIsTuple: "each identifier is a tuple",
+    warnShadowIdent: "shadowed identifier: '$1'",
+    warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.",
+    warnProveField: "cannot prove that field '$1' is accessible",
+    warnProveIndex: "cannot prove index '$1' is valid",
+    warnGcUnsafe: "not GC-safe: '$1'",
+    warnGcUnsafe2: "$1",
+    warnUninit: "'$1' might not have been initialized",
+    warnGcMem: "'$1' uses GC'ed memory",
+    warnDestructor: "usage of a type with a destructor in a non destructible context. This will become a compile time error in the future.",
+    warnLockLevel: "$1",
+    warnResultShadowed: "Special variable 'result' is shadowed.",
+    warnInconsistentSpacing: "Number of spaces around '$#' is not consistent",
+    warnUser: "$1",
+    hintSuccess: "operation successful: $#",
+    hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#; $#)",
+    hintCC: "CC: \'$1\'", # unused
+    hintLineTooLong: "line too long",
+    hintXDeclaredButNotUsed: "'$1' is declared but not used",
+    hintConvToBaseNotNeeded: "conversion to base object is not needed",
+    hintConvFromXtoItselfNotNeeded: "conversion from $1 to itself is pointless",
+    hintExprAlwaysX: "expression evaluates always to '$1'",
+    hintQuitCalled: "quit() called",
+    hintProcessing: "$1",
+    hintCodeBegin: "generated code listing:",
+    hintCodeEnd: "end of listing",
+    hintConf: "used config file '$1'",
+    hintPath: "added path: '$1'",
+    hintConditionAlwaysTrue: "condition is always true: '$1'",
+    hintConditionAlwaysFalse: "condition is always false: '$1'",
+    hintName: "name should be: '$1'",
+    hintPattern: "$1",
+    hintExecuting: "$1",
+    hintLinking: "",
+    hintDependency: "$1",
+    hintSource: "$1",
+    hintPerformance: "$1",
+    hintStackTrace: "$1",
+    hintGCStats: "$1",
+    hintGlobalVar: "global variable declared here",
+    hintUser: "$1",
+    hintUserRaw: "$1",
+    hintExtendedContext: "$1",
+  ]
+
+const
+  WarningsToStr* = ["CannotOpenFile", "OctalEscape",
+    "XIsNeverRead", "XmightNotBeenInit",
+    "Deprecated", "ConfigDeprecated",
+    "SmallLshouldNotBeUsed", "UnknownMagic",
+    "RedefinitionOfLabel", "UnknownSubstitutionX",
+    "LanguageXNotSupported", "FieldXNotSupported",
+    "CommentXIgnored",
+    "TypelessParam", "UseBase", "WriteToForeignHeap",
+    "UnsafeCode", "EachIdentIsTuple", "ShadowIdent",
+    "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit",
+    "GcMem", "Destructor", "LockLevel", "ResultShadowed",
+    "Spacing", "User"]
+
+  HintsToStr* = [
+    "Success", "SuccessX", "CC", "LineTooLong",
+    "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
+    "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
+    "Path", "CondTrue", "CondFalse", "Name", "Pattern", "Exec", "Link", "Dependency",
+    "Source", "Performance", "StackTrace", "GCStats", "GlobalVar",
+    "User", "UserRaw", "ExtendedContext",
+  ]
+
+const
+  fatalMin* = errUnknown
+  fatalMax* = errInternal
+  errMin* = errUnknown
+  errMax* = errUser
+  warnMin* = warnCannotOpenFile
+  warnMax* = pred(hintSuccess)
+  hintMin* = hintSuccess
+  hintMax* = high(TMsgKind)
+
+static:
+  doAssert HintsToStr.len == ord(hintMax) - ord(hintMin) + 1
+  doAssert WarningsToStr.len == ord(warnMax) - ord(warnMin) + 1
+
+type
+  TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints
+  TNoteKinds* = set[TNoteKind]
+
+proc computeNotesVerbosity(): array[0..3, TNoteKinds] =
+  result[3] = {low(TNoteKind)..high(TNoteKind)} - {}
+  result[2] = result[3] - {hintStackTrace, warnUninit, hintExtendedContext}
+  result[1] = result[2] - {warnShadowIdent, warnProveField, warnProveIndex,
+    warnGcUnsafe, hintPath, hintDependency, hintCodeBegin, hintCodeEnd,
+    hintSource, hintGlobalVar, hintGCStats}
+  result[0] = result[1] - {hintSuccessX, hintSuccess, hintConf,
+    hintProcessing, hintPattern, hintExecuting, hintLinking}
+
+const
+  NotesVerbosity* = computeNotesVerbosity()
+  errXMustBeCompileTime* = "'$1' can only be used in compile-time context"
+  errArgsNeedRunOption* = "arguments can only be given if the '--run' option is selected"
+
+type
+  TFileInfo* = object
+    fullPath*: AbsoluteFile    # This is a canonical full filesystem path
+    projPath*: RelativeFile    # This is relative to the project's root
+    shortName*: string         # short name of the module
+    quotedName*: Rope          # cached quoted short name for codegen
+                               # purposes
+    quotedFullName*: Rope      # cached quoted full name for codegen
+                               # purposes
+
+    lines*: seq[string]        # the source code of the module
+                               #   used for better error messages and
+                               #   embedding the original source in the
+                               #   generated code
+    dirtyfile*: AbsoluteFile   # the file that is actually read into memory
+                               # and parsed; usually "" but is used
+                               # for 'nimsuggest'
+    hash*: string              # the checksum of the file
+    dirty*: bool               # for 'nimfix' / 'nimpretty' like tooling
+    when defined(nimpretty):
+      fullContent*: string
+  FileIndex* = distinct int32
+  TLineInfo* = object          # This is designed to be as small as possible,
+                               # because it is used
+                               # in syntax nodes. We save space here by using
+                               # two int16 and an int32.
+                               # On 64 bit and on 32 bit systems this is
+                               # only 8 bytes.
+    line*: uint16
+    col*: int16
+    fileIndex*: FileIndex
+    when defined(nimpretty):
+      offsetA*, offsetB*: int
+      commentOffsetA*, commentOffsetB*: int
+
+  TErrorOutput* = enum
+    eStdOut
+    eStdErr
+
+  TErrorOutputs* = set[TErrorOutput]
+
+  ERecoverableError* = object of ValueError
+  ESuggestDone* = object of Exception
+
+proc `==`*(a, b: FileIndex): bool {.borrow.}
+
+proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} =
+  raise newException(ERecoverableError, msg)
+
+const
+  InvalidFileIDX* = FileIndex(-1)
+
+proc unknownLineInfo*(): TLineInfo =
+  result.line = uint16(0)
+  result.col = int16(-1)
+  result.fileIndex = InvalidFileIDX
+
+type
+  Severity* {.pure.} = enum ## VS Code only supports these three
+    Hint, Warning, Error
+
+const trackPosInvalidFileIdx* = FileIndex(-2) # special marker so that no suggestions
+                                   # are produced within comments and string literals
+
+type
+  MsgConfig* = object ## does not need to be stored in the incremental cache
+    trackPos*: TLineInfo
+    trackPosAttached*: bool ## whether the tracking position was attached to
+                            ## some close token.
+
+    errorOutputs*: TErrorOutputs
+    msgContext*: seq[tuple[info: TLineInfo, detail: string]]
+    lastError*: TLineInfo
+    filenameToIndexTbl*: Table[string, FileIndex]
+    fileInfos*: seq[TFileInfo]
+    systemFileIdx*: FileIndex
+
+
+proc initMsgConfig*(): MsgConfig =
+  result.msgContext = @[]
+  result.lastError = unknownLineInfo()
+  result.filenameToIndexTbl = initTable[string, FileIndex]()
+  result.fileInfos = @[]
+  result.errorOutputs = {eStdOut, eStdErr}
diff --git a/compiler/linter.nim b/compiler/linter.nim
new file mode 100644
index 000000000..a881f2711
--- /dev/null
+++ b/compiler/linter.nim
@@ -0,0 +1,180 @@
+#
+#
+#           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 the style checker.
+
+import
+  strutils, os, intsets, strtabs
+
+import options, ast, astalgo, msgs, semdata, ropes, idents,
+  lineinfos, pathutils
+
+const
+  Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
+
+proc identLen*(line: string, start: int): int =
+  while start+result < line.len and line[start+result] in Letters:
+    inc result
+
+type
+  StyleCheck* {.pure.} = enum None, Warn, Auto
+
+var
+  gOverWrite* = true
+  gStyleCheck*: StyleCheck
+  gCheckExtern*, gOnlyMainfile*: bool
+
+proc overwriteFiles*(conf: ConfigRef) =
+  let doStrip = options.getConfigVar(conf, "pretty.strip").normalize == "on"
+  for i in 0 .. high(conf.m.fileInfos):
+    if conf.m.fileInfos[i].dirty and
+        (not gOnlyMainfile or FileIndex(i) == conf.projectMainIdx):
+      let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath
+                    else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim")
+      try:
+        var f = open(newFile.string, fmWrite)
+        for line in conf.m.fileInfos[i].lines:
+          if doStrip:
+            f.write line.strip(leading = false, trailing = true)
+          else:
+            f.write line
+          f.write(conf.m.fileInfos[i], "\L")
+        f.close
+      except IOError:
+        rawMessage(conf, errGenerated, "cannot open file: " & newFile.string)
+
+proc `=~`(s: string, a: openArray[string]): bool =
+  for x in a:
+    if s.startsWith(x): return true
+
+proc beautifyName(s: string, k: TSymKind): string =
+  # minimal set of rules here for transition:
+  # GC_ is allowed
+
+  let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
+  if allUpper and k in {skConst, skEnumField, skType}: return s
+  result = newStringOfCap(s.len)
+  var i = 0
+  case k
+  of skType, skGenericParam:
+    # Types should start with a capital unless builtins like 'int' etc.:
+    if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string",
+             "char", "byte", "bool", "openArray", "seq", "array", "void",
+             "pointer", "float", "csize", "cdouble", "cchar", "cschar",
+             "cshort", "cu", "nil", "typedesc", "auto", "any",
+             "range", "openarray", "varargs", "set", "cfloat", "ref", "ptr",
+             "untyped", "typed", "static", "sink", "lent", "type"]:
+      result.add s[i]
+    else:
+      result.add toUpperAscii(s[i])
+  of skConst, skEnumField:
+    # for 'const' we keep how it's spelt; either upper case or lower case:
+    result.add s[0]
+  else:
+    # as a special rule, don't transform 'L' to 'l'
+    if s.len == 1 and s[0] == 'L': result.add 'L'
+    elif '_' in s: result.add(s[i])
+    else: result.add toLowerAscii(s[0])
+  inc i
+  while i < s.len:
+    if s[i] == '_':
+      if i > 0 and s[i-1] in {'A'..'Z'}:
+        # don't skip '_' as it's essential for e.g. 'GC_disable'
+        result.add('_')
+        inc i
+        result.add s[i]
+      else:
+        inc i
+        result.add toUpperAscii(s[i])
+    elif allUpper:
+      result.add toLowerAscii(s[i])
+    else:
+      result.add s[i]
+    inc i
+
+proc differ*(line: string, a, b: int, x: string): bool =
+  let y = line[a..b]
+  result = cmpIgnoreStyle(y, x) == 0 and y != x
+
+proc replaceInFile(conf: ConfigRef; info: TLineInfo; newName: string) =
+  let line = conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1]
+  var first = min(info.col.int, line.len)
+  if first < 0: return
+  #inc first, skipIgnoreCase(line, "proc ", first)
+  while first > 0 and line[first-1] in Letters: dec first
+  if first < 0: return
+  if line[first] == '`': inc first
+
+  let last = first+identLen(line, first)-1
+  if differ(line, first, last, newName):
+    # last-first+1 != newName.len or
+    var x = line.substr(0, first-1) & newName & line.substr(last+1)
+    system.shallowCopy(conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1], x)
+    conf.m.fileInfos[info.fileIndex.int].dirty = true
+
+proc lintReport(conf: ConfigRef; info: TLineInfo, beau: string) =
+  if optStyleError in conf.globalOptions:
+    localError(conf, info, "name should be: '$1'" % beau)
+  else:
+    message(conf, info, hintName, beau)
+
+proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
+  let beau = beautifyName(s, k)
+  if s != beau:
+    if gStyleCheck == StyleCheck.Auto:
+      sym.name = getIdent(cache, beau)
+      replaceInFile(conf, info, beau)
+    else:
+      lintReport(conf, info, beau)
+
+proc styleCheckDefImpl(conf: ConfigRef; cache: IdentCache; info: TLineInfo; s: PSym; k: TSymKind) =
+  # operators stay as they are:
+  if k in {skResult, skTemp} or s.name.s[0] notin Letters: return
+  if k in {skType, skGenericParam} and sfAnon in s.flags: return
+  if {sfImportc, sfExportc} * s.flags == {} or gCheckExtern:
+    checkStyle(conf, cache, info, s.name.s, k, s)
+
+proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
+  # operators stay as they are:
+  if k in {skResult, skTemp} or s.name.s[0] notin Letters: return
+  if k in {skType, skGenericParam} and sfAnon in s.flags: return
+  if s.typ != nil and s.typ.kind == tyTypeDesc: return
+  if {sfImportc, sfExportc} * s.flags != {}: return
+  let beau = beautifyName(s.name.s, k)
+  if s.name.s != beau:
+    lintReport(conf, info, beau)
+
+template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
+  if {optStyleHint, optStyleError} * conf.globalOptions != {}:
+    nep1CheckDefImpl(conf, info, s, k)
+  when defined(nimfix):
+    if gStyleCheck != StyleCheck.None: styleCheckDefImpl(conf, cache, info, s, k)
+
+template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym) =
+  styleCheckDef(conf, info, s, s.kind)
+template styleCheckDef*(conf: ConfigRef; s: PSym) =
+  styleCheckDef(conf, s.info, s, s.kind)
+
+proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) =
+  if info.fileIndex.int < 0: return
+  # we simply convert it to what it looks like in the definition
+  # for consistency
+
+  # operators stay as they are:
+  if s.kind in {skResult, skTemp} or s.name.s[0] notin Letters:
+    return
+  if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return
+  let newName = s.name.s
+
+  replaceInFile(conf, info, newName)
+  #if newName == "File": writeStackTrace()
+
+template styleCheckUse*(info: TLineInfo; s: PSym) =
+  when defined(nimfix):
+    if gStyleCheck != StyleCheck.None: styleCheckUseImpl(conf, info, s)
diff --git a/compiler/llstream.nim b/compiler/llstream.nim
index 0a1e09fc8..e1108147f 100644
--- a/compiler/llstream.nim
+++ b/compiler/llstream.nim
@@ -10,7 +10,7 @@
 ## Low-level streams for high performance.
 
 import
-  strutils
+  strutils, pathutils
 
 # support '-d:useGnuReadline' for backwards compatibility:
 when not defined(windows) and (defined(useGnuReadline) or defined(useLinenoise)):
@@ -41,10 +41,10 @@ proc llStreamOpen*(f: File): PLLStream =
   result.f = f
   result.kind = llsFile
 
-proc llStreamOpen*(filename: string, mode: FileMode): PLLStream =
+proc llStreamOpen*(filename: AbsoluteFile, mode: FileMode): PLLStream =
   new(result)
   result.kind = llsFile
-  if not open(result.f, filename, mode): result = nil
+  if not open(result.f, filename.string, mode): result = nil
 
 proc llStreamOpen*(): PLLStream =
   new(result)
@@ -84,13 +84,12 @@ const
   AdditionalLineContinuationOprs = {'#', ':', '='}
 
 proc endsWithOpr*(x: string): bool =
-  # also used by the standard template filter:
   result = x.endsWith(LineContinuationOprs)
 
 proc continueLine(line: string, inTripleString: bool): bool {.inline.} =
-  result = inTripleString or
-      line[0] == ' ' or
-      line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs)
+  result = inTripleString or line.len > 0 and (
+        line[0] == ' ' or
+        line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs))
 
 proc countTriples(s: string): int =
   var i = 0
diff --git a/compiler/lookups.nim b/compiler/lookups.nim
index 089e69ff9..db03ac2e0 100644
--- a/compiler/lookups.nim
+++ b/compiler/lookups.nim
@@ -10,43 +10,56 @@
 # This module implements lookup helpers.
 
 import
-  intsets, ast, astalgo, idents, semdata, types, msgs, options, rodread,
-  renderer, wordrecg, idgen, nimfix.prettybase
+  intsets, ast, astalgo, idents, semdata, types, msgs, options,
+  renderer, wordrecg, idgen, nimfix/prettybase, lineinfos, strutils
 
-proc ensureNoMissingOrUnusedSymbols(scope: PScope)
+proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope)
 
-proc considerQuotedIdent*(n: PNode): PIdent =
+proc noidentError(conf: ConfigRef; n, origin: PNode) =
+  var m = ""
+  if origin != nil:
+    m.add "in expression '" & origin.renderTree & "': "
+  m.add "identifier expected, but found '" & n.renderTree & "'"
+  localError(conf, n.info, m)
+
+proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent =
   ## Retrieve a PIdent from a PNode, taking into account accent nodes.
+  ## ``origin`` can be nil. If it is not nil, it is used for a better
+  ## error message.
+  template handleError(n, origin: PNode) =
+    noidentError(c.config, n, origin)
+    result = getIdent(c.cache, "<Error>")
+
   case n.kind
   of nkIdent: result = n.ident
   of nkSym: result = n.sym.name
   of nkAccQuoted:
     case n.len
-    of 0:
-      localError(n.info, errIdentifierExpected, renderTree(n))
-      result = getIdent"<Error>"
-    of 1: result = considerQuotedIdent(n.sons[0])
+    of 0: handleError(n, origin)
+    of 1: result = considerQuotedIdent(c, n.sons[0], origin)
     else:
       var id = ""
-      for i in 0.. <n.len:
+      for i in 0..<n.len:
         let x = n.sons[i]
         case x.kind
         of nkIdent: id.add(x.ident.s)
         of nkSym: id.add(x.sym.name.s)
-        else:
-          localError(n.info, errIdentifierExpected, renderTree(n))
-          return getIdent"<Error>"
-      result = getIdent(id)
-  of nkOpenSymChoice, nkClosedSymChoice: result = n.sons[0].sym.name
+        of nkLiterals - nkFloatLiterals: id.add(x.renderTree)
+        else: handleError(n, origin)
+      result = getIdent(c.cache, id)
+  of nkOpenSymChoice, nkClosedSymChoice:
+    if n[0].kind == nkSym:
+      result = n.sons[0].sym.name
+    else:
+      handleError(n, origin)
   else:
-    localError(n.info, errIdentifierExpected, renderTree(n))
-    result = getIdent"<Error>"
+    handleError(n, origin)
 
 template addSym*(scope: PScope, s: PSym) =
   strTableAdd(scope.symbols, s)
 
-proc addUniqueSym*(scope: PScope, s: PSym): bool =
-  result = not strTableIncl(scope.symbols, s)
+proc addUniqueSym*(scope: PScope, s: PSym): PSym =
+  result = strTableInclReportConflict(scope.symbols, s)
 
 proc openScope*(c: PContext): PScope {.discardable.} =
   result = PScope(parent: c.currentScope,
@@ -58,7 +71,7 @@ proc rawCloseScope*(c: PContext) =
   c.currentScope = c.currentScope.parent
 
 proc closeScope*(c: PContext) =
-  ensureNoMissingOrUnusedSymbols(c.currentScope)
+  ensureNoMissingOrUnusedSymbols(c, c.currentScope)
   rawCloseScope(c)
 
 iterator walkScopes*(scope: PScope): PScope =
@@ -67,15 +80,15 @@ iterator walkScopes*(scope: PScope): PScope =
     yield current
     current = current.parent
 
-proc skipAlias*(s: PSym; n: PNode): PSym =
+proc skipAlias*(s: PSym; n: PNode; conf: ConfigRef): PSym =
   if s == nil or s.kind != skAlias:
     result = s
   else:
     result = s.owner
-    if gCmd == cmdPretty:
-      prettybase.replaceDeprecated(n.info, s, result)
+    if conf.cmd == cmdPretty:
+      prettybase.replaceDeprecated(conf, n.info, s, result)
     else:
-      message(n.info, warnDeprecated, "use " & result.name.s & " instead; " &
+      message(conf, n.info, warnDeprecated, "use " & result.name.s & " instead; " &
               s.name.s)
 
 proc localSearchInScope*(c: PContext, s: PIdent): PSym =
@@ -87,15 +100,16 @@ proc searchInScopes*(c: PContext, s: PIdent): PSym =
     if result != nil: return
   result = nil
 
-proc debugScopes*(c: PContext; limit=0) {.deprecated.} =
-  var i = 0
-  for scope in walkScopes(c.currentScope):
-    echo "scope ", i
-    for h in 0 .. high(scope.symbols.data):
-      if scope.symbols.data[h] != nil:
-        echo scope.symbols.data[h].name.s
-    if i == limit: break
-    inc i
+when declared(echo):
+  proc debugScopes*(c: PContext; limit=0) {.deprecated.} =
+    var i = 0
+    for scope in walkScopes(c.currentScope):
+      echo "scope ", i
+      for h in 0 .. high(scope.symbols.data):
+        if scope.symbols.data[h] != nil:
+          echo scope.symbols.data[h].name.s
+      if i == limit: break
+      inc i
 
 proc searchInScopes*(c: PContext, s: PIdent, filter: TSymKinds): PSym =
   for scope in walkScopes(c.currentScope):
@@ -112,21 +126,21 @@ proc errorSym*(c: PContext, n: PNode): PSym =
   # ensure that 'considerQuotedIdent' can't fail:
   if m.kind == nkDotExpr: m = m.sons[1]
   let ident = if m.kind in {nkIdent, nkSym, nkAccQuoted}:
-      considerQuotedIdent(m)
+      considerQuotedIdent(c, m)
     else:
-      getIdent("err:" & renderTree(m))
-  result = newSym(skError, ident, getCurrOwner(c), n.info)
+      getIdent(c.cache, "err:" & renderTree(m))
+  result = newSym(skError, ident, getCurrOwner(c), n.info, {})
   result.typ = errorType(c)
   incl(result.flags, sfDiscardable)
   # pretend it's imported from some unknown module to prevent cascading errors:
-  if gCmd != cmdInteractive and c.compilesContextId == 0:
+  if c.config.cmd != cmdInteractive and c.compilesContextId == 0:
     c.importTable.addSym(result)
 
 type
   TOverloadIterMode* = enum
     oimDone, oimNoQualifier, oimSelfModule, oimOtherModule, oimSymChoice,
     oimSymChoiceLocalLookup
-  TOverloadIter*{.final.} = object
+  TOverloadIter* = object
     it*: TIdentIter
     m*: PSym
     mode*: TOverloadIterMode
@@ -134,69 +148,77 @@ type
     scope*: PScope
     inSymChoice: IntSet
 
-proc getSymRepr*(s: PSym): string =
+proc getSymRepr*(conf: ConfigRef; s: PSym): string =
   case s.kind
-  of skProc, skMethod, skConverter, skIterator: result = getProcHeader(s)
-  else: result = s.name.s
+  of skProc, skFunc, skMethod, skConverter, skIterator:
+    result = getProcHeader(conf, s)
+  else:
+    result = s.name.s
 
-proc ensureNoMissingOrUnusedSymbols(scope: PScope) =
+proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) =
   # check if all symbols have been used and defined:
   var it: TTabIter
   var s = initTabIter(it, scope.symbols)
   var missingImpls = 0
   while s != nil:
-    if sfForward in s.flags:
+    if sfForward in s.flags and s.kind notin {skType, skModule}:
       # too many 'implementation of X' errors are annoying
       # and slow 'suggest' down:
       if missingImpls == 0:
-        localError(s.info, errImplOfXexpected, getSymRepr(s))
+        localError(c.config, s.info, "implementation of '$1' expected" %
+            getSymRepr(c.config, s))
       inc missingImpls
-    elif {sfUsed, sfExported} * s.flags == {} and optHints in s.options:
-      # BUGFIX: check options in s!
-      if s.kind notin {skForVar, skParam, skMethod, skUnknown, skGenericParam}:
+    elif {sfUsed, sfExported} * s.flags == {}:
+      if s.kind notin {skForVar, skParam, skMethod, skUnknown, skGenericParam, skEnumField}:
         # XXX: implicit type params are currently skTypes
         # maybe they can be made skGenericParam as well.
         if s.typ != nil and tfImplicitTypeParam notin s.typ.flags and
            s.typ.kind != tyGenericParam:
-          message(s.info, hintXDeclaredButNotUsed, getSymRepr(s))
+          message(c.config, s.info, hintXDeclaredButNotUsed, getSymRepr(c.config, s))
     s = nextIter(it, scope.symbols)
 
-proc wrongRedefinition*(info: TLineInfo, s: string) =
-  if gCmd != cmdInteractive:
-    localError(info, errAttemptToRedefine, s)
+proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string;
+                        conflictsWith: TLineInfo) =
+  if c.config.cmd != cmdInteractive:
+    localError(c.config, info,
+      "redefinition of '$1'; previous declaration here: $2" %
+      [s, c.config $ conflictsWith])
 
 proc addDecl*(c: PContext, sym: PSym, info: TLineInfo) =
-  if not c.currentScope.addUniqueSym(sym):
-    wrongRedefinition(info, sym.name.s)
+  let conflict = c.currentScope.addUniqueSym(sym)
+  if conflict != nil:
+    wrongRedefinition(c, info, sym.name.s, conflict.info)
 
 proc addDecl*(c: PContext, sym: PSym) =
-  if not c.currentScope.addUniqueSym(sym):
-    wrongRedefinition(sym.info, sym.name.s)
+  let conflict = c.currentScope.addUniqueSym(sym)
+  if conflict != nil:
+    wrongRedefinition(c, sym.info, sym.name.s, conflict.info)
 
 proc addPrelimDecl*(c: PContext, sym: PSym) =
   discard c.currentScope.addUniqueSym(sym)
 
-proc addDeclAt*(scope: PScope, sym: PSym) =
-  if not scope.addUniqueSym(sym):
-    wrongRedefinition(sym.info, sym.name.s)
+proc addDeclAt*(c: PContext; scope: PScope, sym: PSym) =
+  let conflict = scope.addUniqueSym(sym)
+  if conflict != nil:
+    wrongRedefinition(c, sym.info, sym.name.s, conflict.info)
 
 proc addInterfaceDeclAux(c: PContext, sym: PSym) =
   if sfExported in sym.flags:
     # add to interface:
     if c.module != nil: strTableAdd(c.module.tab, sym)
-    else: internalError(sym.info, "addInterfaceDeclAux")
+    else: internalError(c.config, sym.info, "addInterfaceDeclAux")
 
 proc addInterfaceDeclAt*(c: PContext, scope: PScope, sym: PSym) =
-  addDeclAt(scope, sym)
+  addDeclAt(c, scope, sym)
   addInterfaceDeclAux(c, sym)
 
-proc addOverloadableSymAt*(scope: PScope, fn: PSym) =
+proc addOverloadableSymAt*(c: PContext; scope: PScope, fn: PSym) =
   if fn.kind notin OverloadableSyms:
-    internalError(fn.info, "addOverloadableSymAt")
+    internalError(c.config, fn.info, "addOverloadableSymAt")
     return
   let check = strTableGet(scope.symbols, fn.name)
   if check != nil and check.kind notin OverloadableSyms:
-    wrongRedefinition(fn.info, fn.name.s)
+    wrongRedefinition(c, fn.info, fn.name.s, check.info)
   else:
     scope.addSym(fn)
 
@@ -207,12 +229,10 @@ proc addInterfaceDecl*(c: PContext, sym: PSym) =
 
 proc addInterfaceOverloadableSymAt*(c: PContext, scope: PScope, sym: PSym) =
   # it adds the symbol to the interface if appropriate
-  addOverloadableSymAt(scope, sym)
+  addOverloadableSymAt(c, scope, sym)
   addInterfaceDeclAux(c, sym)
 
 when defined(nimfix):
-  import strutils
-
   # when we cannot find the identifier, retry with a changed identifer:
   proc altSpelling(x: PIdent): PIdent =
     case x.s[0]
@@ -230,7 +250,7 @@ else:
   template fixSpelling(n: PNode; ident: PIdent; op: untyped) = discard
 
 proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
-  var err = "Error: ambiguous identifier: '" & s.name.s & "'"
+  var err = "ambiguous identifier: '" & s.name.s & "'"
   var ti: TIdentIter
   var candidate = initIdentIter(ti, c.importTable.symbols, s.name)
   var i = 0
@@ -240,22 +260,22 @@ proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) =
     err.add candidate.owner.name.s & "." & candidate.name.s
     candidate = nextIdentIter(ti, c.importTable.symbols)
     inc i
-  localError(info, errGenerated, err)
+  localError(c.config, info, errGenerated, err)
 
 proc errorUndeclaredIdentifier*(c: PContext; info: TLineInfo; name: string) =
   var err = "undeclared identifier: '" & name & "'"
   if c.recursiveDep.len > 0:
-    err.add "\nThis might be caused by a recursive module dependency: "
+    err.add "\nThis might be caused by a recursive module dependency:\n"
     err.add c.recursiveDep
     # prevent excessive errors for 'nim check'
-    c.recursiveDep = nil
-  localError(info, errGenerated, err)
+    c.recursiveDep = ""
+  localError(c.config, info, errGenerated, err)
 
 proc lookUp*(c: PContext, n: PNode): PSym =
   # Looks up a symbol. Generates an error in case of nil.
   case n.kind
   of nkIdent:
-    result = searchInScopes(c, n.ident).skipAlias(n)
+    result = searchInScopes(c, n.ident).skipAlias(n, c.config)
     if result == nil:
       fixSpelling(n, n.ident, searchInScopes)
       errorUndeclaredIdentifier(c, n.info, n.ident.s)
@@ -263,32 +283,35 @@ proc lookUp*(c: PContext, n: PNode): PSym =
   of nkSym:
     result = n.sym
   of nkAccQuoted:
-    var ident = considerQuotedIdent(n)
-    result = searchInScopes(c, ident).skipAlias(n)
+    var ident = considerQuotedIdent(c, n)
+    result = searchInScopes(c, ident).skipAlias(n, c.config)
     if result == nil:
       fixSpelling(n, ident, searchInScopes)
       errorUndeclaredIdentifier(c, n.info, ident.s)
       result = errorSym(c, n)
   else:
-    internalError(n.info, "lookUp")
+    internalError(c.config, n.info, "lookUp")
     return
   if contains(c.ambiguousSymbols, result.id):
     errorUseQualifier(c, n.info, result)
-  if result.kind == skStub: loadStub(result)
+  when false:
+    if result.kind == skStub: loadStub(result)
 
 type
   TLookupFlag* = enum
-    checkAmbiguity, checkUndeclared, checkModule
+    checkAmbiguity, checkUndeclared, checkModule, checkPureEnumFields
 
 proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
   const allExceptModule = {low(TSymKind)..high(TSymKind)}-{skModule,skPackage}
   case n.kind
   of nkIdent, nkAccQuoted:
-    var ident = considerQuotedIdent(n)
+    var ident = considerQuotedIdent(c, n)
     if checkModule in flags:
-      result = searchInScopes(c, ident).skipAlias(n)
+      result = searchInScopes(c, ident).skipAlias(n, c.config)
     else:
-      result = searchInScopes(c, ident, allExceptModule).skipAlias(n)
+      result = searchInScopes(c, ident, allExceptModule).skipAlias(n, c.config)
+    if result == nil and checkPureEnumFields in flags:
+      result = strTableGet(c.pureEnumFields, ident)
     if result == nil and checkUndeclared in flags:
       fixSpelling(n, ident, searchInScopes)
       errorUndeclaredIdentifier(c, n.info, ident.s)
@@ -308,12 +331,12 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
       if n.sons[1].kind == nkIdent:
         ident = n.sons[1].ident
       elif n.sons[1].kind == nkAccQuoted:
-        ident = considerQuotedIdent(n.sons[1])
+        ident = considerQuotedIdent(c, n.sons[1])
       if ident != nil:
         if m == c.module:
-          result = strTableGet(c.topLevelScope.symbols, ident).skipAlias(n)
+          result = strTableGet(c.topLevelScope.symbols, ident).skipAlias(n, c.config)
         else:
-          result = strTableGet(m.tab, ident).skipAlias(n)
+          result = strTableGet(m.tab, ident).skipAlias(n, c.config)
         if result == nil and checkUndeclared in flags:
           fixSpelling(n.sons[1], ident, searchInScopes)
           errorUndeclaredIdentifier(c, n.sons[1].info, ident.s)
@@ -322,21 +345,22 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
         result = n.sons[1].sym
       elif checkUndeclared in flags and
            n.sons[1].kind notin {nkOpenSymChoice, nkClosedSymChoice}:
-        localError(n.sons[1].info, errIdentifierExpected,
+        localError(c.config, n.sons[1].info, "identifier expected, but got: " &
                    renderTree(n.sons[1]))
         result = errorSym(c, n.sons[1])
   else:
     result = nil
-  if result != nil and result.kind == skStub: loadStub(result)
+  when false:
+    if result != nil and result.kind == skStub: loadStub(result)
 
 proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym =
   case n.kind
   of nkIdent, nkAccQuoted:
-    var ident = considerQuotedIdent(n)
+    var ident = considerQuotedIdent(c, n)
     o.scope = c.currentScope
     o.mode = oimNoQualifier
     while true:
-      result = initIdentIter(o.it, o.scope.symbols, ident).skipAlias(n)
+      result = initIdentIter(o.it, o.scope.symbols, ident).skipAlias(n, c.config)
       if result != nil:
         break
       else:
@@ -353,27 +377,31 @@ proc initOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym =
       if n.sons[1].kind == nkIdent:
         ident = n.sons[1].ident
       elif n.sons[1].kind == nkAccQuoted:
-        ident = considerQuotedIdent(n.sons[1])
+        ident = considerQuotedIdent(c, n.sons[1], n)
       if ident != nil:
         if o.m == c.module:
           # a module may access its private members:
           result = initIdentIter(o.it, c.topLevelScope.symbols,
-                                 ident).skipAlias(n)
+                                 ident).skipAlias(n, c.config)
           o.mode = oimSelfModule
         else:
-          result = initIdentIter(o.it, o.m.tab, ident).skipAlias(n)
+          result = initIdentIter(o.it, o.m.tab, ident).skipAlias(n, c.config)
       else:
-        localError(n.sons[1].info, errIdentifierExpected,
-                   renderTree(n.sons[1]))
+        noidentError(c.config, n.sons[1], n)
         result = errorSym(c, n.sons[1])
   of nkClosedSymChoice, nkOpenSymChoice:
     o.mode = oimSymChoice
-    result = n.sons[0].sym
+    if n[0].kind == nkSym:
+      result = n.sons[0].sym
+    else:
+      o.mode = oimDone
+      return nil
     o.symChoiceIndex = 1
     o.inSymChoice = initIntSet()
     incl(o.inSymChoice, result.id)
   else: discard
-  if result != nil and result.kind == skStub: loadStub(result)
+  when false:
+    if result != nil and result.kind == skStub: loadStub(result)
 
 proc lastOverloadScope*(o: TOverloadIter): int =
   case o.mode
@@ -388,18 +416,18 @@ proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym =
     result = nil
   of oimNoQualifier:
     if o.scope != nil:
-      result = nextIdentIter(o.it, o.scope.symbols).skipAlias(n)
+      result = nextIdentIter(o.it, o.scope.symbols).skipAlias(n, c.config)
       while result == nil:
         o.scope = o.scope.parent
         if o.scope == nil: break
-        result = initIdentIter(o.it, o.scope.symbols, o.it.name).skipAlias(n)
+        result = initIdentIter(o.it, o.scope.symbols, o.it.name).skipAlias(n, c.config)
         # BUGFIX: o.it.name <-> n.ident
     else:
       result = nil
   of oimSelfModule:
-    result = nextIdentIter(o.it, c.topLevelScope.symbols).skipAlias(n)
+    result = nextIdentIter(o.it, c.topLevelScope.symbols).skipAlias(n, c.config)
   of oimOtherModule:
-    result = nextIdentIter(o.it, o.m.tab).skipAlias(n)
+    result = nextIdentIter(o.it, o.m.tab).skipAlias(n, c.config)
   of oimSymChoice:
     if o.symChoiceIndex < sonsLen(n):
       result = n.sons[o.symChoiceIndex].sym
@@ -410,30 +438,30 @@ proc nextOverloadIter*(o: var TOverloadIter, c: PContext, n: PNode): PSym =
       o.mode = oimSymChoiceLocalLookup
       o.scope = c.currentScope
       result = firstIdentExcluding(o.it, o.scope.symbols,
-                                   n.sons[0].sym.name, o.inSymChoice).skipAlias(n)
+                                   n.sons[0].sym.name, o.inSymChoice).skipAlias(n, c.config)
       while result == nil:
         o.scope = o.scope.parent
         if o.scope == nil: break
         result = firstIdentExcluding(o.it, o.scope.symbols,
-                                     n.sons[0].sym.name, o.inSymChoice).skipAlias(n)
+                                     n.sons[0].sym.name, o.inSymChoice).skipAlias(n, c.config)
   of oimSymChoiceLocalLookup:
-    result = nextIdentExcluding(o.it, o.scope.symbols, o.inSymChoice).skipAlias(n)
+    result = nextIdentExcluding(o.it, o.scope.symbols, o.inSymChoice).skipAlias(n, c.config)
     while result == nil:
       o.scope = o.scope.parent
       if o.scope == nil: break
       result = firstIdentExcluding(o.it, o.scope.symbols,
-                                   n.sons[0].sym.name, o.inSymChoice).skipAlias(n)
+                                   n.sons[0].sym.name, o.inSymChoice).skipAlias(n, c.config)
 
-  if result != nil and result.kind == skStub: loadStub(result)
+  when false:
+    if result != nil and result.kind == skStub: loadStub(result)
 
-proc pickSym*(c: PContext, n: PNode; kind: TSymKind;
+proc pickSym*(c: PContext, n: PNode; kinds: set[TSymKind];
               flags: TSymFlags = {}): PSym =
   var o: TOverloadIter
   var a = initOverloadIter(o, c, n)
   while a != nil:
-    if a.kind == kind and flags <= a.flags:
-      return a
+    if a.kind in kinds and flags <= a.flags:
+      if result == nil: result = a
+      else: return nil # ambiguous
     a = nextOverloadIter(o, c, n)
 
-proc isInfixAs*(n: PNode): bool =
-  return n.kind == nkInfix and considerQuotedIdent(n[0]).s == "as"
\ No newline at end of file
diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim
index 245cc5f61..d199abcc7 100644
--- a/compiler/lowerings.nim
+++ b/compiler/lowerings.nim
@@ -12,26 +12,27 @@
 const
   genPrefix* = ":tmp"         # prefix for generated names
 
-import ast, astalgo, types, idents, magicsys, msgs, options
+import ast, astalgo, types, idents, magicsys, msgs, options, modulegraphs,
+  lineinfos
 from trees import getMagic
 
 proc newDeref*(n: PNode): PNode {.inline.} =
   result = newNodeIT(nkHiddenDeref, n.info, n.typ.sons[0])
   addSon(result, n)
 
-proc newTupleAccess*(tup: PNode, i: int): PNode =
+proc newTupleAccess*(g: ModuleGraph; tup: PNode, i: int): PNode =
   result = newNodeIT(nkBracketExpr, tup.info, tup.typ.skipTypes(
                      abstractInst).sons[i])
   addSon(result, copyTree(tup))
-  var lit = newNodeIT(nkIntLit, tup.info, getSysType(tyInt))
+  var lit = newNodeIT(nkIntLit, tup.info, getSysType(g, tup.info, tyInt))
   lit.intVal = i
   addSon(result, lit)
 
 proc addVar*(father, v: PNode) =
   var vpart = newNodeI(nkIdentDefs, v.info, 3)
   vpart.sons[0] = v
-  vpart.sons[1] = ast.emptyNode
-  vpart.sons[2] = ast.emptyNode
+  vpart.sons[1] = newNodeI(nkEmpty, v.info)
+  vpart.sons[2] = vpart[1]
   addSon(father, vpart)
 
 proc newAsgnStmt(le, ri: PNode): PNode =
@@ -44,12 +45,12 @@ proc newFastAsgnStmt(le, ri: PNode): PNode =
   result.sons[0] = le
   result.sons[1] = ri
 
-proc lowerTupleUnpacking*(n: PNode; owner: PSym): PNode =
+proc lowerTupleUnpacking*(g: ModuleGraph; n: PNode; owner: PSym): PNode =
   assert n.kind == nkVarTuple
   let value = n.lastSon
   result = newNodeI(nkStmtList, n.info)
 
-  var temp = newSym(skTemp, getIdent(genPrefix), owner, value.info)
+  var temp = newSym(skTemp, getIdent(g.cache, genPrefix), owner, value.info, g.config.options)
   temp.typ = skipTypes(value.typ, abstractInst)
   incl(temp.flags, sfFromGeneric)
 
@@ -61,7 +62,7 @@ proc lowerTupleUnpacking*(n: PNode; owner: PSym): PNode =
   result.add newAsgnStmt(tempAsNode, value)
   for i in 0 .. n.len-3:
     if n.sons[i].kind == nkSym: v.addVar(n.sons[i])
-    result.add newAsgnStmt(n.sons[i], newTupleAccess(tempAsNode, i))
+    result.add newAsgnStmt(n.sons[i], newTupleAccess(g, tempAsNode, i))
 
 proc newTupleAccessRaw*(tup: PNode, i: int): PNode =
   result = newNodeI(nkBracketExpr, tup.info)
@@ -70,17 +71,20 @@ proc newTupleAccessRaw*(tup: PNode, i: int): PNode =
   lit.intVal = i
   addSon(result, lit)
 
-proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode =
+proc newTryFinally*(body, final: PNode): PNode =
+  result = newTree(nkTryStmt, body, newTree(nkFinally, final))
+
+proc lowerTupleUnpackingForAsgn*(g: ModuleGraph; n: PNode; owner: PSym): PNode =
   let value = n.lastSon
   result = newNodeI(nkStmtList, n.info)
 
-  var temp = newSym(skTemp, getIdent(genPrefix), owner, value.info)
+  var temp = newSym(skTemp, getIdent(g.cache, "_"), owner, value.info, owner.options)
   var v = newNodeI(nkLetSection, value.info)
-  let tempAsNode = newIdentNode(getIdent(genPrefix & $temp.id), value.info)
+  let tempAsNode = newSymNode(temp) #newIdentNode(getIdent(genPrefix & $temp.id), value.info)
 
   var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
   vpart.sons[0] = tempAsNode
-  vpart.sons[1] = ast.emptyNode
+  vpart.sons[1] = newNodeI(nkEmpty, value.info)
   vpart.sons[2] = value
   addSon(v, vpart)
   result.add(v)
@@ -89,10 +93,10 @@ proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode =
   for i in 0 .. lhs.len-1:
     result.add newAsgnStmt(lhs.sons[i], newTupleAccessRaw(tempAsNode, i))
 
-proc lowerSwap*(n: PNode; owner: PSym): PNode =
+proc lowerSwap*(g: ModuleGraph; n: PNode; owner: PSym): PNode =
   result = newNodeI(nkStmtList, n.info)
   # note: cannot use 'skTemp' here cause we really need the copy for the VM :-(
-  var temp = newSym(skVar, getIdent(genPrefix), owner, n.info)
+  var temp = newSym(skVar, getIdent(g.cache, genPrefix), owner, n.info, owner.options)
   temp.typ = n.sons[1].typ
   incl(temp.flags, sfFromGeneric)
 
@@ -101,7 +105,7 @@ proc lowerSwap*(n: PNode; owner: PSym): PNode =
 
   var vpart = newNodeI(nkIdentDefs, v.info, 3)
   vpart.sons[0] = tempAsNode
-  vpart.sons[1] = ast.emptyNode
+  vpart.sons[1] = newNodeI(nkEmpty, v.info)
   vpart.sons[2] = n[1]
   addSon(v, vpart)
 
@@ -109,17 +113,19 @@ proc lowerSwap*(n: PNode; owner: PSym): PNode =
   result.add newFastAsgnStmt(n[1], n[2])
   result.add newFastAsgnStmt(n[2], tempAsNode)
 
-proc createObj*(owner: PSym, info: TLineInfo): PType =
+proc createObj*(g: ModuleGraph; owner: PSym, info: TLineInfo; final=true): PType =
   result = newType(tyObject, owner)
-  rawAddSon(result, nil)
-  incl result.flags, tfFinal
+  if final:
+    rawAddSon(result, nil)
+    incl result.flags, tfFinal
+  else:
+    rawAddSon(result, getCompilerProc(g, "RootObj").typ)
   result.n = newNodeI(nkRecList, info)
-  when true:
-    let s = newSym(skType, getIdent("Env_" & info.toFilename & "_" & $info.line),
-                   owner, info)
-    incl s.flags, sfAnon
-    s.typ = result
-    result.sym = s
+  let s = newSym(skType, getIdent(g.cache, "Env_" & toFilename(g.config, info)),
+                  owner, info, owner.options)
+  incl s.flags, sfAnon
+  s.typ = result
+  result.sym = s
 
 proc rawAddField*(obj: PType; field: PSym) =
   assert field.kind == skField
@@ -137,41 +143,98 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
   addSon(result, newSymNode(field))
   result.typ = field.typ
 
-proc addField*(obj: PType; s: PSym) =
+proc rawDirectAccess*(obj, field: PSym): PNode =
+  # returns a.field as a node
+  assert field.kind == skField
+  result = newNodeI(nkDotExpr, field.info)
+  addSon(result, newSymNode obj)
+  addSon(result, newSymNode field)
+  result.typ = field.typ
+
+proc lookupInRecord(n: PNode, id: int): PSym =
+  result = nil
+  case n.kind
+  of nkRecList:
+    for i in countup(0, sonsLen(n) - 1):
+      result = lookupInRecord(n.sons[i], id)
+      if result != nil: return
+  of nkRecCase:
+    if n.sons[0].kind != nkSym: return
+    result = lookupInRecord(n.sons[0], id)
+    if result != nil: return
+    for i in countup(1, sonsLen(n) - 1):
+      case n.sons[i].kind
+      of nkOfBranch, nkElse:
+        result = lookupInRecord(lastSon(n.sons[i]), id)
+        if result != nil: return
+      else: discard
+  of nkSym:
+    if n.sym.id == -abs(id): result = n.sym
+  else: discard
+
+proc addField*(obj: PType; s: PSym; cache: IdentCache) =
   # because of 'gensym' support, we have to mangle the name with its ID.
   # This is hacky but the clean solution is much more complex than it looks.
-  var field = newSym(skField, getIdent(s.name.s & $s.id), s.owner, s.info)
+  var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), s.owner, s.info,
+                     s.options)
+  field.id = -s.id
   let t = skipIntLit(s.typ)
   field.typ = t
   assert t.kind != tyStmt
   field.position = sonsLen(obj.n)
   addSon(obj.n, newSymNode(field))
 
-proc addUniqueField*(obj: PType; s: PSym) =
-  let fieldName = getIdent(s.name.s & $s.id)
-  if lookupInRecord(obj.n, fieldName) == nil:
-    var field = newSym(skField, fieldName, s.owner, s.info)
+proc addUniqueField*(obj: PType; s: PSym; cache: IdentCache): PSym {.discardable.} =
+  result = lookupInRecord(obj.n, s.id)
+  if result == nil:
+    var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len), s.owner, s.info,
+                       s.options)
+    field.id = -s.id
     let t = skipIntLit(s.typ)
     field.typ = t
     assert t.kind != tyStmt
     field.position = sonsLen(obj.n)
     addSon(obj.n, newSymNode(field))
+    result = field
 
 proc newDotExpr(obj, b: PSym): PNode =
   result = newNodeI(nkDotExpr, obj.info)
-  let field = getSymFromList(obj.typ.n, getIdent(b.name.s & $b.id))
+  let field = lookupInRecord(obj.typ.n, b.id)
   assert field != nil, b.name.s
   addSon(result, newSymNode(obj))
   addSon(result, newSymNode(field))
   result.typ = field.typ
 
-proc indirectAccess*(a: PNode, b: string, info: TLineInfo): PNode =
+proc indirectAccess*(a: PNode, b: int, info: TLineInfo): PNode =
   # returns a[].b as a node
   var deref = newNodeI(nkHiddenDeref, info)
   deref.typ = a.typ.skipTypes(abstractInst).sons[0]
   var t = deref.typ.skipTypes(abstractInst)
   var field: PSym
-  let bb = getIdent(b)
+  while true:
+    assert t.kind == tyObject
+    field = lookupInRecord(t.n, b)
+    if field != nil: break
+    t = t.sons[0]
+    if t == nil: break
+    t = t.skipTypes(skipPtrs)
+  #if field == nil:
+  #  echo "FIELD ", b
+  #  debug deref.typ
+  assert field != nil
+  addSon(deref, a)
+  result = newNodeI(nkDotExpr, info)
+  addSon(result, deref)
+  addSon(result, newSymNode(field))
+  result.typ = field.typ
+
+proc indirectAccess(a: PNode, b: string, info: TLineInfo; cache: IdentCache): PNode =
+  # returns a[].b as a node
+  var deref = newNodeI(nkHiddenDeref, info)
+  deref.typ = a.typ.skipTypes(abstractInst).sons[0]
+  var t = deref.typ.skipTypes(abstractInst)
+  var field: PSym
+  let bb = getIdent(cache, b)
   while true:
     assert t.kind == tyObject
     field = getSymFromList(t.n, bb)
@@ -182,7 +245,7 @@ proc indirectAccess*(a: PNode, b: string, info: TLineInfo): PNode =
   #if field == nil:
   #  echo "FIELD ", b
   #  debug deref.typ
-  internalAssert field != nil
+  assert field != nil
   addSon(deref, a)
   result = newNodeI(nkDotExpr, info)
   addSon(result, deref)
@@ -191,11 +254,10 @@ proc indirectAccess*(a: PNode, b: string, info: TLineInfo): PNode =
 
 proc getFieldFromObj*(t: PType; v: PSym): PSym =
   assert v.kind != skField
-  let fieldName = getIdent(v.name.s & $v.id)
   var t = t
   while true:
     assert t.kind == tyObject
-    result = getSymFromList(t.n, fieldName)
+    result = lookupInRecord(t.n, v.id)
     if result != nil: break
     t = t.sons[0]
     if t == nil: break
@@ -203,7 +265,7 @@ proc getFieldFromObj*(t: PType; v: PSym): PSym =
 
 proc indirectAccess*(a: PNode, b: PSym, info: TLineInfo): PNode =
   # returns a[].b as a node
-  result = indirectAccess(a, b.name.s & $b.id, info)
+  result = indirectAccess(a, b.id, info)
 
 proc indirectAccess*(a, b: PSym, info: TLineInfo): PNode =
   result = indirectAccess(newSymNode(a), b, info)
@@ -219,15 +281,16 @@ proc genDeref*(n: PNode): PNode =
                      n.typ.skipTypes(abstractInst).sons[0])
   result.add n
 
-proc callCodegenProc*(name: string, arg1: PNode;
-                      arg2, arg3, optionalArgs: PNode = nil): PNode =
-  result = newNodeI(nkCall, arg1.info)
-  let sym = magicsys.getCompilerProc(name)
+proc callCodegenProc*(g: ModuleGraph; name: string;
+                      info: TLineInfo = unknownLineInfo();
+                      arg1, arg2, arg3, optionalArgs: PNode = nil): PNode =
+  result = newNodeI(nkCall, info)
+  let sym = magicsys.getCompilerProc(g, name)
   if sym == nil:
-    localError(arg1.info, errSystemNeeds, name)
+    localError(g.config, info, "system module needs: " & name)
   else:
     result.add newSymNode(sym)
-    result.add arg1
+    if arg1 != nil: result.add arg1
     if arg2 != nil: result.add arg2
     if arg3 != nil: result.add arg3
     if optionalArgs != nil:
@@ -271,26 +334,39 @@ proc typeNeedsNoDeepCopy(t: PType): bool =
   # note that seq[T] is fine, but 'var seq[T]' is not, so we need to skip 'var'
   # for the stricter check and likewise we can skip 'seq' for a less
   # strict check:
-  if t.kind in {tyVar, tySequence}: t = t.sons[0]
+  if t.kind in {tyVar, tyLent, tySequence}: t = t.lastSon
   result = not containsGarbageCollectedRef(t)
 
-proc addLocalVar(varSection, varInit: PNode; owner: PSym; typ: PType;
+proc hoistExpr*(varSection, expr: PNode, name: PIdent, owner: PSym): PSym =
+  result = newSym(skLet, name, owner, varSection.info, owner.options)
+  result.flags.incl sfHoisted
+  result.typ = expr.typ
+
+  var varDef = newNodeI(nkIdentDefs, varSection.info, 3)
+  varDef.sons[0] = newSymNode(result)
+  varDef.sons[1] = newNodeI(nkEmpty, varSection.info)
+  varDef.sons[2] = expr
+
+  varSection.add varDef
+
+proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; owner: PSym; typ: PType;
                  v: PNode; useShallowCopy=false): PSym =
-  result = newSym(skTemp, getIdent(genPrefix), owner, varSection.info)
+  result = newSym(skTemp, getIdent(g.cache, genPrefix), owner, varSection.info,
+                  owner.options)
   result.typ = typ
   incl(result.flags, sfFromGeneric)
 
   var vpart = newNodeI(nkIdentDefs, varSection.info, 3)
   vpart.sons[0] = newSymNode(result)
-  vpart.sons[1] = ast.emptyNode
-  vpart.sons[2] = if varInit.isNil: v else: ast.emptyNode
+  vpart.sons[1] = newNodeI(nkEmpty, varSection.info)
+  vpart.sons[2] = if varInit.isNil: v else: vpart[1]
   varSection.add vpart
   if varInit != nil:
     if useShallowCopy and typeNeedsNoDeepCopy(typ):
       varInit.add newFastAsgnStmt(newSymNode(result), v)
     else:
       let deepCopyCall = newNodeI(nkCall, varInit.info, 3)
-      deepCopyCall.sons[0] = newSymNode(getSysMagic("deepCopy", mDeepCopy))
+      deepCopyCall.sons[0] = newSymNode(getSysMagic(g, varSection.info, "deepCopy", mDeepCopy))
       deepCopyCall.sons[1] = newSymNode(result)
       deepCopyCall.sons[2] = v
       varInit.add deepCopyCall
@@ -325,58 +401,64 @@ stmtList:
 
 """
 
-proc createWrapperProc(f: PNode; threadParam, argsParam: PSym;
+proc createWrapperProc(g: ModuleGraph; f: PNode; threadParam, argsParam: PSym;
                        varSection, varInit, call, barrier, fv: PNode;
                        spawnKind: TSpawnResult): PSym =
   var body = newNodeI(nkStmtList, f.info)
+  body.flags.incl nfTransf # do not transform further
+
   var threadLocalBarrier: PSym
   if barrier != nil:
     var varSection2 = newNodeI(nkVarSection, barrier.info)
-    threadLocalBarrier = addLocalVar(varSection2, nil, argsParam.owner,
+    threadLocalBarrier = addLocalVar(g, varSection2, nil, argsParam.owner,
                                      barrier.typ, barrier)
     body.add varSection2
-    body.add callCodegenProc("barrierEnter", threadLocalBarrier.newSymNode)
+    body.add callCodegenProc(g, "barrierEnter", threadLocalBarrier.info,
+      threadLocalBarrier.newSymNode)
   var threadLocalProm: PSym
   if spawnKind == srByVar:
-    threadLocalProm = addLocalVar(varSection, nil, argsParam.owner, fv.typ, fv)
+    threadLocalProm = addLocalVar(g, varSection, nil, argsParam.owner, fv.typ, fv)
   elif fv != nil:
-    internalAssert fv.typ.kind == tyGenericInst
-    threadLocalProm = addLocalVar(varSection, nil, argsParam.owner, fv.typ, fv)
+    internalAssert g.config, fv.typ.kind == tyGenericInst
+    threadLocalProm = addLocalVar(g, varSection, nil, argsParam.owner, fv.typ, fv)
   body.add varSection
   body.add varInit
   if fv != nil and spawnKind != srByVar:
     # generate:
     #   fv.owner = threadParam
     body.add newAsgnStmt(indirectAccess(threadLocalProm.newSymNode,
-      "owner", fv.info), threadParam.newSymNode)
+      "owner", fv.info, g.cache), threadParam.newSymNode)
 
-  body.add callCodegenProc("nimArgsPassingDone", threadParam.newSymNode)
+  body.add callCodegenProc(g, "nimArgsPassingDone", threadParam.info,
+    threadParam.newSymNode)
   if spawnKind == srByVar:
     body.add newAsgnStmt(genDeref(threadLocalProm.newSymNode), call)
   elif fv != nil:
     let fk = fv.typ.sons[1].flowVarKind
     if fk == fvInvalid:
-      localError(f.info, "cannot create a flowVar of type: " &
+      localError(g.config, f.info, "cannot create a flowVar of type: " &
         typeToString(fv.typ.sons[1]))
     body.add newAsgnStmt(indirectAccess(threadLocalProm.newSymNode,
-      if fk == fvGC: "data" else: "blob", fv.info), call)
+      if fk == fvGC: "data" else: "blob", fv.info, g.cache), call)
     if fk == fvGC:
       let incRefCall = newNodeI(nkCall, fv.info, 2)
-      incRefCall.sons[0] = newSymNode(getSysMagic("GCref", mGCref))
+      incRefCall.sons[0] = newSymNode(getSysMagic(g, fv.info, "GCref", mGCref))
       incRefCall.sons[1] = indirectAccess(threadLocalProm.newSymNode,
-                                          "data", fv.info)
+                                          "data", fv.info, g.cache)
       body.add incRefCall
     if barrier == nil:
       # by now 'fv' is shared and thus might have beeen overwritten! we need
       # to use the thread-local view instead:
-      body.add callCodegenProc("nimFlowVarSignal", threadLocalProm.newSymNode)
+      body.add callCodegenProc(g, "nimFlowVarSignal", threadLocalProm.info,
+        threadLocalProm.newSymNode)
   else:
     body.add call
   if barrier != nil:
-    body.add callCodegenProc("barrierLeave", threadLocalBarrier.newSymNode)
+    body.add callCodegenProc(g, "barrierLeave", threadLocalBarrier.info,
+      threadLocalBarrier.newSymNode)
 
   var params = newNodeI(nkFormalParams, f.info)
-  params.add emptyNode
+  params.add newNodeI(nkEmpty, f.info)
   params.add threadParam.newSymNode
   params.add argsParam.newSymNode
 
@@ -390,38 +472,43 @@ proc createWrapperProc(f: PNode; threadParam, argsParam: PSym;
   t.n.add argsParam.newSymNode
 
   let name = (if f.kind == nkSym: f.sym.name.s else: genPrefix) & "Wrapper"
-  result = newSym(skProc, getIdent(name), argsParam.owner, f.info)
-  result.ast = newProcNode(nkProcDef, f.info, body, params, newSymNode(result))
+  result = newSym(skProc, getIdent(g.cache, name), argsParam.owner, f.info,
+                  argsParam.options)
+  let emptyNode = newNodeI(nkEmpty, f.info)
+  result.ast = newProcNode(nkProcDef, f.info, body = body,
+      params = params, name = newSymNode(result), pattern = emptyNode,
+      genericParams = emptyNode, pragmas = emptyNode,
+      exceptions = emptyNode)
   result.typ = t
 
 proc createCastExpr(argsParam: PSym; objType: PType): PNode =
   result = newNodeI(nkCast, argsParam.info)
-  result.add emptyNode
+  result.add newNodeI(nkEmpty, argsParam.info)
   result.add newSymNode(argsParam)
   result.typ = newType(tyPtr, objType.owner)
   result.typ.rawAddSon(objType)
 
-proc setupArgsForConcurrency(n: PNode; objType: PType; scratchObj: PSym,
+proc setupArgsForConcurrency(g: ModuleGraph; n: PNode; objType: PType; scratchObj: PSym,
                              castExpr, call,
                              varSection, varInit, result: PNode) =
   let formals = n[0].typ.n
-  let tmpName = getIdent(genPrefix)
-  for i in 1 .. <n.len:
+  let tmpName = getIdent(g.cache, genPrefix)
+  for i in 1 ..< n.len:
     # we pick n's type here, which hopefully is 'tyArray' and not
     # 'tyOpenArray':
     var argType = n[i].typ.skipTypes(abstractInst)
-    if i < formals.len and formals[i].typ.kind == tyVar:
-      localError(n[i].info, "'spawn'ed function cannot have a 'var' parameter")
+    if i < formals.len and formals[i].typ.kind in {tyVar, tyLent}:
+      localError(g.config, n[i].info, "'spawn'ed function cannot have a 'var' parameter")
     #elif containsTyRef(argType):
     #  localError(n[i].info, "'spawn'ed function cannot refer to 'ref'/closure")
 
     let fieldname = if i < formals.len: formals[i].sym.name else: tmpName
-    var field = newSym(skField, fieldname, objType.owner, n.info)
+    var field = newSym(skField, fieldname, objType.owner, n.info, g.config.options)
     field.typ = argType
-    objType.addField(field)
+    objType.addField(field, g.cache)
     result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n[i])
 
-    let temp = addLocalVar(varSection, varInit, objType.owner, argType,
+    let temp = addLocalVar(g, varSection, varInit, objType.owner, argType,
                            indirectAccess(castExpr, field, n.info))
     call.add(newSymNode(temp))
 
@@ -442,27 +529,27 @@ proc getRoot*(n: PNode): PSym =
     if getMagic(n) == mSlice: result = getRoot(n.sons[1])
   else: discard
 
-proc newIntLit*(value: BiggestInt): PNode =
+proc newIntLit*(g: ModuleGraph; info: TLineInfo; value: BiggestInt): PNode =
   result = nkIntLit.newIntNode(value)
-  result.typ = getSysType(tyInt)
+  result.typ = getSysType(g, info, tyInt)
 
-proc genHigh*(n: PNode): PNode =
+proc genHigh*(g: ModuleGraph; n: PNode): PNode =
   if skipTypes(n.typ, abstractVar).kind == tyArray:
-    result = newIntLit(lastOrd(skipTypes(n.typ, abstractVar)))
+    result = newIntLit(g, n.info, lastOrd(g.config, skipTypes(n.typ, abstractVar)))
   else:
     result = newNodeI(nkCall, n.info, 2)
-    result.typ = getSysType(tyInt)
-    result.sons[0] = newSymNode(getSysMagic("high", mHigh))
+    result.typ = getSysType(g, n.info, tyInt)
+    result.sons[0] = newSymNode(getSysMagic(g, n.info, "high", mHigh))
     result.sons[1] = n
 
-proc setupArgsForParallelism(n: PNode; objType: PType; scratchObj: PSym;
+proc setupArgsForParallelism(g: ModuleGraph; n: PNode; objType: PType; scratchObj: PSym;
                              castExpr, call,
                              varSection, varInit, result: PNode) =
   let formals = n[0].typ.n
-  let tmpName = getIdent(genPrefix)
+  let tmpName = getIdent(g.cache, genPrefix)
   # we need to copy the foreign scratch object fields into local variables
   # for correctness: These are called 'threadLocal' here.
-  for i in 1 .. <n.len:
+  for i in 1 ..< n.len:
     let n = n[i]
     let argType = skipTypes(if i < formals.len: formals[i].typ else: n.typ,
                             abstractInst)
@@ -470,73 +557,73 @@ proc setupArgsForParallelism(n: PNode; objType: PType; scratchObj: PSym;
     #  localError(n.info, "'spawn'ed function cannot refer to 'ref'/closure")
 
     let fieldname = if i < formals.len: formals[i].sym.name else: tmpName
-    var field = newSym(skField, fieldname, objType.owner, n.info)
+    var field = newSym(skField, fieldname, objType.owner, n.info, g.config.options)
 
     if argType.kind in {tyVarargs, tyOpenArray}:
       # important special case: we always create a zero-copy slice:
       let slice = newNodeI(nkCall, n.info, 4)
       slice.typ = n.typ
-      slice.sons[0] = newSymNode(createMagic("slice", mSlice))
-      slice.sons[0].typ = getSysType(tyInt) # fake type
-      var fieldB = newSym(skField, tmpName, objType.owner, n.info)
-      fieldB.typ = getSysType(tyInt)
-      objType.addField(fieldB)
+      slice.sons[0] = newSymNode(createMagic(g, "slice", mSlice))
+      slice.sons[0].typ = getSysType(g, n.info, tyInt) # fake type
+      var fieldB = newSym(skField, tmpName, objType.owner, n.info, g.config.options)
+      fieldB.typ = getSysType(g, n.info, tyInt)
+      objType.addField(fieldB, g.cache)
 
       if getMagic(n) == mSlice:
         let a = genAddrOf(n[1])
         field.typ = a.typ
-        objType.addField(field)
+        objType.addField(field, g.cache)
         result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a)
 
-        var fieldA = newSym(skField, tmpName, objType.owner, n.info)
-        fieldA.typ = getSysType(tyInt)
-        objType.addField(fieldA)
+        var fieldA = newSym(skField, tmpName, objType.owner, n.info, g.config.options)
+        fieldA.typ = getSysType(g, n.info, tyInt)
+        objType.addField(fieldA, g.cache)
         result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldA), n[2])
         result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), n[3])
 
-        let threadLocal = addLocalVar(varSection,nil, objType.owner, fieldA.typ,
+        let threadLocal = addLocalVar(g, varSection,nil, objType.owner, fieldA.typ,
                                       indirectAccess(castExpr, fieldA, n.info),
                                       useShallowCopy=true)
         slice.sons[2] = threadLocal.newSymNode
       else:
         let a = genAddrOf(n)
         field.typ = a.typ
-        objType.addField(field)
+        objType.addField(field, g.cache)
         result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a)
-        result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), genHigh(n))
+        result.add newFastAsgnStmt(newDotExpr(scratchObj, fieldB), genHigh(g, n))
 
-        slice.sons[2] = newIntLit(0)
+        slice.sons[2] = newIntLit(g, n.info, 0)
       # the array itself does not need to go through a thread local variable:
       slice.sons[1] = genDeref(indirectAccess(castExpr, field, n.info))
 
-      let threadLocal = addLocalVar(varSection,nil, objType.owner, fieldB.typ,
+      let threadLocal = addLocalVar(g, varSection,nil, objType.owner, fieldB.typ,
                                     indirectAccess(castExpr, fieldB, n.info),
                                     useShallowCopy=true)
       slice.sons[3] = threadLocal.newSymNode
       call.add slice
-    elif (let size = computeSize(argType); size < 0 or size > 16) and
+    elif (let size = computeSize(g.config, argType); size < 0 or size > 16) and
         n.getRoot != nil:
       # it is more efficient to pass a pointer instead:
       let a = genAddrOf(n)
       field.typ = a.typ
-      objType.addField(field)
+      objType.addField(field, g.cache)
       result.add newFastAsgnStmt(newDotExpr(scratchObj, field), a)
-      let threadLocal = addLocalVar(varSection,nil, objType.owner, field.typ,
+      let threadLocal = addLocalVar(g, varSection,nil, objType.owner, field.typ,
                                     indirectAccess(castExpr, field, n.info),
                                     useShallowCopy=true)
       call.add(genDeref(threadLocal.newSymNode))
     else:
       # boring case
       field.typ = argType
-      objType.addField(field)
+      objType.addField(field, g.cache)
       result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n)
-      let threadLocal = addLocalVar(varSection, varInit,
+      let threadLocal = addLocalVar(g, varSection, varInit,
                                     objType.owner, field.typ,
                                     indirectAccess(castExpr, field, n.info),
                                     useShallowCopy=true)
       call.add(threadLocal.newSymNode)
 
-proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType;
+proc wrapProcForSpawn*(g: ModuleGraph; owner: PSym; spawnExpr: PNode; retType: PType;
                        barrier, dest: PNode = nil): PNode =
   # if 'barrier' != nil, then it is in a 'parallel' section and we
   # generate quite different code
@@ -544,35 +631,35 @@ proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType;
   let spawnKind = spawnResult(retType, barrier!=nil)
   case spawnKind
   of srVoid:
-    internalAssert dest == nil
+    internalAssert g.config, dest == nil
     result = newNodeI(nkStmtList, n.info)
   of srFlowVar:
-    internalAssert dest == nil
+    internalAssert g.config, dest == nil
     result = newNodeIT(nkStmtListExpr, n.info, retType)
   of srByVar:
-    if dest == nil: localError(n.info, "'spawn' must not be discarded")
+    if dest == nil: localError(g.config, n.info, "'spawn' must not be discarded")
     result = newNodeI(nkStmtList, n.info)
 
   if n.kind notin nkCallKinds:
-    localError(n.info, "'spawn' takes a call expression")
+    localError(g.config, n.info, "'spawn' takes a call expression")
     return
-  if optThreadAnalysis in gGlobalOptions:
+  if optThreadAnalysis in g.config.globalOptions:
     if {tfThread, tfNoSideEffect} * n[0].typ.flags == {}:
-      localError(n.info, "'spawn' takes a GC safe call expression")
+      localError(g.config, n.info, "'spawn' takes a GC safe call expression")
   var
-    threadParam = newSym(skParam, getIdent"thread", owner, n.info)
-    argsParam = newSym(skParam, getIdent"args", owner, n.info)
+    threadParam = newSym(skParam, getIdent(g.cache, "thread"), owner, n.info, g.config.options)
+    argsParam = newSym(skParam, getIdent(g.cache, "args"), owner, n.info, g.config.options)
   block:
-    let ptrType = getSysType(tyPointer)
+    let ptrType = getSysType(g, n.info, tyPointer)
     threadParam.typ = ptrType
     argsParam.typ = ptrType
     argsParam.position = 1
 
-  var objType = createObj(owner, n.info)
+  var objType = createObj(g, owner, n.info)
   incl(objType.flags, tfFinal)
   let castExpr = createCastExpr(argsParam, objType)
 
-  var scratchObj = newSym(skVar, getIdent"scratch", owner, n.info)
+  var scratchObj = newSym(skVar, getIdent(g.cache, "scratch"), owner, n.info, g.config.options)
   block:
     scratchObj.typ = objType
     incl(scratchObj.flags, sfFromGeneric)
@@ -585,65 +672,66 @@ proc wrapProcForSpawn*(owner: PSym; spawnExpr: PNode; retType: PType;
   # templates and macros are in fact valid here due to the nature of
   # the transformation:
   if fn.kind == nkClosure:
-    localError(n.info, "closure in spawn environment is not allowed")
+    localError(g.config, n.info, "closure in spawn environment is not allowed")
   if not (fn.kind == nkSym and fn.sym.kind in {skProc, skTemplate, skMacro,
-                                               skMethod, skConverter}):
+                                               skFunc, skMethod, skConverter}):
     # for indirect calls we pass the function pointer in the scratchObj
     var argType = n[0].typ.skipTypes(abstractInst)
-    var field = newSym(skField, getIdent"fn", owner, n.info)
+    var field = newSym(skField, getIdent(g.cache, "fn"), owner, n.info, g.config.options)
     field.typ = argType
-    objType.addField(field)
+    objType.addField(field, g.cache)
     result.add newFastAsgnStmt(newDotExpr(scratchObj, field), n[0])
     fn = indirectAccess(castExpr, field, n.info)
   elif fn.kind == nkSym and fn.sym.kind == skIterator:
-    localError(n.info, "iterator in spawn environment is not allowed")
+    localError(g.config, n.info, "iterator in spawn environment is not allowed")
   elif fn.typ.callConv == ccClosure:
-    localError(n.info, "closure in spawn environment is not allowed")
+    localError(g.config, n.info, "closure in spawn environment is not allowed")
 
   call.add(fn)
   var varSection = newNodeI(nkVarSection, n.info)
   var varInit = newNodeI(nkStmtList, n.info)
   if barrier.isNil:
-    setupArgsForConcurrency(n, objType, scratchObj, castExpr, call,
+    setupArgsForConcurrency(g, n, objType, scratchObj, castExpr, call,
                             varSection, varInit, result)
   else:
-    setupArgsForParallelism(n, objType, scratchObj, castExpr, call,
+    setupArgsForParallelism(g, n, objType, scratchObj, castExpr, call,
                             varSection, varInit, result)
 
   var barrierAsExpr: PNode = nil
   if barrier != nil:
     let typ = newType(tyPtr, owner)
-    typ.rawAddSon(magicsys.getCompilerProc("Barrier").typ)
-    var field = newSym(skField, getIdent"barrier", owner, n.info)
+    typ.rawAddSon(magicsys.getCompilerProc(g, "Barrier").typ)
+    var field = newSym(skField, getIdent(g.cache, "barrier"), owner, n.info, g.config.options)
     field.typ = typ
-    objType.addField(field)
+    objType.addField(field, g.cache)
     result.add newFastAsgnStmt(newDotExpr(scratchObj, field), barrier)
     barrierAsExpr = indirectAccess(castExpr, field, n.info)
 
   var fvField, fvAsExpr: PNode = nil
   if spawnKind == srFlowVar:
-    var field = newSym(skField, getIdent"fv", owner, n.info)
+    var field = newSym(skField, getIdent(g.cache, "fv"), owner, n.info, g.config.options)
     field.typ = retType
-    objType.addField(field)
+    objType.addField(field, g.cache)
     fvField = newDotExpr(scratchObj, field)
     fvAsExpr = indirectAccess(castExpr, field, n.info)
     # create flowVar:
     result.add newFastAsgnStmt(fvField, callProc(spawnExpr[^1]))
     if barrier == nil:
-      result.add callCodegenProc("nimFlowVarCreateSemaphore", fvField)
+      result.add callCodegenProc(g, "nimFlowVarCreateSemaphore", fvField.info,
+        fvField)
 
   elif spawnKind == srByVar:
-    var field = newSym(skField, getIdent"fv", owner, n.info)
+    var field = newSym(skField, getIdent(g.cache, "fv"), owner, n.info, g.config.options)
     field.typ = newType(tyPtr, objType.owner)
     field.typ.rawAddSon(retType)
-    objType.addField(field)
+    objType.addField(field, g.cache)
     fvAsExpr = indirectAccess(castExpr, field, n.info)
     result.add newFastAsgnStmt(newDotExpr(scratchObj, field), genAddrOf(dest))
 
-  let wrapper = createWrapperProc(fn, threadParam, argsParam,
+  let wrapper = createWrapperProc(g, fn, threadParam, argsParam,
                                   varSection, varInit, call,
                                   barrierAsExpr, fvAsExpr, spawnKind)
-  result.add callCodegenProc("nimSpawn" & $spawnExpr.len, wrapper.newSymNode,
-                             genAddrOf(scratchObj.newSymNode), nil, spawnExpr)
+  result.add callCodegenProc(g, "nimSpawn" & $spawnExpr.len, wrapper.info,
+    wrapper.newSymNode, genAddrOf(scratchObj.newSymNode), nil, spawnExpr)
 
   if spawnKind == srFlowVar: result.add fvField
diff --git a/compiler/macrocacheimpl.nim b/compiler/macrocacheimpl.nim
new file mode 100644
index 000000000..d23040763
--- /dev/null
+++ b/compiler/macrocacheimpl.nim
@@ -0,0 +1,79 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements helpers for the macro cache.
+
+import lineinfos, ast, modulegraphs, vmdef, magicsys
+
+proc recordInc*(c: PCtx; info: TLineInfo; key: string; by: BiggestInt) =
+  var recorded = newNodeI(nkCommentStmt, info)
+  recorded.add newStrNode("inc", info)
+  recorded.add newStrNode(key, info)
+  recorded.add newIntNode(nkIntLit, by)
+  c.graph.recordStmt(c.graph, c.module, recorded)
+
+proc recordPut*(c: PCtx; info: TLineInfo; key: string; k: string; val: PNode) =
+  var recorded = newNodeI(nkCommentStmt, info)
+  recorded.add newStrNode("put", info)
+  recorded.add newStrNode(key, info)
+  recorded.add newStrNode(k, info)
+  recorded.add copyTree(val)
+  c.graph.recordStmt(c.graph, c.module, recorded)
+
+proc recordAdd*(c: PCtx; info: TLineInfo; key: string; val: PNode) =
+  var recorded = newNodeI(nkCommentStmt, info)
+  recorded.add newStrNode("add", info)
+  recorded.add newStrNode(key, info)
+  recorded.add copyTree(val)
+  c.graph.recordStmt(c.graph, c.module, recorded)
+
+proc recordIncl*(c: PCtx; info: TLineInfo; key: string; val: PNode) =
+  var recorded = newNodeI(nkCommentStmt, info)
+  recorded.add newStrNode("incl", info)
+  recorded.add newStrNode(key, info)
+  recorded.add copyTree(val)
+  c.graph.recordStmt(c.graph, c.module, recorded)
+
+when false:
+  proc genCall3(g: ModuleGraph; m: TMagic; s: string; a, b, c: PNode): PNode =
+    newTree(nkStaticStmt, newTree(nkCall, createMagic(g, s, m).newSymNode, a, b, c))
+
+  proc genCall2(g: ModuleGraph; m: TMagic; s: string; a, b: PNode): PNode =
+    newTree(nkStaticStmt, newTree(nkCall, createMagic(g, s, m).newSymNode, a, b))
+
+  template nodeFrom(s: string): PNode =
+    var res = newStrNode(s, info)
+    res.typ = getSysType(g, info, tyString)
+    res
+
+  template nodeFrom(i: BiggestInt): PNode =
+    var res = newIntNode(i, info)
+    res.typ = getSysType(g, info, tyInt)
+    res
+
+  template nodeFrom(n: PNode): PNode = copyTree(n)
+
+  template record(call) =
+    g.recordStmt(g, c.module, call)
+
+  proc recordInc*(c: PCtx; info: TLineInfo; key: string; by: BiggestInt) =
+    let g = c.graph
+    record genCall2(mNccInc, "inc", nodeFrom key, nodeFrom by)
+
+  proc recordPut*(c: PCtx; info: TLineInfo; key: string; k: string; val: PNode) =
+    let g = c.graph
+    record genCall3(mNctPut, "[]=", nodeFrom key, nodeFrom k, nodeFrom val)
+
+  proc recordAdd*(c: PCtx; info: TLineInfo; key: string; val: PNode) =
+    let g = c.graph
+    record genCall2(mNcsAdd, "add", nodeFrom key, nodeFrom val)
+
+  proc recordIncl*(c: PCtx; info: TLineInfo; key: string; val: PNode) =
+    let g = c.graph
+    record genCall2(mNcsIncl, "incl", nodeFrom key, nodeFrom val)
diff --git a/compiler/magicsys.nim b/compiler/magicsys.nim
index 6a9d69082..aeeb489c0 100644
--- a/compiler/magicsys.nim
+++ b/compiler/magicsys.nim
@@ -10,63 +10,50 @@
 # Built-in types and compilerprocs are registered here.
 
 import
-  ast, astalgo, hashes, msgs, platform, nversion, times, idents, rodread
+  ast, astalgo, hashes, msgs, platform, nversion, times, idents,
+  modulegraphs, lineinfos
 
-var systemModule*: PSym
+export createMagic
 
-var
-  gSysTypes: array[TTypeKind, PType]
-  compilerprocs: TStrTable
-  exposed: TStrTable
+proc nilOrSysInt*(g: ModuleGraph): PType = g.sysTypes[tyInt]
 
-proc nilOrSysInt*: PType = gSysTypes[tyInt]
+proc registerSysType*(g: ModuleGraph; t: PType) =
+  if g.sysTypes[t.kind] == nil: g.sysTypes[t.kind] = t
 
-proc registerSysType*(t: PType) =
-  if gSysTypes[t.kind] == nil: gSysTypes[t.kind] = t
-
-proc newSysType(kind: TTypeKind, size: int): PType =
-  result = newType(kind, systemModule)
+proc newSysType(g: ModuleGraph; kind: TTypeKind, size: int): PType =
+  result = newType(kind, g.systemModule)
   result.size = size
   result.align = size.int16
 
-proc getSysSym*(name: string): PSym =
-  result = strTableGet(systemModule.tab, getIdent(name))
+proc getSysSym*(g: ModuleGraph; info: TLineInfo; name: string): PSym =
+  result = strTableGet(g.systemModule.tab, getIdent(g.cache, name))
   if result == nil:
-    rawMessage(errSystemNeeds, name)
-    result = newSym(skError, getIdent(name), systemModule, systemModule.info)
-    result.typ = newType(tyError, systemModule)
-  if result.kind == skStub: loadStub(result)
+    localError(g.config, info, "system module needs: " & name)
+    result = newSym(skError, getIdent(g.cache, name), g.systemModule, g.systemModule.info, {})
+    result.typ = newType(tyError, g.systemModule)
   if result.kind == skAlias: result = result.owner
 
-proc createMagic*(name: string, m: TMagic): PSym =
-  result = newSym(skProc, getIdent(name), nil, unknownLineInfo())
-  result.magic = m
-
-let
-  opNot* = createMagic("not", mNot)
-  opContains* = createMagic("contains", mInSet)
-
-proc getSysMagic*(name: string, m: TMagic): PSym =
+proc getSysMagic*(g: ModuleGraph; info: TLineInfo; name: string, m: TMagic): PSym =
   var ti: TIdentIter
-  let id = getIdent(name)
-  var r = initIdentIter(ti, systemModule.tab, id)
+  let id = getIdent(g.cache, name)
+  var r = initIdentIter(ti, g.systemModule.tab, id)
   while r != nil:
-    if r.kind == skStub: loadStub(r)
     if r.magic == m:
       # prefer the tyInt variant:
       if r.typ.sons[0] != nil and r.typ.sons[0].kind == tyInt: return r
       result = r
-    r = nextIdentIter(ti, systemModule.tab)
+    r = nextIdentIter(ti, g.systemModule.tab)
   if result != nil: return result
-  rawMessage(errSystemNeeds, name)
-  result = newSym(skError, id, systemModule, systemModule.info)
-  result.typ = newType(tyError, systemModule)
+  localError(g.config, info, "system module needs: " & name)
+  result = newSym(skError, id, g.systemModule, g.systemModule.info, {})
+  result.typ = newType(tyError, g.systemModule)
 
-proc sysTypeFromName*(name: string): PType =
-  result = getSysSym(name).typ
+proc sysTypeFromName*(g: ModuleGraph; info: TLineInfo; name: string): PType =
+  result = getSysSym(g, info, name).typ
 
-proc getSysType*(kind: TTypeKind): PType =
-  result = gSysTypes[kind]
+proc getSysType*(g: ModuleGraph; info: TLineInfo; kind: TTypeKind): PType =
+  template sysTypeFromName(s: string): untyped = sysTypeFromName(g, info, s)
+  result = g.sysTypes[kind]
   if result == nil:
     case kind
     of tyInt: result = sysTypeFromName("int")
@@ -88,112 +75,103 @@ proc getSysType*(kind: TTypeKind): PType =
     of tyString: result = sysTypeFromName("string")
     of tyCString: result = sysTypeFromName("cstring")
     of tyPointer: result = sysTypeFromName("pointer")
-    of tyNil: result = newSysType(tyNil, ptrSize)
-    else: internalError("request for typekind: " & $kind)
-    gSysTypes[kind] = result
+    of tyNil: result = newSysType(g, tyNil, g.config.target.ptrSize)
+    else: internalError(g.config, "request for typekind: " & $kind)
+    g.sysTypes[kind] = result
   if result.kind != kind:
-    internalError("wanted: " & $kind & " got: " & $result.kind)
-  if result == nil: internalError("type not found: " & $kind)
-
-var
-  intTypeCache: array[-5..64, PType]
+    internalError(g.config, "wanted: " & $kind & " got: " & $result.kind)
+  if result == nil: internalError(g.config, "type not found: " & $kind)
 
-proc resetSysTypes* =
-  systemModule = nil
-  initStrTable(compilerprocs)
-  initStrTable(exposed)
-  for i in low(gSysTypes)..high(gSysTypes):
-    gSysTypes[i] = nil
+proc resetSysTypes*(g: ModuleGraph) =
+  g.systemModule = nil
+  initStrTable(g.compilerprocs)
+  initStrTable(g.exposed)
+  for i in low(g.sysTypes)..high(g.sysTypes):
+    g.sysTypes[i] = nil
 
-  for i in low(intTypeCache)..high(intTypeCache):
-    intTypeCache[i] = nil
+  for i in low(g.intTypeCache)..high(g.intTypeCache):
+    g.intTypeCache[i] = nil
 
-proc getIntLitType*(literal: PNode): PType =
+proc getIntLitType*(g: ModuleGraph; literal: PNode): PType =
   # we cache some common integer literal types for performance:
   let value = literal.intVal
-  if value >= low(intTypeCache) and value <= high(intTypeCache):
-    result = intTypeCache[value.int]
+  if value >= low(g.intTypeCache) and value <= high(g.intTypeCache):
+    result = g.intTypeCache[value.int]
     if result == nil:
-      let ti = getSysType(tyInt)
+      let ti = getSysType(g, literal.info, tyInt)
       result = copyType(ti, ti.owner, false)
       result.n = literal
-      intTypeCache[value.int] = result
+      g.intTypeCache[value.int] = result
   else:
-    let ti = getSysType(tyInt)
+    let ti = getSysType(g, literal.info, tyInt)
     result = copyType(ti, ti.owner, false)
     result.n = literal
 
-proc getFloatLitType*(literal: PNode): PType =
+proc getFloatLitType*(g: ModuleGraph; literal: PNode): PType =
   # for now we do not cache these:
-  result = newSysType(tyFloat, size=8)
+  result = newSysType(g, tyFloat, size=8)
   result.n = literal
 
 proc skipIntLit*(t: PType): PType {.inline.} =
-  if t.n != nil:
-    if t.kind in {tyInt, tyFloat}:
-      return getSysType(t.kind)
-  result = t
+  if t.n != nil and t.kind in {tyInt, tyFloat}:
+    result = copyType(t, t.owner, false)
+    result.n = nil
+  else:
+    result = t
 
 proc addSonSkipIntLit*(father, son: PType) =
-  if isNil(father.sons): father.sons = @[]
+  when not defined(nimNoNilSeqs):
+    if isNil(father.sons): father.sons = @[]
   let s = son.skipIntLit
   add(father.sons, s)
   propagateToOwner(father, s)
 
-proc setIntLitType*(result: PNode) =
+proc setIntLitType*(g: ModuleGraph; result: PNode) =
   let i = result.intVal
-  case platform.intSize
-  of 8: result.typ = getIntLitType(result)
+  case g.config.target.intSize
+  of 8: result.typ = getIntLitType(g, result)
   of 4:
     if i >= low(int32) and i <= high(int32):
-      result.typ = getIntLitType(result)
+      result.typ = getIntLitType(g, result)
     else:
-      result.typ = getSysType(tyInt64)
+      result.typ = getSysType(g, result.info, tyInt64)
   of 2:
     if i >= low(int16) and i <= high(int16):
-      result.typ = getIntLitType(result)
+      result.typ = getIntLitType(g, result)
     elif i >= low(int32) and i <= high(int32):
-      result.typ = getSysType(tyInt32)
+      result.typ = getSysType(g, result.info, tyInt32)
     else:
-      result.typ = getSysType(tyInt64)
+      result.typ = getSysType(g, result.info, tyInt64)
   of 1:
     # 8 bit CPUs are insane ...
     if i >= low(int8) and i <= high(int8):
-      result.typ = getIntLitType(result)
+      result.typ = getIntLitType(g, result)
     elif i >= low(int16) and i <= high(int16):
-      result.typ = getSysType(tyInt16)
+      result.typ = getSysType(g, result.info, tyInt16)
     elif i >= low(int32) and i <= high(int32):
-      result.typ = getSysType(tyInt32)
+      result.typ = getSysType(g, result.info, tyInt32)
     else:
-      result.typ = getSysType(tyInt64)
-  else: internalError(result.info, "invalid int size")
+      result.typ = getSysType(g, result.info, tyInt64)
+  else:
+    internalError(g.config, result.info, "invalid int size")
 
-proc getCompilerProc*(name: string): PSym =
-  let ident = getIdent(name)
-  result = strTableGet(compilerprocs, ident)
-  if result == nil:
-    result = strTableGet(rodCompilerprocs, ident)
-    if result != nil:
-      strTableAdd(compilerprocs, result)
-      if result.kind == skStub: loadStub(result)
-      if result.kind == skAlias: result = result.owner
+proc getCompilerProc*(g: ModuleGraph; name: string): PSym =
+  let ident = getIdent(g.cache, name)
+  result = strTableGet(g.compilerprocs, ident)
 
-proc registerCompilerProc*(s: PSym) =
-  strTableAdd(compilerprocs, s)
+proc registerCompilerProc*(g: ModuleGraph; s: PSym) =
+  strTableAdd(g.compilerprocs, s)
 
-proc registerNimScriptSymbol*(s: PSym) =
+proc registerNimScriptSymbol*(g: ModuleGraph; s: PSym) =
   # Nimscript symbols must be al unique:
-  let conflict = strTableGet(exposed, s.name)
+  let conflict = strTableGet(g.exposed, s.name)
   if conflict == nil:
-    strTableAdd(exposed, s)
+    strTableAdd(g.exposed, s)
   else:
-    localError(s.info, "symbol conflicts with other .exportNims symbol at: " &
-      $conflict.info)
-
-proc getNimScriptSymbol*(name: string): PSym =
-  strTableGet(exposed, getIdent(name))
+    localError(g.config, s.info,
+      "symbol conflicts with other .exportNims symbol at: " & g.config$conflict.info)
 
-proc resetNimScriptSymbols*() = initStrTable(exposed)
+proc getNimScriptSymbol*(g: ModuleGraph; name: string): PSym =
+  strTableGet(g.exposed, getIdent(g.cache, name))
 
-initStrTable(compilerprocs)
-initStrTable(exposed)
+proc resetNimScriptSymbols*(g: ModuleGraph) = initStrTable(g.exposed)
diff --git a/compiler/main.nim b/compiler/main.nim
index 5f86e6188..4e28ed483 100644
--- a/compiler/main.nim
+++ b/compiler/main.nim
@@ -9,276 +9,327 @@
 
 # implements the command dispatcher and several commands
 
+when not defined(nimcore):
+  {.error: "nimcore MUST be defined for Nim's core tooling".}
+
 import
   llstream, strutils, ast, astalgo, lexer, syntaxes, renderer, options, msgs,
-  os, condsyms, rodread, rodwrite, times,
-  wordrecg, sem, semdata, idents, passes, docgen, extccomp,
-  cgen, jsgen, json, nversion,
+  os, condsyms, times,
+  wordrecg, sem, semdata, idents, passes, extccomp,
+  cgen, json, nversion,
   platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen,
-  docgen2, service, parser, modules, ccgutils, sigmatch, ropes,
-  modulegraphs
+  parser, modules, ccgutils, sigmatch, ropes,
+  modulegraphs, tables, rod, lineinfos, pathutils
 
-from magicsys import systemModule, resetSysTypes
+when not defined(leanCompiler):
+  import jsgen, docgen, docgen2
 
-proc rodPass =
-  if optSymbolFiles in gGlobalOptions:
-    registerPass(rodwritePass)
+from magicsys import resetSysTypes
 
-proc codegenPass =
-  registerPass cgenPass
+proc codegenPass(g: ModuleGraph) =
+  registerPass g, cgenPass
 
-proc semanticPasses =
-  registerPass verbosePass
-  registerPass semPass
+proc semanticPasses(g: ModuleGraph) =
+  registerPass g, verbosePass
+  registerPass g, semPass
 
-proc writeDepsFile(g: ModuleGraph; project: string) =
-  let f = open(changeFileExt(project, "deps"), fmWrite)
+proc writeDepsFile(g: ModuleGraph; project: AbsoluteFile) =
+  let f = open(changeFileExt(project, "deps").string, fmWrite)
   for m in g.modules:
     if m != nil:
-      f.writeLine(toFullPath(m.position.int32))
+      f.writeLine(toFullPath(g.config, m.position.FileIndex))
+  for k in g.inclToMod.keys:
+    if g.getModule(k).isNil:  # don't repeat includes which are also modules
+      f.writeLine(toFullPath(g.config, k))
   f.close()
 
-proc commandGenDepend(graph: ModuleGraph; cache: IdentCache) =
-  semanticPasses()
-  registerPass(gendependPass)
-  #registerPass(cleanupPass)
-  compileProject(graph, cache)
-  writeDepsFile(graph, gProjectFull)
-  generateDot(gProjectFull)
-  execExternalProgram("dot -Tpng -o" & changeFileExt(gProjectFull, "png") &
-      ' ' & changeFileExt(gProjectFull, "dot"))
-
-proc commandCheck(graph: ModuleGraph; cache: IdentCache) =
-  msgs.gErrorMax = high(int)  # do not stop after first error
-  defineSymbol("nimcheck")
-  semanticPasses()            # use an empty backend for semantic checking only
-  rodPass()
-  compileProject(graph, cache)
-
-proc commandDoc2(graph: ModuleGraph; cache: IdentCache; json: bool) =
-  msgs.gErrorMax = high(int)  # do not stop after first error
-  semanticPasses()
-  if json: registerPass(docgen2JsonPass)
-  else: registerPass(docgen2Pass)
-  #registerPass(cleanupPass())
-  compileProject(graph, cache)
-  finishDoc2Pass(gProjectName)
-
-proc commandCompileToC(graph: ModuleGraph; cache: IdentCache) =
-  extccomp.initVars()
-  semanticPasses()
-  registerPass(cgenPass)
-  rodPass()
-  #registerPass(cleanupPass())
-
-  compileProject(graph, cache)
-  cgenWriteModules(graph.backend, graph.config)
-  if gCmd != cmdRun:
-    let proj = changeFileExt(gProjectFull, "")
-    extccomp.callCCompiler(proj)
-    extccomp.writeJsonBuildInstructions(proj)
-
-proc commandCompileToJS(graph: ModuleGraph; cache: IdentCache) =
-  #incl(gGlobalOptions, optSafeCode)
-  setTarget(osJS, cpuJS)
-  #initDefines()
-  defineSymbol("nimrod") # 'nimrod' is always defined
-  defineSymbol("ecmascript") # For backward compatibility
-  defineSymbol("js")
-  if gCmd == cmdCompileToPHP: defineSymbol("nimphp")
-  semanticPasses()
-  registerPass(JSgenPass)
-  compileProject(graph, cache)
-
-proc interactivePasses(graph: ModuleGraph; cache: IdentCache) =
-  #incl(gGlobalOptions, optSafeCode)
-  #setTarget(osNimrodVM, cpuNimrodVM)
-  initDefines()
-  defineSymbol("nimscript")
-  when hasFFI: defineSymbol("nimffi")
-  registerPass(verbosePass)
-  registerPass(semPass)
-  registerPass(evalPass)
-
-proc commandInteractive(graph: ModuleGraph; cache: IdentCache) =
-  msgs.gErrorMax = high(int)  # do not stop after first error
-  interactivePasses(graph, cache)
-  compileSystemModule(graph, cache)
-  if commandArgs.len > 0:
-    discard graph.compileModule(fileInfoIdx(gProjectFull), cache, {})
+proc commandGenDepend(graph: ModuleGraph) =
+  semanticPasses(graph)
+  registerPass(graph, gendependPass)
+  compileProject(graph)
+  let project = graph.config.projectFull
+  writeDepsFile(graph, project)
+  generateDot(graph, project)
+  execExternalProgram(graph.config, "dot -Tpng -o" &
+      changeFileExt(project, "png").string &
+      ' ' & changeFileExt(project, "dot").string)
+
+proc commandCheck(graph: ModuleGraph) =
+  graph.config.errorMax = high(int)  # do not stop after first error
+  defineSymbol(graph.config.symbols, "nimcheck")
+  semanticPasses(graph)  # use an empty backend for semantic checking only
+  compileProject(graph)
+
+when not defined(leanCompiler):
+  proc commandDoc2(graph: ModuleGraph; json: bool) =
+    graph.config.errorMax = high(int)  # do not stop after first error
+    semanticPasses(graph)
+    if json: registerPass(graph, docgen2JsonPass)
+    else: registerPass(graph, docgen2Pass)
+    compileProject(graph)
+    finishDoc2Pass(graph.config.projectName)
+
+proc commandCompileToC(graph: ModuleGraph) =
+  let conf = graph.config
+  extccomp.initVars(conf)
+  semanticPasses(graph)
+  registerPass(graph, cgenPass)
+
+  compileProject(graph)
+  cgenWriteModules(graph.backend, conf)
+  if conf.cmd != cmdRun:
+    let proj = changeFileExt(conf.projectFull, "")
+    extccomp.callCCompiler(conf, proj)
+    extccomp.writeJsonBuildInstructions(conf, proj)
+    if optGenScript in graph.config.globalOptions:
+      writeDepsFile(graph, toGeneratedFile(conf, proj, ""))
+
+proc commandJsonScript(graph: ModuleGraph) =
+  let proj = changeFileExt(graph.config.projectFull, "")
+  extccomp.runJsonBuildInstructions(graph.config, proj)
+
+when not defined(leanCompiler):
+  proc commandCompileToJS(graph: ModuleGraph) =
+    #incl(gGlobalOptions, optSafeCode)
+    setTarget(graph.config.target, osJS, cpuJS)
+    #initDefines()
+    defineSymbol(graph.config.symbols, "ecmascript") # For backward compatibility
+    defineSymbol(graph.config.symbols, "js")
+    semanticPasses(graph)
+    registerPass(graph, JSgenPass)
+    compileProject(graph)
+
+proc interactivePasses(graph: ModuleGraph) =
+  initDefines(graph.config.symbols)
+  defineSymbol(graph.config.symbols, "nimscript")
+  when hasFFI: defineSymbol(graph.config.symbols, "nimffi")
+  registerPass(graph, verbosePass)
+  registerPass(graph, semPass)
+  registerPass(graph, evalPass)
+
+proc commandInteractive(graph: ModuleGraph) =
+  graph.config.errorMax = high(int)  # do not stop after first error
+  interactivePasses(graph)
+  compileSystemModule(graph)
+  if graph.config.commandArgs.len > 0:
+    discard graph.compileModule(fileInfoIdx(graph.config, graph.config.projectFull), {})
   else:
     var m = graph.makeStdinModule()
     incl(m.flags, sfMainModule)
-    processModule(graph, m, llStreamOpenStdIn(), nil, cache)
+    processModule(graph, m, llStreamOpenStdIn())
 
 const evalPasses = [verbosePass, semPass, evalPass]
 
-proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym; cache: IdentCache) =
-  carryPasses(graph, nodes, module, cache, evalPasses)
+proc evalNim(graph: ModuleGraph; nodes: PNode, module: PSym) =
+  carryPasses(graph, nodes, module, evalPasses)
 
-proc commandEval(graph: ModuleGraph; cache: IdentCache; exp: string) =
-  if systemModule == nil:
-    interactivePasses(graph, cache)
-    compileSystemModule(graph, cache)
+proc commandEval(graph: ModuleGraph; exp: string) =
+  if graph.systemModule == nil:
+    interactivePasses(graph)
+    compileSystemModule(graph)
   let echoExp = "echo \"eval\\t\", " & "repr(" & exp & ")"
-  evalNim(graph, echoExp.parseString(cache), makeStdinModule(graph), cache)
+  evalNim(graph, echoExp.parseString(graph.cache, graph.config),
+    makeStdinModule(graph))
 
-proc commandScan(cache: IdentCache) =
-  var f = addFileExt(mainCommandArg(), NimExt)
+proc commandScan(cache: IdentCache, config: ConfigRef) =
+  var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
   var stream = llStreamOpen(f, fmRead)
   if stream != nil:
     var
       L: TLexer
       tok: TToken
     initToken(tok)
-    openLexer(L, f, stream, cache)
+    openLexer(L, f, stream, cache, config)
     while true:
       rawGetTok(L, tok)
-      printTok(tok)
+      printTok(config, tok)
       if tok.tokType == tkEof: break
     closeLexer(L)
   else:
-    rawMessage(errCannotOpenFile, f)
+    rawMessage(config, errGenerated, "cannot open file: " & f.string)
 
 const
-  SimulateCaasMemReset = false
   PrintRopeCacheStats = false
 
-proc mainCommand*(graph: ModuleGraph; cache: IdentCache) =
-  when SimulateCaasMemReset:
-    gGlobalOptions.incl(optCaasEnabled)
+proc mainCommand*(graph: ModuleGraph) =
+  let conf = graph.config
+  let cache = graph.cache
 
+  setupModuleCache(graph)
   # In "nim serve" scenario, each command must reset the registered passes
-  clearPasses()
-  gLastCmdTime = epochTime()
-  searchPaths.add(options.libpath)
-  when false: # gProjectFull.len != 0:
-    # current path is always looked first for modules
-    prependStr(searchPaths, gProjectPath)
+  clearPasses(graph)
+  conf.lastCmdTime = epochTime()
+  conf.searchPaths.add(conf.libpath)
   setId(100)
-  case command.normalize
+  case conf.command.normalize
   of "c", "cc", "compile", "compiletoc":
     # compile means compileToC currently
-    gCmd = cmdCompileToC
-    commandCompileToC(graph, cache)
+    conf.cmd = cmdCompileToC
+    commandCompileToC(graph)
   of "cpp", "compiletocpp":
-    gCmd = cmdCompileToCpp
-    defineSymbol("cpp")
-    commandCompileToC(graph, cache)
+    conf.cmd = cmdCompileToCpp
+    defineSymbol(graph.config.symbols, "cpp")
+    commandCompileToC(graph)
   of "objc", "compiletooc":
-    gCmd = cmdCompileToOC
-    defineSymbol("objc")
-    commandCompileToC(graph, cache)
+    conf.cmd = cmdCompileToOC
+    defineSymbol(graph.config.symbols, "objc")
+    commandCompileToC(graph)
   of "run":
-    gCmd = cmdRun
+    conf.cmd = cmdRun
     when hasTinyCBackend:
       extccomp.setCC("tcc")
-      commandCompileToC(graph, cache)
+      commandCompileToC(graph)
     else:
-      rawMessage(errInvalidCommandX, command)
+      rawMessage(conf, errGenerated, "'run' command not available; rebuild with -d:tinyc")
   of "js", "compiletojs":
-    gCmd = cmdCompileToJS
-    commandCompileToJS(graph, cache)
-  of "php":
-    gCmd = cmdCompileToPHP
-    commandCompileToJS(graph, cache)
-  of "doc":
-    wantMainModule()
-    gCmd = cmdDoc
-    loadConfigs(DocConfig, cache)
-    commandDoc()
-  of "doc2":
-    gCmd = cmdDoc
-    loadConfigs(DocConfig, cache)
-    defineSymbol("nimdoc")
-    commandDoc2(graph, cache, false)
+    when defined(leanCompiler):
+      quit "compiler wasn't built with JS code generator"
+    else:
+      conf.cmd = cmdCompileToJS
+      commandCompileToJS(graph)
+  of "doc0":
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      wantMainModule(conf)
+      conf.cmd = cmdDoc
+      loadConfigs(DocConfig, cache, conf)
+      commandDoc(cache, conf)
+  of "doc2", "doc":
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      conf.cmd = cmdDoc
+      loadConfigs(DocConfig, cache, conf)
+      defineSymbol(conf.symbols, "nimdoc")
+      commandDoc2(graph, false)
   of "rst2html":
-    gCmd = cmdRst2html
-    loadConfigs(DocConfig, cache)
-    commandRst2Html()
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      conf.cmd = cmdRst2html
+      loadConfigs(DocConfig, cache, conf)
+      commandRst2Html(cache, conf)
   of "rst2tex":
-    gCmd = cmdRst2tex
-    loadConfigs(DocTexConfig, cache)
-    commandRst2TeX()
-  of "jsondoc":
-    wantMainModule()
-    gCmd = cmdDoc
-    loadConfigs(DocConfig, cache)
-    wantMainModule()
-    defineSymbol("nimdoc")
-    commandJson()
-  of "jsondoc2":
-    gCmd = cmdDoc
-    loadConfigs(DocConfig, cache)
-    wantMainModule()
-    defineSymbol("nimdoc")
-    commandDoc2(graph, cache, true)
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      conf.cmd = cmdRst2tex
+      loadConfigs(DocTexConfig, cache, conf)
+      commandRst2TeX(cache, conf)
+  of "jsondoc0":
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      wantMainModule(conf)
+      conf.cmd = cmdDoc
+      loadConfigs(DocConfig, cache, conf)
+      wantMainModule(conf)
+      defineSymbol(conf.symbols, "nimdoc")
+      commandJson(cache, conf)
+  of "jsondoc2", "jsondoc":
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      conf.cmd = cmdDoc
+      loadConfigs(DocConfig, cache, conf)
+      wantMainModule(conf)
+      defineSymbol(conf.symbols, "nimdoc")
+      commandDoc2(graph, true)
+  of "ctags":
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      wantMainModule(conf)
+      conf.cmd = cmdDoc
+      loadConfigs(DocConfig, cache, conf)
+      defineSymbol(conf.symbols, "nimdoc")
+      commandTags(cache, conf)
   of "buildindex":
-    gCmd = cmdDoc
-    loadConfigs(DocConfig, cache)
-    commandBuildIndex()
+    when defined(leanCompiler):
+      quit "compiler wasn't built with documentation generator"
+    else:
+      conf.cmd = cmdDoc
+      loadConfigs(DocConfig, cache, conf)
+      commandBuildIndex(cache, conf)
   of "gendepend":
-    gCmd = cmdGenDepend
-    commandGenDepend(graph, cache)
+    conf.cmd = cmdGenDepend
+    commandGenDepend(graph)
   of "dump":
-    gCmd = cmdDump
-    if getConfigVar("dump.format") == "json":
-      wantMainModule()
+    conf.cmd = cmdDump
+    if getConfigVar(conf, "dump.format") == "json":
+      wantMainModule(conf)
 
       var definedSymbols = newJArray()
-      for s in definedSymbolNames(): definedSymbols.elems.add(%s)
+      for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s)
 
       var libpaths = newJArray()
-      for dir in searchPaths: libpaths.elems.add(%dir)
-
-      var dumpdata = % [
+      for dir in conf.searchPaths: libpaths.elems.add(%dir.string)
+
+      var hints = newJObject() # consider factoring with `listHints`
+      for a in hintMin..hintMax:
+        let key = lineinfos.HintsToStr[ord(a) - ord(hintMin)]
+        hints[key] = %(a in conf.notes)
+      var warnings = newJObject()
+      for a in warnMin..warnMax:
+        let key = lineinfos.WarningsToStr[ord(a) - ord(warnMin)]
+        warnings[key] = %(a in conf.notes)
+
+      var dumpdata = %[
         (key: "version", val: %VersionAsString),
-        (key: "project_path", val: %gProjectFull),
+        (key: "project_path", val: %conf.projectFull.string),
         (key: "defined_symbols", val: definedSymbols),
-        (key: "lib_paths", val: libpaths)
+        (key: "lib_paths", val: %libpaths),
+        (key: "out", val: %conf.outFile.string),
+        (key: "hints", val: hints),
+        (key: "warnings", val: warnings),
       ]
 
-      msgWriteln($dumpdata, {msgStdout, msgSkipHook})
+      msgWriteln(conf, $dumpdata, {msgStdout, msgSkipHook})
     else:
-      msgWriteln("-- list of currently defined symbols --",
+      msgWriteln(conf, "-- list of currently defined symbols --",
                  {msgStdout, msgSkipHook})
-      for s in definedSymbolNames(): msgWriteln(s, {msgStdout, msgSkipHook})
-      msgWriteln("-- end of list --", {msgStdout, msgSkipHook})
+      for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook})
+      msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook})
 
-      for it in searchPaths: msgWriteln(it)
+      for it in conf.searchPaths: msgWriteln(conf, it.string)
   of "check":
-    gCmd = cmdCheck
-    commandCheck(graph, cache)
+    conf.cmd = cmdCheck
+    commandCheck(graph)
   of "parse":
-    gCmd = cmdParse
-    wantMainModule()
-    discard parseFile(gProjectMainIdx, cache)
+    conf.cmd = cmdParse
+    wantMainModule(conf)
+    discard parseFile(conf.projectMainIdx, cache, conf)
   of "scan":
-    gCmd = cmdScan
-    wantMainModule()
-    commandScan(cache)
-    msgWriteln("Beware: Indentation tokens depend on the parser's state!")
+    conf.cmd = cmdScan
+    wantMainModule(conf)
+    commandScan(cache, conf)
+    msgWriteln(conf, "Beware: Indentation tokens depend on the parser's state!")
   of "secret":
-    gCmd = cmdInteractive
-    commandInteractive(graph, cache)
+    conf.cmd = cmdInteractive
+    commandInteractive(graph)
   of "e":
-    commandEval(graph, cache, mainCommandArg())
+    incl conf.globalOptions, optWasNimscript
+    commandEval(graph, mainCommandArg(conf))
   of "nop", "help":
     # prevent the "success" message:
-    gCmd = cmdDump
+    conf.cmd = cmdDump
+  of "jsonscript":
+    conf.cmd = cmdJsonScript
+    commandJsonScript(graph)
   else:
-    rawMessage(errInvalidCommandX, command)
+    rawMessage(conf, errGenerated, "invalid command: " & conf.command)
 
-  if msgs.gErrorCounter == 0 and
-     gCmd notin {cmdInterpret, cmdRun, cmdDump}:
+  if conf.errorCounter == 0 and
+     conf.cmd notin {cmdInterpret, cmdRun, cmdDump}:
     when declared(system.getMaxMem):
-      let usedMem = formatSize(getMaxMem()) & " peekmem"
+      let usedMem = formatSize(getMaxMem()) & " peakmem"
     else:
       let usedMem = formatSize(getTotalMem())
-    rawMessage(hintSuccessX, [$gLinesCompiled,
-               formatFloat(epochTime() - gLastCmdTime, ffDecimal, 3),
+    rawMessage(conf, hintSuccessX, [$conf.linesCompiled,
+               formatFloat(epochTime() - conf.lastCmdTime, ffDecimal, 3),
                usedMem,
-               if condSyms.isDefined("release"): "Release Build"
+               if isDefined(conf, "release"): "Release Build"
                else: "Debug Build"])
 
   when PrintRopeCacheStats:
@@ -289,9 +340,4 @@ proc mainCommand*(graph: ModuleGraph; cache: IdentCache) =
     echo "  efficiency: ", formatFloat(1-(gCacheMisses.float/gCacheTries.float),
                                        ffDecimal, 3)
 
-  when SimulateCaasMemReset:
-    resetMemory()
-
-  resetAttributes()
-
-proc mainCommand*() = mainCommand(newModuleGraph(newConfigRef()), newIdentCache())
+  resetAttributes(conf)
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
index c081a099a..63fa597ff 100644
--- a/compiler/modulegraphs.nim
+++ b/compiler/modulegraphs.nim
@@ -9,7 +9,7 @@
 
 ## This module implements the module graph data structure. The module graph
 ## represents a complete Nim project. Single modules can either be kept in RAM
-## or stored in a ROD file. The ROD file mechanism is not yet integrated here.
+## or stored in a Sqlite database.
 ##
 ## The caching of modules is critical for 'nimsuggest' and is tricky to get
 ## right. If module E is being edited, we need autocompletion (and type
@@ -25,7 +25,8 @@
 ## - Its dependent module stays the same.
 ##
 
-import ast, intsets, tables, options
+import ast, intsets, tables, options, lineinfos, hashes, idents,
+  incremental, btrees
 
 type
   ModuleGraph* = ref object
@@ -34,70 +35,147 @@ type
     deps*: IntSet # the dependency graph or potentially its transitive closure.
     suggestMode*: bool # whether we are in nimsuggest mode or not.
     invalidTransitiveClosure: bool
-    inclToMod*: Table[int32, int32] # mapping of include file to the
-                                    # first module that included it
-    importStack*: seq[int32]  # The current import stack. Used for detecting recursive
-                              # module dependencies.
+    inclToMod*: Table[FileIndex, FileIndex] # mapping of include file to the
+                                            # first module that included it
+    importStack*: seq[FileIndex]  # The current import stack. Used for detecting recursive
+                                  # module dependencies.
     backend*: RootRef # minor hack so that a backend can extend this easily
     config*: ConfigRef
+    cache*: IdentCache
+    vm*: RootRef # unfortunately the 'vm' state is shared project-wise, this will
+                 # be clarified in later compiler implementations.
     doStopCompile*: proc(): bool {.closure.}
     usageSym*: PSym # for nimsuggest
     owners*: seq[PSym]
-    methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]]
-
-{.this: g.}
+    methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
+    systemModule*: PSym
+    sysTypes*: array[TTypeKind, PType]
+    compilerprocs*: TStrTable
+    exposed*: TStrTable
+    intTypeCache*: array[-5..64, PType]
+    opContains*, opNot*: PSym
+    emptyNode*: PNode
+    incr*: IncrementalCtx
+    importModuleCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PSym {.nimcall.}
+    includeFileCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PNode {.nimcall.}
+    recordStmt*: proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.}
+    cacheSeqs*: Table[string, PNode] # state that is shared to suppor the 'macrocache' API
+    cacheCounters*: Table[string, BiggestInt]
+    cacheTables*: Table[string, BTree[string, PNode]]
+    passes*: seq[TPass]
+    onDefinition*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
+    onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
+    onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
+
+  TPassContext* = object of RootObj # the pass's context
+  PPassContext* = ref TPassContext
+
+  TPassOpen* = proc (graph: ModuleGraph; module: PSym): PPassContext {.nimcall.}
+  TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.}
+  TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.}
+
+  TPass* = tuple[open: TPassOpen,
+                 process: TPassProcess,
+                 close: TPassClose,
+                 isFrontend: bool]
+
+proc hash*(x: FileIndex): Hash {.borrow.}
+
+when defined(nimfind):
+  template onUse*(info: TLineInfo; s: PSym) =
+    when compiles(c.c.graph):
+      if c.c.graph.onUsage != nil: c.c.graph.onUsage(c.c.graph, s, info)
+    else:
+      if c.graph.onUsage != nil: c.graph.onUsage(c.graph, s, info)
+
+  template onDef*(info: TLineInfo; s: PSym) =
+    when compiles(c.c.graph):
+      if c.c.graph.onDefinition != nil: c.c.graph.onDefinition(c.c.graph, s, info)
+    else:
+      if c.graph.onDefinition != nil: c.graph.onDefinition(c.graph, s, info)
+
+  template onDefResolveForward*(info: TLineInfo; s: PSym) =
+    when compiles(c.c.graph):
+      if c.c.graph.onDefinitionResolveForward != nil:
+        c.c.graph.onDefinitionResolveForward(c.c.graph, s, info)
+    else:
+      if c.graph.onDefinitionResolveForward != nil:
+        c.graph.onDefinitionResolveForward(c.graph, s, info)
+
+else:
+  template onUse*(info: TLineInfo; s: PSym) = discard
+  template onDef*(info: TLineInfo; s: PSym) = discard
+  template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
 
 proc stopCompile*(g: ModuleGraph): bool {.inline.} =
-  result = doStopCompile != nil and doStopCompile()
+  result = g.doStopCompile != nil and g.doStopCompile()
+
+proc createMagic*(g: ModuleGraph; name: string, m: TMagic): PSym =
+  result = newSym(skProc, getIdent(g.cache, name), nil, unknownLineInfo(), {})
+  result.magic = m
 
-proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph =
+proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
   result = ModuleGraph()
   initStrTable(result.packageSyms)
   result.deps = initIntSet()
   result.modules = @[]
   result.importStack = @[]
-  result.inclToMod = initTable[int32, int32]()
-  if config.isNil:
-    result.config = newConfigRef()
-  else:
-    result.config = config
+  result.inclToMod = initTable[FileIndex, FileIndex]()
+  result.config = config
+  result.cache = cache
   result.owners = @[]
   result.methods = @[]
+  initStrTable(result.compilerprocs)
+  initStrTable(result.exposed)
+  result.opNot = createMagic(result, "not", mNot)
+  result.opContains = createMagic(result, "contains", mInSet)
+  result.emptyNode = newNode(nkEmpty)
+  init(result.incr)
+  result.recordStmt = proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.} =
+    discard
+  result.cacheSeqs = initTable[string, PNode]()
+  result.cacheCounters = initTable[string, BiggestInt]()
+  result.cacheTables = initTable[string, BTree[string, PNode]]()
 
 proc resetAllModules*(g: ModuleGraph) =
-  initStrTable(packageSyms)
-  deps = initIntSet()
-  modules = @[]
-  importStack = @[]
-  inclToMod = initTable[int32, int32]()
-  usageSym = nil
-  owners = @[]
-  methods = @[]
-
-proc getModule*(g: ModuleGraph; fileIdx: int32): PSym =
-  if fileIdx >= 0 and fileIdx < modules.len:
-    result = modules[fileIdx]
+  initStrTable(g.packageSyms)
+  g.deps = initIntSet()
+  g.modules = @[]
+  g.importStack = @[]
+  g.inclToMod = initTable[FileIndex, FileIndex]()
+  g.usageSym = nil
+  g.owners = @[]
+  g.methods = @[]
+  initStrTable(g.compilerprocs)
+  initStrTable(g.exposed)
+
+proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
+  if fileIdx.int32 >= 0 and fileIdx.int32 < g.modules.len:
+    result = g.modules[fileIdx.int32]
 
 proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b
 
-proc addDep*(g: ModuleGraph; m: PSym, dep: int32) =
-  if suggestMode:
-    deps.incl m.position.dependsOn(dep)
+proc addDep*(g: ModuleGraph; m: PSym, dep: FileIndex) =
+  assert m.position == m.info.fileIndex.int32
+  addModuleDep(g.incr, g.config, m.info.fileIndex, dep, isIncludeFile = false)
+  if g.suggestMode:
+    g.deps.incl m.position.dependsOn(dep.int)
     # we compute the transitive closure later when quering the graph lazily.
-    # this improve efficiency quite a lot:
+    # this improves efficiency quite a lot:
     #invalidTransitiveClosure = true
 
-proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) =
-  discard hasKeyOrPut(inclToMod, includeFile, module)
+proc addIncludeDep*(g: ModuleGraph; module, includeFile: FileIndex) =
+  addModuleDep(g.incr, g.config, module, includeFile, isIncludeFile = true)
+  discard hasKeyOrPut(g.inclToMod, includeFile, module)
 
-proc parentModule*(g: ModuleGraph; fileIdx: int32): int32 =
+proc parentModule*(g: ModuleGraph; fileIdx: FileIndex): FileIndex =
   ## returns 'fileIdx' if the file belonging to this index is
   ## directly used as a module or else the module that first
   ## references this include file.
-  if fileIdx >= 0 and fileIdx < modules.len and modules[fileIdx] != nil:
+  if fileIdx.int32 >= 0 and fileIdx.int32 < g.modules.len and g.modules[fileIdx.int32] != nil:
     result = fileIdx
   else:
-    result = inclToMod.getOrDefault(fileIdx)
+    result = g.inclToMod.getOrDefault(fileIdx)
 
 proc transitiveClosure(g: var IntSet; n: int) =
   # warshall's algorithm
@@ -108,23 +186,23 @@ proc transitiveClosure(g: var IntSet; n: int) =
           if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)):
             g.incl i.dependsOn(j)
 
-proc markDirty*(g: ModuleGraph; fileIdx: int32) =
-  let m = getModule fileIdx
+proc markDirty*(g: ModuleGraph; fileIdx: FileIndex) =
+  let m = g.getModule fileIdx
   if m != nil: incl m.flags, sfDirty
 
-proc markClientsDirty*(g: ModuleGraph; fileIdx: int32) =
+proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) =
   # we need to mark its dependent modules D as dirty right away because after
   # nimsuggest is done with this module, the module's dirty flag will be
   # cleared but D still needs to be remembered as 'dirty'.
-  if invalidTransitiveClosure:
-    invalidTransitiveClosure = false
-    transitiveClosure(deps, modules.len)
+  if g.invalidTransitiveClosure:
+    g.invalidTransitiveClosure = false
+    transitiveClosure(g.deps, g.modules.len)
 
   # every module that *depends* on this file is also dirty:
-  for i in 0i32..<modules.len.int32:
-    let m = modules[i]
-    if m != nil and deps.contains(i.dependsOn(fileIdx)):
+  for i in 0i32..<g.modules.len.int32:
+    let m = g.modules[i]
+    if m != nil and g.deps.contains(i.dependsOn(fileIdx.int)):
       incl m.flags, sfDirty
 
 proc isDirty*(g: ModuleGraph; m: PSym): bool =
-  result = suggestMode and sfDirty in m.flags
+  result = g.suggestMode and sfDirty in m.flags
diff --git a/compiler/modulepaths.nim b/compiler/modulepaths.nim
new file mode 100644
index 000000000..f0718c4eb
--- /dev/null
+++ b/compiler/modulepaths.nim
@@ -0,0 +1,168 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2017 Contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+import ast, renderer, strutils, msgs, options, idents, os, lineinfos,
+  pathutils, nimblecmd
+
+when false:
+  const
+    considerParentDirs = not defined(noParentProjects)
+    considerNimbleDirs = not defined(noNimbleDirs)
+
+  proc findInNimbleDir(pkg, subdir, dir: string): string =
+    var best = ""
+    var bestv = ""
+    for k, p in os.walkDir(dir, relative=true):
+      if k == pcDir and p.len > pkg.len+1 and
+          p[pkg.len] == '-' and p.startsWith(pkg):
+        let (_, a) = getPathVersion(p)
+        if bestv.len == 0 or bestv < a:
+          bestv = a
+          best = dir / p
+
+    if best.len > 0:
+      var f: File
+      if open(f, best / changeFileExt(pkg, ".nimble-link")):
+        # the second line contains what we're interested in, see:
+        # https://github.com/nim-lang/nimble#nimble-link
+        var override = ""
+        discard readLine(f, override)
+        discard readLine(f, override)
+        close(f)
+        if not override.isAbsolute():
+          best = best / override
+        else:
+          best = override
+    let f = if subdir.len == 0: pkg else: subdir
+    let res = addFileExt(best / f, "nim")
+    if best.len > 0 and fileExists(res):
+      result = res
+
+when false:
+  proc resolveDollar(project, source, pkg, subdir: string; info: TLineInfo): string =
+    template attempt(a) =
+      let x = addFileExt(a, "nim")
+      if fileExists(x): return x
+
+    case pkg
+    of "stdlib":
+      if subdir.len == 0:
+        return options.libpath
+      else:
+        for candidate in stdlibDirs:
+          attempt(options.libpath / candidate / subdir)
+    of "root":
+      let root = project.splitFile.dir
+      if subdir.len == 0:
+        return root
+      else:
+        attempt(root / subdir)
+    else:
+      when considerParentDirs:
+        var p = parentDir(source.splitFile.dir)
+        # support 'import $karax':
+        let f = if subdir.len == 0: pkg else: subdir
+
+        while p.len > 0:
+          let dir = p / pkg
+          if dirExists(dir):
+            attempt(dir / f)
+            # 2nd attempt: try to use 'karax/karax'
+            attempt(dir / pkg / f)
+            # 3rd attempt: try to use 'karax/src/karax'
+            attempt(dir / "src" / f)
+            attempt(dir / "src" / pkg / f)
+          p = parentDir(p)
+
+      when considerNimbleDirs:
+        if not options.gNoNimblePath:
+          var nimbleDir = getEnv("NIMBLE_DIR")
+          if nimbleDir.len == 0: nimbleDir = getHomeDir() / ".nimble"
+          result = findInNimbleDir(pkg, subdir, nimbleDir / "pkgs")
+          if result.len > 0: return result
+          when not defined(windows):
+            result = findInNimbleDir(pkg, subdir, "/opt/nimble/pkgs")
+            if result.len > 0: return result
+
+  proc scriptableImport(pkg, sub: string; info: TLineInfo): string =
+    result = resolveDollar(gProjectFull, info.toFullPath(), pkg, sub, info)
+    if result.isNil: result = ""
+
+  proc lookupPackage(pkg, subdir: PNode): string =
+    let sub = if subdir != nil: renderTree(subdir, {renderNoComments}).replace(" ") else: ""
+    case pkg.kind
+    of nkStrLit, nkRStrLit, nkTripleStrLit:
+      result = scriptableImport(pkg.strVal, sub, pkg.info)
+    of nkIdent:
+      result = scriptableImport(pkg.ident.s, sub, pkg.info)
+    else:
+      localError(pkg.info, "package name must be an identifier or string literal")
+      result = ""
+
+proc getModuleName*(conf: ConfigRef; n: PNode): string =
+  # This returns a short relative module name without the nim extension
+  # e.g. like "system", "importer" or "somepath/module"
+  # The proc won't perform any checks that the path is actually valid
+  case n.kind
+  of nkStrLit, nkRStrLit, nkTripleStrLit:
+    try:
+      result =
+        pathSubs(conf, n.strVal, toFullPath(conf, n.info).splitFile().dir)
+          .replace(" ")
+    except ValueError:
+      localError(conf, n.info, "invalid path: " & n.strVal)
+      result = n.strVal
+  of nkIdent:
+    result = n.ident.s
+  of nkSym:
+    result = n.sym.name.s
+  of nkInfix:
+    let n0 = n[0]
+    let n1 = n[1]
+    when false:
+      if n1.kind == nkPrefix and n1[0].kind == nkIdent and n1[0].ident.s == "$":
+        if n0.kind == nkIdent and n0.ident.s == "/":
+          result = lookupPackage(n1[1], n[2])
+        else:
+          localError(n.info, "only '/' supported with $package notation")
+          result = ""
+    else:
+      let modname = getModuleName(conf, n[2])
+      # hacky way to implement 'x / y /../ z':
+      result = getModuleName(conf, n1)
+      result.add renderTree(n0, {renderNoComments}).replace(" ")
+      result.add modname
+  of nkPrefix:
+    when false:
+      if n.sons[0].kind == nkIdent and n.sons[0].ident.s == "$":
+        result = lookupPackage(n[1], nil)
+      else:
+        discard
+    # hacky way to implement 'x / y /../ z':
+    result = renderTree(n, {renderNoComments}).replace(" ")
+  of nkDotExpr:
+    localError(conf, n.info, warnDeprecated, "using '.' instead of '/' in import paths")
+    result = renderTree(n, {renderNoComments}).replace(".", "/")
+  of nkImportAs:
+    result = getModuleName(conf, n.sons[0])
+  else:
+    localError(conf, n.info, "invalid module name: '$1'" % n.renderTree)
+    result = ""
+
+proc checkModuleName*(conf: ConfigRef; n: PNode; doLocalError=true): FileIndex =
+  # This returns the full canonical path for a given module import
+  let modulename = getModuleName(conf, n)
+  let fullPath = findModule(conf, modulename, toFullPath(conf, n.info))
+  if fullPath.isEmpty:
+    if doLocalError:
+      let m = if modulename.len > 0: modulename else: $n
+      localError(conf, n.info, "cannot open file: " & m)
+    result = InvalidFileIDX
+  else:
+    result = fileInfoIdx(conf, fullPath)
diff --git a/compiler/modules.nim b/compiler/modules.nim
index 4763ac79b..442305a06 100644
--- a/compiler/modules.nim
+++ b/compiler/modules.nim
@@ -10,143 +10,28 @@
 ## Implements the module handling, including the caching of modules.
 
 import
-  ast, astalgo, magicsys, securehash, rodread, msgs, cgendata, sigmatch, options,
-  idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs
-
-when false:
-  type
-    TNeedRecompile* = enum Maybe, No, Yes, Probing, Recompiled
-    THashStatus* = enum hashNotTaken, hashCached, hashHasChanged, hashNotChanged
-
-    TModuleInMemory* = object
-      hash*: SecureHash
-      deps*: seq[int32] ## XXX: slurped files are currently not tracked
-
-      needsRecompile*: TNeedRecompile
-      hashStatus*: THashStatus
-
-  var
-    gCompiledModules: seq[PSym] = @[]
-    gMemCacheData*: seq[TModuleInMemory] = @[]
-      ## XXX: we should implement recycling of file IDs
-      ## if the user keeps renaming modules, the file IDs will keep growing
-    gFuzzyGraphChecking*: bool # nimsuggest uses this. XXX figure out why.
-
-  proc hashChanged(fileIdx: int32): bool =
-    internalAssert fileIdx >= 0 and fileIdx < gMemCacheData.len
-
-    template updateStatus =
-      gMemCacheData[fileIdx].hashStatus = if result: hashHasChanged
-                                         else: hashNotChanged
-      # echo "TESTING Hash: ", fileIdx.toFilename, " ", result
-
-    case gMemCacheData[fileIdx].hashStatus
-    of hashHasChanged:
-      result = true
-    of hashNotChanged:
-      result = false
-    of hashCached:
-      let newHash = secureHashFile(fileIdx.toFullPath)
-      result = newHash != gMemCacheData[fileIdx].hash
-      gMemCacheData[fileIdx].hash = newHash
-      updateStatus()
-    of hashNotTaken:
-      gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath)
-      result = true
-      updateStatus()
-
-  proc doHash(fileIdx: int32) =
-    if gMemCacheData[fileIdx].hashStatus == hashNotTaken:
-      # echo "FIRST Hash: ", fileIdx.ToFilename
-      gMemCacheData[fileIdx].hash = secureHashFile(fileIdx.toFullPath)
-
-  proc resetModule*(fileIdx: int32) =
-    # echo "HARD RESETTING ", fileIdx.toFilename
-    if fileIdx <% gMemCacheData.len:
-      gMemCacheData[fileIdx].needsRecompile = Yes
-    if fileIdx <% gCompiledModules.len:
-      gCompiledModules[fileIdx] = nil
-    if fileIdx <% cgendata.gModules.len:
-      cgendata.gModules[fileIdx] = nil
-
-  proc resetModule*(module: PSym) =
-    let conflict = getModule(module.position.int32)
-    if conflict == nil: return
-    doAssert conflict == module
-    resetModule(module.position.int32)
-    initStrTable(module.tab)
-
-  proc resetAllModules* =
-    for i in 0..gCompiledModules.high:
-      if gCompiledModules[i] != nil:
-        resetModule(i.int32)
-    resetPackageCache()
-    # for m in cgenModules(): echo "CGEN MODULE FOUND"
-
-  proc resetAllModulesHard* =
-    resetPackageCache()
-    gCompiledModules.setLen 0
-    gMemCacheData.setLen 0
-    magicsys.resetSysTypes()
-    # XXX
-    #gOwners = @[]
-
-  proc checkDepMem(fileIdx: int32): TNeedRecompile =
-    template markDirty =
-      resetModule(fileIdx)
-      return Yes
-
-    if gFuzzyGraphChecking:
-      if gMemCacheData[fileIdx].needsRecompile != Maybe:
-        return gMemCacheData[fileIdx].needsRecompile
-    else:
-      # cycle detection: We claim that a cycle does no harm.
-      if gMemCacheData[fileIdx].needsRecompile == Probing:
-        return No
-
-    if optForceFullMake in gGlobalOptions or hashChanged(fileIdx):
-      markDirty()
-
-    if gMemCacheData[fileIdx].deps != nil:
-      gMemCacheData[fileIdx].needsRecompile = Probing
-      for dep in gMemCacheData[fileIdx].deps:
-        let d = checkDepMem(dep)
-        if d in {Yes, Recompiled}:
-          # echo fileIdx.toFilename, " depends on ", dep.toFilename, " ", d
-          markDirty()
+  ast, astalgo, magicsys, std / sha1, msgs, cgendata, sigmatch, options,
+  idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod,
+  lineinfos, pathutils
 
-    gMemCacheData[fileIdx].needsRecompile = No
-    return No
-
-proc resetSystemArtifacts*() =
-  magicsys.resetSysTypes()
-
-proc newModule(graph: ModuleGraph; fileIdx: int32): PSym =
-  # We cannot call ``newSym`` here, because we have to circumvent the ID
-  # mechanism, which we do in order to assign each module a persistent ID.
-  new(result)
-  result.id = - 1             # for better error checking
-  result.kind = skModule
-  let filename = fileIdx.toFullPath
-  result.name = getIdent(splitFile(filename).name)
-  if not isNimIdentifier(result.name.s):
-    rawMessage(errInvalidModuleName, result.name.s)
+proc resetSystemArtifacts*(g: ModuleGraph) =
+  magicsys.resetSysTypes(g)
 
-  result.info = newLineInfo(fileIdx, 1, 1)
+proc partialInitModule(result: PSym; graph: ModuleGraph; fileIdx: FileIndex; filename: string) =
   let
-    pck = getPackageName(filename)
+    pck = getPackageName(graph.config, filename)
     pck2 = if pck.len > 0: pck else: "unknown"
-    pack = getIdent(pck2)
+    pack = getIdent(graph.cache, pck2)
   var packSym = graph.packageSyms.strTableGet(pack)
   if packSym == nil:
-    packSym = newSym(skPackage, getIdent(pck2), nil, result.info)
+    packSym = newSym(skPackage, getIdent(graph.cache, pck2), nil, result.info)
     initStrTable(packSym.tab)
     graph.packageSyms.strTableAdd(packSym)
-
   result.owner = packSym
-  result.position = fileIdx
+  result.position = int fileIdx
 
-  growCache graph.modules, fileIdx
+  if int(fileIdx) >= graph.modules.len:
+    setLen(graph.modules, int(fileIdx) + 1)
   graph.modules[result.position] = result
 
   incl(result.flags, sfUsed)
@@ -154,93 +39,106 @@ proc newModule(graph: ModuleGraph; fileIdx: int32): PSym =
   strTableAdd(result.tab, result) # a module knows itself
   let existing = strTableGet(packSym.tab, result.name)
   if existing != nil and existing.info.fileIndex != result.info.fileIndex:
-    localError(result.info, "module names need to be unique per Nimble package; module clashes with " & existing.info.fileIndex.toFullPath)
+    localError(graph.config, result.info,
+      "module names need to be unique per Nimble package; module clashes with " &
+        toFullPath(graph.config, existing.info.fileIndex))
   # strTableIncl() for error corrections:
   discard strTableIncl(packSym.tab, result)
 
-proc compileModule*(graph: ModuleGraph; fileIdx: int32; cache: IdentCache, flags: TSymFlags): PSym =
+proc newModule(graph: ModuleGraph; fileIdx: FileIndex): PSym =
+  # We cannot call ``newSym`` here, because we have to circumvent the ID
+  # mechanism, which we do in order to assign each module a persistent ID.
+  new(result)
+  result.id = -1             # for better error checking
+  result.kind = skModule
+  let filename = toFullPath(graph.config, fileIdx)
+  result.name = getIdent(graph.cache, splitFile(filename).name)
+  if not isNimIdentifier(result.name.s):
+    rawMessage(graph.config, errGenerated, "invalid module name: " & result.name.s)
+  result.info = newLineInfo(fileIdx, 1, 1)
+  partialInitModule(result, graph, fileIdx, filename)
+
+proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags): PSym =
   result = graph.getModule(fileIdx)
   if result == nil:
-    #growCache gMemCacheData, fileIdx
-    #gMemCacheData[fileIdx].needsRecompile = Probing
-    result = newModule(graph, fileIdx)
-    var rd: PRodReader
-    result.flags = result.flags + flags
-    if sfMainModule in result.flags:
-      gMainPackageId = result.owner.id
-
-    if gCmd in {cmdCompileToC, cmdCompileToCpp, cmdCheck, cmdIdeTools}:
-      rd = handleSymbolFile(result, cache)
-      if result.id < 0:
-        internalError("handleSymbolFile should have set the module's ID")
-        return
+    let filename = toFullPath(graph.config, fileIdx)
+    let (r, id) = loadModuleSym(graph, fileIdx, AbsoluteFile filename)
+    result = r
+    if result == nil:
+      result = newModule(graph, fileIdx)
+      result.flags = result.flags + flags
+      if sfMainModule in result.flags:
+        graph.config.mainPackageId = result.owner.id
+      result.id = id
+      registerModule(graph, result)
     else:
-      result.id = getID()
+      partialInitModule(result, graph, fileIdx, filename)
+      result.id = id
+      assert result.id < 0
     discard processModule(graph, result,
-      if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil,
-      rd, cache)
-    #if optCaasEnabled in gGlobalOptions:
-    #  gMemCacheData[fileIdx].needsRecompile = Recompiled
-    #  if validFile: doHash fileIdx
+      if sfMainModule in flags and graph.config.projectIsStdin: stdin.llStreamOpen else: nil)
   elif graph.isDirty(result):
     result.flags.excl sfDirty
     # reset module fields:
     initStrTable(result.tab)
     result.ast = nil
     discard processModule(graph, result,
-      if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil,
-      nil, cache)
+      if sfMainModule in flags and graph.config.projectIsStdin: stdin.llStreamOpen else: nil)
     graph.markClientsDirty(fileIdx)
-    when false:
-      if checkDepMem(fileIdx) == Yes:
-        result = compileModule(fileIdx, cache, flags)
-      else:
-        result = gCompiledModules[fileIdx]
-
-proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: int32;
-                   cache: IdentCache): PSym {.procvar.} =
+
+proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PSym {.procvar.} =
   # this is called by the semantic checking phase
-  result = compileModule(graph, fileIdx, cache, {})
+  assert graph.config != nil
+  result = compileModule(graph, fileIdx, {})
   graph.addDep(s, fileIdx)
   #if sfSystemModule in result.flags:
   #  localError(result.info, errAttemptToRedefine, result.name.s)
   # restore the notes for outer module:
-  gNotes = if s.owner.id == gMainPackageId: gMainPackageNotes
-           else: ForeignPackageNotes
+  graph.config.notes =
+    if s.owner.id == graph.config.mainPackageId: graph.config.mainPackageNotes
+    else: graph.config.foreignPackageNotes
 
-proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: int32;
-                    cache: IdentCache): PNode {.procvar.} =
-  result = syntaxes.parseFile(fileIdx, cache)
+proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode {.procvar.} =
+  result = syntaxes.parseFile(fileIdx, graph.cache, graph.config)
   graph.addDep(s, fileIdx)
-  graph.addIncludeDep(s.position.int32, fileIdx)
-
-proc compileSystemModule*(graph: ModuleGraph; cache: IdentCache) =
-  if magicsys.systemModule == nil:
-    systemFileIdx = fileInfoIdx(options.libpath/"system.nim")
-    discard graph.compileModule(systemFileIdx, cache, {sfSystemModule})
-
-proc wantMainModule* =
-  if gProjectFull.len == 0:
-    fatal(gCmdLineInfo, errCommandExpectsFilename)
-  gProjectMainIdx = addFileExt(gProjectFull, NimExt).fileInfoIdx
-
-passes.gIncludeFile = includeModule
-passes.gImportModule = importModule
-
-proc compileProject*(graph: ModuleGraph; cache: IdentCache;
-                     projectFileIdx = -1'i32) =
-  wantMainModule()
-  let systemFileIdx = fileInfoIdx(options.libpath / "system.nim")
-  let projectFile = if projectFileIdx < 0: gProjectMainIdx else: projectFileIdx
+  graph.addIncludeDep(s.position.FileIndex, fileIdx)
+
+proc connectCallbacks*(graph: ModuleGraph) =
+  graph.includeFileCallback = includeModule
+  graph.importModuleCallback = importModule
+
+proc compileSystemModule*(graph: ModuleGraph) =
+  if graph.systemModule == nil:
+    connectCallbacks(graph)
+    graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
+        graph.config.libpath / RelativeFile"system.nim")
+    discard graph.compileModule(graph.config.m.systemFileIdx, {sfSystemModule})
+
+proc wantMainModule*(conf: ConfigRef) =
+  if conf.projectFull.isEmpty:
+    fatal(conf, newLineInfo(conf, AbsoluteFile"command line", 1, 1), errGenerated,
+        "command expects a filename")
+  conf.projectMainIdx = fileInfoIdx(conf, addFileExt(conf.projectFull, NimExt))
+
+proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) =
+  connectCallbacks(graph)
+  let conf = graph.config
+  wantMainModule(conf)
+  let systemFileIdx = fileInfoIdx(conf, conf.libpath / RelativeFile"system.nim")
+  let projectFile = if projectFileIdx == InvalidFileIDX: conf.projectMainIdx else: projectFileIdx
   graph.importStack.add projectFile
   if projectFile == systemFileIdx:
-    discard graph.compileModule(projectFile, cache, {sfMainModule, sfSystemModule})
+    discard graph.compileModule(projectFile, {sfMainModule, sfSystemModule})
   else:
-    graph.compileSystemModule(cache)
-    discard graph.compileModule(projectFile, cache, {sfMainModule})
+    graph.compileSystemModule()
+    discard graph.compileModule(projectFile, {sfMainModule})
 
-proc makeModule*(graph: ModuleGraph; filename: string): PSym =
-  result = graph.newModule(fileInfoIdx filename)
+proc makeModule*(graph: ModuleGraph; filename: AbsoluteFile): PSym =
+  result = graph.newModule(fileInfoIdx(graph.config, filename))
   result.id = getID()
+  registerModule(graph, result)
+
+proc makeModule*(graph: ModuleGraph; filename: string): PSym =
+  result = makeModule(graph, AbsoluteFile filename)
 
-proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule"stdin"
+proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule(AbsoluteFile"stdin")
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index 2db3646b5..7e6b67cbe 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -8,656 +8,114 @@
 #
 
 import
-  options, strutils, os, tables, ropes, platform, terminal, macros
+  options, strutils, os, tables, ropes, platform, terminal, macros,
+  lineinfos, pathutils
 
-type
-  TMsgKind* = enum
-    errUnknown, errInternal, errIllFormedAstX, errCannotOpenFile, errGenerated,
-    errXCompilerDoesNotSupportCpp, errStringLiteralExpected,
-    errIntLiteralExpected, errInvalidCharacterConstant,
-    errClosingTripleQuoteExpected, errClosingQuoteExpected,
-    errTabulatorsAreNotAllowed, errInvalidToken, errLineTooLong,
-    errInvalidNumber, errInvalidNumberOctalCode, errNumberOutOfRange,
-    errNnotAllowedInCharacter, errClosingBracketExpected, errMissingFinalQuote,
-    errIdentifierExpected, errNewlineExpected, errInvalidModuleName,
-    errOperatorExpected, errTokenExpected, errStringAfterIncludeExpected,
-    errRecursiveDependencyX, errOnOrOffExpected, errNoneSpeedOrSizeExpected,
-    errInvalidPragma, errUnknownPragma, errInvalidDirectiveX,
-    errAtPopWithoutPush, errEmptyAsm, errInvalidIndentation,
-    errExceptionExpected, errExceptionAlreadyHandled,
-    errYieldNotAllowedHere, errYieldNotAllowedInTryStmt,
-    errInvalidNumberOfYieldExpr, errCannotReturnExpr, errAttemptToRedefine,
-    errStmtInvalidAfterReturn, errStmtExpected, errInvalidLabel,
-    errInvalidCmdLineOption, errCmdLineArgExpected, errCmdLineNoArgExpected,
-    errInvalidVarSubstitution, errUnknownVar, errUnknownCcompiler,
-    errOnOrOffExpectedButXFound, errOnOffOrListExpectedButXFound,
-    errNoneBoehmRefcExpectedButXFound,
-    errNoneSpeedOrSizeExpectedButXFound, errGuiConsoleOrLibExpectedButXFound,
-    errUnknownOS, errUnknownCPU, errGenOutExpectedButXFound,
-    errArgsNeedRunOption, errInvalidMultipleAsgn, errColonOrEqualsExpected,
-    errExprExpected, errUndeclaredField,
-    errUndeclaredRoutine, errUseQualifier,
-    errTypeExpected,
-    errSystemNeeds, errExecutionOfProgramFailed, errNotOverloadable,
-    errInvalidArgForX, errStmtHasNoEffect, errXExpectsTypeOrValue,
-    errXExpectsArrayType, errIteratorCannotBeInstantiated, errExprXAmbiguous,
-    errConstantDivisionByZero, errOrdinalTypeExpected,
-    errOrdinalOrFloatTypeExpected, errOverOrUnderflow,
-    errCannotEvalXBecauseIncompletelyDefined, errChrExpectsRange0_255,
-    errDynlibRequiresExportc, errUndeclaredFieldX, errNilAccess,
-    errIndexOutOfBounds, errIndexTypesDoNotMatch, errBracketsInvalidForType,
-    errValueOutOfSetBounds, errFieldInitTwice, errFieldNotInit,
-    errExprXCannotBeCalled, errExprHasNoType, errExprXHasNoType,
-    errCastNotInSafeMode, errExprCannotBeCastToX, errCommaOrParRiExpected,
-    errCurlyLeOrParLeExpected, errSectionExpected, errRangeExpected,
-    errMagicOnlyInSystem, errPowerOfTwoExpected,
-    errStringMayNotBeEmpty, errCallConvExpected, errProcOnlyOneCallConv,
-    errSymbolMustBeImported, errExprMustBeBool, errConstExprExpected,
-    errDuplicateCaseLabel, errRangeIsEmpty, errSelectorMustBeOfCertainTypes,
-    errSelectorMustBeOrdinal, errOrdXMustNotBeNegative, errLenXinvalid,
-    errWrongNumberOfVariables, errExprCannotBeRaised, errBreakOnlyInLoop,
-    errTypeXhasUnknownSize, errConstNeedsConstExpr, errConstNeedsValue,
-    errResultCannotBeOpenArray, errSizeTooBig, errSetTooBig,
-    errBaseTypeMustBeOrdinal, errInheritanceOnlyWithNonFinalObjects,
-    errInheritanceOnlyWithEnums, errIllegalRecursionInTypeX,
-    errCannotInstantiateX, errExprHasNoAddress, errXStackEscape,
-    errVarForOutParamNeeded,
-    errPureTypeMismatch, errTypeMismatch, errButExpected, errButExpectedX,
-    errAmbiguousCallXYZ, errWrongNumberOfArguments,
-    errWrongNumberOfArgumentsInCall,
-    errMissingGenericParamsForTemplate,
-    errXCannotBePassedToProcVar,
-    errXCannotBeInParamDecl, errPragmaOnlyInHeaderOfProcX, errImplOfXNotAllowed,
-    errImplOfXexpected, errNoSymbolToBorrowFromFound, errDiscardValueX,
-    errInvalidDiscard, errIllegalConvFromXtoY, errCannotBindXTwice,
-    errInvalidOrderInArrayConstructor,
-    errInvalidOrderInEnumX, errEnumXHasHoles, errExceptExpected, errInvalidTry,
-    errOptionExpected, errXisNoLabel, errNotAllCasesCovered,
-    errUnknownSubstitionVar, errComplexStmtRequiresInd, errXisNotCallable,
-    errNoPragmasAllowedForX, errNoGenericParamsAllowedForX,
-    errInvalidParamKindX, errDefaultArgumentInvalid, errNamedParamHasToBeIdent,
-    errNoReturnTypeForX, errConvNeedsOneArg, errInvalidPragmaX,
-    errXNotAllowedHere, errInvalidControlFlowX,
-    errXisNoType, errCircumNeedsPointer, errInvalidExpression,
-    errInvalidExpressionX, errEnumHasNoValueX, errNamedExprExpected,
-    errNamedExprNotAllowed, errXExpectsOneTypeParam,
-    errArrayExpectsTwoTypeParams, errInvalidVisibilityX, errInitHereNotAllowed,
-    errXCannotBeAssignedTo, errIteratorNotAllowed, errXNeedsReturnType,
-    errNoReturnTypeDeclared,
-    errNoCommand, errInvalidCommandX, errXOnlyAtModuleScope,
-    errXNeedsParamObjectType,
-    errTemplateInstantiationTooNested, errInstantiationFrom,
-    errInvalidIndexValueForTuple, errCommandExpectsFilename,
-    errMainModuleMustBeSpecified,
-    errXExpected,
-    errTIsNotAConcreteType,
-    errCastToANonConcreteType,
-    errInvalidSectionStart, errGridTableNotImplemented, errGeneralParseError,
-    errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile,
-    errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitly,
-    errOnlyACallOpCanBeDelegator, errUsingNoSymbol,
-    errMacroBodyDependsOnGenericTypes,
-    errDestructorNotGenericEnough,
-    errInlineIteratorsAsProcParams,
-    errXExpectsTwoArguments,
-    errXExpectsObjectTypes, errXcanNeverBeOfThisSubtype, errTooManyIterations,
-    errCannotInterpretNodeX, errFieldXNotFound, errInvalidConversionFromTypeX,
-    errAssertionFailed, errCannotGenerateCodeForX, errXRequiresOneArgument,
-    errUnhandledExceptionX, errCyclicTree, errXisNoMacroOrTemplate,
-    errXhasSideEffects, errIteratorExpected, errLetNeedsInit,
-    errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX,
-    errXCannotBeClosure, errXMustBeCompileTime,
-    errCannotInferTypeOfTheLiteral,
-    errCannotInferReturnType,
-    errGenericLambdaNotAllowed,
-    errProcHasNoConcreteType,
-    errCompilerDoesntSupportTarget,
-    errUser,
-    warnCannotOpenFile,
-    warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit,
-    warnDeprecated, warnConfigDeprecated,
-    warnSmallLshouldNotBeUsed, warnUnknownMagic, warnRedefinitionOfLabel,
-    warnUnknownSubstitutionX, warnLanguageXNotSupported,
-    warnFieldXNotSupported, warnCommentXIgnored,
-    warnNilStatement, warnTypelessParam,
-    warnUseBase, warnWriteToForeignHeap, warnUnsafeCode,
-    warnEachIdentIsTuple, warnShadowIdent,
-    warnProveInit, warnProveField, warnProveIndex, warnGcUnsafe, warnGcUnsafe2,
-    warnUninit, warnGcMem, warnDestructor, warnLockLevel, warnResultShadowed,
-    warnUser,
-    hintSuccess, hintSuccessX,
-    hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
-    hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
-    hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
-    hintConditionAlwaysTrue, hintName, hintPattern,
-    hintExecuting, hintLinking, hintDependency,
-    hintSource, hintStackTrace, hintGCStats,
-    hintUser
-
-const
-  MsgKindToStr*: array[TMsgKind, string] = [
-    errUnknown: "unknown error",
-    errInternal: "internal error: $1",
-    errIllFormedAstX: "illformed AST: $1",
-    errCannotOpenFile: "cannot open \'$1\'",
-    errGenerated: "$1",
-    errXCompilerDoesNotSupportCpp: "\'$1\' compiler does not support C++",
-    errStringLiteralExpected: "string literal expected",
-    errIntLiteralExpected: "integer literal expected",
-    errInvalidCharacterConstant: "invalid character constant",
-    errClosingTripleQuoteExpected: "closing \"\"\" expected, but end of file reached",
-    errClosingQuoteExpected: "closing \" expected",
-    errTabulatorsAreNotAllowed: "tabulators are not allowed",
-    errInvalidToken: "invalid token: $1",
-    errLineTooLong: "line too long",
-    errInvalidNumber: "$1 is not a valid number",
-    errInvalidNumberOctalCode: "$1 is not a valid number; did you mean octal? Then use one of '0o', '0c' or '0C'.",
-    errNumberOutOfRange: "number $1 out of valid range",
-    errNnotAllowedInCharacter: "\\n not allowed in character literal",
-    errClosingBracketExpected: "closing ']' expected, but end of file reached",
-    errMissingFinalQuote: "missing final \' for character literal",
-    errIdentifierExpected: "identifier expected, but found \'$1\'",
-    errNewlineExpected: "newline expected, but found \'$1\'",
-    errInvalidModuleName: "invalid module name: '$1'",
-    errOperatorExpected: "operator expected, but found \'$1\'",
-    errTokenExpected: "\'$1\' expected",
-    errStringAfterIncludeExpected: "string after \'include\' expected",
-    errRecursiveDependencyX: "recursive dependency: \'$1\'",
-    errOnOrOffExpected: "\'on\' or \'off\' expected",
-    errNoneSpeedOrSizeExpected: "\'none\', \'speed\' or \'size\' expected",
-    errInvalidPragma: "invalid pragma",
-    errUnknownPragma: "unknown pragma: \'$1\'",
-    errInvalidDirectiveX: "invalid directive: \'$1\'",
-    errAtPopWithoutPush: "\'pop\' without a \'push\' pragma",
-    errEmptyAsm: "empty asm statement",
-    errInvalidIndentation: "invalid indentation",
-    errExceptionExpected: "exception expected",
-    errExceptionAlreadyHandled: "exception already handled",
-    errYieldNotAllowedHere: "'yield' only allowed in an iterator",
-    errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator",
-    errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expressions",
-    errCannotReturnExpr: "current routine cannot return an expression",
-    errAttemptToRedefine: "redefinition of \'$1\'",
-    errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\', \'raise\' or \'continue'",
-    errStmtExpected: "statement expected",
-    errInvalidLabel: "\'$1\' is no label",
-    errInvalidCmdLineOption: "invalid command line option: \'$1\'",
-    errCmdLineArgExpected: "argument for command line option expected: \'$1\'",
-    errCmdLineNoArgExpected: "invalid argument for command line option: \'$1\'",
-    errInvalidVarSubstitution: "invalid variable substitution in \'$1\'",
-    errUnknownVar: "unknown variable: \'$1\'",
-    errUnknownCcompiler: "unknown C compiler: \'$1\'",
-    errOnOrOffExpectedButXFound: "\'on\' or \'off\' expected, but \'$1\' found",
-    errOnOffOrListExpectedButXFound: "\'on\', \'off\' or \'list\' expected, but \'$1\' found",
-    errNoneBoehmRefcExpectedButXFound: "'none', 'boehm' or 'refc' expected, but '$1' found",
-    errNoneSpeedOrSizeExpectedButXFound: "'none', 'speed' or 'size' expected, but '$1' found",
-    errGuiConsoleOrLibExpectedButXFound: "'gui', 'console' or 'lib' expected, but '$1' found",
-    errUnknownOS: "unknown OS: '$1'",
-    errUnknownCPU: "unknown CPU: '$1'",
-    errGenOutExpectedButXFound: "'c', 'c++' or 'yaml' expected, but '$1' found",
-    errArgsNeedRunOption: "arguments can only be given if the '--run' option is selected",
-    errInvalidMultipleAsgn: "multiple assignment is not allowed",
-    errColonOrEqualsExpected: "\':\' or \'=\' expected, but found \'$1\'",
-    errExprExpected: "expression expected, but found \'$1\'",
-    errUndeclaredField: "undeclared field: \'$1\'",
-    errUndeclaredRoutine: "attempting to call undeclared routine: \'$1\'",
-    errUseQualifier: "ambiguous identifier: \'$1\' -- use a qualifier",
-    errTypeExpected: "type expected",
-    errSystemNeeds: "system module needs \'$1\'",
-    errExecutionOfProgramFailed: "execution of an external program failed: '$1'",
-    errNotOverloadable: "overloaded \'$1\' leads to ambiguous calls",
-    errInvalidArgForX: "invalid argument for \'$1\'",
-    errStmtHasNoEffect: "statement has no effect",
-    errXExpectsTypeOrValue: "\'$1\' expects a type or value",
-    errXExpectsArrayType: "\'$1\' expects an array type",
-    errIteratorCannotBeInstantiated: "'$1' cannot be instantiated because its body has not been compiled yet",
-    errExprXAmbiguous: "expression '$1' ambiguous in this context",
-    errConstantDivisionByZero: "division by zero",
-    errOrdinalTypeExpected: "ordinal type expected",
-    errOrdinalOrFloatTypeExpected: "ordinal or float type expected",
-    errOverOrUnderflow: "over- or underflow",
-    errCannotEvalXBecauseIncompletelyDefined: "cannot evaluate '$1' because type is not defined completely",
-    errChrExpectsRange0_255: "\'chr\' expects an int in the range 0..255",
-    errDynlibRequiresExportc: "\'dynlib\' requires \'exportc\'",
-    errUndeclaredFieldX: "undeclared field: \'$1\'",
-    errNilAccess: "attempt to access a nil address",
-    errIndexOutOfBounds: "index out of bounds",
-    errIndexTypesDoNotMatch: "index types do not match",
-    errBracketsInvalidForType: "\'[]\' operator invalid for this type",
-    errValueOutOfSetBounds: "value out of set bounds",
-    errFieldInitTwice: "field initialized twice: \'$1\'",
-    errFieldNotInit: "field \'$1\' not initialized",
-    errExprXCannotBeCalled: "expression \'$1\' cannot be called",
-    errExprHasNoType: "expression has no type",
-    errExprXHasNoType: "expression \'$1\' has no type (or is ambiguous)",
-    errCastNotInSafeMode: "\'cast\' not allowed in safe mode",
-    errExprCannotBeCastToX: "expression cannot be cast to $1",
-    errCommaOrParRiExpected: "',' or ')' expected",
-    errCurlyLeOrParLeExpected: "\'{\' or \'(\' expected",
-    errSectionExpected: "section (\'type\', \'proc\', etc.) expected",
-    errRangeExpected: "range expected",
-    errMagicOnlyInSystem: "\'magic\' only allowed in system module",
-    errPowerOfTwoExpected: "power of two expected",
-    errStringMayNotBeEmpty: "string literal may not be empty",
-    errCallConvExpected: "calling convention expected",
-    errProcOnlyOneCallConv: "a proc can only have one calling convention",
-    errSymbolMustBeImported: "symbol must be imported if 'lib' pragma is used",
-    errExprMustBeBool: "expression must be of type 'bool'",
-    errConstExprExpected: "constant expression expected",
-    errDuplicateCaseLabel: "duplicate case label",
-    errRangeIsEmpty: "range is empty",
-    errSelectorMustBeOfCertainTypes: "selector must be of an ordinal type, float or string",
-    errSelectorMustBeOrdinal: "selector must be of an ordinal type",
-    errOrdXMustNotBeNegative: "ord($1) must not be negative",
-    errLenXinvalid: "len($1) must be less than 32768",
-    errWrongNumberOfVariables: "wrong number of variables",
-    errExprCannotBeRaised: "only a 'ref object' can be raised",
-    errBreakOnlyInLoop: "'break' only allowed in loop construct",
-    errTypeXhasUnknownSize: "type \'$1\' has unknown size",
-    errConstNeedsConstExpr: "a constant can only be initialized with a constant expression",
-    errConstNeedsValue: "a constant needs a value",
-    errResultCannotBeOpenArray: "the result type cannot be on open array",
-    errSizeTooBig: "computing the type\'s size produced an overflow",
-    errSetTooBig: "set is too large",
-    errBaseTypeMustBeOrdinal: "base type of a set must be an ordinal",
-    errInheritanceOnlyWithNonFinalObjects: "inheritance only works with non-final objects",
-    errInheritanceOnlyWithEnums: "inheritance only works with an enum",
-    errIllegalRecursionInTypeX: "illegal recursion in type \'$1\'",
-    errCannotInstantiateX: "cannot instantiate: \'$1\'",
-    errExprHasNoAddress: "expression has no address",
-    errXStackEscape: "address of '$1' may not escape its stack frame",
-    errVarForOutParamNeeded: "for a \'var\' type a variable needs to be passed",
-    errPureTypeMismatch: "type mismatch",
-    errTypeMismatch: "type mismatch: got (",
-    errButExpected: "but expected one of: ",
-    errButExpectedX: "but expected \'$1\'",
-    errAmbiguousCallXYZ: "ambiguous call; both $1 and $2 match for: $3",
-    errWrongNumberOfArguments: "wrong number of arguments",
-    errWrongNumberOfArgumentsInCall: "wrong number of arguments in call to '$1'",
-    errMissingGenericParamsForTemplate: "'$1' has unspecified generic parameters",
-    errXCannotBePassedToProcVar: "\'$1\' cannot be passed to a procvar",
-    errXCannotBeInParamDecl: "$1 cannot be declared in parameter declaration",
-    errPragmaOnlyInHeaderOfProcX: "pragmas are only allowed in the header of a proc; redefinition of $1",
-    errImplOfXNotAllowed: "implementation of \'$1\' is not allowed",
-    errImplOfXexpected: "implementation of \'$1\' expected",
-    errNoSymbolToBorrowFromFound: "no symbol to borrow from found",
-    errDiscardValueX: "value of type '$1' has to be discarded",
-    errInvalidDiscard: "statement returns no value that can be discarded",
-    errIllegalConvFromXtoY: "conversion from $1 to $2 is invalid",
-    errCannotBindXTwice: "cannot bind parameter \'$1\' twice",
-    errInvalidOrderInArrayConstructor: "invalid order in array constructor",
-    errInvalidOrderInEnumX: "invalid order in enum \'$1\'",
-    errEnumXHasHoles: "enum \'$1\' has holes",
-    errExceptExpected: "\'except\' or \'finally\' expected",
-    errInvalidTry: "after catch all \'except\' or \'finally\' no section may follow",
-    errOptionExpected: "option expected, but found \'$1\'",
-    errXisNoLabel: "\'$1\' is not a label",
-    errNotAllCasesCovered: "not all cases are covered",
-    errUnknownSubstitionVar: "unknown substitution variable: \'$1\'",
-    errComplexStmtRequiresInd: "complex statement requires indentation",
-    errXisNotCallable: "\'$1\' is not callable",
-    errNoPragmasAllowedForX: "no pragmas allowed for $1",
-    errNoGenericParamsAllowedForX: "no generic parameters allowed for $1",
-    errInvalidParamKindX: "invalid param kind: \'$1\'",
-    errDefaultArgumentInvalid: "default argument invalid",
-    errNamedParamHasToBeIdent: "named parameter has to be an identifier",
-    errNoReturnTypeForX: "no return type allowed for $1",
-    errConvNeedsOneArg: "a type conversion needs exactly one argument",
-    errInvalidPragmaX: "invalid pragma: $1",
-    errXNotAllowedHere: "$1 not allowed here",
-    errInvalidControlFlowX: "invalid control flow: $1",
-    errXisNoType: "invalid type: \'$1\'",
-    errCircumNeedsPointer: "'[]' needs a pointer or reference type",
-    errInvalidExpression: "invalid expression",
-    errInvalidExpressionX: "invalid expression: \'$1\'",
-    errEnumHasNoValueX: "enum has no value \'$1\'",
-    errNamedExprExpected: "named expression expected",
-    errNamedExprNotAllowed: "named expression not allowed here",
-    errXExpectsOneTypeParam: "\'$1\' expects one type parameter",
-    errArrayExpectsTwoTypeParams: "array expects two type parameters",
-    errInvalidVisibilityX: "invalid visibility: \'$1\'",
-    errInitHereNotAllowed: "initialization not allowed here",
-    errXCannotBeAssignedTo: "\'$1\' cannot be assigned to",
-    errIteratorNotAllowed: "iterators can only be defined at the module\'s top level",
-    errXNeedsReturnType: "$1 needs a return type",
-    errNoReturnTypeDeclared: "no return type declared",
-    errNoCommand: "no command given",
-    errInvalidCommandX: "invalid command: \'$1\'",
-    errXOnlyAtModuleScope: "\'$1\' is only allowed at top level",
-    errXNeedsParamObjectType: "'$1' needs a parameter that has an object type",
-    errTemplateInstantiationTooNested: "template/macro instantiation too nested",
-    errInstantiationFrom: "template/generic instantiation from here",
-    errInvalidIndexValueForTuple: "invalid index value for tuple subscript",
-    errCommandExpectsFilename: "command expects a filename argument",
-    errMainModuleMustBeSpecified: "please, specify a main module in the project configuration file",
-    errXExpected: "\'$1\' expected",
-    errTIsNotAConcreteType: "\'$1\' is not a concrete type.",
-    errCastToANonConcreteType: "cannot cast to a non concrete type: \'$1\'",
-    errInvalidSectionStart: "invalid section start",
-    errGridTableNotImplemented: "grid table is not implemented",
-    errGeneralParseError: "general parse error",
-    errNewSectionExpected: "new section expected",
-    errWhitespaceExpected: "whitespace expected, got \'$1\'",
-    errXisNoValidIndexFile: "\'$1\' is no valid index file",
-    errCannotRenderX: "cannot render reStructuredText element \'$1\'",
-    errVarVarTypeNotAllowed: "type \'var var\' is not allowed",
-    errInstantiateXExplicitly: "instantiate '$1' explicitly",
-    errOnlyACallOpCanBeDelegator: "only a call operator can be a delegator",
-    errUsingNoSymbol: "'$1' is not a variable, constant or a proc name",
-    errMacroBodyDependsOnGenericTypes: "the macro body cannot be compiled, " &
-                                       "because the parameter '$1' has a generic type",
-    errDestructorNotGenericEnough: "Destructor signature is too specific. " &
-                                   "A destructor must be associated will all instantiations of a generic type",
-    errInlineIteratorsAsProcParams: "inline iterators can be used as parameters only for " &
-                                    "templates, macros and other inline iterators",
-    errXExpectsTwoArguments: "\'$1\' expects two arguments",
-    errXExpectsObjectTypes: "\'$1\' expects object types",
-    errXcanNeverBeOfThisSubtype: "\'$1\' can never be of this subtype",
-    errTooManyIterations: "interpretation requires too many iterations",
-    errCannotInterpretNodeX: "cannot evaluate \'$1\'",
-    errFieldXNotFound: "field \'$1\' cannot be found",
-    errInvalidConversionFromTypeX: "invalid conversion from type \'$1\'",
-    errAssertionFailed: "assertion failed",
-    errCannotGenerateCodeForX: "cannot generate code for \'$1\'",
-    errXRequiresOneArgument: "$1 requires one parameter",
-    errUnhandledExceptionX: "unhandled exception: $1",
-    errCyclicTree: "macro returned a cyclic abstract syntax tree",
-    errXisNoMacroOrTemplate: "\'$1\' is no macro or template",
-    errXhasSideEffects: "\'$1\' can have side effects",
-    errIteratorExpected: "iterator within for loop context expected",
-    errLetNeedsInit: "'let' symbol requires an initialization",
-    errThreadvarCannotInit: "a thread var cannot be initialized explicitly",
-    errWrongSymbolX: "usage of \'$1\' is a user-defined error",
-    errIllegalCaptureX: "illegal capture '$1'",
-    errXCannotBeClosure: "'$1' cannot have 'closure' calling convention",
-    errXMustBeCompileTime: "'$1' can only be used in compile-time context",
-    errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1",
-    errCannotInferReturnType: "cannot infer the return type of the proc",
-    errGenericLambdaNotAllowed: "A nested proc can have generic parameters only when " &
-                                "it is used as an operand to another routine and the types " &
-                                "of the generic paramers can be inferred from the expected signature.",
-    errProcHasNoConcreteType: "'$1' doesn't have a concrete type, due to unspecified generic parameters.",
-    errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target",
-    errUser: "$1",
-    warnCannotOpenFile: "cannot open \'$1\'",
-    warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored",
-    warnXIsNeverRead: "\'$1\' is never read",
-    warnXmightNotBeenInit: "\'$1\' might not have been initialized",
-    warnDeprecated: "$1 is deprecated",
-    warnConfigDeprecated: "config file '$1' is deprecated",
-    warnSmallLshouldNotBeUsed: "\'l\' should not be used as an identifier; may look like \'1\' (one)",
-    warnUnknownMagic: "unknown magic \'$1\' might crash the compiler",
-    warnRedefinitionOfLabel: "redefinition of label \'$1\'",
-    warnUnknownSubstitutionX: "unknown substitution \'$1\'",
-    warnLanguageXNotSupported: "language \'$1\' not supported",
-    warnFieldXNotSupported: "field \'$1\' not supported",
-    warnCommentXIgnored: "comment \'$1\' ignored",
-    warnNilStatement: "'nil' statement is deprecated; use an empty 'discard' statement instead",
-    warnTypelessParam: "'$1' has no type. Typeless parameters are deprecated; only allowed for 'template'",
-    warnUseBase: "use {.base.} for base methods; baseless methods are deprecated",
-    warnWriteToForeignHeap: "write to foreign heap",
-    warnUnsafeCode: "unsafe code: '$1'",
-    warnEachIdentIsTuple: "each identifier is a tuple",
-    warnShadowIdent: "shadowed identifier: '$1'",
-    warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.",
-    warnProveField: "cannot prove that field '$1' is accessible",
-    warnProveIndex: "cannot prove index '$1' is valid",
-    warnGcUnsafe: "not GC-safe: '$1'",
-    warnGcUnsafe2: "$1",
-    warnUninit: "'$1' might not have been initialized",
-    warnGcMem: "'$1' uses GC'ed memory",
-    warnDestructor: "usage of a type with a destructor in a non destructible context. This will become a compile time error in the future.",
-    warnLockLevel: "$1",
-    warnResultShadowed: "Special variable 'result' is shadowed.",
-    warnUser: "$1",
-    hintSuccess: "operation successful",
-    hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#; $#)",
-    hintLineTooLong: "line too long",
-    hintXDeclaredButNotUsed: "\'$1\' is declared but not used",
-    hintConvToBaseNotNeeded: "conversion to base object is not needed",
-    hintConvFromXtoItselfNotNeeded: "conversion from $1 to itself is pointless",
-    hintExprAlwaysX: "expression evaluates always to \'$1\'",
-    hintQuitCalled: "quit() called",
-    hintProcessing: "$1",
-    hintCodeBegin: "generated code listing:",
-    hintCodeEnd: "end of listing",
-    hintConf: "used config file \'$1\'",
-    hintPath: "added path: '$1'",
-    hintConditionAlwaysTrue: "condition is always true: '$1'",
-    hintName: "name should be: '$1'",
-    hintPattern: "$1",
-    hintExecuting: "$1",
-    hintLinking: "",
-    hintDependency: "$1",
-    hintSource: "$1",
-    hintStackTrace: "$1",
-    hintGCStats: "$1",
-    hintUser: "$1"]
-
-const
-  WarningsToStr*: array[0..30, string] = ["CannotOpenFile", "OctalEscape",
-    "XIsNeverRead", "XmightNotBeenInit",
-    "Deprecated", "ConfigDeprecated",
-    "SmallLshouldNotBeUsed", "UnknownMagic",
-    "RedefinitionOfLabel", "UnknownSubstitutionX",
-    "LanguageXNotSupported", "FieldXNotSupported",
-    "CommentXIgnored", "NilStmt",
-    "TypelessParam", "UseBase", "WriteToForeignHeap",
-    "UnsafeCode", "EachIdentIsTuple", "ShadowIdent",
-    "ProveInit", "ProveField", "ProveIndex", "GcUnsafe", "GcUnsafe2", "Uninit",
-    "GcMem", "Destructor", "LockLevel", "ResultShadowed", "User"]
-
-  HintsToStr*: array[0..22, string] = ["Success", "SuccessX", "LineTooLong",
-    "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
-    "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
-    "Path", "CondTrue", "Name", "Pattern", "Exec", "Link", "Dependency",
-    "Source", "StackTrace", "GCStats",
-    "User"]
-
-const
-  fatalMin* = errUnknown
-  fatalMax* = errInternal
-  errMin* = errUnknown
-  errMax* = errUser
-  warnMin* = warnCannotOpenFile
-  warnMax* = pred(hintSuccess)
-  hintMin* = hintSuccess
-  hintMax* = high(TMsgKind)
-
-type
-  TNoteKind* = range[warnMin..hintMax] # "notes" are warnings or hints
-  TNoteKinds* = set[TNoteKind]
-
-  TFileInfo* = object
-    fullPath: string           # This is a canonical full filesystem path
-    projPath*: string          # This is relative to the project's root
-    shortName*: string         # short name of the module
-    quotedName*: Rope          # cached quoted short name for codegen
-                               # purposes
-    quotedFullName*: Rope      # cached quoted full name for codegen
-                               # purposes
-
-    lines*: seq[Rope]          # the source code of the module
-                               #   used for better error messages and
-                               #   embedding the original source in the
-                               #   generated code
-    dirtyfile: string          # the file that is actually read into memory
-                               # and parsed; usually 'nil' but is used
-                               # for 'nimsuggest'
-
-  TLineInfo* = object          # This is designed to be as small as possible,
-                               # because it is used
-                               # in syntax nodes. We save space here by using
-                               # two int16 and an int32.
-                               # On 64 bit and on 32 bit systems this is
-                               # only 8 bytes.
-    line*, col*: int16
-    fileIndex*: int32
-
-  TErrorOutput* = enum
-    eStdOut
-    eStdErr
-    eInMemory
-
-  TErrorOutputs* = set[TErrorOutput]
-
-  ERecoverableError* = object of ValueError
-  ESuggestDone* = object of Exception
-
-const
-  NotesVerbosity*: array[0..3, TNoteKinds] = [
-    {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit,
-                                         warnProveField, warnProveIndex,
-                                         warnGcUnsafe,
-                                         hintSuccessX, hintPath, hintConf,
-                                         hintProcessing, hintPattern,
-                                         hintDependency,
-                                         hintExecuting, hintLinking,
-                                         hintCodeBegin, hintCodeEnd,
-                                         hintSource, hintStackTrace,
-                                         hintGCStats},
-    {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent, warnUninit,
-                                         warnProveField, warnProveIndex,
-                                         warnGcUnsafe,
-                                         hintPath,
-                                         hintDependency,
-                                         hintCodeBegin, hintCodeEnd,
-                                         hintSource, hintStackTrace,
-                                         hintGCStats},
-    {low(TNoteKind)..high(TNoteKind)} - {hintStackTrace, warnUninit},
-    {low(TNoteKind)..high(TNoteKind)}]
-
-const
-  InvalidFileIDX* = int32(-1)
-
-var
-  ForeignPackageNotes*: TNoteKinds = {hintProcessing, warnUnknownMagic,
-    hintQuitCalled, hintExecuting}
-  filenameToIndexTbl = initTable[string, int32]()
-  fileInfos*: seq[TFileInfo] = @[]
-  systemFileIdx*: int32
-
-proc toCChar*(c: char): string =
+proc toCChar*(c: char; result: var string) =
   case c
-  of '\0'..'\x1F', '\x80'..'\xFF': result = '\\' & toOctal(c)
-  of '\'', '\"', '\\', '?': result = '\\' & c
-  else: result = $(c)
+  of '\0'..'\x1F', '\x7F'..'\xFF':
+    result.add '\\'
+    result.add toOctal(c)
+  of '\'', '\"', '\\', '?':
+    result.add '\\'
+    result.add c
+  else:
+    result.add c
 
 proc makeCString*(s: string): Rope =
-  const
-    MaxLineLength = 64
+  const MaxLineLength = 64
   result = nil
   var res = newStringOfCap(int(s.len.toFloat * 1.1) + 1)
   add(res, "\"")
   for i in countup(0, len(s) - 1):
     if (i + 1) mod MaxLineLength == 0:
-      add(res, '\"')
-      add(res, tnl)
-      add(res, '\"')
-    add(res, toCChar(s[i]))
+      add(res, "\"\L\"")
+    toCChar(s[i], res)
   add(res, '\"')
   add(result, rope(res))
 
 
-proc newFileInfo(fullPath, projPath: string): TFileInfo =
+proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
   result.fullPath = fullPath
   #shallow(result.fullPath)
   result.projPath = projPath
   #shallow(result.projPath)
-  let fileName = projPath.extractFilename
+  let fileName = fullPath.extractFilename
   result.shortName = fileName.changeFileExt("")
   result.quotedName = fileName.makeCString
-  result.quotedFullName = fullPath.makeCString
-  if optEmbedOrigSrc in gGlobalOptions or true:
-    result.lines = @[]
-
-proc fileInfoKnown*(filename: string): bool =
+  result.quotedFullName = fullPath.string.makeCString
+  result.lines = @[]
+  when defined(nimpretty):
+    if not result.fullPath.isEmpty:
+      try:
+        result.fullContent = readFile(result.fullPath.string)
+      except IOError:
+        #rawMessage(errCannotOpenFile, result.fullPath)
+        # XXX fixme
+        result.fullContent = ""
+
+when defined(nimpretty):
+  proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string =
+    substr(conf.m.fileInfos[fid.int].fullContent, a, b)
+
+proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
   var
-    canon: string
+    canon: AbsoluteFile
   try:
-    canon = canonicalizePath(filename)
-  except:
+    canon = canonicalizePath(conf, filename)
+  except OSError:
     canon = filename
-  result = filenameToIndexTbl.hasKey(canon)
+  result = conf.m.filenameToIndexTbl.hasKey(canon.string)
 
-proc fileInfoIdx*(filename: string; isKnownFile: var bool): int32 =
+proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex =
   var
-    canon: string
+    canon: AbsoluteFile
     pseudoPath = false
 
   try:
-    canon = canonicalizePath(filename)
-    shallow(canon)
-  except:
+    canon = canonicalizePath(conf, filename)
+    shallow(canon.string)
+  except OSError:
     canon = filename
     # The compiler uses "filenames" such as `command line` or `stdin`
     # This flag indicates that we are working with such a path here
     pseudoPath = true
 
-  if filenameToIndexTbl.hasKey(canon):
-    result = filenameToIndexTbl[canon]
+  if conf.m.filenameToIndexTbl.hasKey(canon.string):
+    result = conf.m.filenameToIndexTbl[canon.string]
   else:
     isKnownFile = false
-    result = fileInfos.len.int32
-    fileInfos.add(newFileInfo(canon, if pseudoPath: filename
-                                     else: canon.shortenDir))
-    filenameToIndexTbl[canon] = result
+    result = conf.m.fileInfos.len.FileIndex
+    conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename
+                                            else: relativeTo(canon, conf.projectPath)))
+    conf.m.filenameToIndexTbl[canon.string] = result
 
-proc fileInfoIdx*(filename: string): int32 =
+proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
   var dummy: bool
-  result = fileInfoIdx(filename, dummy)
+  result = fileInfoIdx(conf, filename, dummy)
 
-proc newLineInfo*(fileInfoIdx: int32, line, col: int): TLineInfo =
+proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
   result.fileIndex = fileInfoIdx
-  result.line = int16(line)
+  result.line = uint16(line)
   result.col = int16(col)
 
-proc newLineInfo*(filename: string, line, col: int): TLineInfo {.inline.} =
-  result = newLineInfo(filename.fileInfoIdx, line, col)
-
-fileInfos.add(newFileInfo("", "command line"))
-var gCmdLineInfo* = newLineInfo(int32(0), 1, 1)
-
-fileInfos.add(newFileInfo("", "compilation artifact"))
-var gCodegenLineInfo* = newLineInfo(int32(1), 1, 1)
-
-proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} =
-  raise newException(ERecoverableError, msg)
-
-proc sourceLine*(i: TLineInfo): Rope
-
-var
-  gNotes*: TNoteKinds = NotesVerbosity[1] # defaults to verbosity of 1
-  gErrorCounter*: int = 0     # counts the number of errors
-  gHintCounter*: int = 0
-  gWarnCounter*: int = 0
-  gErrorMax*: int = 1         # stop after gErrorMax errors
-  gMainPackageNotes*: TNoteKinds = NotesVerbosity[1]
-
-proc unknownLineInfo*(): TLineInfo =
-  result.line = int16(-1)
-  result.col = int16(-1)
-  result.fileIndex = -1
-
-type
-  Severity* {.pure.} = enum ## VS Code only supports these three
-    Hint, Warning, Error
+proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} =
+  result = newLineInfo(fileInfoIdx(conf, filename), line, col)
 
-var
-  msgContext: seq[TLineInfo] = @[]
-  lastError = unknownLineInfo()
 
-  errorOutputs* = {eStdOut, eStdErr}
-  writelnHook*: proc (output: string) {.closure.}
-  structuredErrorHook*: proc (info: TLineInfo; msg: string; severity: Severity) {.closure.}
+proc concat(strings: openarray[string]): string =
+  var totalLen = 0
+  for s in strings: totalLen += s.len
+  result = newStringOfCap totalLen
+  for s in strings: result.add s
 
-proc suggestWriteln*(s: string) =
-  if eStdOut in errorOutputs:
-    if isNil(writelnHook):
+proc suggestWriteln*(conf: ConfigRef; s: string) =
+  if eStdOut in conf.m.errorOutputs:
+    if isNil(conf.writelnHook):
       writeLine(stdout, s)
       flushFile(stdout)
     else:
-      writelnHook(s)
+      conf.writelnHook(s)
 
 proc msgQuit*(x: int8) = quit x
 proc msgQuit*(x: string) = quit x
@@ -678,77 +136,87 @@ const
   HintTitle    = "Hint: "
   HintColor    = fgGreen
 
-proc getInfoContextLen*(): int = return msgContext.len
-proc setInfoContextLen*(L: int) = setLen(msgContext, L)
+proc getInfoContextLen*(conf: ConfigRef): int = return conf.m.msgContext.len
+proc setInfoContextLen*(conf: ConfigRef; L: int) = setLen(conf.m.msgContext, L)
 
-proc pushInfoContext*(info: TLineInfo) =
-  msgContext.add(info)
+proc pushInfoContext*(conf: ConfigRef; info: TLineInfo; detail: string = "") =
+  conf.m.msgContext.add((info, detail))
 
-proc popInfoContext*() =
-  setLen(msgContext, len(msgContext) - 1)
+proc popInfoContext*(conf: ConfigRef) =
+  setLen(conf.m.msgContext, len(conf.m.msgContext) - 1)
 
-proc getInfoContext*(index: int): TLineInfo =
-  let L = msgContext.len
+proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
+  let L = conf.m.msgContext.len
   let i = if index < 0: L + index else: index
   if i >=% L: result = unknownLineInfo()
-  else: result = msgContext[i]
+  else: result = conf.m.msgContext[i].info
 
-template toFilename*(fileIdx: int32): string =
-  (if fileIdx < 0: "???" else: fileInfos[fileIdx].projPath)
+template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
+  if fileIdx.int32 < 0 or conf == nil:
+    "???"
+  else:
+    conf.m.fileInfos[fileIdx.int32].projPath.string
 
-proc toFullPath*(fileIdx: int32): string =
-  if fileIdx < 0: result = "???"
-  else: result = fileInfos[fileIdx].fullPath
+proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
+  if fileIdx.int32 < 0 or conf == nil: result = "???"
+  else: result = conf.m.fileInfos[fileIdx.int32].fullPath.string
 
-proc setDirtyFile*(fileIdx: int32; filename: string) =
-  assert fileIdx >= 0
-  fileInfos[fileIdx].dirtyFile = filename
+proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) =
+  assert fileIdx.int32 >= 0
+  conf.m.fileInfos[fileIdx.int32].dirtyFile = filename
 
-proc toFullPathConsiderDirty*(fileIdx: int32): string =
-  if fileIdx < 0:
-    result = "???"
-  elif not fileInfos[fileIdx].dirtyFile.isNil:
-    result = fileInfos[fileIdx].dirtyFile
+proc setHash*(conf: ConfigRef; fileIdx: FileIndex; hash: string) =
+  assert fileIdx.int32 >= 0
+  shallowCopy(conf.m.fileInfos[fileIdx.int32].hash, hash)
+
+proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string =
+  assert fileIdx.int32 >= 0
+  shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash)
+
+proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
+  if fileIdx.int32 < 0:
+    result = AbsoluteFile"???"
+  elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty:
+    result = conf.m.fileInfos[fileIdx.int32].dirtyFile
   else:
-    result = fileInfos[fileIdx].fullPath
+    result = conf.m.fileInfos[fileIdx.int32].fullPath
 
-template toFilename*(info: TLineInfo): string =
-  info.fileIndex.toFilename
+template toFilename*(conf: ConfigRef; info: TLineInfo): string =
+  toFilename(conf, info.fileIndex)
 
-template toFullPath*(info: TLineInfo): string =
-  info.fileIndex.toFullPath
+template toFullPath*(conf: ConfigRef; info: TLineInfo): string =
+  toFullPath(conf, info.fileIndex)
 
-proc toMsgFilename*(info: TLineInfo): string =
-  if info.fileIndex < 0:
+proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string =
+  if info.fileIndex.int32 < 0:
     result = "???"
-  elif gListFullPaths:
-    result = fileInfos[info.fileIndex].fullPath
+    return
+  let absPath = conf.m.fileInfos[info.fileIndex.int32].fullPath.string
+  let relPath = conf.m.fileInfos[info.fileIndex.int32].projPath.string
+  if optListFullPaths in conf.globalOptions:
+    result = absPath
   else:
-    result = fileInfos[info.fileIndex].projPath
+    result = if absPath.len < relPath.len: absPath else: relPath
 
 proc toLinenumber*(info: TLineInfo): int {.inline.} =
-  result = info.line
+  result = int info.line
 
 proc toColumn*(info: TLineInfo): int {.inline.} =
   result = info.col
 
-proc toFileLine*(info: TLineInfo): string {.inline.} =
-  result = info.toFilename & ":" & $info.line
+proc toFileLine*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
+  result = toFilename(conf, info) & ":" & $info.line
 
-proc toFileLineCol*(info: TLineInfo): string {.inline.} =
-  result = info.toFilename & "(" & $info.line & "," & $info.col & ")"
+proc toFileLineCol*(conf: ConfigRef; info: TLineInfo): string {.inline.} =
+  result = toFilename(conf, info) & "(" & $info.line & ", " & $info.col & ")"
 
-proc `$`*(info: TLineInfo): string = toFileLineCol(info)
+proc `$`*(conf: ConfigRef; info: TLineInfo): string = toFileLineCol(conf, info)
 
-proc `??`* (info: TLineInfo, filename: string): bool =
-  # only for debugging purposes
-  result = filename in info.toFilename
+proc `$`*(info: TLineInfo): string {.error.} = discard
 
-const trackPosInvalidFileIdx* = -2 # special marker so that no suggestions
-                                   # are produced within comments and string literals
-var gTrackPos*: TLineInfo
-var gTrackPosAttached*: bool ## whether the tracking position was attached to some
-                             ## close token.
+proc `??`* (conf: ConfigRef; info: TLineInfo, filename: string): bool =
+  # only for debugging purposes
+  result = filename in toFilename(conf, info)
 
 type
   MsgFlag* = enum  ## flags altering msgWriteln behavior
@@ -756,7 +224,7 @@ type
     msgSkipHook    ## skip message hook even if it is present
   MsgFlags* = set[MsgFlag]
 
-proc msgWriteln*(s: string, flags: MsgFlags = {}) =
+proc msgWriteln*(conf: ConfigRef; s: string, flags: MsgFlags = {}) =
   ## Writes given message string to stderr by default.
   ## If ``--stdout`` option is given, writes to stdout instead. If message hook
   ## is present, then it is used to output message rather than stderr/stdout.
@@ -764,16 +232,16 @@ proc msgWriteln*(s: string, flags: MsgFlags = {}) =
 
   ## This is used for 'nim dump' etc. where we don't have nimsuggest
   ## support.
-  #if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
+  #if conf.cmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
 
-  if not isNil(writelnHook) and msgSkipHook notin flags:
-    writelnHook(s)
-  elif optStdout in gGlobalOptions or msgStdout in flags:
-    if eStdOut in errorOutputs:
+  if not isNil(conf.writelnHook) and msgSkipHook notin flags:
+    conf.writelnHook(s)
+  elif optStdout in conf.globalOptions or msgStdout in flags:
+    if eStdOut in conf.m.errorOutputs:
       writeLine(stdout, s)
       flushFile(stdout)
   else:
-    if eStdErr in errorOutputs:
+    if eStdErr in conf.m.errorOutputs:
       writeLine(stderr, s)
       # On Windows stderr is fully-buffered when piped, regardless of C std.
       when defined(windows):
@@ -804,21 +272,18 @@ macro callStyledWriteLineStderr(args: varargs[typed]): untyped =
     result.add(arg)
 
 template callWritelnHook(args: varargs[string, `$`]) =
-  var s = ""
-  for arg in args:
-    s.add arg
-  writelnHook s
+  conf.writelnHook concat(args)
 
 template styledMsgWriteln*(args: varargs[typed]) =
-  if not isNil(writelnHook):
+  if not isNil(conf.writelnHook):
     callIgnoringStyle(callWritelnHook, nil, args)
-  elif optStdout in gGlobalOptions:
-    if eStdOut in errorOutputs:
+  elif optStdout in conf.globalOptions:
+    if eStdOut in conf.m.errorOutputs:
       callIgnoringStyle(writeLine, stdout, args)
       flushFile(stdout)
   else:
-    if eStdErr in errorOutputs:
-      if optUseColors in gGlobalOptions:
+    if eStdErr in conf.m.errorOutputs:
+      if optUseColors in conf.globalOptions:
         callStyledWriteLineStderr(args)
       else:
         callIgnoringStyle(writeLine, stderr, args)
@@ -846,27 +311,27 @@ proc log*(s: string) {.procvar.} =
     f.writeLine(s)
     close(f)
 
-proc quit(msg: TMsgKind) =
-  if defined(debug) or msg == errInternal or hintStackTrace in gNotes:
-    if stackTraceAvailable() and isNil(writelnHook):
+proc quit(conf: ConfigRef; msg: TMsgKind) =
+  if defined(debug) or msg == errInternal or hintStackTrace in conf.notes:
+    if stackTraceAvailable() and isNil(conf.writelnHook):
       writeStackTrace()
     else:
       styledMsgWriteln(fgRed, "No stack traceback available\n" &
           "To create a stacktrace, rerun compilation with ./koch temp " &
-          options.command & " <file>")
+          conf.command & " <file>")
   quit 1
 
-proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) =
+proc handleError(conf: ConfigRef; msg: TMsgKind, eh: TErrorHandling, s: string) =
   if msg >= fatalMin and msg <= fatalMax:
-    if gCmd == cmdIdeTools: log(s)
-    quit(msg)
+    if conf.cmd == cmdIdeTools: log(s)
+    quit(conf, msg)
   if msg >= errMin and msg <= errMax:
-    inc(gErrorCounter)
-    options.gExitcode = 1'i8
-    if gErrorCounter >= gErrorMax:
-      quit(msg)
-    elif eh == doAbort and gCmd != cmdIdeTools:
-      quit(msg)
+    inc(conf.errorCounter)
+    conf.exitcode = 1'i8
+    if conf.errorCounter >= conf.errorMax:
+      quit(conf, msg)
+    elif eh == doAbort and conf.cmd != cmdIdeTools:
+      quit(conf, msg)
     elif eh == doRaise:
       raiseRecoverableError(s)
 
@@ -876,26 +341,33 @@ proc `==`*(a, b: TLineInfo): bool =
 proc exactEquals*(a, b: TLineInfo): bool =
   result = a.fileIndex == b.fileIndex and a.line == b.line and a.col == b.col
 
-proc writeContext(lastinfo: TLineInfo) =
+proc writeContext(conf: ConfigRef; lastinfo: TLineInfo) =
+  const instantiationFrom = "template/generic instantiation from here"
+  const instantiationOfFrom = "template/generic instantiation of `$1` from here"
   var info = lastinfo
-  for i in countup(0, len(msgContext) - 1):
-    if msgContext[i] != lastinfo and msgContext[i] != info:
-      if structuredErrorHook != nil:
-        structuredErrorHook(msgContext[i], getMessageStr(errInstantiationFrom, ""),
-                            Severity.Error)
+  for i in 0 ..< len(conf.m.msgContext):
+    let context = conf.m.msgContext[i]
+    if context.info != lastinfo and context.info != info:
+      if conf.structuredErrorHook != nil:
+        conf.structuredErrorHook(conf, context.info, instantiationFrom,
+                                 Severity.Error)
       else:
+        let message = if context.detail == "":
+          instantiationFrom
+        else:
+          instantiationOfFrom.format(context.detail)
         styledMsgWriteln(styleBright,
-                         PosFormat % [toMsgFilename(msgContext[i]),
-                                      coordToStr(msgContext[i].line),
-                                      coordToStr(msgContext[i].col+1)],
+                         PosFormat % [toMsgFilename(conf, context.info),
+                                      coordToStr(context.info.line.int),
+                                      coordToStr(context.info.col+1)],
                          resetStyle,
-                         getMessageStr(errInstantiationFrom, ""))
-    info = msgContext[i]
+                         message)
+    info = context.info
 
-proc ignoreMsgBecauseOfIdeTools(msg: TMsgKind): bool =
-  msg >= errGenerated and gCmd == cmdIdeTools and optIdeDebug notin gGlobalOptions
+proc ignoreMsgBecauseOfIdeTools(conf: ConfigRef; msg: TMsgKind): bool =
+  msg >= errGenerated and conf.cmd == cmdIdeTools and optIdeDebug notin conf.globalOptions
 
-proc rawMessage*(msg: TMsgKind, args: openArray[string]) =
+proc rawMessage*(conf: ConfigRef; msg: TMsgKind, args: openArray[string]) =
   var
     title: string
     color: ForegroundColor
@@ -904,62 +376,81 @@ proc rawMessage*(msg: TMsgKind, args: openArray[string]) =
   case msg
   of errMin..errMax:
     sev = Severity.Error
-    writeContext(unknownLineInfo())
+    writeContext(conf, unknownLineInfo())
     title = ErrorTitle
     color = ErrorColor
   of warnMin..warnMax:
     sev = Severity.Warning
-    if optWarns notin gOptions: return
-    if msg notin gNotes: return
-    writeContext(unknownLineInfo())
+    if optWarns notin conf.options: return
+    if msg notin conf.notes: return
+    writeContext(conf, unknownLineInfo())
     title = WarningTitle
     color = WarningColor
     kind = WarningsToStr[ord(msg) - ord(warnMin)]
-    inc(gWarnCounter)
+    inc(conf.warnCounter)
   of hintMin..hintMax:
     sev = Severity.Hint
-    if optHints notin gOptions: return
-    if msg notin gNotes: return
+    if optHints notin conf.options: return
+    if msg notin conf.notes: return
     title = HintTitle
     color = HintColor
-    kind = HintsToStr[ord(msg) - ord(hintMin)]
-    inc(gHintCounter)
+    if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
+    inc(conf.hintCounter)
   let s = msgKindToString(msg) % args
 
-  if structuredErrorHook != nil:
-    structuredErrorHook(unknownLineInfo(), s & (if kind != nil: KindFormat % kind else: ""), sev)
+  if conf.structuredErrorHook != nil:
+    conf.structuredErrorHook(conf, unknownLineInfo(),
+      s & (if kind.len > 0: KindFormat % kind else: ""), sev)
 
-  if not ignoreMsgBecauseOfIdeTools(msg):
-    if kind != nil:
+  if not ignoreMsgBecauseOfIdeTools(conf, msg):
+    if kind.len > 0:
       styledMsgWriteln(color, title, resetStyle, s,
                        KindColor, `%`(KindFormat, kind))
     else:
       styledMsgWriteln(color, title, resetStyle, s)
-  handleError(msg, doAbort, s)
+  handleError(conf, msg, doAbort, s)
 
-proc rawMessage*(msg: TMsgKind, arg: string) =
-  rawMessage(msg, [arg])
+proc rawMessage*(conf: ConfigRef; msg: TMsgKind, arg: string) =
+  rawMessage(conf, msg, [arg])
 
-proc resetAttributes* =
-  if {optUseColors, optStdout} * gGlobalOptions == {optUseColors}:
+proc resetAttributes*(conf: ConfigRef) =
+  if {optUseColors, optStdout} * conf.globalOptions == {optUseColors}:
     terminal.resetAttributes(stderr)
 
-proc writeSurroundingSrc(info: TLineInfo) =
+proc addSourceLine(conf: ConfigRef; fileIdx: FileIndex, line: string) =
+  conf.m.fileInfos[fileIdx.int32].lines.add line
+
+proc sourceLine*(conf: ConfigRef; i: TLineInfo): string =
+  if i.fileIndex.int32 < 0: return ""
+
+  if not optPreserveOrigSource(conf) and conf.m.fileInfos[i.fileIndex.int32].lines.len == 0:
+    try:
+      for line in lines(toFullPath(conf, i)):
+        addSourceLine conf, i.fileIndex, line.string
+    except IOError:
+      discard
+  assert i.fileIndex.int32 < conf.m.fileInfos.len
+  # can happen if the error points to EOF:
+  if i.line.int > conf.m.fileInfos[i.fileIndex.int32].lines.len: return ""
+
+  result = conf.m.fileInfos[i.fileIndex.int32].lines[i.line.int-1]
+
+proc writeSurroundingSrc(conf: ConfigRef; info: TLineInfo) =
   const indent = "  "
-  msgWriteln(indent & $info.sourceLine)
-  msgWriteln(indent & spaces(info.col) & '^')
+  msgWriteln(conf, indent & $sourceLine(conf, info))
+  msgWriteln(conf, indent & spaces(info.col) & '^')
 
-proc formatMsg*(info: TLineInfo, msg: TMsgKind, arg: string): string =
+proc formatMsg*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string): string =
   let title = case msg
               of warnMin..warnMax: WarningTitle
               of hintMin..hintMax: HintTitle
               else: ErrorTitle
-  result = PosFormat % [toMsgFilename(info), coordToStr(info.line),
+  result = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int),
                         coordToStr(info.col+1)] &
            title &
            getMessageStr(msg, arg)
 
-proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string,
+proc liMessage(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg: string,
                eh: TErrorHandling) =
   var
     title: string
@@ -970,134 +461,108 @@ proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string,
   case msg
   of errMin..errMax:
     sev = Severity.Error
-    writeContext(info)
+    writeContext(conf, info)
     title = ErrorTitle
     color = ErrorColor
     # we try to filter error messages so that not two error message
     # in the same file and line are produced:
     #ignoreMsg = lastError == info and eh != doAbort
-    lastError = info
+    conf.m.lastError = info
   of warnMin..warnMax:
     sev = Severity.Warning
-    ignoreMsg = optWarns notin gOptions or msg notin gNotes
-    if not ignoreMsg: writeContext(info)
+    ignoreMsg = optWarns notin conf.options or msg notin conf.notes
+    if not ignoreMsg: writeContext(conf, info)
     title = WarningTitle
     color = WarningColor
     kind = WarningsToStr[ord(msg) - ord(warnMin)]
-    inc(gWarnCounter)
+    inc(conf.warnCounter)
   of hintMin..hintMax:
     sev = Severity.Hint
-    ignoreMsg = optHints notin gOptions or msg notin gNotes
+    ignoreMsg = optHints notin conf.options or msg notin conf.notes
     title = HintTitle
     color = HintColor
-    kind = HintsToStr[ord(msg) - ord(hintMin)]
-    inc(gHintCounter)
+    if msg != hintUserRaw: kind = HintsToStr[ord(msg) - ord(hintMin)]
+    inc(conf.hintCounter)
   # NOTE: currently line info line numbers start with 1,
   # but column numbers start with 0, however most editors expect
   # first column to be 1, so we need to +1 here
-  let x = PosFormat % [toMsgFilename(info), coordToStr(info.line),
+  let x = PosFormat % [toMsgFilename(conf, info), coordToStr(info.line.int),
                        coordToStr(info.col+1)]
   let s = getMessageStr(msg, arg)
 
   if not ignoreMsg:
-    if structuredErrorHook != nil:
-      structuredErrorHook(info, s & (if kind != nil: KindFormat % kind else: ""), sev)
-    if not ignoreMsgBecauseOfIdeTools(msg):
-      if kind != nil:
+    if conf.structuredErrorHook != nil:
+      conf.structuredErrorHook(conf, info, s & (if kind.len > 0: KindFormat % kind else: ""), sev)
+    if not ignoreMsgBecauseOfIdeTools(conf, msg):
+      if kind.len > 0:
         styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s,
                          KindColor, `%`(KindFormat, kind))
       else:
         styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s)
-      if msg in errMin..errMax and hintSource in gNotes:
-        info.writeSurroundingSrc
-  handleError(msg, eh, s)
+      if hintSource in conf.notes:
+        conf.writeSurroundingSrc(info)
+  handleError(conf, msg, eh, s)
 
-proc fatal*(info: TLineInfo, msg: TMsgKind, arg = "") =
-  liMessage(info, msg, arg, doAbort)
+proc fatal*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
+  # this fixes bug #7080 so that it is at least obvious 'fatal'
+  # was executed.
+  conf.m.errorOutputs = {eStdOut, eStdErr}
+  liMessage(conf, info, msg, arg, doAbort)
 
-proc globalError*(info: TLineInfo, msg: TMsgKind, arg = "") =
-  liMessage(info, msg, arg, doRaise)
+proc globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
+  liMessage(conf, info, msg, arg, doRaise)
 
-proc globalError*(info: TLineInfo, arg: string) =
-  liMessage(info, errGenerated, arg, doRaise)
+proc globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
+  liMessage(conf, info, errGenerated, arg, doRaise)
 
-proc localError*(info: TLineInfo, msg: TMsgKind, arg = "") =
-  liMessage(info, msg, arg, doNothing)
+proc localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
+  liMessage(conf, info, msg, arg, doNothing)
 
-proc localError*(info: TLineInfo, arg: string) =
-  liMessage(info, errGenerated, arg, doNothing)
+proc localError*(conf: ConfigRef; info: TLineInfo, arg: string) =
+  liMessage(conf, info, errGenerated, arg, doNothing)
 
-proc localError*(info: TLineInfo, format: string, params: openarray[string]) =
-  localError(info, format % params)
+proc localError*(conf: ConfigRef; info: TLineInfo, format: string, params: openarray[string]) =
+  localError(conf, info, format % params)
 
-proc message*(info: TLineInfo, msg: TMsgKind, arg = "") =
-  liMessage(info, msg, arg, doNothing)
+proc message*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
+  liMessage(conf, info, msg, arg, doNothing)
 
-proc internalError*(info: TLineInfo, errMsg: string) =
-  if gCmd == cmdIdeTools and structuredErrorHook.isNil: return
-  writeContext(info)
-  liMessage(info, errInternal, errMsg, doAbort)
+proc internalError*(conf: ConfigRef; info: TLineInfo, errMsg: string) =
+  if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
+  writeContext(conf, info)
+  liMessage(conf, info, errInternal, errMsg, doAbort)
 
-proc internalError*(errMsg: string) =
-  if gCmd == cmdIdeTools and structuredErrorHook.isNil: return
-  writeContext(unknownLineInfo())
-  rawMessage(errInternal, errMsg)
+proc internalError*(conf: ConfigRef; errMsg: string) =
+  if conf.cmd == cmdIdeTools and conf.structuredErrorHook.isNil: return
+  writeContext(conf, unknownLineInfo())
+  rawMessage(conf, errInternal, errMsg)
 
-template assertNotNil*(e): untyped =
-  if e == nil: internalError($instantiationInfo())
+template assertNotNil*(conf: ConfigRef; e): untyped =
+  if e == nil: internalError(conf, $instantiationInfo())
   e
 
-template internalAssert*(e: bool) =
-  if not e: internalError($instantiationInfo())
-
-proc addSourceLine*(fileIdx: int32, line: string) =
-  fileInfos[fileIdx].lines.add line.rope
-
-proc sourceLine*(i: TLineInfo): Rope =
-  if i.fileIndex < 0: return nil
-
-  if not optPreserveOrigSource and fileInfos[i.fileIndex].lines.len == 0:
-    try:
-      for line in lines(i.toFullPath):
-        addSourceLine i.fileIndex, line.string
-    except IOError:
-      discard
-  internalAssert i.fileIndex < fileInfos.len
-  # can happen if the error points to EOF:
-  if i.line > fileInfos[i.fileIndex].lines.len: return nil
-
-  result = fileInfos[i.fileIndex].lines[i.line-1]
+template internalAssert*(conf: ConfigRef, e: bool) =
+  if not e: internalError(conf, $instantiationInfo())
 
-proc quotedFilename*(i: TLineInfo): Rope =
-  internalAssert i.fileIndex >= 0
-  if optExcessiveStackTrace in gGlobalOptions:
-    result = fileInfos[i.fileIndex].quotedFullName
+proc quotedFilename*(conf: ConfigRef; i: TLineInfo): Rope =
+  assert i.fileIndex.int32 >= 0
+  if optExcessiveStackTrace in conf.globalOptions:
+    result = conf.m.fileInfos[i.fileIndex.int32].quotedFullName
   else:
-    result = fileInfos[i.fileIndex].quotedName
-
-ropes.errorHandler = proc (err: RopesError, msg: string, useWarning: bool) =
-  case err
-  of rInvalidFormatStr:
-    internalError("ropes: invalid format string: " & msg)
-  of rCannotOpenFile:
-    rawMessage(if useWarning: warnCannotOpenFile else: errCannotOpenFile, msg)
+    result = conf.m.fileInfos[i.fileIndex.int32].quotedName
 
-proc listWarnings*() =
-  msgWriteln("Warnings:")
+proc listWarnings*(conf: ConfigRef) =
+  msgWriteln(conf, "Warnings:")
   for warn in warnMin..warnMax:
-    msgWriteln("  [$1] $2" % [
-      if warn in gNotes: "x" else: " ",
-      msgs.WarningsToStr[ord(warn) - ord(warnMin)]
+    msgWriteln(conf, "  [$1] $2" % [
+      if warn in conf.notes: "x" else: " ",
+      lineinfos.WarningsToStr[ord(warn) - ord(warnMin)]
     ])
 
-proc listHints*() =
-  msgWriteln("Hints:")
+proc listHints*(conf: ConfigRef) =
+  msgWriteln(conf, "Hints:")
   for hint in hintMin..hintMax:
-    msgWriteln("  [$1] $2" % [
-      if hint in gNotes: "x" else: " ",
-      msgs.HintsToStr[ord(hint) - ord(hintMin)]
+    msgWriteln(conf, "  [$1] $2" % [
+      if hint in conf.notes: "x" else: " ",
+      lineinfos.HintsToStr[ord(hint) - ord(hintMin)]
     ])
-
-# enable colors by default on terminals
-if terminal.isatty(stderr):
-  incl(gGlobalOptions, optUseColors)
diff --git a/compiler/ndi.nim b/compiler/ndi.nim
index a7ca02193..f672b1b76 100644
--- a/compiler/ndi.nim
+++ b/compiler/ndi.nim
@@ -10,7 +10,7 @@
 ## This module implements the generation of ``.ndi`` files for better debugging
 ## support of Nim code. "ndi" stands for "Nim debug info".
 
-import ast, msgs, ropes
+import ast, msgs, ropes, options, pathutils
 
 type
   NdiFile* = object
@@ -18,22 +18,22 @@ type
     f: File
     buf: string
 
-proc doWrite(f: var NdiFile; s: PSym) =
+proc doWrite(f: var NdiFile; s: PSym; conf: ConfigRef) =
   f.buf.setLen 0
   f.buf.add s.info.line.int
   f.buf.add "\t"
   f.buf.add s.info.col.int
   f.f.write(s.name.s, "\t")
   f.f.writeRope(s.loc.r)
-  f.f.writeLine("\t", s.info.toFullPath, "\t", f.buf)
+  f.f.writeLine("\t", toFullPath(conf, s.info), "\t", f.buf)
 
-template writeMangledName*(f: NdiFile; s: PSym) =
-  if f.enabled: doWrite(f, s)
+template writeMangledName*(f: NdiFile; s: PSym; conf: ConfigRef) =
+  if f.enabled: doWrite(f, s, conf)
 
-proc open*(f: var NdiFile; filename: string) =
-  f.enabled = filename.len > 0
+proc open*(f: var NdiFile; filename: AbsoluteFile; conf: ConfigRef) =
+  f.enabled = not filename.isEmpty
   if f.enabled:
-    f.f = open(filename, fmWrite, 8000)
+    f.f = open(filename.string, fmWrite, 8000)
     f.buf = newStringOfCap(20)
 
 proc close*(f: var NdiFile) =
diff --git a/compiler/nim.cfg b/compiler/nim.cfg
index 853ae7e00..5104f9fcd 100644
--- a/compiler/nim.cfg
+++ b/compiler/nim.cfg
@@ -1,10 +1,10 @@
 # Special configuration file for the Nim project
 
 hint[XDeclaredButNotUsed]:off
-path:"llvm"
-path:"$projectPath/.."
 
 define:booting
+define:nimcore
+#define:nimIncremental
 #import:"$projectpath/testability"
 
 @if windows:
@@ -13,6 +13,5 @@ define:booting
 
 define:useStdoutAsStdmsg
 
-cs:partial
 #define:useNodeIds
 #gc:markAndSweep
diff --git a/compiler/nim.nim b/compiler/nim.nim
index 56885e9f1..cbd9d6f39 100644
--- a/compiler/nim.nim
+++ b/compiler/nim.nim
@@ -9,19 +9,20 @@
 
 when defined(gcc) and defined(windows):
   when defined(x86):
-    {.link: "icons/nim.res".}
+    {.link: "../icons/nim.res".}
   else:
-    {.link: "icons/nim_icon.o".}
+    {.link: "../icons/nim_icon.o".}
 
 when defined(amd64) and defined(windows) and defined(vcc):
-  {.link: "icons/nim-amd64-windows-vcc.res".}
+  {.link: "../icons/nim-amd64-windows-vcc.res".}
 when defined(i386) and defined(windows) and defined(vcc):
-  {.link: "icons/nim-i386-windows-vcc.res".}
+  {.link: "../icons/nim-i386-windows-vcc.res".}
 
 import
   commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes,
-  extccomp, strutils, os, osproc, platform, main, parseopt, service,
-  nodejs, scriptconfig, idents, modulegraphs
+  extccomp, strutils, os, osproc, platform, main, parseopt,
+  nodejs, scriptconfig, idents, modulegraphs, lineinfos, cmdlinehelper,
+  pathutils
 
 when hasTinyCBackend:
   import tccgen
@@ -30,84 +31,71 @@ when defined(profiler) or defined(memProfiler):
   {.hint: "Profiling support is turned on!".}
   import nimprof
 
-proc prependCurDir(f: string): string =
+proc prependCurDir(f: AbsoluteFile): AbsoluteFile =
   when defined(unix):
-    if os.isAbsolute(f): result = f
-    else: result = "./" & f
+    if os.isAbsolute(f.string): result = f
+    else: result = AbsoluteFile("./" & f.string)
   else:
     result = f
 
-proc handleCmdLine(cache: IdentCache; config: ConfigRef) =
+proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
+  var p = parseopt.initOptParser(cmd)
+  var argsCount = 0
+  while true:
+    parseopt.next(p)
+    case p.kind
+    of cmdEnd: break
+    of cmdLongoption, cmdShortOption:
+      if p.key == " ":
+        p.key = "-"
+        if processArgument(pass, p, argsCount, config): break
+      else:
+        processSwitch(pass, p, config)
+    of cmdArgument:
+      if processArgument(pass, p, argsCount, config): break
+  if pass == passCmd2:
+    if {optRun, optWasNimscript} * config.globalOptions == {} and
+        config.arguments.len > 0 and config.command.normalize notin ["run", "e"]:
+      rawMessage(config, errGenerated, errArgsNeedRunOption)
+
+proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
+  let self = NimProg(
+    supportsStdinFile: true,
+    processCmdLine: processCmdLine,
+    mainCommand: mainCommand
+  )
+  self.initDefinesProg(conf, "nim_compiler")
   if paramCount() == 0:
-    writeCommandLineUsage()
-  else:
-    # Process command line arguments:
-    processCmdLine(passCmd1, "")
-    if gProjectName == "-":
-      gProjectName = "stdinfile"
-      gProjectFull = "stdinfile"
-      gProjectPath = canonicalizePath getCurrentDir()
-      gProjectIsStdin = true
-    elif gProjectName != "":
-      try:
-        gProjectFull = canonicalizePath(gProjectName)
-      except OSError:
-        gProjectFull = gProjectName
-      let p = splitFile(gProjectFull)
-      let dir = if p.dir.len > 0: p.dir else: getCurrentDir()
-      gProjectPath = canonicalizePath dir
-      gProjectName = p.name
+    writeCommandLineUsage(conf, conf.helpWritten)
+    return
+
+  self.processCmdLineAndProjectPath(conf)
+  if not self.loadConfigsAndRunMainCommand(cache, conf): return
+  if optHints in conf.options and hintGCStats in conf.notes: echo(GC_getStatistics())
+  #echo(GC_getStatistics())
+  if conf.errorCounter != 0: return
+  when hasTinyCBackend:
+    if conf.cmd == cmdRun:
+      tccgen.run(conf.arguments)
+  if optRun in conf.globalOptions:
+    if conf.cmd == cmdCompileToJS:
+      var ex: string
+      if not conf.outFile.isEmpty:
+        ex = conf.outFile.prependCurDir.quoteShell
+      else:
+        ex = quoteShell(
+          completeCFilePath(conf, changeFileExt(conf.projectFull, "js").prependCurDir))
+      execExternalProgram(conf, findNodeJs() & " " & ex & ' ' & conf.arguments)
     else:
-      gProjectPath = canonicalizePath getCurrentDir()
-    loadConfigs(DefaultConfig, config) # load all config files
-    let scriptFile = gProjectFull.changeFileExt("nims")
-    if fileExists(scriptFile):
-      runNimScript(cache, scriptFile, freshDefines=false, config)
-      # 'nim foo.nims' means to just run the NimScript file and do nothing more:
-      if scriptFile == gProjectFull: return
-    elif fileExists(gProjectPath / "config.nims"):
-      # directory wide NimScript file
-      runNimScript(cache, gProjectPath / "config.nims", freshDefines=false, config)
-    # now process command line arguments again, because some options in the
-    # command line can overwite the config file's settings
-    extccomp.initVars()
-    processCmdLine(passCmd2, "")
-    if options.command == "":
-      rawMessage(errNoCommand, command)
-    mainCommand(newModuleGraph(config), cache)
-    if optHints in gOptions and hintGCStats in gNotes: echo(GC_getStatistics())
-    #echo(GC_getStatistics())
-    if msgs.gErrorCounter == 0:
-      when hasTinyCBackend:
-        if gCmd == cmdRun:
-          tccgen.run(commands.arguments)
-      if optRun in gGlobalOptions:
-        if gCmd == cmdCompileToJS:
-          var ex: string
-          if options.outFile.len > 0:
-            ex = options.outFile.prependCurDir.quoteShell
-          else:
-            ex = quoteShell(
-              completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir))
-          execExternalProgram(findNodeJs() & " " & ex & ' ' & commands.arguments)
-        elif gCmd == cmdCompileToPHP:
-          var ex: string
-          if options.outFile.len > 0:
-            ex = options.outFile.prependCurDir.quoteShell
-          else:
-            ex = quoteShell(
-              completeCFilePath(changeFileExt(gProjectFull, "php").prependCurDir))
-          execExternalProgram("php " & ex & ' ' & commands.arguments)
-        else:
-          var binPath: string
-          if options.outFile.len > 0:
-            # If the user specified an outFile path, use that directly.
-            binPath = options.outFile.prependCurDir
-          else:
-            # Figure out ourselves a valid binary name.
-            binPath = changeFileExt(gProjectFull, ExeExt).prependCurDir
-          var ex = quoteShell(binPath)
-          execExternalProgram(ex & ' ' & commands.arguments)
+      var binPath: AbsoluteFile
+      if not conf.outFile.isEmpty:
+        # If the user specified an outFile path, use that directly.
+        binPath = conf.outFile.prependCurDir
+      else:
+        # Figure out ourselves a valid binary name.
+        binPath = changeFileExt(conf.projectFull, ExeExt).prependCurDir
+      var ex = quoteShell(binPath)
+      execExternalProgram(conf, ex & ' ' & conf.arguments)
 
 when declared(GC_setMaxPause):
   GC_setMaxPause 2_000
@@ -115,8 +103,10 @@ when declared(GC_setMaxPause):
 when compileOption("gc", "v2") or compileOption("gc", "refc"):
   # the new correct mark&sweet collector is too slow :-/
   GC_disableMarkAndSweep()
-condsyms.initDefines()
 
 when not defined(selftest):
-  handleCmdLine(newIdentCache(), newConfigRef())
-  msgQuit(int8(msgs.gErrorCounter > 0))
+  let conf = newConfigRef()
+  handleCmdLine(newIdentCache(), conf)
+  when declared(GC_setMaxPause):
+    echo GC_getStatistics()
+  msgQuit(int8(conf.errorCounter > 0))
diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim
index e6466fc24..fa938556b 100644
--- a/compiler/nimblecmd.nim
+++ b/compiler/nimblecmd.nim
@@ -9,70 +9,150 @@
 
 ## Implements some helper procs for Nimble (Nim's package manager) support.
 
-import parseutils, strutils, strtabs, os, options, msgs
-
-proc addPath*(path: string, info: TLineInfo) =
-  if not options.searchPaths.contains(path):
-    options.searchPaths.insert(path, 0)
-
-proc versionSplitPos(s: string): int =
-  result = s.len-2
-  while result > 1 and s[result] in {'0'..'9', '.'}: dec result
-  if s[result] != '-': result = s.len
-
-const
-  latest = "head"
-
-proc `<.`(a, b: string): bool =
-  # wether a has a smaller version than b:
-  if a == latest: return false
-  var i = 0
-  var j = 0
-  var verA = 0
-  var verB = 0
-  while true:
-    let ii = parseInt(a, verA, i)
-    let jj = parseInt(b, verB, j)
-    # if A has no number left, but B has, B is preferred:  0.8 vs 0.8.3
-    if ii <= 0 or jj <= 0: return jj > 0
-    if verA < verB: return true
-    elif verA > verB: return false
-    # else: same version number; continue:
-    inc i, ii
-    inc j, jj
-    if a[i] == '.': inc i
-    if b[j] == '.': inc j
-
-proc addPackage(packages: StringTableRef, p: string) =
-  let x = versionSplitPos(p)
-  let name = p.substr(0, x-1)
-  if x < p.len:
-    let version = p.substr(x+1)
-    if packages.getOrDefault(name) <. version:
-      packages[name] = version
+import parseutils, strutils, strtabs, os, options, msgs, sequtils,
+  lineinfos, pathutils
+
+proc addPath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) =
+  if not conf.searchPaths.contains(path):
+    conf.searchPaths.insert(path, 0)
+
+type
+  Version* = distinct string
+
+proc `$`*(ver: Version): string {.borrow.}
+
+proc newVersion*(ver: string): Version =
+  doAssert(ver.len == 0 or ver[0] in {'#', '\0'} + Digits,
+           "Wrong version: " & ver)
+  return Version(ver)
+
+proc isSpecial(ver: Version): bool =
+  return ($ver).len > 0 and ($ver)[0] == '#'
+
+proc isValidVersion(v: string): bool =
+  if v.len > 0:
+    if v[0] in {'#'} + Digits: return true
+
+proc `<`*(ver: Version, ver2: Version): bool =
+  ## This is synced from Nimble's version module.
+
+  # Handling for special versions such as "#head" or "#branch".
+  if ver.isSpecial or ver2.isSpecial:
+    if ver2.isSpecial and ($ver2).normalize == "#head":
+      return ($ver).normalize != "#head"
+
+    if not ver2.isSpecial:
+      # `#aa111 < 1.1`
+      return ($ver).normalize != "#head"
+
+  # Handling for normal versions such as "0.1.0" or "1.0".
+  var sVer = string(ver).split('.')
+  var sVer2 = string(ver2).split('.')
+  for i in 0..max(sVer.len, sVer2.len)-1:
+    var sVerI = 0
+    if i < sVer.len:
+      discard parseInt(sVer[i], sVerI)
+    var sVerI2 = 0
+    if i < sVer2.len:
+      discard parseInt(sVer2[i], sVerI2)
+    if sVerI < sVerI2:
+      return true
+    elif sVerI == sVerI2:
+      discard
+    else:
+      return false
+
+proc getPathVersion*(p: string): tuple[name, version: string] =
+  ## Splits path ``p`` in the format ``/home/user/.nimble/pkgs/package-0.1``
+  ## into ``(/home/user/.nimble/pkgs/package, 0.1)``
+  result.name = ""
+  result.version = ""
+
+  const specialSeparator = "-#"
+  var sepIdx = p.find(specialSeparator)
+  if sepIdx == -1:
+    sepIdx = p.rfind('-')
+
+  if sepIdx == -1:
+    result.name = p
+    return
+
+  for i in sepIdx..<p.len:
+    if p[i] in {DirSep, AltSep}:
+      result.name = p
+      return
+
+  result.name = p[0 .. sepIdx - 1]
+  result.version = p.substr(sepIdx + 1)
+
+proc addPackage(conf: ConfigRef; packages: StringTableRef, p: string; info: TLineInfo) =
+  let (name, ver) = getPathVersion(p)
+  if isValidVersion(ver):
+    let version = newVersion(ver)
+    if packages.getOrDefault(name).newVersion < version or
+      (not packages.hasKey(name)):
+      packages[name] = $version
   else:
-    packages[name] = latest
+    localError(conf, info, "invalid package name: " & p)
 
 iterator chosen(packages: StringTableRef): string =
   for key, val in pairs(packages):
-    let res = if val == latest: key else: key & '-' & val
+    let res = if val.len == 0: key else: key & '-' & val
     yield res
 
-proc addNimblePath(p: string, info: TLineInfo) =
-  if not contains(options.searchPaths, p):
-    message(info, hintPath, p)
-    options.lazyPaths.insert(p, 0)
+proc addNimblePath(conf: ConfigRef; p: string, info: TLineInfo) =
+  var path = p
+  let nimbleLinks = toSeq(walkPattern(p / "*.nimble-link"))
+  if nimbleLinks.len > 0:
+    # If the user has more than one .nimble-link file then... we just ignore it.
+    # Spec for these files is available in Nimble's readme:
+    # https://github.com/nim-lang/nimble#nimble-link
+    let nimbleLinkLines = readFile(nimbleLinks[0]).splitLines()
+    path = nimbleLinkLines[1]
+    if not path.isAbsolute():
+      path = p / path
+
+  if not contains(conf.searchPaths, AbsoluteDir path):
+    message(conf, info, hintPath, path)
+    conf.lazyPaths.insert(AbsoluteDir path, 0)
 
-proc addPathRec(dir: string, info: TLineInfo) =
+proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) =
   var packages = newStringTable(modeStyleInsensitive)
   var pos = dir.len-1
   if dir[pos] in {DirSep, AltSep}: inc(pos)
   for k,p in os.walkDir(dir):
     if k == pcDir and p[pos] != '.':
-      addPackage(packages, p)
+      addPackage(conf, packages, p, info)
   for p in packages.chosen:
-    addNimblePath(p, info)
+    addNimblePath(conf, p, info)
+
+proc nimblePath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) =
+  addPathRec(conf, path.string, info)
+  addNimblePath(conf, path.string, info)
+
+when isMainModule:
+  proc v(s: string): Version = s.newVersion
+  # #head is special in the sense that it's assumed to always be newest.
+  doAssert v"1.0" < v"#head"
+  doAssert v"1.0" < v"1.1"
+  doAssert v"1.0.1" < v"1.1"
+  doAssert v"1" < v"1.1"
+  doAssert v"#aaaqwe" < v"1.1" # We cannot assume that a branch is newer.
+  doAssert v"#a111" < v"#head"
+
+  let conf = newConfigRef()
+  var rr = newStringTable()
+  addPackage conf, rr, "irc-#a111", unknownLineInfo()
+  addPackage conf, rr, "irc-#head", unknownLineInfo()
+  addPackage conf, rr, "irc-0.1.0", unknownLineInfo()
+  #addPackage conf, rr, "irc", unknownLineInfo()
+  #addPackage conf, rr, "another", unknownLineInfo()
+  addPackage conf, rr, "another-0.1", unknownLineInfo()
+
+  addPackage conf, rr, "ab-0.1.3", unknownLineInfo()
+  addPackage conf, rr, "ab-0.1", unknownLineInfo()
+  addPackage conf, rr, "justone-1.0", unknownLineInfo()
+
+  doAssert toSeq(rr.chosen) ==
+    @["irc-#head", "another-0.1", "ab-0.1.3", "justone-1.0"]
 
-proc nimblePath*(path: string, info: TLineInfo) =
-  addPathRec(path, info)
-  addNimblePath(path, info)
diff --git a/compiler/nimconf.nim b/compiler/nimconf.nim
index 808159b8f..c0aeab7e3 100644
--- a/compiler/nimconf.nim
+++ b/compiler/nimconf.nim
@@ -11,7 +11,7 @@
 
 import
   llstream, nversion, commands, os, strutils, msgs, platform, condsyms, lexer,
-  options, idents, wordrecg, strtabs
+  options, idents, wordrecg, strtabs, lineinfos, pathutils
 
 # ---------------- configuration file parser -----------------------------
 # we use Nim's scanner here to save space and work
@@ -27,12 +27,12 @@ proc parseAtom(L: var TLexer, tok: var TToken; config: ConfigRef): bool =
     ppGetTok(L, tok)
     result = parseExpr(L, tok, config)
     if tok.tokType == tkParRi: ppGetTok(L, tok)
-    else: lexMessage(L, errTokenExpected, "\')\'")
+    else: lexMessage(L, errGenerated, "expected closing ')'")
   elif tok.ident.id == ord(wNot):
     ppGetTok(L, tok)
     result = not parseAtom(L, tok, config)
   else:
-    result = isDefined(tok.ident)
+    result = isDefined(config, tok.ident.s)
     ppGetTok(L, tok)
 
 proc parseAndExpr(L: var TLexer, tok: var TToken; config: ConfigRef): bool =
@@ -53,12 +53,12 @@ proc evalppIf(L: var TLexer, tok: var TToken; config: ConfigRef): bool =
   ppGetTok(L, tok)            # skip 'if' or 'elif'
   result = parseExpr(L, tok, config)
   if tok.tokType == tkColon: ppGetTok(L, tok)
-  else: lexMessage(L, errTokenExpected, "\':\'")
+  else: lexMessage(L, errGenerated, "expected ':'")
 
-var condStack: seq[bool] = @[]
+#var condStack: seq[bool] = @[]
 
-proc doEnd(L: var TLexer, tok: var TToken) =
-  if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if")
+proc doEnd(L: var TLexer, tok: var TToken; condStack: var seq[bool]) =
+  if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if")
   ppGetTok(L, tok)            # skip 'end'
   setLen(condStack, high(condStack))
 
@@ -66,20 +66,22 @@ type
   TJumpDest = enum
     jdEndif, jdElseEndif
 
-proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef)
-proc doElse(L: var TLexer, tok: var TToken; config: ConfigRef) =
-  if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if")
+proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef;
+                     condStack: var seq[bool])
+proc doElse(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) =
+  if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if")
   ppGetTok(L, tok)
   if tok.tokType == tkColon: ppGetTok(L, tok)
-  if condStack[high(condStack)]: jumpToDirective(L, tok, jdEndif, config)
+  if condStack[high(condStack)]: jumpToDirective(L, tok, jdEndif, config, condStack)
 
-proc doElif(L: var TLexer, tok: var TToken; config: ConfigRef) =
-  if high(condStack) < 0: lexMessage(L, errTokenExpected, "@if")
+proc doElif(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) =
+  if high(condStack) < 0: lexMessage(L, errGenerated, "expected @if")
   var res = evalppIf(L, tok, config)
-  if condStack[high(condStack)] or not res: jumpToDirective(L, tok, jdElseEndif, config)
+  if condStack[high(condStack)] or not res: jumpToDirective(L, tok, jdElseEndif, config, condStack)
   else: condStack[high(condStack)] = true
 
-proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef) =
+proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: ConfigRef;
+                     condStack: var seq[bool]) =
   var nestedIfs = 0
   while true:
     if tok.ident != nil and tok.ident.s == "@":
@@ -89,39 +91,39 @@ proc jumpToDirective(L: var TLexer, tok: var TToken, dest: TJumpDest; config: Co
         inc(nestedIfs)
       of wElse:
         if dest == jdElseEndif and nestedIfs == 0:
-          doElse(L, tok, config)
+          doElse(L, tok, config, condStack)
           break
       of wElif:
         if dest == jdElseEndif and nestedIfs == 0:
-          doElif(L, tok, config)
+          doElif(L, tok, config, condStack)
           break
       of wEnd:
         if nestedIfs == 0:
-          doEnd(L, tok)
+          doEnd(L, tok, condStack)
           break
         if nestedIfs > 0: dec(nestedIfs)
       else:
         discard
       ppGetTok(L, tok)
     elif tok.tokType == tkEof:
-      lexMessage(L, errTokenExpected, "@end")
+      lexMessage(L, errGenerated, "expected @end")
     else:
       ppGetTok(L, tok)
 
-proc parseDirective(L: var TLexer, tok: var TToken; config: ConfigRef) =
+proc parseDirective(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) =
   ppGetTok(L, tok)            # skip @
   case whichKeyword(tok.ident)
   of wIf:
     setLen(condStack, len(condStack) + 1)
     let res = evalppIf(L, tok, config)
     condStack[high(condStack)] = res
-    if not res: jumpToDirective(L, tok, jdElseEndif, config)
-  of wElif: doElif(L, tok, config)
-  of wElse: doElse(L, tok, config)
-  of wEnd: doEnd(L, tok)
+    if not res: jumpToDirective(L, tok, jdElseEndif, config, condStack)
+  of wElif: doElif(L, tok, config, condStack)
+  of wElse: doElse(L, tok, config, condStack)
+  of wEnd: doEnd(L, tok, condStack)
   of wWrite:
     ppGetTok(L, tok)
-    msgs.msgWriteln(strtabs.`%`(tokToStr(tok), options.gConfigVars,
+    msgs.msgWriteln(config, strtabs.`%`(tokToStr(tok), config.configVars,
                                 {useEnvironment, useKey}))
     ppGetTok(L, tok)
   else:
@@ -144,60 +146,63 @@ proc parseDirective(L: var TLexer, tok: var TToken; config: ConfigRef) =
       ppGetTok(L, tok)
       os.putEnv(key, os.getEnv(key) & tokToStr(tok))
       ppGetTok(L, tok)
-    else: lexMessage(L, errInvalidDirectiveX, tokToStr(tok))
+    else:
+      lexMessage(L, errGenerated, "invalid directive: '$1'" % tokToStr(tok))
 
-proc confTok(L: var TLexer, tok: var TToken; config: ConfigRef) =
+proc confTok(L: var TLexer, tok: var TToken; config: ConfigRef; condStack: var seq[bool]) =
   ppGetTok(L, tok)
   while tok.ident != nil and tok.ident.s == "@":
-    parseDirective(L, tok, config)    # else: give the token to the parser
+    parseDirective(L, tok, config, condStack)    # else: give the token to the parser
 
 proc checkSymbol(L: TLexer, tok: TToken) =
-  if tok.tokType notin {tkSymbol..pred(tkIntLit), tkStrLit..tkTripleStrLit}:
-    lexMessage(L, errIdentifierExpected, tokToStr(tok))
+  if tok.tokType notin {tkSymbol..tkInt64Lit, tkStrLit..tkTripleStrLit}:
+    lexMessage(L, errGenerated, "expected identifier, but got: " & tokToStr(tok))
 
-proc parseAssignment(L: var TLexer, tok: var TToken; config: ConfigRef) =
+proc parseAssignment(L: var TLexer, tok: var TToken;
+                     config: ConfigRef; condStack: var seq[bool]) =
   if tok.ident.s == "-" or tok.ident.s == "--":
-    confTok(L, tok, config)           # skip unnecessary prefix
+    confTok(L, tok, config, condStack)           # skip unnecessary prefix
   var info = getLineInfo(L, tok) # save for later in case of an error
   checkSymbol(L, tok)
   var s = tokToStr(tok)
-  confTok(L, tok, config)             # skip symbol
+  confTok(L, tok, config, condStack)             # skip symbol
   var val = ""
   while tok.tokType == tkDot:
     add(s, '.')
-    confTok(L, tok, config)
+    confTok(L, tok, config, condStack)
     checkSymbol(L, tok)
     add(s, tokToStr(tok))
-    confTok(L, tok, config)
+    confTok(L, tok, config, condStack)
   if tok.tokType == tkBracketLe:
     # BUGFIX: val, not s!
-    # BUGFIX: do not copy '['!
-    confTok(L, tok, config)
+    confTok(L, tok, config, condStack)
     checkSymbol(L, tok)
+    add(val, '[')
     add(val, tokToStr(tok))
-    confTok(L, tok, config)
-    if tok.tokType == tkBracketRi: confTok(L, tok, config)
-    else: lexMessage(L, errTokenExpected, "']'")
+    confTok(L, tok, config, condStack)
+    if tok.tokType == tkBracketRi: confTok(L, tok, config, condStack)
+    else: lexMessage(L, errGenerated, "expected closing ']'")
     add(val, ']')
   let percent = tok.ident != nil and tok.ident.s == "%="
   if tok.tokType in {tkColon, tkEquals} or percent:
     if len(val) > 0: add(val, ':')
-    confTok(L, tok, config)           # skip ':' or '=' or '%'
+    confTok(L, tok, config, condStack)           # skip ':' or '=' or '%'
     checkSymbol(L, tok)
     add(val, tokToStr(tok))
-    confTok(L, tok, config)           # skip symbol
+    confTok(L, tok, config, condStack)           # skip symbol
     while tok.ident != nil and tok.ident.s == "&":
-      confTok(L, tok, config)
+      confTok(L, tok, config, condStack)
       checkSymbol(L, tok)
       add(val, tokToStr(tok))
-      confTok(L, tok, config)
+      confTok(L, tok, config, condStack)
   if percent:
-    processSwitch(s, strtabs.`%`(val, options.gConfigVars,
+    processSwitch(s, strtabs.`%`(val, config.configVars,
                                 {useEnvironment, useEmpty}), passPP, info, config)
   else:
     processSwitch(s, val, passPP, info, config)
 
-proc readConfigFile(filename: string; cache: IdentCache; config: ConfigRef) =
+proc readConfigFile(filename: AbsoluteFile; cache: IdentCache;
+                    config: ConfigRef): bool =
   var
     L: TLexer
     tok: TToken
@@ -205,54 +210,58 @@ proc readConfigFile(filename: string; cache: IdentCache; config: ConfigRef) =
   stream = llStreamOpen(filename, fmRead)
   if stream != nil:
     initToken(tok)
-    openLexer(L, filename, stream, cache)
+    openLexer(L, filename, stream, cache, config)
     tok.tokType = tkEof       # to avoid a pointless warning
-    confTok(L, tok, config)           # read in the first token
-    while tok.tokType != tkEof: parseAssignment(L, tok, config)
-    if len(condStack) > 0: lexMessage(L, errTokenExpected, "@end")
+    var condStack: seq[bool] = @[]
+    confTok(L, tok, config, condStack)           # read in the first token
+    while tok.tokType != tkEof: parseAssignment(L, tok, config, condStack)
+    if len(condStack) > 0: lexMessage(L, errGenerated, "expected @end")
     closeLexer(L)
-    rawMessage(hintConf, filename)
+    return true
 
-proc getUserConfigPath(filename: string): string =
-  result = joinPath(getConfigDir(), filename)
+proc getUserConfigPath*(filename: RelativeFile): AbsoluteFile =
+  result = getConfigDir().AbsoluteDir / RelativeDir"nim" / filename
 
-proc getSystemConfigPath(filename: string): string =
+proc getSystemConfigPath*(conf: ConfigRef; filename: RelativeFile): AbsoluteFile =
   # try standard configuration file (installation did not distribute files
   # the UNIX way)
-  let p = getPrefixDir()
-  result = joinPath([p, "config", filename])
+  let p = getPrefixDir(conf)
+  result = p / RelativeDir"config" / filename
   when defined(unix):
-    if not existsFile(result): result = joinPath([p, "etc", filename])
-    if not existsFile(result): result = "/etc/" & filename
+    if not fileExists(result): result = p / RelativeDir"etc/nim" / filename
+    if not fileExists(result): result = AbsoluteDir"/etc/nim" / filename
+
+proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef) =
+  setDefaultLibpath(conf)
 
-proc loadConfigs*(cfg: string; cache: IdentCache; config: ConfigRef = nil) =
-  setDefaultLibpath()
+  var configFiles = newSeq[AbsoluteFile]()
 
-  if optSkipConfigFile notin gGlobalOptions:
-    readConfigFile(getSystemConfigPath(cfg), cache, config)
+  template readConfigFile(path) =
+    let configPath = path
+    if readConfigFile(configPath, cache, conf):
+      add(configFiles, configPath)
 
-  if optSkipUserConfigFile notin gGlobalOptions:
-    readConfigFile(getUserConfigPath(cfg), cache, config)
+  if optSkipSystemConfigFile notin conf.globalOptions:
+    readConfigFile(getSystemConfigPath(conf, cfg))
 
-  var pd = if gProjectPath.len > 0: gProjectPath else: getCurrentDir()
-  if optSkipParentConfigFiles notin gGlobalOptions:
-    for dir in parentDirs(pd, fromRoot=true, inclusive=false):
-      readConfigFile(dir / cfg, cache, config)
+  if optSkipUserConfigFile notin conf.globalOptions:
+    readConfigFile(getUserConfigPath(cfg))
 
-  if optSkipProjConfigFile notin gGlobalOptions:
-    readConfigFile(pd / cfg, cache, config)
+  let pd = if not conf.projectPath.isEmpty: conf.projectPath else: AbsoluteDir(getCurrentDir())
+  if optSkipParentConfigFiles notin conf.globalOptions:
+    for dir in parentDirs(pd.string, fromRoot=true, inclusive=false):
+      readConfigFile(AbsoluteDir(dir) / cfg)
 
-    if gProjectName.len != 0:
+  if optSkipProjConfigFile notin conf.globalOptions:
+    readConfigFile(pd / cfg)
+
+    if conf.projectName.len != 0:
       # new project wide config file:
-      var projectConfig = changeFileExt(gProjectFull, "nimcfg")
-      if not fileExists(projectConfig):
-        projectConfig = changeFileExt(gProjectFull, "nim.cfg")
+      var projectConfig = changeFileExt(conf.projectFull, "nimcfg")
       if not fileExists(projectConfig):
-        projectConfig = changeFileExt(gProjectFull, "nimrod.cfg")
-        if fileExists(projectConfig):
-          rawMessage(warnDeprecated, projectConfig)
-      readConfigFile(projectConfig, cache, config)
+        projectConfig = changeFileExt(conf.projectFull, "nim.cfg")
+      readConfigFile(projectConfig)
 
-proc loadConfigs*(cfg: string; config: ConfigRef = nil) =
-  # for backwards compatibility only.
-  loadConfigs(cfg, newIdentCache(), config)
+  for filename in configFiles:
+    # delayed to here so that `hintConf` is honored
+    rawMessage(conf, hintConf, filename.string)
diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim
index aca03fc16..1d7157cdf 100644
--- a/compiler/nimeval.nim
+++ b/compiler/nimeval.nim
@@ -1,7 +1,7 @@
 #
 #
 #           The Nim Compiler
-#        (c) Copyright 2013 Andreas Rumpf
+#        (c) Copyright 2018 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
@@ -9,26 +9,119 @@
 
 ## exposes the Nim VM to clients.
 import
-  ast, modules, passes, passaux, condsyms,
-  options, nimconf, sem, semdata, llstream, vm, modulegraphs, idents
-
-proc execute*(program: string) =
-  passes.gIncludeFile = includeModule
-  passes.gImportModule = importModule
-  initDefines()
-  loadConfigs(DefaultConfig)
-
-  initDefines()
-  defineSymbol("nimrodvm")
-  when hasFFI: defineSymbol("nimffi")
-  registerPass(verbosePass)
-  registerPass(semPass)
-  registerPass(evalPass)
-
-  searchPaths.add options.libpath
-  var graph = newModuleGraph()
+  ast, astalgo, modules, passes, condsyms,
+  options, sem, semdata, llstream, vm, vmdef,
+  modulegraphs, idents, os, pathutils
+
+type
+  Interpreter* = ref object ## Use Nim as an interpreter with this object
+    mainModule: PSym
+    graph: ModuleGraph
+    scriptName: string
+
+iterator exportedSymbols*(i: Interpreter): PSym =
+  assert i != nil
+  assert i.mainModule != nil, "no main module selected"
+  var it: TTabIter
+  var s = initTabIter(it, i.mainModule.tab)
+  while s != nil:
+    yield s
+    s = nextIter(it, i.mainModule.tab)
+
+proc selectUniqueSymbol*(i: Interpreter; name: string;
+                         symKinds: set[TSymKind] = {skLet, skVar}): PSym =
+  ## Can be used to access a unique symbol of ``name`` and
+  ## the given ``symKinds`` filter.
+  assert i != nil
+  assert i.mainModule != nil, "no main module selected"
+  let n = getIdent(i.graph.cache, name)
+  var it: TIdentIter
+  var s = initIdentIter(it, i.mainModule.tab, n)
+  result = nil
+  while s != nil:
+    if s.kind in symKinds:
+      if result == nil: result = s
+      else: return nil # ambiguous
+    s = nextIdentIter(it, i.mainModule.tab)
+
+proc selectRoutine*(i: Interpreter; name: string): PSym =
+  ## Selects a declared rountine (proc/func/etc) from the main module.
+  ## The routine needs to have the export marker ``*``. The only matching
+  ## routine is returned and ``nil`` if it is overloaded.
+  result = selectUniqueSymbol(i, name, {skTemplate, skMacro, skFunc,
+                                        skMethod, skProc, skConverter})
+
+proc callRoutine*(i: Interpreter; routine: PSym; args: openArray[PNode]): PNode =
+  assert i != nil
+  result = vm.execProc(PCtx i.graph.vm, routine, args)
+
+proc getGlobalValue*(i: Interpreter; letOrVar: PSym): PNode =
+  result = vm.getGlobalValue(PCtx i.graph.vm, letOrVar)
+
+proc implementRoutine*(i: Interpreter; pkg, module, name: string;
+                       impl: proc (a: VmArgs) {.closure, gcsafe.}) =
+  assert i != nil
+  let vm = PCtx(i.graph.vm)
+  vm.registerCallback(pkg & "." & module & "." & name, impl)
+
+proc evalScript*(i: Interpreter; scriptStream: PLLStream = nil) =
+  ## This can also be used to *reload* the script.
+  assert i != nil
+  assert i.mainModule != nil, "no main module selected"
+  initStrTable(i.mainModule.tab)
+  i.mainModule.ast = nil
+
+  let s = if scriptStream != nil: scriptStream
+          else: llStreamOpen(findFile(i.graph.config, i.scriptName), fmRead)
+  processModule(i.graph, i.mainModule, s)
+
+proc findNimStdLib*(): string =
+  ## Tries to find a path to a valid "system.nim" file.
+  ## Returns "" on failure.
+  try:
+    let nimexe = os.findExe("nim")
+    if nimexe.len == 0: return ""
+    result = nimexe.splitPath()[0] /../ "lib"
+    if not fileExists(result / "system.nim"):
+      when defined(unix):
+        result = nimexe.expandSymlink.splitPath()[0] /../ "lib"
+        if not fileExists(result / "system.nim"): return ""
+  except OSError, ValueError:
+    return ""
+
+proc findNimStdLibCompileTime*(): string =
+  ## Same as ``findNimStdLib`` but uses source files used at compile time,
+  ## and asserts on error.
+  const sourcePath = currentSourcePath()
+  result = sourcePath.parentDir.parentDir / "lib"
+  doAssert fileExists(result / "system.nim"), "result:" & result
+
+proc createInterpreter*(scriptName: string;
+                        searchPaths: openArray[string];
+                        flags: TSandboxFlags = {}): Interpreter =
+  var conf = newConfigRef()
   var cache = newIdentCache()
-  var m = makeStdinModule(graph)
+  var graph = newModuleGraph(cache, conf)
+  connectCallbacks(graph)
+  initDefines(conf.symbols)
+  defineSymbol(conf.symbols, "nimscript")
+  defineSymbol(conf.symbols, "nimconfig")
+  registerPass(graph, semPass)
+  registerPass(graph, evalPass)
+
+  for p in searchPaths:
+    conf.searchPaths.add(AbsoluteDir p)
+    if conf.libpath.isEmpty: conf.libpath = AbsoluteDir p
+
+  var m = graph.makeModule(scriptName)
   incl(m.flags, sfMainModule)
-  compileSystemModule(graph,cache)
-  processModule(graph,m, llStreamOpen(program), nil, cache)
+  var vm = newCtx(m, cache, graph)
+  vm.mode = emRepl
+  vm.features = flags
+  graph.vm = vm
+  graph.compileSystemModule()
+  result = Interpreter(mainModule: m, graph: graph, scriptName: scriptName)
+
+proc destroyInterpreter*(i: Interpreter) =
+  ## destructor.
+  discard "currently nothing to do."
diff --git a/compiler/nimfix/nimfix.nim b/compiler/nimfix/nimfix.nim
index a97d88078..58b019cd3 100644
--- a/compiler/nimfix/nimfix.nim
+++ b/compiler/nimfix/nimfix.nim
@@ -11,7 +11,7 @@
 
 import strutils, os, parseopt
 import compiler/[options, commands, modules, sem,
-  passes, passaux, nimfix/pretty,
+  passes, passaux, linter,
   msgs, nimconf,
   extccomp, condsyms,
   modulegraphs, idents]
@@ -38,7 +38,7 @@ In addition, all command line options of Nim are supported.
 proc mainCommand =
   registerPass verbosePass
   registerPass semPass
-  gCmd = cmdPretty
+  conf.cmd = cmdPretty
   searchPaths.add options.libpath
   if gProjectFull.len != 0:
     # current path is always looked first for modules
@@ -47,7 +47,7 @@ proc mainCommand =
   compileProject(newModuleGraph(), newIdentCache())
   pretty.overwriteFiles()
 
-proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
+proc processCmdLine*(pass: TCmdLinePass, cmd: string, config: ConfigRef) =
   var p = parseopt.initOptParser(cmd)
   var argsCount = 0
   gOnlyMainfile = true
@@ -76,16 +76,16 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
       of "wholeproject": gOnlyMainfile = false
       of "besteffort": msgs.gErrorMax = high(int) # don't stop after first error
       else:
-        processSwitch(pass, p)
+        processSwitch(pass, p, config)
     of cmdArgument:
       options.gProjectName = unixToNativePath(p.key)
       # if processArgument(pass, p, argsCount): break
 
-proc handleCmdLine() =
+proc handleCmdLine(config: ConfigRef) =
   if paramCount() == 0:
     stdout.writeLine(Usage)
   else:
-    processCmdLine(passCmd1, "")
+    processCmdLine(passCmd1, "", config)
     if gProjectName != "":
       try:
         gProjectFull = canonicalizePath(gProjectName)
@@ -96,11 +96,11 @@ proc handleCmdLine() =
       gProjectName = p.name
     else:
       gProjectPath = getCurrentDir()
-    loadConfigs(DefaultConfig) # load all config files
+    loadConfigs(DefaultConfig, config) # load all config files
     # now process command line arguments again, because some options in the
     # command line can overwite the config file's settings
     extccomp.initVars()
-    processCmdLine(passCmd2, "")
+    processCmdLine(passCmd2, "", config)
     mainCommand()
 
 when compileOption("gc", "v2") or compileOption("gc", "refc"):
@@ -108,4 +108,4 @@ when compileOption("gc", "v2") or compileOption("gc", "refc"):
 
 condsyms.initDefines()
 defineSymbol "nimfix"
-handleCmdline()
+handleCmdline newConfigRef()
diff --git a/compiler/nimfix/pretty.nim b/compiler/nimfix/pretty.nim
deleted file mode 100644
index 8ba922927..000000000
--- a/compiler/nimfix/pretty.nim
+++ /dev/null
@@ -1,154 +0,0 @@
-#
-#
-#           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 the code "prettifier". This is part of the toolchain
-## to convert Nim code into a consistent style.
-
-import
-  strutils, os, intsets, strtabs
-
-import compiler/options, compiler/ast, compiler/astalgo, compiler/msgs,
-  compiler/semdata, compiler/nimfix/prettybase, compiler/ropes, compiler/idents
-
-type
-  StyleCheck* {.pure.} = enum None, Warn, Auto
-
-var
-  gOverWrite* = true
-  gStyleCheck*: StyleCheck
-  gCheckExtern*, gOnlyMainfile*: bool
-
-proc overwriteFiles*() =
-  let doStrip = options.getConfigVar("pretty.strip").normalize == "on"
-  for i in 0 .. high(gSourceFiles):
-    if gSourceFiles[i].dirty and not gSourceFiles[i].isNimfixFile and
-        (not gOnlyMainfile or gSourceFiles[i].fileIdx == gProjectMainIdx):
-      let newFile = if gOverWrite: gSourceFiles[i].fullpath
-                    else: gSourceFiles[i].fullpath.changeFileExt(".pretty.nim")
-      try:
-        var f = open(newFile, fmWrite)
-        for line in gSourceFiles[i].lines:
-          if doStrip:
-            f.write line.strip(leading = false, trailing = true)
-          else:
-            f.write line
-          f.write(gSourceFiles[i].newline)
-        f.close
-      except IOError:
-        rawMessage(errCannotOpenFile, newFile)
-
-proc `=~`(s: string, a: openArray[string]): bool =
-  for x in a:
-    if s.startsWith(x): return true
-
-proc beautifyName(s: string, k: TSymKind): string =
-  # minimal set of rules here for transition:
-  # GC_ is allowed
-
-  let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
-  if allUpper and k in {skConst, skEnumField, skType}: return s
-  result = newStringOfCap(s.len)
-  var i = 0
-  case k
-  of skType, skGenericParam:
-    # Types should start with a capital unless builtins like 'int' etc.:
-    if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string",
-             "char", "byte", "bool", "openArray", "seq", "array", "void",
-             "pointer", "float", "csize", "cdouble", "cchar", "cschar",
-             "cshort", "cu", "nil", "expr", "stmt", "typedesc", "auto", "any",
-             "range", "openarray", "varargs", "set", "cfloat"
-             ]:
-      result.add s[i]
-    else:
-      result.add toUpperAscii(s[i])
-  of skConst, skEnumField:
-    # for 'const' we keep how it's spelt; either upper case or lower case:
-    result.add s[0]
-  else:
-    # as a special rule, don't transform 'L' to 'l'
-    if s.len == 1 and s[0] == 'L': result.add 'L'
-    elif '_' in s: result.add(s[i])
-    else: result.add toLowerAscii(s[0])
-  inc i
-  while i < s.len:
-    if s[i] == '_':
-      if i > 0 and s[i-1] in {'A'..'Z'}:
-        # don't skip '_' as it's essential for e.g. 'GC_disable'
-        result.add('_')
-        inc i
-        result.add s[i]
-      else:
-        inc i
-        result.add toUpperAscii(s[i])
-    elif allUpper:
-      result.add toLowerAscii(s[i])
-    else:
-      result.add s[i]
-    inc i
-
-proc replaceInFile(info: TLineInfo; newName: string) =
-  loadFile(info)
-
-  let line = gSourceFiles[info.fileIndex].lines[info.line-1]
-  var first = min(info.col.int, line.len)
-  if first < 0: return
-  #inc first, skipIgnoreCase(line, "proc ", first)
-  while first > 0 and line[first-1] in prettybase.Letters: dec first
-  if first < 0: return
-  if line[first] == '`': inc first
-
-  let last = first+identLen(line, first)-1
-  if differ(line, first, last, newName):
-    # last-first+1 != newName.len or
-    var x = line.substr(0, first-1) & newName & line.substr(last+1)
-    system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
-    gSourceFiles[info.fileIndex].dirty = true
-
-proc checkStyle(info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
-  let beau = beautifyName(s, k)
-  if s != beau:
-    if gStyleCheck == StyleCheck.Auto:
-      sym.name = getIdent(beau)
-      replaceInFile(info, beau)
-    else:
-      message(info, hintName, beau)
-
-proc styleCheckDefImpl(info: TLineInfo; s: PSym; k: TSymKind) =
-  # operators stay as they are:
-  if k in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters: return
-  if k in {skType, skGenericParam} and sfAnon in s.flags: return
-  if {sfImportc, sfExportc} * s.flags == {} or gCheckExtern:
-    checkStyle(info, s.name.s, k, s)
-
-template styleCheckDef*(info: TLineInfo; s: PSym; k: TSymKind) =
-  when defined(nimfix):
-    if gStyleCheck != StyleCheck.None: styleCheckDefImpl(info, s, k)
-
-template styleCheckDef*(info: TLineInfo; s: PSym) =
-  styleCheckDef(info, s, s.kind)
-template styleCheckDef*(s: PSym) =
-  styleCheckDef(s.info, s, s.kind)
-
-proc styleCheckUseImpl(info: TLineInfo; s: PSym) =
-  if info.fileIndex < 0: return
-  # we simply convert it to what it looks like in the definition
-  # for consistency
-
-  # operators stay as they are:
-  if s.kind in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters:
-    return
-  if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return
-  let newName = s.name.s
-
-  replaceInFile(info, newName)
-  #if newName == "File": writeStackTrace()
-
-template styleCheckUse*(info: TLineInfo; s: PSym) =
-  when defined(nimfix):
-    if gStyleCheck != StyleCheck.None: styleCheckUseImpl(info, s)
diff --git a/compiler/nimfix/prettybase.nim b/compiler/nimfix/prettybase.nim
index 0f17cbcb1..c3e16e5ba 100644
--- a/compiler/nimfix/prettybase.nim
+++ b/compiler/nimfix/prettybase.nim
@@ -7,64 +7,13 @@
 #    distribution, for details about the copyright.
 #
 
-import strutils, lexbase, streams
-import compiler/ast, compiler/msgs, compiler/idents
+import strutils except Letters
+import lexbase, streams
+import ".." / [ast, msgs, lineinfos, idents, options, linter]
 from os import splitFile
 
-type
-  TSourceFile* = object
-    lines*: seq[string]
-    dirty*, isNimfixFile*: bool
-    fullpath*, newline*: string
-    fileIdx*: int32
-
-var
-  gSourceFiles*: seq[TSourceFile] = @[]
-
-proc loadFile*(info: TLineInfo) =
-  let i = info.fileIndex
-  if i >= gSourceFiles.len:
-    gSourceFiles.setLen(i+1)
-  if gSourceFiles[i].lines.isNil:
-    gSourceFiles[i].fileIdx = info.fileIndex
-    gSourceFiles[i].lines = @[]
-    let path = info.toFullPath
-    gSourceFiles[i].fullpath = path
-    gSourceFiles[i].isNimfixFile = path.splitFile.ext == ".nimfix"
-    # we want to die here for IOError:
-    for line in lines(path):
-      gSourceFiles[i].lines.add(line)
-    # extract line ending of the file:
-    var lex: BaseLexer
-    open(lex, newFileStream(path, fmRead))
-    var pos = lex.bufpos
-    while true:
-      case lex.buf[pos]
-      of '\c':
-        gSourceFiles[i].newline = "\c\L"
-        break
-      of '\L', '\0':
-        gSourceFiles[i].newline = "\L"
-        break
-      else: discard
-      inc pos
-    close(lex)
-
-const
-  Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
-
-proc identLen*(line: string, start: int): int =
-  while start+result < line.len and line[start+result] in Letters:
-    inc result
-
-proc differ*(line: string, a, b: int, x: string): bool =
-  let y = line[a..b]
-  result = cmpIgnoreStyle(y, x) == 0 and y != x
-
-proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PIdent) =
-  loadFile(info)
-
-  let line = gSourceFiles[info.fileIndex].lines[info.line-1]
+proc replaceDeprecated*(conf: ConfigRef; info: TLineInfo; oldSym, newSym: PIdent) =
+  let line = sourceLine(conf, info)
   var first = min(info.col.int, line.len)
   if first < 0: return
   #inc first, skipIgnoreCase(line, "proc ", first)
@@ -75,20 +24,18 @@ proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PIdent) =
   let last = first+identLen(line, first)-1
   if cmpIgnoreStyle(line[first..last], oldSym.s) == 0:
     var x = line.substr(0, first-1) & newSym.s & line.substr(last+1)
-    system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
-    gSourceFiles[info.fileIndex].dirty = true
+    system.shallowCopy(conf.m.fileInfos[info.fileIndex.int32].lines[info.line.int-1], x)
+    conf.m.fileInfos[info.fileIndex.int32].dirty = true
     #if newSym.s == "File": writeStackTrace()
 
-proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PSym) =
-  replaceDeprecated(info, oldSym.name, newSym.name)
-
-proc replaceComment*(info: TLineInfo) =
-  loadFile(info)
+proc replaceDeprecated*(conf: ConfigRef; info: TLineInfo; oldSym, newSym: PSym) =
+  replaceDeprecated(conf, info, oldSym.name, newSym.name)
 
-  let line = gSourceFiles[info.fileIndex].lines[info.line-1]
+proc replaceComment*(conf: ConfigRef; info: TLineInfo) =
+  let line = sourceLine(conf, info)
   var first = info.col.int
   if line[first] != '#': inc first
 
   var x = line.substr(0, first-1) & "discard " & line.substr(first+1).escape
-  system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
-  gSourceFiles[info.fileIndex].dirty = true
+  system.shallowCopy(conf.m.fileInfos[info.fileIndex.int32].lines[info.line.int-1], x)
+  conf.m.fileInfos[info.fileIndex.int32].dirty = true
diff --git a/compiler/nimlexbase.nim b/compiler/nimlexbase.nim
index 047890c44..2e7416645 100644
--- a/compiler/nimlexbase.nim
+++ b/compiler/nimlexbase.nim
@@ -46,6 +46,7 @@ type
                               # private data:
     sentinel*: int
     lineStart*: int           # index of last line start in buffer
+    offsetBase*: int          # use ``offsetBase + bufpos`` to get the offset
 
 
 proc openBaseLexer*(L: var TBaseLexer, inputstream: PLLStream,
@@ -122,7 +123,8 @@ proc fillBaseLexer(L: var TBaseLexer, pos: int): int =
     result = pos + 1          # nothing to do
   else:
     fillBuffer(L)
-    L.bufpos = 0              # XXX: is this really correct?
+    L.offsetBase += pos + 1
+    L.bufpos = 0
     result = 0
   L.lineStart = result
 
@@ -146,6 +148,7 @@ proc skipUTF8BOM(L: var TBaseLexer) =
 proc openBaseLexer(L: var TBaseLexer, inputstream: PLLStream, bufLen = 8192) =
   assert(bufLen > 0)
   L.bufpos = 0
+  L.offsetBase = 0
   L.bufLen = bufLen
   L.buf = cast[cstring](alloc(bufLen * chrSize))
   L.sentinel = bufLen - 1
diff --git a/compiler/nimsets.nim b/compiler/nimsets.nim
index 94507adf0..b00353e20 100644
--- a/compiler/nimsets.nim
+++ b/compiler/nimsets.nim
@@ -10,29 +10,13 @@
 # this unit handles Nim sets; it implements symbolic sets
 
 import
-  ast, astalgo, trees, nversion, msgs, platform, bitsets, types, renderer
-
-proc toBitSet*(s: PNode, b: var TBitSet)
-  # this function is used for case statement checking:
-proc overlap*(a, b: PNode): bool
-proc inSet*(s: PNode, elem: PNode): bool
-proc someInSet*(s: PNode, a, b: PNode): bool
-proc emptyRange*(a, b: PNode): bool
-proc setHasRange*(s: PNode): bool
-  # returns true if set contains a range (needed by the code generator)
-  # these are used for constant folding:
-proc unionSets*(a, b: PNode): PNode
-proc diffSets*(a, b: PNode): PNode
-proc intersectSets*(a, b: PNode): PNode
-proc symdiffSets*(a, b: PNode): PNode
-proc containsSets*(a, b: PNode): bool
-proc equalSets*(a, b: PNode): bool
-proc cardSet*(s: PNode): BiggestInt
-# implementation
-
-proc inSet(s: PNode, elem: PNode): bool =
+  ast, astalgo, trees, nversion, lineinfos, platform, bitsets, types, renderer,
+  options
+
+proc inSet*(s: PNode, elem: PNode): bool =
+  assert s.kind == nkCurly
   if s.kind != nkCurly:
-    internalError(s.info, "inSet")
+    #internalError(s.info, "inSet")
     return false
   for i in countup(0, sonsLen(s) - 1):
     if s.sons[i].kind == nkRange:
@@ -44,7 +28,7 @@ proc inSet(s: PNode, elem: PNode): bool =
         return true
   result = false
 
-proc overlap(a, b: PNode): bool =
+proc overlap*(a, b: PNode): bool =
   if a.kind == nkRange:
     if b.kind == nkRange:
       # X..Y and C..D overlap iff (X <= D and C <= Y)
@@ -58,10 +42,11 @@ proc overlap(a, b: PNode): bool =
     else:
       result = sameValue(a, b)
 
-proc someInSet(s: PNode, a, b: PNode): bool =
+proc someInSet*(s: PNode, a, b: PNode): bool =
   # checks if some element of a..b is in the set s
+  assert s.kind == nkCurly
   if s.kind != nkCurly:
-    internalError(s.info, "SomeInSet")
+    #internalError(s.info, "SomeInSet")
     return false
   for i in countup(0, sonsLen(s) - 1):
     if s.sons[i].kind == nkRange:
@@ -74,10 +59,10 @@ proc someInSet(s: PNode, a, b: PNode): bool =
         return true
   result = false
 
-proc toBitSet(s: PNode, b: var TBitSet) =
+proc toBitSet*(conf: ConfigRef; s: PNode, b: var TBitSet) =
   var first, j: BiggestInt
-  first = firstOrd(s.typ.sons[0])
-  bitSetInit(b, int(getSize(s.typ)))
+  first = firstOrd(conf, s.typ.sons[0])
+  bitSetInit(b, int(getSize(conf, s.typ)))
   for i in countup(0, sonsLen(s) - 1):
     if s.sons[i].kind == nkRange:
       j = getOrdValue(s.sons[i].sons[0])
@@ -87,13 +72,13 @@ proc toBitSet(s: PNode, b: var TBitSet) =
     else:
       bitSetIncl(b, getOrdValue(s.sons[i]) - first)
 
-proc toTreeSet(s: TBitSet, settype: PType, info: TLineInfo): PNode =
+proc toTreeSet*(conf: ConfigRef; s: TBitSet, settype: PType, info: TLineInfo): PNode =
   var
     a, b, e, first: BiggestInt # a, b are interval borders
     elemType: PType
     n: PNode
   elemType = settype.sons[0]
-  first = firstOrd(elemType)
+  first = firstOrd(conf, elemType)
   result = newNodeI(nkCurly, info)
   result.typ = settype
   result.info = info
@@ -123,54 +108,52 @@ proc toTreeSet(s: TBitSet, settype: PType, info: TLineInfo): PNode =
 
 template nodeSetOp(a, b: PNode, op: untyped) {.dirty.} =
   var x, y: TBitSet
-  toBitSet(a, x)
-  toBitSet(b, y)
+  toBitSet(conf, a, x)
+  toBitSet(conf, b, y)
   op(x, y)
-  result = toTreeSet(x, a.typ, a.info)
+  result = toTreeSet(conf, x, a.typ, a.info)
 
-proc unionSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetUnion)
-proc diffSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetDiff)
-proc intersectSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetIntersect)
-proc symdiffSets(a, b: PNode): PNode = nodeSetOp(a, b, bitSetSymDiff)
+proc unionSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetUnion)
+proc diffSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetDiff)
+proc intersectSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetIntersect)
+proc symdiffSets*(conf: ConfigRef; a, b: PNode): PNode = nodeSetOp(a, b, bitSetSymDiff)
 
-proc containsSets(a, b: PNode): bool =
+proc containsSets*(conf: ConfigRef; a, b: PNode): bool =
   var x, y: TBitSet
-  toBitSet(a, x)
-  toBitSet(b, y)
+  toBitSet(conf, a, x)
+  toBitSet(conf, b, y)
   result = bitSetContains(x, y)
 
-proc equalSets(a, b: PNode): bool =
+proc equalSets*(conf: ConfigRef; a, b: PNode): bool =
   var x, y: TBitSet
-  toBitSet(a, x)
-  toBitSet(b, y)
+  toBitSet(conf, a, x)
+  toBitSet(conf, b, y)
   result = bitSetEquals(x, y)
 
-proc complement*(a: PNode): PNode =
+proc complement*(conf: ConfigRef; a: PNode): PNode =
   var x: TBitSet
-  toBitSet(a, x)
+  toBitSet(conf, a, x)
   for i in countup(0, high(x)): x[i] = not x[i]
-  result = toTreeSet(x, a.typ, a.info)
+  result = toTreeSet(conf, x, a.typ, a.info)
 
-proc cardSet(s: PNode): BiggestInt =
-  # here we can do better than converting it into a compact set
-  # we just count the elements directly
-  result = 0
-  for i in countup(0, sonsLen(s) - 1):
-    if s.sons[i].kind == nkRange:
-      result = result + getOrdValue(s.sons[i].sons[1]) -
-          getOrdValue(s.sons[i].sons[0]) + 1
-    else:
-      inc(result)
+proc deduplicate*(conf: ConfigRef; a: PNode): PNode =
+  var x: TBitSet
+  toBitSet(conf, a, x)
+  result = toTreeSet(conf, x, a.typ, a.info)
+
+proc cardSet*(conf: ConfigRef; a: PNode): BiggestInt =
+  var x: TBitSet
+  toBitSet(conf, a, x)
+  result = bitSetCard(x)
 
-proc setHasRange(s: PNode): bool =
+proc setHasRange*(s: PNode): bool =
+  assert s.kind == nkCurly
   if s.kind != nkCurly:
-    internalError(s.info, "SetHasRange")
     return false
   for i in countup(0, sonsLen(s) - 1):
     if s.sons[i].kind == nkRange:
       return true
   result = false
 
-proc emptyRange(a, b: PNode): bool =
+proc emptyRange*(a, b: PNode): bool =
   result = not leValue(a, b)  # a > b iff not (a <= b)
-
diff --git a/compiler/nversion.nim b/compiler/nversion.nim
index 4d4fe6c95..8981ae213 100644
--- a/compiler/nversion.nim
+++ b/compiler/nversion.nim
@@ -13,5 +13,8 @@
 const
   MaxSetElements* = 1 shl 16  # (2^16) to support unicode character sets?
   VersionAsString* = system.NimVersion
-  RodFileVersion* = "1222"       # modify this if the rod-format changes!
+  RodFileVersion* = "1223"       # modify this if the rod-format changes!
 
+  NimCompilerApiVersion* = 3 ## Check for the existance of this before accessing it
+                             ## as older versions of the compiler API do not
+                             ## declare this.
diff --git a/compiler/options.nim b/compiler/options.nim
index 6372cddac..80d665d62 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -8,23 +8,25 @@
 #
 
 import
-  os, strutils, strtabs, osproc, sets
+  os, strutils, strtabs, osproc, sets, lineinfos, platform,
+  prefixmatches, pathutils
+
+from terminal import isatty
+from times import utc, fromUnix, local, getTime, format, DateTime
 
 const
   hasTinyCBackend* = defined(tinyc)
   useEffectSystem* = true
   useWriteTracking* = false
   hasFFI* = defined(useFFI)
-  newScopeForIf* = true
-  useCaas* = not defined(noCaas)
-  noTimeMachine* = defined(avoidTimeMachine) and defined(macosx)
+  copyrightYear* = "2018"
 
 type                          # please make sure we have under 32 options
                               # (improves code efficiency a lot!)
   TOption* = enum             # **keep binary compatible**
     optNone, optObjCheck, optFieldCheck, optRangeCheck, optBoundsCheck,
     optOverflowCheck, optNilCheck,
-    optNaNCheck, optInfCheck,
+    optNaNCheck, optInfCheck, optMoveCheck,
     optAssert, optLineDir, optWarns, optHints,
     optOptimizeSpeed, optOptimizeSize, optStackTrace, # stack tracing support
     optLineTrace,             # line tracing support (includes stack tracing)
@@ -35,13 +37,16 @@ type                          # please make sure we have under 32 options
     optImplicitStatic,        # optimization: implicit at compile time
                               # evaluation
     optPatterns,              # en/disable pattern matching
-    optMemTracker
+    optMemTracker,
+    optHotCodeReloading,
+    optLaxStrings,
+    optNilSeqs
 
   TOptions* = set[TOption]
   TGlobalOption* = enum       # **keep binary compatible**
-    gloptNone, optForceFullMake, optDeadCodeElim,
+    gloptNone, optForceFullMake,
+    optWasNimscript,
     optListCmd, optCompileOnly, optNoLinking,
-    optReportConceptFailures, # report 'compiles' or 'concept' matching failures
     optCDebug,                # turn on debugging information
     optGenDynLib,             # generate a dynamic library
     optGenStaticLib,          # generate a static library
@@ -49,12 +54,12 @@ type                          # please make sure we have under 32 options
     optGenScript,             # generate a script file to compile the *.c files
     optGenMapping,            # generate a mapping file
     optRun,                   # run the compiled project
-    optSymbolFiles,           # use symbol files for speeding up compilation
-    optCaasEnabled            # compiler-as-a-service is running
-    optSkipConfigFile,        # skip the general config file
-    optSkipProjConfigFile,    # skip the project's config file
-    optSkipUserConfigFile,    # skip the users's config file
-    optSkipParentConfigFiles, # skip parent dir's config files
+    optStyleHint,             # check that the names adhere to NEP-1
+    optStyleError,            # enforce that the names adhere to NEP-1
+    optSkipSystemConfigFile,  # skip the system's cfg/nims config file
+    optSkipProjConfigFile,    # skip the project's cfg/nims config file
+    optSkipUserConfigFile,    # skip the users's cfg/nims config file
+    optSkipParentConfigFiles, # skip parent dir's cfg/nims config files
     optNoMain,                # do not generate a "main" proc
     optUseColors,             # use colors for hints, warnings, and errors
     optThreads,               # support for multi-threading
@@ -69,19 +74,24 @@ type                          # please make sure we have under 32 options
     optIdeTerse               # idetools: use terse descriptions
     optNoCppExceptions        # use C exception handling even with CPP
     optExcessiveStackTrace    # fully qualified module filenames
+    optShowAllMismatches      # show all overloading resolution candidates
+    optWholeProject           # for 'doc2': output any dependency
+    optMixedMode              # true if some module triggered C++ codegen
+    optListFullPaths
+    optNoNimblePath
+    optDynlibOverrideAll
 
   TGlobalOptions* = set[TGlobalOption]
 
 const
-  harmlessOptions* = {optForceFullMake, optNoLinking, optReportConceptFailures,
-    optRun, optUseColors, optStdout}
+  harmlessOptions* = {optForceFullMake, optNoLinking, optRun,
+                      optUseColors, optStdout}
 
 type
   TCommands* = enum           # Nim's commands
                               # **keep binary compatible**
     cmdNone, cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
     cmdCompileToJS,
-    cmdCompileToPHP,
     cmdCompileToLLVM, cmdInterpret, cmdPretty, cmdDoc,
     cmdGenDepend, cmdDump,
     cmdCheck,                 # semantic checking for whole project
@@ -92,170 +102,404 @@ type
     cmdRst2html,              # convert a reStructuredText file to HTML
     cmdRst2tex,               # convert a reStructuredText file to TeX
     cmdInteractive,           # start interactive session
-    cmdRun                    # run the project via TCC backend
+    cmdRun,                   # run the project via TCC backend
+    cmdJsonScript             # compile a .json build file
   TStringSeq* = seq[string]
   TGCMode* = enum             # the selected GC
-    gcNone, gcBoehm, gcGo, gcStack, gcMarkAndSweep, gcRefc,
-    gcV2, gcGenerational
+    gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcDestructors,
+    gcRefc, gcV2, gcGo
+    # gcRefc and the GCs that follow it use a write barrier,
+    # as far as usesWriteBarrier() is concerned
 
   IdeCmd* = enum
     ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod,
     ideHighlight, ideOutline, ideKnown, ideMsg
 
-  ConfigRef* = ref object ## eventually all global configuration should be moved here
-    cppDefines*: HashSet[string]
+  Feature* = enum  ## experimental features; DO NOT RENAME THESE!
+    implicitDeref,
+    dotOperators,
+    callOperator,
+    parallel,
+    destructor,
+    notnil,
+    dynamicBindSym,
+    forLoopMacros,
+    caseStmtMacros,
+    codeReordering,
+
+  SymbolFilesOption* = enum
+    disabledSf, writeOnlySf, readOnlySf, v2Sf
+
+  TSystemCC* = enum
+    ccNone, ccGcc, ccNintendoSwitch, ccLLVM_Gcc, ccCLang, ccLcc, ccBcc, ccDmc, ccWcc, ccVcc,
+    ccTcc, ccPcc, ccUcc, ccIcl, ccIcc
+
+  CfileFlag* {.pure.} = enum
+    Cached,    ## no need to recompile this time
+    External   ## file was introduced via .compile pragma
+
+  Cfile* = object
+    cname*, obj*: AbsoluteFile
+    flags*: set[CFileFlag]
+  CfileList* = seq[Cfile]
+
+  Suggest* = ref object
+    section*: IdeCmd
+    qualifiedPath*: seq[string]
+    name*: ptr string         # not used beyond sorting purposes; name is also
+                              # part of 'qualifiedPath'
+    filePath*: string
+    line*: int                   # Starts at 1
+    column*: int                 # Starts at 0
+    doc*: string           # Not escaped (yet)
+    forth*: string               # type
+    quality*: range[0..100]   # matching quality
+    isGlobal*: bool # is a global variable
+    contextFits*: bool # type/non-type context matches
+    prefix*: PrefixMatch
+    symkind*: byte
+    scope*, localUsages*, globalUsages*: int # more usages is better
+    tokenLen*: int
+    version*: int
+  Suggestions* = seq[Suggest]
+
+  ConfigRef* = ref object ## every global configuration
+                          ## fields marked with '*' are subject to
+                          ## the incremental compilation mechanisms
+                          ## (+) means "part of the dependency"
+    target*: Target       # (+)
+    linesCompiled*: int  # all lines that have been compiled
+    options*: TOptions    # (+)
+    globalOptions*: TGlobalOptions # (+)
+    m*: MsgConfig
+    evalTemplateCounter*: int
+    evalMacroCounter*: int
+    exitcode*: int8
+    cmd*: TCommands  # the command
+    selectedGC*: TGCMode       # the selected GC (+)
+    verbosity*: int            # how verbose the compiler is
+    numberOfProcessors*: int   # number of processors
+    evalExpr*: string          # expression for idetools --eval
+    lastCmdTime*: float        # when caas is enabled, we measure each command
+    symbolFiles*: SymbolFilesOption
+
+    cppDefines*: HashSet[string] # (*)
     headerFile*: string
+    features*: set[Feature]
+    arguments*: string ## the arguments to be passed to the program that
+                       ## should be run
+    helpWritten*: bool
+    ideCmd*: IdeCmd
+    oldNewlines*: bool
+    cCompiler*: TSystemCC
+    enableNotes*: TNoteKinds
+    disableNotes*: TNoteKinds
+    foreignPackageNotes*: TNoteKinds
+    notes*: TNoteKinds
+    mainPackageNotes*: TNoteKinds
+    mainPackageId*: int
+    errorCounter*: int
+    hintCounter*: int
+    warnCounter*: int
+    errorMax*: int
+    configVars*: StringTableRef
+    symbols*: StringTableRef ## We need to use a StringTableRef here as defined
+                             ## symbols are always guaranteed to be style
+                             ## insensitive. Otherwise hell would break lose.
+    packageCache*: StringTableRef
+    searchPaths*: seq[AbsoluteDir]
+    lazyPaths*: seq[AbsoluteDir]
+    outFile*: AbsoluteFile
+    prefixDir*, libpath*, nimcacheDir*: AbsoluteDir
+    dllOverrides, moduleOverrides*: StringTableRef
+    projectName*: string # holds a name like 'nim'
+    projectPath*: AbsoluteDir # holds a path like /home/alice/projects/nim/compiler/
+    projectFull*: AbsoluteFile # projectPath/projectName
+    projectIsStdin*: bool # whether we're compiling from stdin
+    projectMainIdx*: FileIndex # the canonical path id of the main module
+    command*: string # the main command (e.g. cc, check, scan, etc)
+    commandArgs*: seq[string] # any arguments after the main command
+    keepComments*: bool # whether the parser needs to keep comments
+    implicitImports*: seq[string] # modules that are to be implicitly imported
+    implicitIncludes*: seq[string] # modules that are to be implicitly included
+    docSeeSrcUrl*: string # if empty, no seeSrc will be generated. \
+    # The string uses the formatting variables `path` and `line`.
+
+     # the used compiler
+    cIncludes*: seq[AbsoluteDir]  # directories to search for included files
+    cLibs*: seq[AbsoluteDir]      # directories to search for lib files
+    cLinkedLibs*: seq[string]     # libraries to link
+
+    externalToLink*: seq[string]  # files to link in addition to the file
+                                  # we compiled (*)
+    linkOptionsCmd*: string
+    compileOptionsCmd*: seq[string]
+    linkOptions*: string          # (*)
+    compileOptions*: string       # (*)
+    ccompilerpath*: string
+    toCompile*: CfileList         # (*)
+    suggestionResultHook*: proc (result: Suggest) {.closure.}
+    suggestVersion*: int
+    suggestMaxResults*: int
+    lastLineInfo*: TLineInfo
+    writelnHook*: proc (output: string) {.closure.}
+    structuredErrorHook*: proc (config: ConfigRef; info: TLineInfo; msg: string;
+                                severity: Severity) {.closure.}
+    cppCustomNamespace*: string
+
+template depConfigFields*(fn) {.dirty.} =
+  fn(target)
+  fn(options)
+  fn(globalOptions)
+  fn(selectedGC)
+
+const oldExperimentalFeatures* = {implicitDeref, dotOperators, callOperator, parallel}
+
+const
+  ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck,
+    optOverflowCheck, optBoundsCheck, optAssert, optNaNCheck, optInfCheck,
+    optMoveCheck}
+
+  DefaultOptions* = {optObjCheck, optFieldCheck, optRangeCheck,
+    optBoundsCheck, optOverflowCheck, optAssert, optWarns,
+    optHints, optStackTrace, optLineTrace,
+    optPatterns, optNilCheck, optMoveCheck}
+  DefaultGlobalOptions* = {optThreadAnalysis}
+
+proc getSrcTimestamp(): DateTime =
+  try:
+    result = utc(fromUnix(parseInt(getEnv("SOURCE_DATE_EPOCH",
+                                          "not a number"))))
+  except ValueError:
+    # Environment variable malformed.
+    # https://reproducible-builds.org/specs/source-date-epoch/: "If the
+    # value is malformed, the build process SHOULD exit with a non-zero
+    # error code", which this doesn't do. This uses local time, because
+    # that maintains compatibility with existing usage.
+    result = utc getTime()
+
+proc getDateStr*(): string =
+  result = format(getSrcTimestamp(), "yyyy-MM-dd")
+
+proc getClockStr*(): string =
+  result = format(getSrcTimestamp(), "HH:mm:ss")
+
+template newPackageCache*(): untyped =
+  newStringTable(when FileSystemCaseSensitive:
+                   modeCaseInsensitive
+                 else:
+                   modeCaseSensitive)
 
 proc newConfigRef*(): ConfigRef =
-  result = ConfigRef(cppDefines: initSet[string](),
-    headerFile: "")
+  result = ConfigRef(
+    selectedGC: gcRefc,
+    cCompiler: ccGcc,
+    verbosity: 1,
+    options: DefaultOptions,
+    globalOptions: DefaultGlobalOptions,
+    m: initMsgConfig(),
+    evalExpr: "",
+    cppDefines: initSet[string](),
+    headerFile: "", features: {}, foreignPackageNotes: {hintProcessing, warnUnknownMagic,
+    hintQuitCalled, hintExecuting},
+    notes: NotesVerbosity[1], mainPackageNotes: NotesVerbosity[1],
+    configVars: newStringTable(modeStyleInsensitive),
+    symbols: newStringTable(modeStyleInsensitive),
+    packageCache: newPackageCache(),
+    searchPaths: @[],
+    lazyPaths: @[],
+    outFile: AbsoluteFile"", prefixDir: AbsoluteDir"",
+    libpath: AbsoluteDir"", nimcacheDir: AbsoluteDir"",
+    dllOverrides: newStringTable(modeCaseInsensitive),
+    moduleOverrides: newStringTable(modeStyleInsensitive),
+    projectName: "", # holds a name like 'nim'
+    projectPath: AbsoluteDir"", # holds a path like /home/alice/projects/nim/compiler/
+    projectFull: AbsoluteFile"", # projectPath/projectName
+    projectIsStdin: false, # whether we're compiling from stdin
+    projectMainIdx: FileIndex(0'i32), # the canonical path id of the main module
+    command: "", # the main command (e.g. cc, check, scan, etc)
+    commandArgs: @[], # any arguments after the main command
+    keepComments: true, # whether the parser needs to keep comments
+    implicitImports: @[], # modules that are to be implicitly imported
+    implicitIncludes: @[], # modules that are to be implicitly included
+    docSeeSrcUrl: "",
+    cIncludes: @[],   # directories to search for included files
+    cLibs: @[],       # directories to search for lib files
+    cLinkedLibs: @[],  # libraries to link
+
+    externalToLink: @[],
+    linkOptionsCmd: "",
+    compileOptionsCmd: @[],
+    linkOptions: "",
+    compileOptions: "",
+    ccompilerpath: "",
+    toCompile: @[],
+    arguments: "",
+    suggestMaxResults: 10_000
+  )
+  setTargetFromSystem(result.target)
+  # enable colors by default on terminals
+  if terminal.isatty(stderr):
+    incl(result.globalOptions, optUseColors)
+
+proc newPartialConfigRef*(): ConfigRef =
+  ## create a new ConfigRef that is only good enough for error reporting.
+  result = ConfigRef(
+    selectedGC: gcRefc,
+    verbosity: 1,
+    options: DefaultOptions,
+    globalOptions: DefaultGlobalOptions,
+    foreignPackageNotes: {hintProcessing, warnUnknownMagic,
+    hintQuitCalled, hintExecuting},
+    notes: NotesVerbosity[1], mainPackageNotes: NotesVerbosity[1])
 
 proc cppDefine*(c: ConfigRef; define: string) =
   c.cppDefines.incl define
 
-var
-  gIdeCmd*: IdeCmd
-
-const
-  ChecksOptions* = {optObjCheck, optFieldCheck, optRangeCheck, optNilCheck,
-    optOverflowCheck, optBoundsCheck, optAssert, optNaNCheck, optInfCheck}
-
-var
-  gOptions*: TOptions = {optObjCheck, optFieldCheck, optRangeCheck,
-                         optBoundsCheck, optOverflowCheck, optAssert, optWarns,
-                         optHints, optStackTrace, optLineTrace,
-                         optPatterns, optNilCheck}
-  gGlobalOptions*: TGlobalOptions = {optThreadAnalysis}
-  gExitcode*: int8
-  gCmd*: TCommands = cmdNone  # the command
-  gSelectedGC* = gcRefc       # the selected GC
-  searchPaths*: seq[string] = @[]
-  lazyPaths*: seq[string]   = @[]
-  outFile*: string = ""
-  docSeeSrcUrl*: string = ""  # if empty, no seeSrc will be generated. \
-  # The string uses the formatting variables `path` and `line`.
-  #headerFile*: string = ""
-  gVerbosity* = 1             # how verbose the compiler is
-  gNumberOfProcessors*: int   # number of processors
-  gWholeProject*: bool        # for 'doc2': output any dependency
-  gEvalExpr* = ""             # expression for idetools --eval
-  gLastCmdTime*: float        # when caas is enabled, we measure each command
-  gListFullPaths*: bool
-  isServing*: bool = false
-  gNoNimblePath* = false
-  gExperimentalMode*: bool
-
-proc importantComments*(): bool {.inline.} = gCmd in {cmdDoc, cmdIdeTools}
-proc usesNativeGC*(): bool {.inline.} = gSelectedGC >= gcRefc
-
-template compilationCachePresent*: untyped =
-  {optCaasEnabled, optSymbolFiles} * gGlobalOptions != {}
-
-template optPreserveOrigSource*: untyped =
-  optEmbedOrigSrc in gGlobalOptions
+proc isDefined*(conf: ConfigRef; symbol: string): bool =
+  if conf.symbols.hasKey(symbol):
+    result = conf.symbols[symbol] != "false"
+  elif cmpIgnoreStyle(symbol, CPU[conf.target.targetCPU].name) == 0:
+    result = true
+  elif cmpIgnoreStyle(symbol, platform.OS[conf.target.targetOS].name) == 0:
+    result = true
+  else:
+    case symbol.normalize
+    of "x86": result = conf.target.targetCPU == cpuI386
+    of "itanium": result = conf.target.targetCPU == cpuIa64
+    of "x8664": result = conf.target.targetCPU == cpuAmd64
+    of "posix", "unix":
+      result = conf.target.targetOS in {osLinux, osMorphos, osSkyos, osIrix, osPalmos,
+                            osQnx, osAtari, osAix,
+                            osHaiku, osVxWorks, osSolaris, osNetbsd,
+                            osFreebsd, osOpenbsd, osDragonfly, osMacosx,
+                            osAndroid, osNintendoSwitch}
+    of "linux":
+      result = conf.target.targetOS in {osLinux, osAndroid}
+    of "bsd":
+      result = conf.target.targetOS in {osNetbsd, osFreebsd, osOpenbsd, osDragonfly}
+    of "emulatedthreadvars":
+      result = platform.OS[conf.target.targetOS].props.contains(ospLacksThreadVars)
+    of "msdos": result = conf.target.targetOS == osDos
+    of "mswindows", "win32": result = conf.target.targetOS == osWindows
+    of "macintosh": result = conf.target.targetOS in {osMacos, osMacosx}
+    of "osx": result = conf.target.targetOS == osMacosx
+    of "sunos": result = conf.target.targetOS == osSolaris
+    of "nintendoswitch":
+      result = conf.target.targetOS == osNintendoSwitch
+    of "littleendian": result = CPU[conf.target.targetCPU].endian == platform.littleEndian
+    of "bigendian": result = CPU[conf.target.targetCPU].endian == platform.bigEndian
+    of "cpu8": result = CPU[conf.target.targetCPU].bit == 8
+    of "cpu16": result = CPU[conf.target.targetCPU].bit == 16
+    of "cpu32": result = CPU[conf.target.targetCPU].bit == 32
+    of "cpu64": result = CPU[conf.target.targetCPU].bit == 64
+    of "nimrawsetjmp":
+      result = conf.target.targetOS in {osSolaris, osNetbsd, osFreebsd, osOpenbsd,
+                            osDragonfly, osMacosx}
+    else: discard
+
+proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in {cmdDoc, cmdIdeTools}
+proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc
+
+template compilationCachePresent*(conf: ConfigRef): untyped =
+  false
+#  conf.symbolFiles in {v2Sf, writeOnlySf}
+
+template optPreserveOrigSource*(conf: ConfigRef): untyped =
+  optEmbedOrigSrc in conf.globalOptions
 
 const
-  genSubDir* = "nimcache"
+  genSubDir* = RelativeDir"nimcache"
   NimExt* = "nim"
   RodExt* = "rod"
   HtmlExt* = "html"
   JsonExt* = "json"
+  TagsExt* = "tags"
   TexExt* = "tex"
   IniExt* = "ini"
-  DefaultConfig* = "nim.cfg"
-  DocConfig* = "nimdoc.cfg"
-  DocTexConfig* = "nimdoc.tex.cfg"
-
-# additional configuration variables:
-var
-  gConfigVars* = newStringTable(modeStyleInsensitive)
-  gDllOverrides = newStringTable(modeCaseInsensitive)
-  gModuleOverrides* = newStringTable(modeStyleInsensitive)
-  gPrefixDir* = "" # Overrides the default prefix dir in getPrefixDir proc.
-  libpath* = ""
-  gProjectName* = "" # holds a name like 'nim'
-  gProjectPath* = "" # holds a path like /home/alice/projects/nim/compiler/
-  gProjectFull* = "" # projectPath/projectName
-  gProjectIsStdin* = false # whether we're compiling from stdin
-  gProjectMainIdx*: int32 # the canonical path id of the main module
-  nimcacheDir* = ""
-  command* = "" # the main command (e.g. cc, check, scan, etc)
-  commandArgs*: seq[string] = @[] # any arguments after the main command
-  gKeepComments*: bool = true # whether the parser needs to keep comments
-  implicitImports*: seq[string] = @[] # modules that are to be implicitly imported
-  implicitIncludes*: seq[string] = @[] # modules that are to be implicitly included
+  DefaultConfig* = RelativeFile"nim.cfg"
+  DefaultConfigNims* = RelativeFile"config.nims"
+  DocConfig* = RelativeFile"nimdoc.cfg"
+  DocTexConfig* = RelativeFile"nimdoc.tex.cfg"
 
 const oKeepVariableNames* = true
 
-proc mainCommandArg*: string =
+template compilingLib*(conf: ConfigRef): bool =
+  gGlobalOptions * {optGenGuiApp, optGenDynLib} != {}
+
+proc mainCommandArg*(conf: ConfigRef): string =
   ## This is intended for commands like check or parse
   ## which will work on the main project file unless
   ## explicitly given a specific file argument
-  if commandArgs.len > 0:
-    result = commandArgs[0]
+  if conf.commandArgs.len > 0:
+    result = conf.commandArgs[0]
   else:
-    result = gProjectName
+    result = conf.projectName
 
-proc existsConfigVar*(key: string): bool =
-  result = hasKey(gConfigVars, key)
+proc existsConfigVar*(conf: ConfigRef; key: string): bool =
+  result = hasKey(conf.configVars, key)
 
-proc getConfigVar*(key: string): string =
-  result = gConfigVars.getOrDefault key
+proc getConfigVar*(conf: ConfigRef; key: string, default = ""): string =
+  result = conf.configVars.getOrDefault(key, default)
 
-proc setConfigVar*(key, val: string) =
-  gConfigVars[key] = val
+proc setConfigVar*(conf: ConfigRef; key, val: string) =
+  conf.configVars[key] = val
 
-proc getOutFile*(filename, ext: string): string =
-  if options.outFile != "": result = options.outFile
-  else: result = changeFileExt(filename, ext)
+proc getOutFile*(conf: ConfigRef; filename: RelativeFile, ext: string): AbsoluteFile =
+  if not conf.outFile.isEmpty: result = conf.outFile
+  else: result = conf.projectPath / changeFileExt(filename, ext)
 
-proc getPrefixDir*(): string =
+proc getPrefixDir*(conf: ConfigRef): AbsoluteDir =
   ## Gets the prefix dir, usually the parent directory where the binary resides.
   ##
-  ## This is overridden by some tools (namely nimsuggest) via the ``gPrefixDir``
-  ## global.
-  if gPrefixDir != "": result = gPrefixDir
-  else:
-    result = splitPath(getAppDir()).head
+  ## This is overridden by some tools (namely nimsuggest) via the ``conf.prefixDir``
+  ## field.
+  if not conf.prefixDir.isEmpty: result = conf.prefixDir
+  else: result = AbsoluteDir splitPath(getAppDir()).head
 
-proc setDefaultLibpath*() =
+proc setDefaultLibpath*(conf: ConfigRef) =
   # set default value (can be overwritten):
-  if libpath == "":
+  if conf.libpath.isEmpty:
     # choose default libpath:
-    var prefix = getPrefixDir()
+    var prefix = getPrefixDir(conf)
     when defined(posix):
-      if prefix == "/usr": libpath = "/usr/lib/nim"
-      elif prefix == "/usr/local": libpath = "/usr/local/lib/nim"
-      else: libpath = joinPath(prefix, "lib")
-    else: libpath = joinPath(prefix, "lib")
+      if prefix == AbsoluteDir"/usr":
+        conf.libpath = AbsoluteDir"/usr/lib/nim"
+      elif prefix == AbsoluteDir"/usr/local":
+        conf.libpath = AbsoluteDir"/usr/local/lib/nim"
+      else:
+        conf.libpath = prefix / RelativeDir"lib"
+    else:
+      conf.libpath = prefix / RelativeDir"lib"
 
     # Special rule to support other tools (nimble) which import the compiler
     # modules and make use of them.
     let realNimPath = findExe("nim")
     # Find out if $nim/../../lib/system.nim exists.
-    let parentNimLibPath = realNimPath.parentDir().parentDir() / "lib"
-    if not fileExists(libpath / "system.nim") and
+    let parentNimLibPath = realNimPath.parentDir.parentDir / "lib"
+    if not fileExists(conf.libpath.string / "system.nim") and
         fileExists(parentNimlibPath / "system.nim"):
-      libpath = parentNimLibPath
+      conf.libpath = AbsoluteDir parentNimLibPath
 
-proc canonicalizePath*(path: string): string =
+proc canonicalizePath*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile =
   # on Windows, 'expandFilename' calls getFullPathName which doesn't do
   # case corrections, so we have to use this convoluted way of retrieving
   # the true filename (see tests/modules and Nimble uses 'import Uri' instead
   # of 'import uri'):
   when defined(windows):
-    result = path.expandFilename
-    for x in walkFiles(result):
-      return x
+    result = AbsoluteFile path.string.expandFilename
+    for x in walkFiles(result.string):
+      return AbsoluteFile x
   else:
-    result = path.expandFilename
+    result = AbsoluteFile path.string.expandFilename
 
-proc shortenDir*(dir: string): string =
+proc shortenDir*(conf: ConfigRef; dir: string): string {.
+    deprecated: "use 'relativeTo' instead".} =
   ## returns the interesting part of a dir
-  var prefix = gProjectPath & DirSep
+  var prefix = conf.projectPath.string & DirSep
   if startsWith(dir, prefix):
     return substr(dir, len(prefix))
-  prefix = getPrefixDir() & DirSep
+  prefix = getPrefixDir(conf).string & DirSep
   if startsWith(dir, prefix):
     return substr(dir, len(prefix))
   result = dir
@@ -266,147 +510,148 @@ proc removeTrailingDirSep*(path: string): string =
   else:
     result = path
 
-proc disableNimblePath*() =
-  gNoNimblePath = true
-  lazyPaths.setLen(0)
+proc disableNimblePath*(conf: ConfigRef) =
+  incl conf.globalOptions, optNoNimblePath
+  conf.lazyPaths.setLen(0)
 
 include packagehandling
 
-proc getNimcacheDir*: string =
-  result = if nimcacheDir.len > 0: nimcacheDir else: gProjectPath.shortenDir /
-                                                         genSubDir
-
-
-proc pathSubs*(p, config: string): string =
+proc getOsCacheDir(): string =
+  when defined(posix):
+    result = getEnv("XDG_CACHE_HOME", getHomeDir() / ".cache") / "nim"
+  else:
+    result = getHomeDir() / genSubDir.string
+
+proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
+  # XXX projectName should always be without a file extension!
+  result = if not conf.nimcacheDir.isEmpty:
+             conf.nimcacheDir
+           elif conf.cmd == cmdCompileToJS:
+             conf.projectPath / genSubDir
+           else:
+            AbsoluteDir(getOsCacheDir() / splitFile(conf.projectName).name &
+               (if isDefined(conf, "release"): "_r" else: "_d"))
+
+proc pathSubs*(conf: ConfigRef; p, config: string): string =
   let home = removeTrailingDirSep(os.getHomeDir())
   result = unixToNativePath(p % [
-    "nim", getPrefixDir(),
-    "lib", libpath,
+    "nim", getPrefixDir(conf).string,
+    "lib", conf.libpath.string,
     "home", home,
     "config", config,
-    "projectname", options.gProjectName,
-    "projectpath", options.gProjectPath,
-    "projectdir", options.gProjectPath,
-    "nimcache", getNimcacheDir()])
-  if '~' in result:
-    result = result.replace("~", home)
-
-proc toGeneratedFile*(path, ext: string): string =
+    "projectname", conf.projectName,
+    "projectpath", conf.projectPath.string,
+    "projectdir", conf.projectPath.string,
+    "nimcache", getNimcacheDir(conf).string])
+  if "~/" in result:
+    result = result.replace("~/", home & '/')
+
+proc toGeneratedFile*(conf: ConfigRef; path: AbsoluteFile,
+                      ext: string): AbsoluteFile =
   ## converts "/home/a/mymodule.nim", "rod" to "/home/a/nimcache/mymodule.rod"
-  var (head, tail) = splitPath(path)
-  #if len(head) > 0: head = shortenDir(head & dirSep)
-  result = joinPath([getNimcacheDir(), changeFileExt(tail, ext)])
-  #echo "toGeneratedFile(", path, ", ", ext, ") = ", result
-
-when noTimeMachine:
-  var alreadyExcludedDirs = initSet[string]()
-  proc excludeDirFromTimeMachine(dir: string) {.raises: [].} =
-    ## Calls a macosx command on the directory to exclude it from backups.
-    ##
-    ## The macosx tmutil command is invoked to mark the specified path as an
-    ## item to be excluded from time machine backups. If a path already exists
-    ## with files before excluding it, newer files won't be added to the
-    ## directory, but previous files won't be removed from the backup until the
-    ## user deletes that directory.
-    ##
-    ## The whole proc is optional and will ignore all kinds of errors. The only
-    ## way to be sure that it works is to call ``tmutil isexcluded path``.
-    if alreadyExcludedDirs.contains(dir): return
-    alreadyExcludedDirs.incl(dir)
-    try:
-      var p = startProcess("/usr/bin/tmutil", args = ["addexclusion", dir])
-      discard p.waitForExit
-      p.close
-    except Exception:
-      discard
-
-proc completeGeneratedFilePath*(f: string, createSubDir: bool = true): string =
-  var (head, tail) = splitPath(f)
-  #if len(head) > 0: head = removeTrailingDirSep(shortenDir(head & dirSep))
-  var subdir = getNimcacheDir() # / head
+  let (head, tail) = splitPath(path.string)
+  result = getNimcacheDir(conf) / RelativeFile changeFileExt(tail, ext)
+
+proc completeGeneratedFilePath*(conf: ConfigRef; f: AbsoluteFile,
+                                createSubDir: bool = true): AbsoluteFile =
+  let (head, tail) = splitPath(f.string)
+  let subdir = getNimcacheDir(conf)
   if createSubDir:
     try:
-      createDir(subdir)
-      when noTimeMachine:
-        excludeDirFromTimeMachine(subdir)
+      createDir(subdir.string)
     except OSError:
-      writeLine(stdout, "cannot create directory: " & subdir)
+      writeLine(stdout, "cannot create directory: " & subdir.string)
       quit(1)
-  result = joinPath(subdir, tail)
+  result = subdir / RelativeFile tail
   #echo "completeGeneratedFilePath(", f, ") = ", result
 
-proc rawFindFile(f: string): string =
-  for it in searchPaths:
-    result = joinPath(it, f)
-    if existsFile(result):
-      return result.canonicalizePath
-  result = ""
-
-proc rawFindFile2(f: string): string =
-  for i, it in lazyPaths:
-    result = joinPath(it, f)
-    if existsFile(result):
+proc rawFindFile(conf: ConfigRef; f: RelativeFile; suppressStdlib: bool): AbsoluteFile =
+  for it in conf.searchPaths:
+    if suppressStdlib and it.string.startsWith(conf.libpath.string):
+      continue
+    result = it / f
+    if fileExists(result):
+      return canonicalizePath(conf, result)
+  result = AbsoluteFile""
+
+proc rawFindFile2(conf: ConfigRef; f: RelativeFile): AbsoluteFile =
+  for i, it in conf.lazyPaths:
+    result = it / f
+    if fileExists(result):
       # bring to front
       for j in countDown(i,1):
-        swap(lazyPaths[j], lazyPaths[j-1])
+        swap(conf.lazyPaths[j], conf.lazyPaths[j-1])
 
-      return result.canonicalizePath
-  result = ""
+      return canonicalizePath(conf, result)
+  result = AbsoluteFile""
 
-template patchModule() {.dirty.} =
-  if result.len > 0 and gModuleOverrides.len > 0:
-    let key = getPackageName(result) & "_" & splitFile(result).name
-    if gModuleOverrides.hasKey(key):
-      let ov = gModuleOverrides[key]
-      if ov.len > 0: result = ov
+template patchModule(conf: ConfigRef) {.dirty.} =
+  if not result.isEmpty and conf.moduleOverrides.len > 0:
+    let key = getPackageName(conf, result.string) & "_" & splitFile(result).name
+    if conf.moduleOverrides.hasKey(key):
+      let ov = conf.moduleOverrides[key]
+      if ov.len > 0: result = AbsoluteFile(ov)
 
-proc findFile*(f: string): string {.procvar.} =
+proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): AbsoluteFile {.procvar.} =
   if f.isAbsolute:
-    result = if f.existsFile: f else: ""
+    result = if f.existsFile: AbsoluteFile(f) else: AbsoluteFile""
   else:
-    result = f.rawFindFile
-    if result.len == 0:
-      result = f.toLowerAscii.rawFindFile
-      if result.len == 0:
-        result = f.rawFindFile2
-        if result.len == 0:
-          result = f.toLowerAscii.rawFindFile2
-  patchModule()
-
-proc findModule*(modulename, currentModule: string): string =
+    result = rawFindFile(conf, RelativeFile f, suppressStdlib)
+    if result.isEmpty:
+      result = rawFindFile(conf, RelativeFile f.toLowerAscii, suppressStdlib)
+      if result.isEmpty:
+        result = rawFindFile2(conf, RelativeFile f)
+        if result.isEmpty:
+          result = rawFindFile2(conf, RelativeFile f.toLowerAscii)
+  patchModule(conf)
+
+const stdlibDirs = [
+  "pure", "core", "arch",
+  "pure/collections",
+  "pure/concurrency", "impure",
+  "wrappers", "wrappers/linenoise",
+  "windows", "posix", "js"]
+
+proc findModule*(conf: ConfigRef; modulename, currentModule: string): AbsoluteFile =
   # returns path to module
-  when defined(nimfix):
-    # '.nimfix' modules are preferred over '.nim' modules so that specialized
-    # versions can be kept for 'nimfix'.
-    block:
-      let m = addFileExt(modulename, "nimfix")
-      let currentPath = currentModule.splitFile.dir
-      result = currentPath / m
-      if not existsFile(result):
-        result = findFile(m)
-        if existsFile(result): return result
-  let m = addFileExt(modulename, NimExt)
-  let currentPath = currentModule.splitFile.dir
-  result = currentPath / m
-  if not existsFile(result):
-    result = findFile(m)
-  patchModule()
-
-proc findProjectNimFile*(pkg: string): string =
+  const pkgPrefix = "pkg/"
+  const stdPrefix = "std/"
+  var m = addFileExt(modulename, NimExt)
+  if m.startsWith(pkgPrefix):
+    result = findFile(conf, m.substr(pkgPrefix.len), suppressStdlib = true)
+  else:
+    if m.startsWith(stdPrefix):
+      let stripped = m.substr(stdPrefix.len)
+      for candidate in stdlibDirs:
+        let path = (conf.libpath.string / candidate / stripped)
+        if fileExists(path):
+          m = path
+          break
+    let currentPath = currentModule.splitFile.dir
+    result = AbsoluteFile currentPath / m
+    if not fileExists(result):
+      result = findFile(conf, m)
+  patchModule(conf)
+
+proc findProjectNimFile*(conf: ConfigRef; pkg: string): string =
   const extensions = [".nims", ".cfg", ".nimcfg", ".nimble"]
   var candidates: seq[string] = @[]
-  for k, f in os.walkDir(pkg, relative=true):
-    if k == pcFile and f != "config.nims":
-      let (_, name, ext) = splitFile(f)
-      if ext in extensions:
-        let x = changeFileExt(pkg / name, ".nim")
-        if fileExists(x):
-          candidates.add x
-  for c in candidates:
-    # nim-foo foo  or  foo  nfoo
-    if (pkg in c) or (c in pkg): return c
-  if candidates.len >= 1:
-    return candidates[0]
+  var dir = pkg
+  while true:
+    for k, f in os.walkDir(dir, relative=true):
+      if k == pcFile and f != "config.nims":
+        let (_, name, ext) = splitFile(f)
+        if ext in extensions:
+          let x = changeFileExt(dir / name, ".nim")
+          if fileExists(x):
+            candidates.add x
+    for c in candidates:
+      # nim-foo foo  or  foo  nfoo
+      if (pkg in c) or (c in pkg): return c
+    if candidates.len >= 1:
+      return candidates[0]
+    dir = parentDir(dir)
+    if dir == "": break
   return ""
 
 proc canonDynlibName(s: string): string =
@@ -417,25 +662,12 @@ proc canonDynlibName(s: string): string =
   else:
     result = s.substr(start)
 
-proc inclDynlibOverride*(lib: string) =
-  gDllOverrides[lib.canonDynlibName] = "true"
-
-proc isDynlibOverride*(lib: string): bool =
-  result = gDllOverrides.hasKey(lib.canonDynlibName)
-
-proc binaryStrSearch*(x: openArray[string], y: string): int =
-  var a = 0
-  var b = len(x) - 1
-  while a <= b:
-    var mid = (a + b) div 2
-    var c = cmpIgnoreCase(x[mid], y)
-    if c < 0:
-      a = mid + 1
-    elif c > 0:
-      b = mid - 1
-    else:
-      return mid
-  result = - 1
+proc inclDynlibOverride*(conf: ConfigRef; lib: string) =
+  conf.dllOverrides[lib.canonDynlibName] = "true"
+
+proc isDynlibOverride*(conf: ConfigRef; lib: string): bool =
+  result = optDynlibOverrideAll in conf.globalOptions or
+     conf.dllOverrides.hasKey(lib.canonDynlibName)
 
 proc parseIdeCmd*(s: string): IdeCmd =
   case s:
diff --git a/compiler/packagehandling.nim b/compiler/packagehandling.nim
index 758411e39..f94c3d72c 100644
--- a/compiler/packagehandling.nim
+++ b/compiler/packagehandling.nim
@@ -15,23 +15,16 @@ iterator myParentDirs(p: string): string =
     if current.len == 0: break
     yield current
 
-template newPackageCache(): untyped =
-  newStringTable(when FileSystemCaseSensitive:
-                   modeCaseInsensitive
-                 else:
-                   modeCaseSensitive)
+proc resetPackageCache*(conf: ConfigRef) =
+  conf.packageCache = newPackageCache()
 
-var packageCache = newPackageCache()
-
-proc resetPackageCache*() = packageCache = newPackageCache()
-
-proc getPackageName*(path: string): string =
+proc getPackageName*(conf: ConfigRef; path: string): string =
   var parents = 0
   block packageSearch:
     for d in myParentDirs(path):
-      if packageCache.hasKey(d):
+      if conf.packageCache.hasKey(d):
         #echo "from cache ", d, " |", packageCache[d], "|", path.splitFile.name
-        return packageCache[d]
+        return conf.packageCache[d]
       inc parents
       for file in walkFiles(d / "*.nimble"):
         result = file.splitFile.name
@@ -40,17 +33,18 @@ proc getPackageName*(path: string): string =
         result = file.splitFile.name
         break packageSearch
   # we also store if we didn't find anything:
-  if result.isNil: result = ""
+  when not defined(nimNoNilSeqs):
+    if result.isNil: result = ""
   for d in myParentDirs(path):
     #echo "set cache ", d, " |", result, "|", parents
-    packageCache[d] = result
+    conf.packageCache[d] = result
     dec parents
     if parents <= 0: break
 
-proc withPackageName*(path: string): string =
-  let x = path.getPackageName
+proc withPackageName*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile =
+  let x = getPackageName(conf, path.string)
   if x.len == 0:
     result = path
   else:
     let (p, file, ext) = path.splitFile
-    result = (p / (x & '_' & file)) & ext
+    result = p / RelativeFile((x & '_' & file) & ext)
diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim
index c51d406ac..bbaf7a069 100644
--- a/compiler/parampatterns.nim
+++ b/compiler/parampatterns.nim
@@ -10,7 +10,8 @@
 ## This module implements the pattern matching features for term rewriting
 ## macro support.
 
-import strutils, ast, astalgo, types, msgs, idents, renderer, wordrecg, trees
+import strutils, ast, astalgo, types, msgs, idents, renderer, wordrecg, trees,
+  options
 
 # we precompile the pattern here for efficiency into some internal
 # stack based VM :-) Why? Because it's fun; I did no benchmarks to see if that
@@ -41,8 +42,8 @@ type
 const
   MaxStackSize* = 64 ## max required stack size by the VM
 
-proc patternError(n: PNode) =
-  localError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))
+proc patternError(n: PNode; conf: ConfigRef) =
+  localError(conf, n.info, "illformed AST: " & renderTree(n, {renderNoComments}))
 
 proc add(code: var TPatternCode, op: TOpcode) {.inline.} =
   add(code, chr(ord(op)))
@@ -53,42 +54,42 @@ proc whichAlias*(p: PSym): TAliasRequest =
   else:
     result = aqNone
 
-proc compileConstraints(p: PNode, result: var TPatternCode) =
+proc compileConstraints(p: PNode, result: var TPatternCode; conf: ConfigRef) =
   case p.kind
   of nkCallKinds:
     if p.sons[0].kind != nkIdent:
-      patternError(p.sons[0])
+      patternError(p.sons[0], conf)
       return
     let op = p.sons[0].ident
     if p.len == 3:
       if op.s == "|" or op.id == ord(wOr):
-        compileConstraints(p.sons[1], result)
-        compileConstraints(p.sons[2], result)
+        compileConstraints(p.sons[1], result, conf)
+        compileConstraints(p.sons[2], result, conf)
         result.add(ppOr)
       elif op.s == "&" or op.id == ord(wAnd):
-        compileConstraints(p.sons[1], result)
-        compileConstraints(p.sons[2], result)
+        compileConstraints(p.sons[1], result, conf)
+        compileConstraints(p.sons[2], result, conf)
         result.add(ppAnd)
       else:
-        patternError(p)
+        patternError(p, conf)
     elif p.len == 2 and (op.s == "~" or op.id == ord(wNot)):
-      compileConstraints(p.sons[1], result)
+      compileConstraints(p.sons[1], result, conf)
       result.add(ppNot)
     else:
-      patternError(p)
+      patternError(p, conf)
   of nkAccQuoted, nkPar:
     if p.len == 1:
-      compileConstraints(p.sons[0], result)
+      compileConstraints(p.sons[0], result, conf)
     else:
-      patternError(p)
+      patternError(p, conf)
   of nkIdent:
     let spec = p.ident.s.normalize
     case spec
-    of "atom":  result.add(ppAtom)
-    of "lit":   result.add(ppLit)
-    of "sym":   result.add(ppSym)
+    of "atom": result.add(ppAtom)
+    of "lit": result.add(ppLit)
+    of "sym": result.add(ppSym)
     of "ident": result.add(ppIdent)
-    of "call":  result.add(ppCall)
+    of "call": result.add(ppCall)
     of "alias": result[0] = chr(aqShouldAlias.ord)
     of "noalias": result[0] = chr(aqNoAlias.ord)
     of "lvalue": result.add(ppLValue)
@@ -97,24 +98,24 @@ proc compileConstraints(p: PNode, result: var TPatternCode) =
     of "nosideeffect": result.add(ppNoSideEffect)
     else:
       # check all symkinds:
-      internalAssert int(high(TSymKind)) < 255
+      internalAssert conf, int(high(TSymKind)) < 255
       for i in low(TSymKind)..high(TSymKind):
         if cmpIgnoreStyle(($i).substr(2), spec) == 0:
           result.add(ppSymKind)
           result.add(chr(i.ord))
           return
       # check all nodekinds:
-      internalAssert int(high(TNodeKind)) < 255
+      internalAssert conf, int(high(TNodeKind)) < 255
       for i in low(TNodeKind)..high(TNodeKind):
         if cmpIgnoreStyle($i, spec) == 0:
           result.add(ppNodeKind)
           result.add(chr(i.ord))
           return
-      patternError(p)
+      patternError(p, conf)
   else:
-    patternError(p)
+    patternError(p, conf)
 
-proc semNodeKindConstraints*(p: PNode): PNode =
+proc semNodeKindConstraints*(p: PNode; conf: ConfigRef): PNode =
   ## does semantic checking for a node kind pattern and compiles it into an
   ## efficient internal format.
   assert p.kind == nkCurlyExpr
@@ -122,12 +123,12 @@ proc semNodeKindConstraints*(p: PNode): PNode =
   result.strVal = newStringOfCap(10)
   result.strVal.add(chr(aqNone.ord))
   if p.len >= 2:
-    for i in 1.. <p.len:
-      compileConstraints(p.sons[i], result.strVal)
+    for i in 1..<p.len:
+      compileConstraints(p.sons[i], result.strVal, conf)
     if result.strVal.len > MaxStackSize-1:
-      internalError(p.info, "parameter pattern too complex")
+      internalError(conf, p.info, "parameter pattern too complex")
   else:
-    patternError(p)
+    patternError(p, conf)
   result.strVal.add(ppEof)
 
 type
@@ -152,7 +153,7 @@ proc checkForSideEffects*(n: PNode): TSideEffectAnalysis =
       # indirect call: assume side effect:
       return seSideEffect
     # we need to check n[0] too: (FwithSideEffectButReturnsProcWithout)(args)
-    for i in 0 .. <n.len:
+    for i in 0 ..< n.len:
       let ret = checkForSideEffects(n.sons[i])
       if ret == seSideEffect: return ret
       elif ret == seUnknown and result == seNoSideEffect:
@@ -163,7 +164,7 @@ proc checkForSideEffects*(n: PNode): TSideEffectAnalysis =
   else:
     # assume no side effect:
     result = seNoSideEffect
-    for i in 0 .. <n.len:
+    for i in 0 ..< n.len:
       let ret = checkForSideEffects(n.sons[i])
       if ret == seSideEffect: return ret
       elif ret == seUnknown and result == seNoSideEffect:
@@ -178,6 +179,36 @@ type
     arDiscriminant,           # is a discriminant
     arStrange                 # it is a strange beast like 'typedesc[var T]'
 
+proc exprRoot*(n: PNode): PSym =
+  var it = n
+  while true:
+    case it.kind
+    of nkSym: return it.sym
+    of nkHiddenDeref, nkDerefExpr:
+      if it[0].typ.skipTypes(abstractInst).kind in {tyPtr, tyRef}:
+        # 'ptr' is unsafe anyway and 'ref' is always on the heap,
+        # so allow these derefs:
+        break
+      else:
+        it = it[0]
+    of nkDotExpr, nkBracketExpr, nkHiddenAddr,
+       nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
+      it = it[0]
+    of nkHiddenStdConv, nkHiddenSubConv, nkConv:
+      it = it[1]
+    of nkStmtList, nkStmtListExpr:
+      if it.len > 0 and it.typ != nil: it = it.lastSon
+      else: break
+    of nkCallKinds:
+      if it.typ != nil and it.typ.kind == tyVar and it.len > 1:
+        # See RFC #7373, calls returning 'var T' are assumed to
+        # return a view into the first argument (if there is one):
+        it = it[1]
+      else:
+        break
+    else:
+      break
+
 proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult =
   ## 'owner' can be nil!
   result = arNone
@@ -186,10 +217,10 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult
     if n.typ != nil and n.typ.kind == tyVar:
       result = arLValue
   of nkSym:
-    let kinds = if isUnsafeAddr: {skVar, skResult, skTemp, skParam, skLet}
+    let kinds = if isUnsafeAddr: {skVar, skResult, skTemp, skParam, skLet, skForVar}
                 else: {skVar, skResult, skTemp}
     if n.sym.kind in kinds:
-      if owner != nil and owner.id == n.sym.owner.id and
+      if owner != nil and owner == n.sym.owner and
           sfGlobal notin n.sym.flags:
         result = arLocalLValue
       else:
@@ -205,7 +236,8 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult
       result = arLValue
     else:
       result = isAssignable(owner, n.sons[0], isUnsafeAddr)
-    if result != arNone and sfDiscriminant in n.sons[1].sym.flags:
+    if result != arNone and n[1].kind == nkSym and
+        sfDiscriminant in n[1].sym.flags:
       result = arDiscriminant
   of nkBracketExpr:
     if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind in
@@ -222,7 +254,10 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult
     elif compareTypes(n.typ, n.sons[1].typ, dcEqIgnoreDistinct):
       # types that are equal modulo distinction preserve l-value:
       result = isAssignable(owner, n.sons[1], isUnsafeAddr)
-  of nkHiddenDeref, nkDerefExpr, nkHiddenAddr:
+  of nkHiddenDeref:
+    if n[0].typ.kind == tyLent: result = arDiscriminant
+    else: result = arLValue
+  of nkDerefExpr, nkHiddenAddr:
     result = arLValue
   of nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr:
     result = isAssignable(owner, n.sons[0], isUnsafeAddr)
@@ -235,6 +270,13 @@ proc isAssignable*(owner: PSym, n: PNode; isUnsafeAddr=false): TAssignableResult
   of nkStmtList, nkStmtListExpr:
     if n.typ != nil:
       result = isAssignable(owner, n.lastSon, isUnsafeAddr)
+  of nkVarTy:
+    # XXX: The fact that this is here is a bit of a hack.
+    # The goal is to allow the use of checks such as "foo(var T)"
+    # within concepts. Semantically, it's not correct to say that
+    # nkVarTy denotes an lvalue, but the example above is the only
+    # possible code which will get us here
+    result = arLValue
   else:
     discard
 
diff --git a/compiler/parser.nim b/compiler/parser.nim
index 362a5c286..54b360a24 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -17,6 +17,8 @@
 
 # In fact the grammar is generated from this file:
 when isMainModule:
+  # Leave a note in grammar.txt that it is generated:
+  #| # This file is generated by compiler/parser.nim.
   import pegs
   var outp = open("doc/grammar.txt", fmWrite)
   for line in lines("compiler/parser.nim"):
@@ -25,19 +27,31 @@ when isMainModule:
   outp.close
 
 import
-  llstream, lexer, idents, strutils, ast, astalgo, msgs
+  llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos,
+  pathutils
+
+when defined(nimpretty2):
+  import layouter
 
 type
-  TParser*{.final.} = object   # A TParser object represents a file that
+  TParser* = object            # A TParser object represents a file that
                                # is being parsed
     currInd: int               # current indentation level
-    firstTok, strongSpaces: bool # Has the first token been read?
-                                 # Is strongSpaces on?
+    firstTok: bool             # Has the first token been read?
     hasProgress: bool          # some while loop requires progress ensurance
     lex*: TLexer               # The lexer that is used for parsing
     tok*: TToken               # The current token
     inPragma*: int             # Pragma level
     inSemiStmtList*: int
+    emptyNode: PNode
+    when defined(nimpretty2):
+      em*: Emitter
+
+  SymbolMode = enum
+    smNormal, smAllowNil, smAfterDot
+
+  TPrimaryMode = enum
+    pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix
 
 proc parseAll*(p: var TParser): PNode
 proc closeParser*(p: var TParser)
@@ -62,10 +76,17 @@ proc optPar*(p: var TParser)
 proc optInd*(p: var TParser, n: PNode)
 proc indAndComment*(p: var TParser, n: PNode)
 proc setBaseFlags*(n: PNode, base: TNumericalBase)
-proc parseSymbol*(p: var TParser, allowNil = false): PNode
+proc parseSymbol*(p: var TParser, mode = smNormal): PNode
 proc parseTry(p: var TParser; isExpr: bool): PNode
 proc parseCase(p: var TParser): PNode
 proc parseStmtPragma(p: var TParser): PNode
+proc parsePragma(p: var TParser): PNode
+proc postExprBlocks(p: var TParser, x: PNode): PNode
+proc parseExprStmt(p: var TParser): PNode
+proc parseBlock(p: var TParser): PNode
+proc primary(p: var TParser, mode: TPrimaryMode): PNode
+proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode
+
 # implementation
 
 proc getTok(p: var TParser) =
@@ -73,34 +94,45 @@ proc getTok(p: var TParser) =
   ## `tok` member.
   rawGetTok(p.lex, p.tok)
   p.hasProgress = true
-
-proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream,
-                 cache: IdentCache;
-                 strongSpaces=false) =
+  when defined(nimpretty2):
+    emitTok(p.em, p.lex, p.tok)
+    while p.tok.tokType == tkComment:
+      rawGetTok(p.lex, p.tok)
+      emitTok(p.em, p.lex, p.tok)
+
+proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream,
+                 cache: IdentCache; config: ConfigRef) =
   ## Open a parser, using the given arguments to set up its internal state.
   ##
   initToken(p.tok)
-  openLexer(p.lex, fileIdx, inputStream, cache)
+  openLexer(p.lex, fileIdx, inputStream, cache, config)
+  when defined(nimpretty2):
+    openEmitter(p.em, cache, config, fileIdx)
   getTok(p)                   # read the first token
   p.firstTok = true
-  p.strongSpaces = strongSpaces
+  p.emptyNode = newNode(nkEmpty)
 
-proc openParser*(p: var TParser, filename: string, inputStream: PLLStream,
-                 cache: IdentCache;
-                 strongSpaces=false) =
-  openParser(p, filename.fileInfoIdx, inputStream, cache, strongSpaces)
+proc openParser*(p: var TParser, filename: AbsoluteFile, inputStream: PLLStream,
+                 cache: IdentCache; config: ConfigRef) =
+  openParser(p, fileInfoIdx(config, filename), inputStream, cache, config)
 
 proc closeParser(p: var TParser) =
   ## Close a parser, freeing up its resources.
   closeLexer(p.lex)
+  when defined(nimpretty2):
+    closeEmitter(p.em)
 
 proc parMessage(p: TParser, msg: TMsgKind, arg = "") =
   ## Produce and emit the parser message `arg` to output.
   lexMessageTok(p.lex, msg, p.tok, arg)
 
-proc parMessage(p: TParser, msg: TMsgKind, tok: TToken) =
+proc parMessage(p: TParser, msg: string, tok: TToken) =
   ## Produce and emit a parser message to output about the token `tok`
-  parMessage(p, msg, prettyTok(tok))
+  parMessage(p, errGenerated, msg % prettyTok(tok))
+
+proc parMessage(p: TParser, arg: string) =
+  ## Produce and emit the parser message `arg` to output.
+  lexMessageTok(p.lex, errGenerated, p.tok, arg)
 
 template withInd(p, body: untyped) =
   let oldInd = p.currInd
@@ -115,8 +147,15 @@ template sameOrNoInd(p): bool = p.tok.indent == p.currInd or p.tok.indent < 0
 proc rawSkipComment(p: var TParser, node: PNode) =
   if p.tok.tokType == tkComment:
     if node != nil:
-      if node.comment == nil: node.comment = ""
-      add(node.comment, p.tok.literal)
+      when not defined(nimNoNilSeqs):
+        if node.comment == nil: node.comment = ""
+      when defined(nimpretty):
+        if p.tok.commentOffsetB > p.tok.commentOffsetA:
+          add node.comment, fileSection(p.lex.config, p.lex.fileIdx, p.tok.commentOffsetA, p.tok.commentOffsetB)
+        else:
+          add node.comment, p.tok.literal
+      else:
+        add(node.comment, p.tok.literal)
     else:
       parMessage(p, errInternal, "skipComment")
     getTok(p)
@@ -127,6 +166,12 @@ proc skipComment(p: var TParser, node: PNode) =
 proc flexComment(p: var TParser, node: PNode) =
   if p.tok.indent < 0 or realInd(p): rawSkipComment(p, node)
 
+const
+  errInvalidIndentation = "invalid indentation"
+  errIdentifierExpected = "identifier expected, but got '$1'"
+  errExprExpected = "expression expected, but found '$1'"
+  errTokenExpected = "'$1' expected"
+
 proc skipInd(p: var TParser) =
   if p.tok.indent >= 0:
     if not realInd(p): parMessage(p, errInvalidIndentation)
@@ -145,11 +190,11 @@ proc getTokNoInd(p: var TParser) =
 
 proc expectIdentOrKeyw(p: TParser) =
   if p.tok.tokType != tkSymbol and not isKeyword(p.tok.tokType):
-    lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok))
+    lexMessage(p.lex, errGenerated, errIdentifierExpected % prettyTok(p.tok))
 
 proc expectIdent(p: TParser) =
   if p.tok.tokType != tkSymbol:
-    lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok))
+    lexMessage(p.lex, errGenerated, errIdentifierExpected % prettyTok(p.tok))
 
 proc eat(p: var TParser, tokType: TTokType) =
   ## Move the parser to the next token if the current token is of type
@@ -157,7 +202,8 @@ proc eat(p: var TParser, tokType: TTokType) =
   if p.tok.tokType == tokType:
     getTok(p)
   else:
-    lexMessageTok(p.lex, errTokenExpected, p.tok, TokTypeToStr[tokType])
+    lexMessage(p.lex, errGenerated,
+      "expected: '" & TokTypeToStr[tokType] & "', but got: '" & prettyTok(p.tok) & "'")
 
 proc parLineInfo(p: TParser): TLineInfo =
   ## Retrieve the line information associated with the parser's current state.
@@ -193,7 +239,6 @@ proc newIdentNodeP(ident: PIdent, p: TParser): PNode =
 proc parseExpr(p: var TParser): PNode
 proc parseStmt(p: var TParser): PNode
 proc parseTypeDesc(p: var TParser): PNode
-proc parseDoBlocks(p: var TParser, call: PNode)
 proc parseParamList(p: var TParser, retColon = true): PNode
 
 proc isSigilLike(tok: TToken): bool {.inline.} =
@@ -204,41 +249,6 @@ proc isRightAssociative(tok: TToken): bool {.inline.} =
   result = tok.tokType == tkOpr and tok.ident.s[0] == '^'
   # or (let L = tok.ident.s.len; L > 1 and tok.ident.s[L-1] == '>'))
 
-proc getPrecedence(tok: TToken, strongSpaces: bool): int =
-  ## Calculates the precedence of the given token.
-  template considerStrongSpaces(x): untyped =
-    x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0)
-
-  case tok.tokType
-  of tkOpr:
-    let L = tok.ident.s.len
-    let relevantChar = tok.ident.s[0]
-
-    # arrow like?
-    if L > 1 and tok.ident.s[L-1] == '>' and
-      tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1)
-
-    template considerAsgn(value: untyped) =
-      result = if tok.ident.s[L-1] == '=': 1 else: value
-
-    case relevantChar
-    of '$', '^': considerAsgn(10)
-    of '*', '%', '/', '\\': considerAsgn(9)
-    of '~': result = 8
-    of '+', '-', '|': considerAsgn(8)
-    of '&': considerAsgn(7)
-    of '=', '<', '>', '!': result = 5
-    of '.': considerAsgn(6)
-    of '?': result = 2
-    else: considerAsgn(2)
-  of tkDiv, tkMod, tkShl, tkShr: result = 9
-  of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5
-  of tkDotDot: result = 6
-  of tkAnd: result = 4
-  of tkOr, tkXor, tkPtr, tkRef: result = 3
-  else: return -10
-  result = considerStrongSpaces(result)
-
 proc isOperator(tok: TToken): bool =
   ## Determines if the given token is an operator type token.
   tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
@@ -250,24 +260,13 @@ proc isUnary(p: TParser): bool =
      p.tok.strongSpaceB == 0 and
      p.tok.strongSpaceA > 0:
       result = true
-      # versions prior to 0.13.0 used to do this:
-      when false:
-        if p.strongSpaces:
-          result = true
-        else:
-          parMessage(p, warnDeprecated,
-            "will be parsed as unary operator; inconsistent spacing")
 
 proc checkBinary(p: TParser) {.inline.} =
   ## Check if the current parser token is a binary operator.
   # we don't check '..' here as that's too annoying
-  if p.strongSpaces and p.tok.tokType == tkOpr:
+  if p.tok.tokType == tkOpr:
     if p.tok.strongSpaceB > 0 and p.tok.strongSpaceA != p.tok.strongSpaceB:
-      parMessage(p, errGenerated,
-                 "Number of spaces around '$#' not consistent" %
-                 prettyTok(p.tok))
-    elif p.tok.strongSpaceA notin {0,1,2,4,8}:
-      parMessage(p, errGenerated, "Number of spaces must be 0,1,2,4 or 8")
+      parMessage(p, warnInconsistentSpacing, prettyTok(p.tok))
 
 #| module = stmt ^* (';' / IND{=})
 #|
@@ -283,10 +282,10 @@ proc checkBinary(p: TParser) {.inline.} =
 #|
 #| prefixOperator = operator
 #|
-#| optInd = COMMENT?
+#| optInd = COMMENT? IND?
 #| optPar = (IND{>} | IND{=})?
 #|
-#| simpleExpr = arrowExpr (OP0 optInd arrowExpr)*
+#| simpleExpr = arrowExpr (OP0 optInd arrowExpr)* pragma?
 #| arrowExpr = assignExpr (OP1 optInd assignExpr)*
 #| assignExpr = orExpr (OP2 optInd orExpr)*
 #| orExpr = andExpr (OP3 optInd andExpr)*
@@ -302,13 +301,26 @@ proc colcom(p: var TParser, n: PNode) =
   eat(p, tkColon)
   skipComment(p, n)
 
-proc parseSymbol(p: var TParser, allowNil = false): PNode =
+const tkBuiltInMagics = {tkType, tkStatic, tkAddr}
+
+proc parseSymbol(p: var TParser, mode = smNormal): PNode =
   #| symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`'
-  #|        | IDENT | 'addr' | 'type'
+  #|        | IDENT | KEYW
   case p.tok.tokType
-  of tkSymbol, tkAddr, tkType:
+  of tkSymbol:
     result = newIdentNodeP(p.tok.ident, p)
     getTok(p)
+  of tokKeywordLow..tokKeywordHigh:
+    if p.tok.tokType in tkBuiltInMagics or mode == smAfterDot:
+      # for backwards compatibility these 2 are always valid:
+      result = newIdentNodeP(p.tok.ident, p)
+      getTok(p)
+    elif p.tok.tokType == tkNil and mode == smAllowNil:
+      result = newNodeP(nkNilLit, p)
+      getTok(p)
+    else:
+      parMessage(p, errIdentifierExpected, p.tok)
+      result = p.emptyNode
   of tkAccent:
     result = newNodeP(nkAccQuoted, p)
     getTok(p)
@@ -334,16 +346,12 @@ proc parseSymbol(p: var TParser, allowNil = false): PNode =
         break
     eat(p, tkAccent)
   else:
-    if allowNil and p.tok.tokType == tkNil:
-      result = newNodeP(nkNilLit, p)
-      getTok(p)
-    else:
-      parMessage(p, errIdentifierExpected, p.tok)
-      # BUGFIX: We must consume a token here to prevent endless loops!
-      # But: this really sucks for idetools and keywords, so we don't do it
-      # if it is a keyword:
-      #if not isKeyword(p.tok.tokType): getTok(p)
-      result = ast.emptyNode
+    parMessage(p, errIdentifierExpected, p.tok)
+    # BUGFIX: We must consume a token here to prevent endless loops!
+    # But: this really sucks for idetools and keywords, so we don't do it
+    # if it is a keyword:
+    #if not isKeyword(p.tok.tokType): getTok(p)
+    result = p.emptyNode
 
 proc colonOrEquals(p: var TParser, a: PNode): PNode =
   if p.tok.tokType == tkColon:
@@ -364,10 +372,15 @@ proc colonOrEquals(p: var TParser, a: PNode): PNode =
 proc exprColonEqExpr(p: var TParser): PNode =
   #| exprColonEqExpr = expr (':'|'=' expr)?
   var a = parseExpr(p)
-  result = colonOrEquals(p, a)
+  if p.tok.tokType == tkDo:
+    result = postExprBlocks(p, a)
+  else:
+    result = colonOrEquals(p, a)
 
 proc exprList(p: var TParser, endTok: TTokType, result: PNode) =
   #| exprList = expr ^+ comma
+  when defined(nimpretty2):
+    inc p.em.doIndentMore
   getTok(p)
   optInd(p, result)
   # progress guaranteed
@@ -377,20 +390,8 @@ proc exprList(p: var TParser, endTok: TTokType, result: PNode) =
     if p.tok.tokType != tkComma: break
     getTok(p)
     optInd(p, a)
-
-proc dotExpr(p: var TParser, a: PNode): PNode =
-  #| dotExpr = expr '.' optInd symbol
-  var info = p.parLineInfo
-  getTok(p)
-  result = newNodeI(nkDotExpr, info)
-  optInd(p, result)
-  addSon(result, a)
-  addSon(result, parseSymbol(p))
-
-proc qualifiedIdent(p: var TParser): PNode =
-  #| qualifiedIdent = symbol ('.' optInd symbol)?
-  result = parseSymbol(p)
-  if p.tok.tokType == tkDot: result = dotExpr(p, result)
+  when defined(nimpretty2):
+    dec p.em.doIndentMore
 
 proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) =
   assert(endTok in {tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi})
@@ -402,6 +403,9 @@ proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) =
     addSon(result, a)
     if p.tok.tokType != tkComma: break
     getTok(p)
+    # (1,) produces a tuple expression
+    if endTok == tkParRi and p.tok.tokType == tkParRi and result.kind == nkPar:
+      result.kind = nkTupleConstr
     skipComment(p, a)
   optPar(p)
   eat(p, endTok)
@@ -412,6 +416,33 @@ proc exprColonEqExprList(p: var TParser, kind: TNodeKind,
   result = newNodeP(kind, p)
   exprColonEqExprListAux(p, endTok, result)
 
+proc dotExpr(p: var TParser, a: PNode): PNode =
+  #| dotExpr = expr '.' optInd (symbol | '[:' exprList ']')
+  #| explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )?
+  var info = p.parLineInfo
+  getTok(p)
+  result = newNodeI(nkDotExpr, info)
+  optInd(p, result)
+  addSon(result, a)
+  addSon(result, parseSymbol(p, smAfterDot))
+  if p.tok.tokType == tkBracketLeColon and p.tok.strongSpaceA <= 0:
+    var x = newNodeI(nkBracketExpr, p.parLineInfo)
+    # rewrite 'x.y[:z]()' to 'y[z](x)'
+    x.add result[1]
+    exprList(p, tkBracketRi, x)
+    eat(p, tkBracketRi)
+    var y = newNodeI(nkCall, p.parLineInfo)
+    y.add x
+    y.add result[0]
+    if p.tok.tokType == tkParLe and p.tok.strongSpaceA <= 0:
+      exprColonEqExprListAux(p, tkParRi, y)
+    result = y
+
+proc qualifiedIdent(p: var TParser): PNode =
+  #| qualifiedIdent = symbol ('.' optInd symbol)?
+  result = parseSymbol(p)
+  if p.tok.tokType == tkDot: result = dotExpr(p, result)
+
 proc setOrTableConstr(p: var TParser): PNode =
   #| setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}'
   result = newNodeP(nkCurly, p)
@@ -469,9 +500,6 @@ proc parseGStrLit(p: var TParser, a: PNode): PNode =
   else:
     result = a
 
-type
-  TPrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix
-
 proc complexOrSimpleStmt(p: var TParser): PNode
 proc simpleExpr(p: var TParser, mode = pmNormal): PNode
 
@@ -503,9 +531,10 @@ proc parsePar(p: var TParser): PNode =
   result = newNodeP(nkPar, p)
   getTok(p)
   optInd(p, result)
+  flexComment(p, result)
   if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase,
                        tkTry, tkDefer, tkFinally, tkExcept, tkFor, tkBlock,
-                       tkConst, tkLet, tkWhen, tkVar,
+                       tkConst, tkLet, tkWhen, tkVar, tkFor,
                        tkMixin}:
     # XXX 'bind' used to be an expression, so we exclude it here;
     # tests/reject/tbind2 fails otherwise.
@@ -519,7 +548,9 @@ proc parsePar(p: var TParser): PNode =
     result.add(parseStmtPragma(p))
   elif p.tok.tokType != tkParRi:
     var a = simpleExpr(p)
-    if p.tok.tokType == tkEquals:
+    if p.tok.tokType == tkDo:
+      result = postExprBlocks(p, a)
+    elif p.tok.tokType == tkEquals:
       # special case: allow assignments
       let asgn = newNodeP(nkAsgn, p)
       getTok(p)
@@ -540,6 +571,9 @@ proc parsePar(p: var TParser): PNode =
       if p.tok.tokType == tkComma:
         getTok(p)
         skipComment(p, a)
+        # (1,) produces a tuple expression:
+        if p.tok.tokType == tkParRi:
+          result.kind = nkTupleConstr
         # progress guaranteed
         while p.tok.tokType != tkParRi and p.tok.tokType != tkEof:
           var a = exprColonEqExpr(p)
@@ -564,7 +598,7 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode =
   #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')'
   #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']'
   case p.tok.tokType
-  of tkSymbol, tkType, tkAddr:
+  of tkSymbol, tkBuiltInMagics, tkOut:
     result = newIdentNodeP(p.tok.ident, p)
     getTok(p)
     result = parseGStrLit(p, result)
@@ -658,7 +692,7 @@ proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode =
   else:
     parMessage(p, errExprExpected, p.tok)
     getTok(p)  # we must consume a token here to prevend endless loops!
-    result = ast.emptyNode
+    result = p.emptyNode
 
 proc namedParams(p: var TParser, callee: PNode,
                  kind: TNodeKind, endTok: TTokType): PNode =
@@ -668,8 +702,27 @@ proc namedParams(p: var TParser, callee: PNode,
   # progress guaranteed
   exprColonEqExprListAux(p, endTok, result)
 
-proc parseMacroColon(p: var TParser, x: PNode): PNode
-proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode =
+proc commandParam(p: var TParser, isFirstParam: var bool; mode: TPrimaryMode): PNode =
+  if mode == pmTypeDesc:
+    result = simpleExpr(p, mode)
+  else:
+    result = parseExpr(p)
+  if p.tok.tokType == tkDo:
+    result = postExprBlocks(p, result)
+  elif p.tok.tokType == tkEquals and not isFirstParam:
+    let lhs = result
+    result = newNodeP(nkExprEqExpr, p)
+    getTok(p)
+    addSon(result, lhs)
+    addSon(result, parseExpr(p))
+  isFirstParam = false
+
+const
+  tkTypeClasses = {tkRef, tkPtr, tkVar, tkStatic, tkType,
+                   tkEnum, tkTuple, tkObject, tkProc}
+
+proc primarySuffix(p: var TParser, r: PNode,
+                   baseIndent: int, mode: TPrimaryMode): PNode =
   #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks?
   #|       | doBlocks
   #|       | '.' optInd symbol generalizedLit?
@@ -679,30 +732,24 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode =
   result = r
 
   template somePar() =
-    if p.tok.strongSpaceA > 0:
-      if p.strongSpaces:
-        break
-      else:
-        parMessage(p, warnDeprecated,
-          "a [b] will be parsed as command syntax; spacing")
+    if p.tok.strongSpaceA > 0: break
   # progress guaranteed
   while p.tok.indent < 0 or
        (p.tok.tokType == tkDot and p.tok.indent >= baseIndent):
     case p.tok.tokType
     of tkParLe:
       # progress guaranteed
-      somePar()
+      if p.tok.strongSpaceA > 0:
+        # inside type sections, expressions such as `ref (int, bar)`
+        # are parsed as a nkCommand with a single tuple argument (nkPar)
+        if mode == pmTypeDef:
+          result = newNodeP(nkCommand, p)
+          result.addSon r
+          result.addSon primary(p, pmNormal)
+        break
       result = namedParams(p, result, nkCall, tkParRi)
       if result.len > 1 and result.sons[1].kind == nkExprColonExpr:
         result.kind = nkObjConstr
-      else:
-        parseDoBlocks(p, result)
-    of tkDo:
-      # progress guaranteed
-      var a = result
-      result = newNodeP(nkCall, p)
-      addSon(result, a)
-      parseDoBlocks(p, result)
     of tkDot:
       # progress guaranteed
       result = dotExpr(p, result)
@@ -715,17 +762,24 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode =
       # progress guaranteed
       somePar()
       result = namedParams(p, result, nkCurlyExpr, tkCurlyRi)
-    of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, tkAddr, tkType:
-      if p.inPragma == 0:
+    of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast,
+       tkOpr, tkDotDot, tkTypeClasses - {tkRef, tkPtr}:
+        # XXX: In type sections we allow the free application of the
+        # command syntax, with the exception of expressions such as
+        # `foo ref` or `foo ptr`. Unfortunately, these two are also
+        # used as infix operators for the memory regions feature and
+        # the current parsing rules don't play well here.
+      if p.inPragma == 0 and (isUnary(p) or p.tok.tokType notin {tkOpr, tkDotDot}):
         # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet
         # solution, but pragmas.nim can't handle that
         let a = result
         result = newNodeP(nkCommand, p)
         addSon(result, a)
+        var isFirstParam = true
         when true:
           # progress NOT guaranteed
           p.hasProgress = false
-          addSon result, parseExpr(p)
+          addSon result, commandParam(p, isFirstParam, mode)
           if not p.hasProgress: break
         else:
           while p.tok.tokType != tkEof:
@@ -734,22 +788,16 @@ proc primarySuffix(p: var TParser, r: PNode, baseIndent: int): PNode =
             if p.tok.tokType != tkComma: break
             getTok(p)
             optInd(p, x)
-          if p.tok.tokType == tkDo:
-            parseDoBlocks(p, result)
-          else:
-            result = parseMacroColon(p, result)
+          result = postExprBlocks(p, result)
       break
     else:
       break
 
-proc primary(p: var TParser, mode: TPrimaryMode): PNode
-proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode
-
 proc parseOperators(p: var TParser, headNode: PNode,
                     limit: int, mode: TPrimaryMode): PNode =
   result = headNode
   # expand while operators have priorities higher than 'limit'
-  var opPrec = getPrecedence(p.tok, p.strongSpaces)
+  var opPrec = getPrecedence(p.tok, false)
   let modeB = if mode == pmTypeDef: pmTypeDesc else: mode
   # the operator itself must not start on a new line:
   # progress guaranteed
@@ -766,14 +814,24 @@ proc parseOperators(p: var TParser, headNode: PNode,
     addSon(a, result)
     addSon(a, b)
     result = a
-    opPrec = getPrecedence(p.tok, p.strongSpaces)
+    opPrec = getPrecedence(p.tok, false)
 
 proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
   result = primary(p, mode)
+  if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)) and
+     mode == pmNormal:
+    var pragmaExp = newNodeP(nkPragmaExpr, p)
+    pragmaExp.addSon result
+    pragmaExp.addSon p.parsePragma
+    result = pragmaExp
   result = parseOperators(p, result, limit, mode)
 
 proc simpleExpr(p: var TParser, mode = pmNormal): PNode =
+  when defined(nimpretty2):
+    inc p.em.doIndentMore
   result = simpleExprAux(p, -1, mode)
+  when defined(nimpretty2):
+    dec p.em.doIndentMore
 
 proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode =
   #| condExpr = expr colcom expr optInd
@@ -781,21 +839,57 @@ proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode =
   #|          'else' colcom expr
   #| ifExpr = 'if' condExpr
   #| whenExpr = 'when' condExpr
-  result = newNodeP(kind, p)
-  while true:
-    getTok(p)                 # skip `if`, `elif`
-    var branch = newNodeP(nkElifExpr, p)
+  when true:
+    result = newNodeP(kind, p)
+    while true:
+      getTok(p)                 # skip `if`, `when`, `elif`
+      var branch = newNodeP(nkElifExpr, p)
+      optInd(p, branch)
+      addSon(branch, parseExpr(p))
+      colcom(p, branch)
+      addSon(branch, parseStmt(p))
+      skipComment(p, branch)
+      addSon(result, branch)
+      if p.tok.tokType != tkElif: break # or not sameOrNoInd(p): break
+    if p.tok.tokType == tkElse: # and sameOrNoInd(p):
+      var branch = newNodeP(nkElseExpr, p)
+      eat(p, tkElse)
+      colcom(p, branch)
+      addSon(branch, parseStmt(p))
+      addSon(result, branch)
+  else:
+    var
+      b: PNode
+      wasIndented = false
+    result = newNodeP(kind, p)
+
+    getTok(p)
+    let branch = newNodeP(nkElifExpr, p)
     addSon(branch, parseExpr(p))
     colcom(p, branch)
+    let oldInd = p.currInd
+    if realInd(p):
+      p.currInd = p.tok.indent
+      wasIndented = true
     addSon(branch, parseExpr(p))
-    optInd(p, branch)
-    addSon(result, branch)
-    if p.tok.tokType != tkElif: break
-  var branch = newNodeP(nkElseExpr, p)
-  eat(p, tkElse)
-  colcom(p, branch)
-  addSon(branch, parseExpr(p))
-  addSon(result, branch)
+    result.add branch
+    while sameInd(p) or not wasIndented:
+      case p.tok.tokType
+      of tkElif:
+        b = newNodeP(nkElifExpr, p)
+        getTok(p)
+        optInd(p, b)
+        addSon(b, parseExpr(p))
+      of tkElse:
+        b = newNodeP(nkElseExpr, p)
+        getTok(p)
+      else: break
+      colcom(p, b)
+      addSon(b, parseStmt(p))
+      addSon(result, b)
+      if b.kind == nkElseExpr: break
+    if wasIndented:
+      p.currInd = oldInd
 
 proc parsePragma(p: var TParser): PNode =
   #| pragma = '{.' optInd (exprColonExpr comma?)* optPar ('.}' | '}')
@@ -812,8 +906,12 @@ proc parsePragma(p: var TParser): PNode =
       getTok(p)
       skipComment(p, a)
   optPar(p)
-  if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: getTok(p)
-  else: parMessage(p, errTokenExpected, ".}")
+  if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}:
+    when defined(nimpretty2):
+      if p.tok.tokType == tkCurlyRi: curlyRiWasPragma(p.em)
+    getTok(p)
+  else:
+    parMessage(p, "expected '.}'")
   dec p.inPragma
 
 proc identVis(p: var TParser; allowDot=false): PNode =
@@ -821,6 +919,8 @@ proc identVis(p: var TParser; allowDot=false): PNode =
   #| identVisDot = symbol '.' optInd symbol opr?
   var a = parseSymbol(p)
   if p.tok.tokType == tkOpr:
+    when defined(nimpretty2):
+      starWasExportMarker(p.em)
     result = newNodeP(nkPostfix, p)
     addSon(result, newIdentNodeP(p.tok.ident, p))
     addSon(result, a)
@@ -845,6 +945,7 @@ type
   TDeclaredIdentFlag = enum
     withPragma,               # identifier may have pragma
     withBothOptional          # both ':' and '=' parts are optional
+    withDot                   # allow 'var ident.ident = value'
   TDeclaredIdentFlags = set[TDeclaredIdentFlag]
 
 proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode =
@@ -858,7 +959,7 @@ proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode =
   while true:
     case p.tok.tokType
     of tkSymbol, tkAccent:
-      if withPragma in flags: a = identWithPragma(p)
+      if withPragma in flags: a = identWithPragma(p, allowDot=withdot in flags)
       else: a = parseSymbol(p)
       if a.kind == nkEmpty: return
     else: break
@@ -871,15 +972,15 @@ proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode =
     optInd(p, result)
     addSon(result, parseTypeDesc(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, newNodeP(nkEmpty, p))
     if p.tok.tokType != tkEquals and withBothOptional notin flags:
-      parMessage(p, errColonOrEqualsExpected, p.tok)
+      parMessage(p, "':' or '=' expected, but got '$1'", p.tok)
   if p.tok.tokType == tkEquals:
     getTok(p)
     optInd(p, result)
     addSon(result, parseExpr(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, newNodeP(nkEmpty, p))
 
 proc parseTuple(p: var TParser, indentAllowed = false): PNode =
   #| inlTupleDecl = 'tuple'
@@ -897,6 +998,8 @@ proc parseTuple(p: var TParser, indentAllowed = false): PNode =
       var a = parseIdentColonEquals(p, {})
       addSon(result, a)
       if p.tok.tokType notin {tkComma, tkSemiColon}: break
+      when defined(nimpretty2):
+        commaWasSemicolon(p.em)
       getTok(p)
       skipComment(p, a)
     optPar(p)
@@ -919,6 +1022,8 @@ proc parseTuple(p: var TParser, indentAllowed = false): PNode =
             parMessage(p, errIdentifierExpected, p.tok)
             break
           if not sameInd(p): break
+  elif p.tok.tokType == tkParLe:
+    parMessage(p, errGenerated, "the syntax for tuple types is 'tuple[...]', not 'tuple(...)'")
   else:
     result = newNodeP(nkTupleClassTy, p)
 
@@ -928,7 +1033,10 @@ proc parseParamList(p: var TParser, retColon = true): PNode =
   #| paramListColon = paramList? (':' optInd typeDesc)?
   var a: PNode
   result = newNodeP(nkFormalParams, p)
-  addSon(result, ast.emptyNode) # return type
+  addSon(result, p.emptyNode) # return type
+  when defined(nimpretty2):
+    inc p.em.doIndentMore
+    inc p.em.keepIndents
   let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0
   if hasParLe:
     getTok(p)
@@ -940,11 +1048,16 @@ proc parseParamList(p: var TParser, retColon = true): PNode =
         a = parseIdentColonEquals(p, {withBothOptional, withPragma})
       of tkParRi:
         break
+      of tkVar:
+        parMessage(p, errGenerated, "the syntax is 'parameter: var T', not 'var parameter: T'")
+        break
       else:
-        parMessage(p, errTokenExpected, ")")
+        parMessage(p, "expected closing ')'")
         break
       addSon(result, a)
       if p.tok.tokType notin {tkComma, tkSemiColon}: break
+      when defined(nimpretty2):
+        commaWasSemicolon(p.em)
       getTok(p)
       skipComment(p, a)
     optPar(p)
@@ -957,34 +1070,29 @@ proc parseParamList(p: var TParser, retColon = true): PNode =
     result.sons[0] = parseTypeDesc(p)
   elif not retColon and not hasParle:
     # Mark as "not there" in order to mark for deprecation in the semantic pass:
-    result = ast.emptyNode
+    result = p.emptyNode
+  when defined(nimpretty2):
+    dec p.em.doIndentMore
+    dec p.em.keepIndents
 
 proc optPragmas(p: var TParser): PNode =
   if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)):
     result = parsePragma(p)
   else:
-    result = ast.emptyNode
+    result = p.emptyNode
 
-proc parseDoBlock(p: var TParser): PNode =
+proc parseDoBlock(p: var TParser; info: TLineInfo): PNode =
   #| doBlock = 'do' paramListArrow pragmas? colcom stmt
-  let info = parLineInfo(p)
-  getTok(p)
   let params = parseParamList(p, retColon=false)
   let pragmas = optPragmas(p)
   colcom(p, result)
-  result = newProcNode(nkDo, info, parseStmt(p),
-                       params = params,
-                       pragmas = pragmas)
-
-proc parseDoBlocks(p: var TParser, call: PNode) =
-  #| doBlocks = doBlock ^* IND{=}
-  if p.tok.tokType == tkDo:
-    #withInd(p):
-    #  addSon(call, parseDoBlock(p))
-    while sameOrNoInd(p) and p.tok.tokType == tkDo:
-      addSon(call, parseDoBlock(p))
+  result = parseStmt(p)
+  if params.kind != nkEmpty:
+    result = newProcNode(nkDo, info,
+      body = result, params = params, name = p.emptyNode, pattern = p.emptyNode,
+      genericParams = p.emptyNode, pragmas = pragmas, exceptions = p.emptyNode)
 
-proc parseProcExpr(p: var TParser, isExpr: bool): PNode =
+proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode =
   #| procExpr = 'proc' paramListColon pragmas? ('=' COMMENT? stmt)?
   # either a proc type or a anonymous proc
   let info = parLineInfo(p)
@@ -995,9 +1103,9 @@ proc parseProcExpr(p: var TParser, isExpr: bool): PNode =
   if p.tok.tokType == tkEquals and isExpr:
     getTok(p)
     skipComment(p, result)
-    result = newProcNode(nkLambda, info, parseStmt(p),
-                         params = params,
-                         pragmas = pragmas)
+    result = newProcNode(kind, info, body = parseStmt(p),
+      params = params, name = p.emptyNode, pattern = p.emptyNode,
+      genericParams = p.emptyNode, pragmas = pragmas, exceptions = p.emptyNode)
   else:
     result = newNodeI(nkProcTy, info)
     if hasSignature:
@@ -1006,17 +1114,17 @@ proc parseProcExpr(p: var TParser, isExpr: bool): PNode =
 
 proc isExprStart(p: TParser): bool =
   case p.tok.tokType
-  of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf,
-     tkProc, tkIterator, tkBind, tkAddr,
+  of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkFor,
+     tkProc, tkFunc, tkIterator, tkBind, tkBuiltInMagics,
      tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr,
-     tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut:
+     tkTuple, tkObject, tkWhen, tkCase, tkOut:
     result = true
   else: result = false
 
-proc parseSymbolList(p: var TParser, result: PNode, allowNil = false) =
+proc parseSymbolList(p: var TParser, result: PNode) =
   # progress guaranteed
   while true:
-    var s = parseSymbol(p, allowNil)
+    var s = parseSymbol(p, smAllowNil)
     if s.kind == nkEmpty: break
     addSon(result, s)
     if p.tok.tokType != tkComma: break
@@ -1028,25 +1136,53 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind,
   #| distinct = 'distinct' optInd typeDesc
   result = newNodeP(kind, p)
   getTok(p)
+  if p.tok.indent != -1 and p.tok.indent <= p.currInd: return
   optInd(p, result)
   if not isOperator(p.tok) and isExprStart(p):
     addSon(result, primary(p, mode))
-  if kind == nkDistinctTy and p.tok.tokType in {tkWith, tkWithout}:
-    let nodeKind = if p.tok.tokType == tkWith: nkWith
-                   else: nkWithout
+  if kind == nkDistinctTy and p.tok.tokType == tkSymbol:
+    # XXX document this feature!
+    var nodeKind: TNodeKind
+    if p.tok.ident.s == "with":
+      nodeKind = nkWith
+    elif p.tok.ident.s == "without":
+      nodeKind = nkWithout
+    else:
+      return result
     getTok(p)
     let list = newNodeP(nodeKind, p)
     result.addSon list
-    parseSymbolList(p, list, allowNil = true)
+    parseSymbolList(p, list)
+
+proc parseFor(p: var TParser): PNode =
+  #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt
+  #| forExpr = forStmt
+  result = newNodeP(nkForStmt, p)
+  getTokNoInd(p)
+  var a = identWithPragma(p)
+  addSon(result, a)
+  while p.tok.tokType == tkComma:
+    getTok(p)
+    optInd(p, a)
+    a = identWithPragma(p)
+    addSon(result, a)
+  eat(p, tkIn)
+  addSon(result, parseExpr(p))
+  colcom(p, result)
+  addSon(result, parseStmt(p))
 
 proc parseExpr(p: var TParser): PNode =
-  #| expr = (ifExpr
+  #| expr = (blockExpr
+  #|       | ifExpr
   #|       | whenExpr
   #|       | caseExpr
+  #|       | forExpr
   #|       | tryExpr)
   #|       / simpleExpr
   case p.tok.tokType:
+  of tkBlock: result = parseBlock(p)
   of tkIf: result = parseIfExpr(p, nkIfExpr)
+  of tkFor: result = parseFor(p)
   of tkWhen: result = parseIfExpr(p, nkWhenExpr)
   of tkCase: result = parseCase(p)
   of tkTry: result = parseTry(p, isExpr=true)
@@ -1061,7 +1197,6 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode =
   #|          | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum'
   #| primary = typeKeyw typeDescK
   #|         /  prefixOperator* identOrLiteral primarySuffix*
-  #|         / 'static' primary
   #|         / 'bind' primary
   if isOperator(p.tok):
     let isSigil = isSigilLike(p.tok)
@@ -1074,28 +1209,19 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode =
       #XXX prefix operators
       let baseInd = p.lex.currLineIndent
       addSon(result, primary(p, pmSkipSuffix))
-      result = primarySuffix(p, result, baseInd)
+      result = primarySuffix(p, result, baseInd, mode)
     else:
       addSon(result, primary(p, pmNormal))
     return
 
   case p.tok.tokType:
   of tkTuple: result = parseTuple(p, mode == pmTypeDef)
-  of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef})
+  of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}, nkLambda)
+  of tkFunc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}, nkFuncDef)
   of tkIterator:
-    when false:
-      if mode in {pmTypeDesc, pmTypeDef}:
-        result = parseProcExpr(p, false)
-        result.kind = nkIteratorTy
-      else:
-        # no anon iterators for now:
-        parMessage(p, errExprExpected, p.tok)
-        getTok(p)  # we must consume a token here to prevend endless loops!
-        result = ast.emptyNode
-    else:
-      result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef})
-      if result.kind == nkLambda: result.kind = nkIteratorDef
-      else: result.kind = nkIteratorTy
+    result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef}, nkLambda)
+    if result.kind == nkLambda: result.kind = nkIteratorDef
+    else: result.kind = nkIteratorTy
   of tkEnum:
     if mode == pmTypeDef:
       result = parseEnum(p)
@@ -1108,30 +1234,17 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode =
     else:
       result = newNodeP(nkObjectTy, p)
       getTok(p)
-  of tkGeneric, tkConcept:
+  of tkConcept:
     if mode == pmTypeDef:
-      let wasGeneric = p.tok.tokType == tkGeneric
       result = parseTypeClass(p)
-      # hack so that it's remembered and can be marked as deprecated in
-      # sem'check:
-      if wasGeneric: result.flags.incl nfBase2
-    else:
-      parMessage(p, errInvalidToken, p.tok)
-  of tkStatic:
-    let info = parLineInfo(p)
-    getTokNoInd(p)
-    let next = primary(p, pmNormal)
-    if next.kind == nkBracket and next.sonsLen == 1:
-      result = newNode(nkStaticTy, info, @[next.sons[0]])
     else:
-      result = newNode(nkStaticExpr, info, @[next])
+      parMessage(p, "the 'concept' keyword is only valid in 'type' sections")
   of tkBind:
     result = newNodeP(nkBind, p)
     getTok(p)
     optInd(p, result)
     addSon(result, primary(p, pmNormal))
   of tkVar: result = parseTypeDescKAux(p, nkVarTy, mode)
-  of tkOut: result = parseTypeDescKAux(p, nkVarTy, mode)
   of tkRef: result = parseTypeDescKAux(p, nkRefTy, mode)
   of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, mode)
   of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, mode)
@@ -1139,16 +1252,31 @@ proc primary(p: var TParser, mode: TPrimaryMode): PNode =
     let baseInd = p.lex.currLineIndent
     result = identOrLiteral(p, mode)
     if mode != pmSkipSuffix:
-      result = primarySuffix(p, result, baseInd)
+      result = primarySuffix(p, result, baseInd, mode)
+
+proc binaryNot(p: var TParser; a: PNode): PNode =
+  if p.tok.tokType == tkNot:
+    let notOpr = newIdentNodeP(p.tok.ident, p)
+    getTok(p)
+    optInd(p, notOpr)
+    let b = parseExpr(p)
+    result = newNodeP(nkInfix, p)
+    result.add notOpr
+    result.add a
+    result.add b
+  else:
+    result = a
 
 proc parseTypeDesc(p: var TParser): PNode =
-  #| typeDesc = simpleExpr
+  #| typeDesc = simpleExpr ('not' expr)?
   result = simpleExpr(p, pmTypeDesc)
+  result = binaryNot(p, result)
 
 proc parseTypeDefAux(p: var TParser): PNode =
-  #| typeDefAux = simpleExpr
+  #| typeDefAux = simpleExpr ('not' expr)?
   #|            | 'concept' typeClass
   result = simpleExpr(p, pmTypeDef)
+  result = binaryNot(p, result)
 
 proc makeCall(n: PNode): PNode =
   ## Creates a call if the given node isn't already a call.
@@ -1158,53 +1286,83 @@ proc makeCall(n: PNode): PNode =
     result = newNodeI(nkCall, n.info)
     result.add n
 
-proc parseMacroColon(p: var TParser, x: PNode): PNode =
-  #| macroColon = ':' stmt? ( IND{=} 'of' exprList ':' stmt
-  #|                        | IND{=} 'elif' expr ':' stmt
-  #|                        | IND{=} 'except' exprList ':' stmt
-  #|                        | IND{=} 'else' ':' stmt )*
+proc postExprBlocks(p: var TParser, x: PNode): PNode =
+  #| postExprBlocks = ':' stmt? ( IND{=} doBlock
+  #|                            | IND{=} 'of' exprList ':' stmt
+  #|                            | IND{=} 'elif' expr ':' stmt
+  #|                            | IND{=} 'except' exprList ':' stmt
+  #|                            | IND{=} 'else' ':' stmt )*
   result = x
-  if p.tok.tokType == tkColon and p.tok.indent < 0:
+  if p.tok.indent >= 0: return
+
+  var
+    openingParams = p.emptyNode
+    openingPragmas = p.emptyNode
+
+  if p.tok.tokType == tkDo:
+    getTok(p)
+    openingParams = parseParamList(p, retColon=false)
+    openingPragmas = optPragmas(p)
+
+  if p.tok.tokType == tkColon:
     result = makeCall(result)
     getTok(p)
     skipComment(p, result)
-    let stmtList = newNodeP(nkStmtList, p)
     if p.tok.tokType notin {tkOf, tkElif, tkElse, tkExcept}:
-      let body = parseStmt(p)
-      stmtList.add body
-      #addSon(result, makeStmtList(body))
-    # progress guaranteed
+      var stmtList = newNodeP(nkStmtList, p)
+      stmtList.add parseStmt(p)
+      # to keep backwards compatibility (see tests/vm/tstringnil)
+      if stmtList[0].kind == nkStmtList: stmtList = stmtList[0]
+
+      stmtList.flags.incl nfBlockArg
+      if openingParams.kind != nkEmpty:
+        result.add newProcNode(nkDo, stmtList.info, body = stmtList,
+                               params = openingParams,
+                               name = p.emptyNode, pattern = p.emptyNode,
+                               genericParams = p.emptyNode,
+                               pragmas = openingPragmas,
+                               exceptions = p.emptyNode)
+      else:
+        result.add stmtList
+
     while sameInd(p):
-      var b: PNode
-      case p.tok.tokType
-      of tkOf:
-        b = newNodeP(nkOfBranch, p)
-        exprList(p, tkColon, b)
-      of tkElif:
-        b = newNodeP(nkElifBranch, p)
-        getTok(p)
-        optInd(p, b)
-        addSon(b, parseExpr(p))
-      of tkExcept:
-        b = newNodeP(nkExceptBranch, p)
-        exprList(p, tkColon, b)
-      of tkElse:
-        b = newNodeP(nkElse, p)
+      var nextBlock: PNode
+      let nextToken = p.tok.tokType
+      if nextToken == tkDo:
+        let info = parLineInfo(p)
         getTok(p)
-      else: break
-      eat(p, tkColon)
-      addSon(b, parseStmt(p))
-      addSon(stmtList, b)
-      if b.kind == nkElse: break
-    if stmtList.len == 1 and stmtList[0].kind == nkStmtList:
-      # to keep backwards compatibility (see tests/vm/tstringnil)
-      result.add stmtList[0]
-    else:
-      result.add stmtList
+        nextBlock = parseDoBlock(p, info)
+      else:
+        case nextToken:
+        of tkOf:
+          nextBlock = newNodeP(nkOfBranch, p)
+          exprList(p, tkColon, nextBlock)
+        of tkElif:
+          nextBlock = newNodeP(nkElifBranch, p)
+          getTok(p)
+          optInd(p, nextBlock)
+          nextBlock.addSon parseExpr(p)
+        of tkExcept:
+          nextBlock = newNodeP(nkExceptBranch, p)
+          exprList(p, tkColon, nextBlock)
+        of tkElse:
+          nextBlock = newNodeP(nkElse, p)
+          getTok(p)
+        else: break
+        eat(p, tkColon)
+        nextBlock.addSon parseStmt(p)
+
+      nextBlock.flags.incl nfBlockArg
+      result.add nextBlock
+
+      if nextBlock.kind == nkElse: break
+  else:
+    if openingParams.kind != nkEmpty:
+      parMessage(p, "expected ':'")
 
 proc parseExprStmt(p: var TParser): PNode =
   #| exprStmt = simpleExpr
-  #|          (( '=' optInd expr )
+  #|          (( '=' optInd expr colonBody? )
   #|          / ( expr ^+ comma
   #|              doBlocks
   #|               / macroColon
@@ -1215,36 +1373,29 @@ proc parseExprStmt(p: var TParser): PNode =
     getTok(p)
     optInd(p, result)
     var b = parseExpr(p)
+    b = postExprBlocks(p, b)
     addSon(result, a)
     addSon(result, b)
   else:
     # simpleExpr parsed 'p a' from 'p a, b'?
+    var isFirstParam = false
     if p.tok.indent < 0 and p.tok.tokType == tkComma and a.kind == nkCommand:
       result = a
       while true:
         getTok(p)
         optInd(p, result)
-        var e = parseExpr(p)
-        addSon(result, e)
+        addSon(result, commandParam(p, isFirstParam, pmNormal))
         if p.tok.tokType != tkComma: break
     elif p.tok.indent < 0 and isExprStart(p):
-      if a.kind == nkCommand:
-        result = a
-      else:
-        result = newNode(nkCommand, a.info, @[a])
+      result = newNode(nkCommand, a.info, @[a])
       while true:
-        var e = parseExpr(p)
-        addSon(result, e)
+        addSon(result, commandParam(p, isFirstParam, pmNormal))
         if p.tok.tokType != tkComma: break
         getTok(p)
         optInd(p, result)
     else:
       result = a
-    if p.tok.tokType == tkDo and p.tok.indent < 0:
-      result = makeCall(result)
-      parseDoBlocks(p, result)
-      return result
-    result = parseMacroColon(p, result)
+    result = postExprBlocks(p, result)
 
 proc parseModuleName(p: var TParser, kind: TNodeKind): PNode =
   result = parseExpr(p)
@@ -1329,12 +1480,14 @@ proc parseReturnOrRaise(p: var TParser, kind: TNodeKind): PNode =
   getTok(p)
   if p.tok.tokType == tkComment:
     skipComment(p, result)
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   elif p.tok.indent >= 0 and p.tok.indent <= p.currInd or not isExprStart(p):
     # NL terminates:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   else:
-    addSon(result, parseExpr(p))
+    var e = parseExpr(p)
+    e = postExprBlocks(p, e)
+    addSon(result, e)
 
 proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode =
   #| condStmt = expr colcom stmt COMMENT?
@@ -1379,7 +1532,7 @@ proc parseCase(p: var TParser): PNode =
   #|             | IND{=} ofBranches)
   var
     b: PNode
-    inElif= false
+    inElif = false
     wasIndented = false
   result = newNodeP(nkCaseStmt, p)
   getTok(p)
@@ -1440,8 +1593,7 @@ proc parseTry(p: var TParser; isExpr: bool): PNode =
     colcom(p, b)
     addSon(b, parseStmt(p))
     addSon(result, b)
-    if b.kind == nkFinally: break
-  if b == nil: parMessage(p, errTokenExpected, "except")
+  if b == nil: parMessage(p, "expected 'except'")
 
 proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode =
   #| exceptBlock = 'except' colcom stmt
@@ -1450,27 +1602,12 @@ proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode =
   colcom(p, result)
   addSon(result, parseStmt(p))
 
-proc parseFor(p: var TParser): PNode =
-  #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt
-  result = newNodeP(nkForStmt, p)
-  getTokNoInd(p)
-  var a = identWithPragma(p)
-  addSon(result, a)
-  while p.tok.tokType == tkComma:
-    getTok(p)
-    optInd(p, a)
-    a = identWithPragma(p)
-    addSon(result, a)
-  eat(p, tkIn)
-  addSon(result, parseExpr(p))
-  colcom(p, result)
-  addSon(result, parseStmt(p))
-
 proc parseBlock(p: var TParser): PNode =
   #| blockStmt = 'block' symbol? colcom stmt
+  #| blockExpr = 'block' symbol? colcom stmt
   result = newNodeP(nkBlockStmt, p)
   getTokNoInd(p)
-  if p.tok.tokType == tkColon: addSon(result, ast.emptyNode)
+  if p.tok.tokType == tkColon: addSon(result, p.emptyNode)
   else: addSon(result, parseSymbol(p))
   colcom(p, result)
   addSon(result, parseStmt(p))
@@ -1484,19 +1621,19 @@ proc parseStaticOrDefer(p: var TParser; k: TNodeKind): PNode =
   addSon(result, parseStmt(p))
 
 proc parseAsm(p: var TParser): PNode =
-  #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLE_STR_LIT)
+  #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLESTR_LIT)
   result = newNodeP(nkAsmStmt, p)
   getTokNoInd(p)
   if p.tok.tokType == tkCurlyDotLe: addSon(result, parsePragma(p))
-  else: addSon(result, ast.emptyNode)
+  else: addSon(result, p.emptyNode)
   case p.tok.tokType
   of tkStrLit: addSon(result, newStrNodeP(nkStrLit, p.tok.literal, p))
   of tkRStrLit: addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p))
   of tkTripleStrLit: addSon(result,
                             newStrNodeP(nkTripleStrLit, p.tok.literal, p))
   else:
-    parMessage(p, errStringLiteralExpected)
-    addSon(result, ast.emptyNode)
+    parMessage(p, "the 'asm' statement takes a string literal")
+    addSon(result, p.emptyNode)
     return
   getTok(p)
 
@@ -1507,6 +1644,13 @@ proc parseGenericParam(p: var TParser): PNode =
   # progress guaranteed
   while true:
     case p.tok.tokType
+    of tkIn, tkOut:
+      let x = p.lex.cache.getIdent(if p.tok.tokType == tkIn: "in" else: "out")
+      a = newNodeP(nkPrefix, p)
+      a.addSon newIdentNodeP(x, p)
+      getTok(p)
+      expectIdent(p)
+      a.addSon(parseSymbol(p))
     of tkSymbol, tkAccent:
       a = parseSymbol(p)
       if a.kind == nkEmpty: return
@@ -1520,13 +1664,13 @@ proc parseGenericParam(p: var TParser): PNode =
     optInd(p, result)
     addSon(result, parseExpr(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   if p.tok.tokType == tkEquals:
     getTok(p)
     optInd(p, result)
     addSon(result, parseExpr(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
 
 proc parseGenericParamList(p: var TParser): PNode =
   #| genericParamList = '[' optInd
@@ -1535,10 +1679,12 @@ proc parseGenericParamList(p: var TParser): PNode =
   getTok(p)
   optInd(p, result)
   # progress guaranteed
-  while p.tok.tokType in {tkSymbol, tkAccent}:
+  while p.tok.tokType in {tkSymbol, tkAccent, tkIn, tkOut}:
     var a = parseGenericParam(p)
     addSon(result, a)
     if p.tok.tokType notin {tkComma, tkSemiColon}: break
+    when defined(nimpretty2):
+      commaWasSemicolon(p.em)
     getTok(p)
     skipComment(p, a)
   optPar(p)
@@ -1562,22 +1708,22 @@ proc parseRoutine(p: var TParser, kind: TNodeKind): PNode =
   optInd(p, result)
   addSon(result, identVis(p))
   if p.tok.tokType == tkCurlyLe and p.validInd: addSon(result, p.parsePattern)
-  else: addSon(result, ast.emptyNode)
+  else: addSon(result, p.emptyNode)
   if p.tok.tokType == tkBracketLe and p.validInd:
     result.add(p.parseGenericParamList)
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   addSon(result, p.parseParamList)
   if p.tok.tokType == tkCurlyDotLe and p.validInd: addSon(result, p.parsePragma)
-  else: addSon(result, ast.emptyNode)
+  else: addSon(result, p.emptyNode)
   # empty exception tracking:
-  addSon(result, ast.emptyNode)
+  addSon(result, p.emptyNode)
   if p.tok.tokType == tkEquals and p.validInd:
     getTok(p)
     skipComment(p, result)
     addSon(result, parseStmt(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   indAndComment(p, result)
 
 proc newCommentStmt(p: var TParser): PNode =
@@ -1619,7 +1765,7 @@ proc parseSection(p: var TParser, kind: TNodeKind,
     parMessage(p, errIdentifierExpected, p.tok)
 
 proc parseConstant(p: var TParser): PNode =
-  #| constant = identWithPragma (colon typedesc)? '=' optInd expr indAndComment
+  #| constant = identWithPragma (colon typeDesc)? '=' optInd expr indAndComment
   result = newNodeP(nkConstDef, p)
   addSon(result, identWithPragma(p))
   if p.tok.tokType == tkColon:
@@ -1627,7 +1773,7 @@ proc parseConstant(p: var TParser): PNode =
     optInd(p, result)
     addSon(result, parseTypeDesc(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   eat(p, tkEquals)
   optInd(p, result)
   addSon(result, parseExpr(p))
@@ -1637,7 +1783,7 @@ proc parseEnum(p: var TParser): PNode =
   #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+
   result = newNodeP(nkEnumTy, p)
   getTok(p)
-  addSon(result, ast.emptyNode)
+  addSon(result, p.emptyNode)
   optInd(p, result)
   flexComment(p, result)
   # progress guaranteed
@@ -1667,7 +1813,7 @@ proc parseEnum(p: var TParser): PNode =
         p.tok.tokType == tkEof:
       break
   if result.len <= 1:
-    lexMessageTok(p.lex, errIdentifierExpected, p.tok, prettyTok(p.tok))
+    parMessage(p, errIdentifierExpected, p.tok)
 
 proc parseObjectPart(p: var TParser): PNode
 proc parseObjectWhen(p: var TParser): PNode =
@@ -1708,7 +1854,7 @@ proc parseObjectCase(p: var TParser): PNode =
   addSon(a, identWithPragma(p))
   eat(p, tkColon)
   addSon(a, parseTypeDesc(p))
-  addSon(a, ast.emptyNode)
+  addSon(a, p.emptyNode)
   addSon(result, a)
   if p.tok.tokType == tkColon: getTok(p)
   flexComment(p, result)
@@ -1767,7 +1913,7 @@ proc parseObjectPart(p: var TParser): PNode =
       result = newNodeP(nkNilLit, p)
       getTok(p)
     else:
-      result = ast.emptyNode
+      result = p.emptyNode
 
 proc parseObject(p: var TParser): PNode =
   #| object = 'object' pragma? ('of' typeDesc)? COMMENT? objectPart
@@ -1776,25 +1922,33 @@ proc parseObject(p: var TParser): PNode =
   if p.tok.tokType == tkCurlyDotLe and p.validInd:
     addSon(result, parsePragma(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   if p.tok.tokType == tkOf and p.tok.indent < 0:
     var a = newNodeP(nkOfInherit, p)
     getTok(p)
     addSon(a, parseTypeDesc(p))
     addSon(result, a)
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   if p.tok.tokType == tkComment:
     skipComment(p, result)
   # an initial IND{>} HAS to follow:
   if not realInd(p):
-    addSon(result, emptyNode)
+    addSon(result, p.emptyNode)
     return
   addSon(result, parseObjectPart(p))
 
 proc parseTypeClassParam(p: var TParser): PNode =
-  if p.tok.tokType in {tkOut, tkVar}:
-    result = newNodeP(nkVarTy, p)
+  let modifier = case p.tok.tokType
+    of tkOut, tkVar: nkVarTy
+    of tkPtr: nkPtrTy
+    of tkRef: nkRefTy
+    of tkStatic: nkStaticTy
+    of tkType: nkTypeOfExpr
+    else: nkEmpty
+
+  if modifier != nkEmpty:
+    result = newNodeP(modifier, p)
     getTok(p)
     result.addSon(p.parseSymbol)
   else:
@@ -1815,7 +1969,7 @@ proc parseTypeClass(p: var TParser): PNode =
   if p.tok.tokType == tkCurlyDotLe and p.validInd:
     addSon(result, parsePragma(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   if p.tok.tokType == tkOf and p.tok.indent < 0:
     var a = newNodeP(nkOfInherit, p)
     getTok(p)
@@ -1826,12 +1980,12 @@ proc parseTypeClass(p: var TParser): PNode =
       getTok(p)
     addSon(result, a)
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   if p.tok.tokType == tkComment:
     skipComment(p, result)
   # an initial IND{>} HAS to follow:
   if not realInd(p):
-    addSon(result, emptyNode)
+    addSon(result, p.emptyNode)
   else:
     addSon(result, parseStmt(p))
 
@@ -1844,14 +1998,14 @@ proc parseTypeDef(p: var TParser): PNode =
   if p.tok.tokType == tkBracketLe and p.validInd:
     addSon(result, parseGenericParamList(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   if p.tok.tokType == tkEquals:
     result.info = parLineInfo(p)
     getTok(p)
     optInd(p, result)
     addSon(result, parseTypeDefAux(p))
   else:
-    addSon(result, ast.emptyNode)
+    addSon(result, p.emptyNode)
   indAndComment(p, result)    # special extension!
 
 proc parseVarTuple(p: var TParser): PNode =
@@ -1861,12 +2015,12 @@ proc parseVarTuple(p: var TParser): PNode =
   optInd(p, result)
   # progress guaranteed
   while p.tok.tokType in {tkSymbol, tkAccent}:
-    var a = identWithPragma(p)
+    var a = identWithPragma(p, allowDot=true)
     addSon(result, a)
     if p.tok.tokType != tkComma: break
     getTok(p)
     skipComment(p, a)
-  addSon(result, ast.emptyNode)         # no type desc
+  addSon(result, p.emptyNode)         # no type desc
   optPar(p)
   eat(p, tkParRi)
   eat(p, tkEquals)
@@ -1874,9 +2028,11 @@ proc parseVarTuple(p: var TParser): PNode =
   addSon(result, parseExpr(p))
 
 proc parseVariable(p: var TParser): PNode =
-  #| variable = (varTuple / identColonEquals) indAndComment
+  #| colonBody = colcom stmt doBlocks?
+  #| variable = (varTuple / identColonEquals) colonBody? indAndComment
   if p.tok.tokType == tkParLe: result = parseVarTuple(p)
-  else: result = parseIdentColonEquals(p, {withPragma})
+  else: result = parseIdentColonEquals(p, {withPragma, withDot})
+  result[^1] = postExprBlocks(p, result[^1])
   indAndComment(p, result)
 
 proc parseBind(p: var TParser, k: TNodeKind): PNode =
@@ -1925,7 +2081,7 @@ proc simpleStmt(p: var TParser): PNode =
   of tkComment: result = newCommentStmt(p)
   else:
     if isExprStart(p): result = parseExprStmt(p)
-    else: result = ast.emptyNode
+    else: result = p.emptyNode
   if result.kind notin {nkEmpty, nkCommentStmt}: skipComment(p, result)
 
 proc complexOrSimpleStmt(p: var TParser): PNode =
@@ -1956,6 +2112,7 @@ proc complexOrSimpleStmt(p: var TParser): PNode =
   of tkDefer: result = parseStaticOrDefer(p, nkDefer)
   of tkAsm: result = parseAsm(p)
   of tkProc: result = parseRoutine(p, nkProcDef)
+  of tkFunc: result = parseRoutine(p, nkFuncDef)
   of tkMethod: result = parseRoutine(p, nkMethodDef)
   of tkIterator: result = parseRoutine(p, nkIteratorDef)
   of tkMacro: result = parseRoutine(p, nkMacroDef)
@@ -2006,16 +2163,21 @@ proc parseStmt(p: var TParser): PNode =
         if a.kind != nkEmpty:
           addSon(result, a)
         else:
-          parMessage(p, errExprExpected, p.tok)
-          getTok(p)
+          # This is done to make the new 'if' expressions work better.
+          # XXX Eventually we need to be more strict here.
+          if p.tok.tokType notin {tkElse, tkElif}:
+            parMessage(p, errExprExpected, p.tok)
+            getTok(p)
+          else:
+            break
         if not p.hasProgress and p.tok.tokType == tkEof: break
   else:
     # the case statement is only needed for better error messages:
     case p.tok.tokType
-    of tkIf, tkWhile, tkCase, tkTry, tkFor, tkBlock, tkAsm, tkProc, tkIterator,
-       tkMacro, tkType, tkConst, tkWhen, tkVar:
-      parMessage(p, errComplexStmtRequiresInd)
-      result = ast.emptyNode
+    of tkIf, tkWhile, tkCase, tkTry, tkFor, tkBlock, tkAsm, tkProc, tkFunc,
+       tkIterator, tkMacro, tkType, tkConst, tkWhen, tkVar:
+      parMessage(p, "complex statement requires indentation")
+      result = p.emptyNode
     else:
       if p.inSemiStmtList > 0:
         result = simpleStmt(p)
@@ -2052,13 +2214,18 @@ proc parseAll(p: var TParser): PNode =
 proc parseTopLevelStmt(p: var TParser): PNode =
   ## Implements an iterator which, when called repeatedly, returns the next
   ## top-level statement or emptyNode if end of stream.
-  result = ast.emptyNode
+  result = p.emptyNode
   # progress guaranteed
   while true:
     if p.tok.indent != 0:
       if p.firstTok and p.tok.indent < 0: discard
       elif p.tok.tokType != tkSemiColon:
-        parMessage(p, errInvalidIndentation)
+        # special casing for better error messages:
+        if p.tok.tokType == tkOpr and p.tok.ident.s == "*":
+          parMessage(p, errGenerated,
+            "invalid indentation; an export marker '*' follows the declared identifier")
+        else:
+          parMessage(p, errInvalidIndentation)
     p.firstTok = false
     case p.tok.tokType
     of tkSemiColon:
@@ -2072,8 +2239,8 @@ proc parseTopLevelStmt(p: var TParser): PNode =
       if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok)
       break
 
-proc parseString*(s: string; cache: IdentCache; filename: string = "";
-                  line: int = 0;
+proc parseString*(s: string; cache: IdentCache; config: ConfigRef;
+                  filename: string = ""; line: int = 0;
                   errorHandler: TErrorHandler = nil): PNode =
   ## Parses a string into an AST, returning the top node.
   ## `filename` and `line`, although optional, provide info so that the
@@ -2083,10 +2250,8 @@ proc parseString*(s: string; cache: IdentCache; filename: string = "";
   stream.lineOffset = line
 
   var parser: TParser
-  # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong
-  # spaces...
   parser.lex.errorHandler = errorHandler
-  openParser(parser, filename, stream, cache, false)
+  openParser(parser, AbsoluteFile filename, stream, cache, config)
 
   result = parser.parseAll
   closeParser(parser)
diff --git a/compiler/passaux.nim b/compiler/passaux.nim
index 2065d5893..09f656d58 100644
--- a/compiler/passaux.nim
+++ b/compiler/passaux.nim
@@ -10,22 +10,26 @@
 ## implements some little helper passes
 
 import
-  strutils, ast, astalgo, passes, idents, msgs, options, idgen
+  strutils, ast, astalgo, passes, idents, msgs, options, idgen, lineinfos
 
-from modulegraphs import ModuleGraph
+from modulegraphs import ModuleGraph, PPassContext
 
-proc verboseOpen(graph: ModuleGraph; s: PSym; cache: IdentCache): PPassContext =
+type
+  VerboseRef = ref object of PPassContext
+    config: ConfigRef
+
+proc verboseOpen(graph: ModuleGraph; s: PSym): PPassContext =
   #MessageOut('compiling ' + s.name.s);
-  result = nil                # we don't need a context
-  rawMessage(hintProcessing, s.name.s)
+  result = VerboseRef(config: graph.config)
+  rawMessage(graph.config, hintProcessing, s.name.s)
 
 proc verboseProcess(context: PPassContext, n: PNode): PNode =
   result = n
-  if context != nil: internalError("logpass: context is not nil")
-  if gVerbosity == 3:
+  let v = VerboseRef(context)
+  if v.config.verbosity == 3:
     # system.nim deactivates all hints, for verbosity:3 we want the processing
     # messages nonetheless, so we activate them again unconditionally:
-    incl(msgs.gNotes, hintProcessing)
-    message(n.info, hintProcessing, $idgen.gFrontendId)
+    incl(v.config.notes, hintProcessing)
+    message(v.config, n.info, hintProcessing, $idgen.gFrontendId)
 
 const verbosePass* = makePass(open = verboseOpen, process = verboseProcess)
diff --git a/compiler/passes.nim b/compiler/passes.nim
index 7966ee88d..9ccd2240a 100644
--- a/compiler/passes.nim
+++ b/compiler/passes.nim
@@ -7,71 +7,36 @@
 #    distribution, for details about the copyright.
 #
 
-# This module implements the passes functionality. A pass must implement the
-# `TPass` interface.
+## This module implements the passes functionality. A pass must implement the
+## `TPass` interface.
 
 import
   strutils, options, ast, astalgo, llstream, msgs, platform, os,
   condsyms, idents, renderer, types, extccomp, math, magicsys, nversion,
-  nimsets, syntaxes, times, rodread, idgen, modulegraphs
+  nimsets, syntaxes, times, idgen, modulegraphs, reorder, rod,
+  lineinfos, pathutils
 
 type
-  TPassContext* = object of RootObj # the pass's context
-    fromCache*: bool  # true if created by "openCached"
-
-  PPassContext* = ref TPassContext
-
-  TPassOpen* = proc (graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext {.nimcall.}
-  TPassOpenCached* =
-    proc (graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext {.nimcall.}
-  TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.}
-  TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.}
-
-  TPass* = tuple[open: TPassOpen, openCached: TPassOpenCached,
-                 process: TPassProcess, close: TPassClose]
-
   TPassData* = tuple[input: PNode, closeOutput: PNode]
-  TPasses* = openArray[TPass]
 
 # a pass is a tuple of procedure vars ``TPass.close`` may produce additional
 # nodes. These are passed to the other close procedures.
 # This mechanism used to be used for the instantiation of generics.
 
 proc makePass*(open: TPassOpen = nil,
-               openCached: TPassOpenCached = nil,
                process: TPassProcess = nil,
-               close: TPassClose = nil): TPass =
+               close: TPassClose = nil,
+               isFrontend = false): TPass =
   result.open = open
-  result.openCached = openCached
   result.close = close
   result.process = process
+  result.isFrontend = isFrontend
 
-# the semantic checker needs these:
-var
-  gImportModule*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PSym {.nimcall.}
-  gIncludeFile*: proc (graph: ModuleGraph; m: PSym, fileIdx: int32; cache: IdentCache): PNode {.nimcall.}
-
-# implementation
-
-proc skipCodegen*(n: PNode): bool {.inline.} =
+proc skipCodegen*(config: ConfigRef; n: PNode): bool {.inline.} =
   # can be used by codegen passes to determine whether they should do
   # something with `n`. Currently, this ignores `n` and uses the global
   # error count instead.
-  result = msgs.gErrorCounter > 0
-
-proc astNeeded*(s: PSym): bool =
-  # The ``rodwrite`` module uses this to determine if the body of a proc
-  # needs to be stored. The passes manager frees s.sons[codePos] when
-  # appropriate to free the procedure body's memory. This is important
-  # to keep memory usage down.
-  if (s.kind in {skMethod, skProc}) and
-      ({sfCompilerProc, sfCompileTime} * s.flags == {}) and
-      (s.typ.callConv != ccInline) and
-      (s.ast.sons[genericParamsPos].kind == nkEmpty):
-    result = false
-    # XXX this doesn't really make sense with excessive CTFE
-  else:
-    result = true
+  result = config.errorCounter > 0
 
 const
   maxPasses = 10
@@ -79,130 +44,130 @@ const
 type
   TPassContextArray = array[0..maxPasses - 1, PPassContext]
 
-var
-  gPasses: array[0..maxPasses - 1, TPass]
-  gPassesLen*: int
+proc clearPasses*(g: ModuleGraph) =
+  g.passes.setLen(0)
 
-proc clearPasses* =
-  gPassesLen = 0
+proc registerPass*(g: ModuleGraph; p: TPass) =
+  internalAssert g.config, g.passes.len < maxPasses
+  g.passes.add(p)
 
-proc registerPass*(p: TPass) =
-  gPasses[gPassesLen] = p
-  inc(gPassesLen)
-
-proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; cache: IdentCache;
+proc carryPass*(g: ModuleGraph; p: TPass, module: PSym;
                 m: TPassData): TPassData =
-  var c = p.open(g, module, cache)
+  var c = p.open(g, module)
   result.input = p.process(c, m.input)
   result.closeOutput = if p.close != nil: p.close(g, c, m.closeOutput)
                        else: m.closeOutput
 
 proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym;
-                  cache: IdentCache; passes: TPasses) =
+                  passes: openArray[TPass]) =
   var passdata: TPassData
   passdata.input = nodes
   for pass in passes:
-    passdata = carryPass(g, pass, module, cache, passdata)
+    passdata = carryPass(g, pass, module, passdata)
 
 proc openPasses(g: ModuleGraph; a: var TPassContextArray;
-                module: PSym; cache: IdentCache) =
-  for i in countup(0, gPassesLen - 1):
-    if not isNil(gPasses[i].open):
-      a[i] = gPasses[i].open(g, module, cache)
+                module: PSym) =
+  for i in countup(0, g.passes.len - 1):
+    if not isNil(g.passes[i].open):
+      a[i] = g.passes[i].open(g, module)
     else: a[i] = nil
 
-proc openPassesCached(g: ModuleGraph; a: var TPassContextArray, module: PSym,
-                      rd: PRodReader) =
-  for i in countup(0, gPassesLen - 1):
-    if not isNil(gPasses[i].openCached):
-      a[i] = gPasses[i].openCached(g, module, rd)
-      if a[i] != nil:
-        a[i].fromCache = true
-    else:
-      a[i] = nil
-
 proc closePasses(graph: ModuleGraph; a: var TPassContextArray) =
   var m: PNode = nil
-  for i in countup(0, gPassesLen - 1):
-    if not isNil(gPasses[i].close): m = gPasses[i].close(graph, a[i], m)
+  for i in countup(0, graph.passes.len - 1):
+    if not isNil(graph.passes[i].close): m = graph.passes[i].close(graph, a[i], m)
     a[i] = nil                # free the memory here
 
-proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool =
+proc processTopLevelStmt(graph: ModuleGraph, n: PNode, a: var TPassContextArray): bool =
   # this implements the code transformation pipeline
   var m = n
-  for i in countup(0, gPassesLen - 1):
-    if not isNil(gPasses[i].process):
-      m = gPasses[i].process(a[i], m)
+  for i in countup(0, graph.passes.len - 1):
+    if not isNil(graph.passes[i].process):
+      m = graph.passes[i].process(a[i], m)
       if isNil(m): return false
   result = true
 
-proc processTopLevelStmtCached(n: PNode, a: var TPassContextArray) =
-  # this implements the code transformation pipeline
-  var m = n
-  for i in countup(0, gPassesLen - 1):
-    if not isNil(gPasses[i].openCached): m = gPasses[i].process(a[i], m)
-
-proc closePassesCached(graph: ModuleGraph; a: var TPassContextArray) =
-  var m: PNode = nil
-  for i in countup(0, gPassesLen - 1):
-    if not isNil(gPasses[i].openCached) and not isNil(gPasses[i].close):
-      m = gPasses[i].close(graph, a[i], m)
-    a[i] = nil                # free the memory here
-
-proc resolveMod(module, relativeTo: string): int32 =
-  let fullPath = findModule(module, relativeTo)
-  if fullPath.len == 0:
+proc resolveMod(conf: ConfigRef; module, relativeTo: string): FileIndex =
+  let fullPath = findModule(conf, module, relativeTo)
+  if fullPath.isEmpty:
     result = InvalidFileIDX
   else:
-    result = fullPath.fileInfoIdx
+    result = fileInfoIdx(conf, fullPath)
 
-proc processImplicits(implicits: seq[string], nodeKind: TNodeKind,
+proc processImplicits(graph: ModuleGraph; implicits: seq[string], nodeKind: TNodeKind,
                       a: var TPassContextArray; m: PSym) =
   # XXX fixme this should actually be relative to the config file!
-  let relativeTo = m.info.toFullPath
+  let gCmdLineInfo = newLineInfo(FileIndex(0), 1, 1)
+  let relativeTo = toFullPath(graph.config, m.info)
   for module in items(implicits):
     # implicit imports should not lead to a module importing itself
-    if m.position != resolveMod(module, relativeTo):
+    if m.position != resolveMod(graph.config, module, relativeTo).int32:
       var importStmt = newNodeI(nodeKind, gCmdLineInfo)
       var str = newStrNode(nkStrLit, module)
       str.info = gCmdLineInfo
       importStmt.addSon str
-      if not processTopLevelStmt(importStmt, a): break
+      if not processTopLevelStmt(graph, importStmt, a): break
 
-proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
-                    rd: PRodReader; cache: IdentCache): bool {.discardable.} =
+const
+  imperativeCode = {low(TNodeKind)..high(TNodeKind)} - {nkTemplateDef, nkProcDef, nkMethodDef,
+    nkMacroDef, nkConverterDef, nkIteratorDef, nkFuncDef, nkPragma,
+    nkExportStmt, nkExportExceptStmt, nkFromStmt, nkImportStmt, nkImportExceptStmt}
+
+proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool {.discardable.} =
   if graph.stopCompile(): return true
   var
     p: TParsers
     a: TPassContextArray
     s: PLLStream
     fileIdx = module.fileIdx
-  if rd == nil:
-    openPasses(graph, a, module, cache)
+  if module.id < 0:
+    # new module caching mechanism:
+    for i in 0 ..< graph.passes.len:
+      if not isNil(graph.passes[i].open) and not graph.passes[i].isFrontend:
+        a[i] = graph.passes[i].open(graph, module)
+      else:
+        a[i] = nil
+
+    if not graph.stopCompile():
+      let n = loadNode(graph, module)
+      var m = n
+      for i in 0 ..< graph.passes.len:
+        if not isNil(graph.passes[i].process) and not graph.passes[i].isFrontend:
+          m = graph.passes[i].process(a[i], m)
+          if isNil(m):
+            break
+
+    var m: PNode = nil
+    for i in 0 ..< graph.passes.len:
+      if not isNil(graph.passes[i].close) and not graph.passes[i].isFrontend:
+        m = graph.passes[i].close(graph, a[i], m)
+      a[i] = nil
+  else:
+    openPasses(graph, a, module)
     if stream == nil:
-      let filename = fileIdx.toFullPathConsiderDirty
+      let filename = toFullPathConsiderDirty(graph.config, fileIdx)
       s = llStreamOpen(filename, fmRead)
       if s == nil:
-        rawMessage(errCannotOpenFile, filename)
+        rawMessage(graph.config, errCannotOpenFile, filename.string)
         return false
     else:
       s = stream
     while true:
-      openParsers(p, fileIdx, s, cache)
+      openParsers(p, fileIdx, s, graph.cache, graph.config)
 
       if sfSystemModule notin module.flags:
         # XXX what about caching? no processing then? what if I change the
         # modules to include between compilation runs? we'd need to track that
         # in ROD files. I think we should enable this feature only
         # for the interactive mode.
-        processImplicits implicitImports, nkImportStmt, a, module
-        processImplicits implicitIncludes, nkIncludeStmt, a, module
+        processImplicits graph, graph.config.implicitImports, nkImportStmt, a, module
+        processImplicits graph, graph.config.implicitIncludes, nkIncludeStmt, a, module
 
       while true:
         if graph.stopCompile(): break
         var n = parseTopLevelStmt(p)
         if n.kind == nkEmpty: break
-        if sfNoForward in module.flags:
+        if {sfNoForward, sfReorder} * module.flags != {}:
           # read everything, no streaming possible
           var sl = newNodeI(nkStmtList, n.info)
           sl.add n
@@ -210,19 +175,32 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream,
             var n = parseTopLevelStmt(p)
             if n.kind == nkEmpty: break
             sl.add n
-          discard processTopLevelStmt(sl, a)
+          if sfReorder in module.flags:
+            sl = reorder(graph, sl, module)
+          discard processTopLevelStmt(graph, sl, a)
           break
-        elif not processTopLevelStmt(n, a): break
+        elif n.kind in imperativeCode:
+          # read everything until the next proc declaration etc.
+          var sl = newNodeI(nkStmtList, n.info)
+          sl.add n
+          var rest: PNode = nil
+          while true:
+            var n = parseTopLevelStmt(p)
+            if n.kind == nkEmpty or n.kind notin imperativeCode:
+              rest = n
+              break
+            sl.add n
+          #echo "-----\n", sl
+          if not processTopLevelStmt(graph, sl, a): break
+          if rest != nil:
+            #echo "-----\n", rest
+            if not processTopLevelStmt(graph, rest, a): break
+        else:
+          #echo "----- single\n", n
+          if not processTopLevelStmt(graph, n, a): break
       closeParsers(p)
       if s.kind != llsStdIn: break
     closePasses(graph, a)
     # id synchronization point for more consistent code generation:
     idSynchronizationPoint(1000)
-  else:
-    openPassesCached(graph, a, module, rd)
-    var n = loadInitSection(rd)
-    for i in countup(0, sonsLen(n) - 1):
-      if graph.stopCompile(): break
-      processTopLevelStmtCached(n.sons[i], a)
-    closePassesCached(graph, a)
   result = true
diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim
new file mode 100644
index 000000000..703467bc4
--- /dev/null
+++ b/compiler/pathutils.nim
@@ -0,0 +1,245 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Path handling utilities for Nim. Strictly typed code in order
+## to avoid the never ending time sink in getting path handling right.
+## Might be a candidate for the stdlib later.
+
+import os, strutils
+
+type
+  AbsoluteFile* = distinct string
+  AbsoluteDir* = distinct string
+  RelativeFile* = distinct string
+  RelativeDir* = distinct string
+
+proc isEmpty*(x: AbsoluteFile): bool {.inline.} = x.string.len == 0
+proc isEmpty*(x: AbsoluteDir): bool {.inline.} = x.string.len == 0
+proc isEmpty*(x: RelativeFile): bool {.inline.} = x.string.len == 0
+proc isEmpty*(x: RelativeDir): bool {.inline.} = x.string.len == 0
+
+proc copyFile*(source, dest: AbsoluteFile) =
+  os.copyFile(source.string, dest.string)
+
+proc removeFile*(x: AbsoluteFile) {.borrow.}
+
+proc splitFile*(x: AbsoluteFile): tuple[dir: AbsoluteDir, name, ext: string] =
+  let (a, b, c) = splitFile(x.string)
+  result = (dir: AbsoluteDir(a), name: b, ext: c)
+
+proc extractFilename*(x: AbsoluteFile): string {.borrow.}
+
+proc fileExists*(x: AbsoluteFile): bool {.borrow.}
+proc dirExists*(x: AbsoluteDir): bool {.borrow.}
+
+proc quoteShell*(x: AbsoluteFile): string {.borrow.}
+proc quoteShell*(x: AbsoluteDir): string {.borrow.}
+
+proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.}
+
+proc createDir*(x: AbsoluteDir) {.borrow.}
+
+type
+  PathIter = object
+    i, prev: int
+    notFirst: bool
+
+proc hasNext(it: PathIter; x: string): bool =
+  it.i < x.len
+
+proc next(it: var PathIter; x: string): (int, int) =
+  it.prev = it.i
+  if not it.notFirst and x[it.i] in {DirSep, AltSep}:
+    # absolute path:
+    inc it.i
+  else:
+    while it.i < x.len and x[it.i] notin {DirSep, AltSep}: inc it.i
+  if it.i > it.prev:
+    result = (it.prev, it.i-1)
+  elif hasNext(it, x):
+    result = next(it, x)
+
+  # skip all separators:
+  while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i
+  it.notFirst = true
+
+iterator dirs(x: string): (int, int) =
+  var it: PathIter
+  while hasNext(it, x): yield next(it, x)
+
+proc isDot(x: string; bounds: (int, int)): bool =
+  bounds[1] == bounds[0] and x[bounds[0]] == '.'
+
+proc isDotDot(x: string; bounds: (int, int)): bool =
+  bounds[1] == bounds[0] + 1 and x[bounds[0]] == '.' and x[bounds[0]+1] == '.'
+
+proc isSlash(x: string; bounds: (int, int)): bool =
+  bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep}
+
+const canonDirSep = when isMainModule: '/' else: DirSep
+
+proc canon(x: string; result: var string; state: var int) =
+  # state: 0th bit set if isAbsolute path. Other bits count
+  # the number of path components.
+  for b in dirs(x):
+    if (state shr 1 == 0) and isSlash(x, b):
+      result.add canonDirSep
+      state = state or 1
+    elif result.len > (state and 1) and isDotDot(x, b):
+      var d = result.len
+      # f/..
+      while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}:
+        dec d
+      if d > 0: setLen(result, d-1)
+    elif isDot(x, b):
+      discard "discard the dot"
+    elif b[1] >= b[0]:
+      if result.len > 0 and result[^1] notin {DirSep, AltSep}:
+        result.add canonDirSep
+      result.add substr(x, b[0], b[1])
+    inc state, 2
+
+proc canon(x: string): string =
+  # - Turn multiple slashes into single slashes.
+  # - Resolve '/foo/../bar' to '/bar'.
+  # - Remove './' from the path.
+  result = newStringOfCap(x.len)
+  var state = 0
+  canon(x, result, state)
+
+when FileSystemCaseSensitive:
+  template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b)
+else:
+  template `!=?`(a, b: char): bool = a != b
+
+proc relativeTo(full, base: string; sep = canonDirSep): string =
+  if full.len == 0: return ""
+  var f, b: PathIter
+  var ff = (0, -1)
+  var bb = (0, -1) # (int, int)
+  result = newStringOfCap(full.len)
+  # skip the common prefix:
+  while f.hasNext(full) and b.hasNext(base):
+    ff = next(f, full)
+    bb = next(b, base)
+    let diff = ff[1] - ff[0]
+    if diff != bb[1] - bb[0]: break
+    var same = true
+    for i in 0..diff:
+      if full[i + ff[0]] !=? base[i + bb[0]]:
+        same = false
+        break
+    if not same: break
+    ff = (0, -1)
+    bb = (0, -1)
+  #  for i in 0..diff:
+  #    result.add base[i + bb[0]]
+
+  # /foo/bar/xxx/ -- base
+  # /foo/bar/baz  -- full path
+  #   ../baz
+  # every directory that is in 'base', needs to add '..'
+  while true:
+    if bb[1] >= bb[0]:
+      if result.len > 0 and result[^1] != sep:
+        result.add sep
+      result.add ".."
+    if not b.hasNext(base): break
+    bb = b.next(base)
+
+  # add the rest of 'full':
+  while true:
+    if ff[1] >= ff[0]:
+      if result.len > 0 and result[^1] != sep:
+        result.add sep
+      for i in 0..ff[1] - ff[0]:
+        result.add full[i + ff[0]]
+    if not f.hasNext(full): break
+    ff = f.next(full)
+
+when true:
+  proc eqImpl(x, y: string): bool =
+    when FileSystemCaseSensitive:
+      result = cmpIgnoreCase(canon x, canon y) == 0
+    else:
+      result = canon(x) == canon(y)
+
+  proc `==`*(x, y: AbsoluteFile): bool = eqImpl(x.string, y.string)
+  proc `==`*(x, y: AbsoluteDir): bool = eqImpl(x.string, y.string)
+  proc `==`*(x, y: RelativeFile): bool = eqImpl(x.string, y.string)
+  proc `==`*(x, y: RelativeDir): bool = eqImpl(x.string, y.string)
+
+  proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile =
+    #assert isAbsolute(base.string)
+    assert(not isAbsolute(f.string))
+    result = AbsoluteFile newStringOfCap(base.string.len + f.string.len)
+    var state = 0
+    canon(base.string, result.string, state)
+    canon(f.string, result.string, state)
+
+  proc `/`*(base: AbsoluteDir; f: RelativeDir): AbsoluteDir =
+    #assert isAbsolute(base.string)
+    assert(not isAbsolute(f.string))
+    result = AbsoluteDir newStringOfCap(base.string.len + f.string.len)
+    var state = 0
+    canon(base.string, result.string, state)
+    canon(f.string, result.string, state)
+
+  proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir;
+                   sep = canonDirSep): RelativeFile =
+    RelativeFile(relativeTo(fullPath.string, baseFilename.string, sep))
+
+  proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile =
+    if isAbsolute(file): result = AbsoluteFile(file)
+    else: result = base / RelativeFile file
+
+  proc changeFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
+  proc changeFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
+
+  proc addFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.}
+  proc addFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.}
+
+  proc writeFile*(x: AbsoluteFile; content: string) {.borrow.}
+
+when isMainModule:
+  doAssert canon"/foo/../bar" == "/bar"
+  doAssert canon"foo/../bar" == "bar"
+
+  doAssert canon"/f/../bar///" == "/bar"
+  doAssert canon"f/..////bar" == "bar"
+
+  doAssert canon"../bar" == "../bar"
+  doAssert canon"/../bar" == "/../bar"
+
+  doAssert canon("foo/../../bar/") == "../bar"
+  doAssert canon("./bla/blob/") == "bla/blob"
+  doAssert canon(".hiddenFile") == ".hiddenFile"
+  doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim"
+
+  doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long"
+  doAssert canon("") == ""
+  doAssert canon("foobar") == "foobar"
+  doAssert canon("f/////////") == "f"
+
+  doAssert relativeTo("/foo/bar//baz.nim", "/foo") == "bar/baz.nim"
+
+  doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other/bad") == "../../me/bar/z.nim"
+
+  doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other") == "../me/bar/z.nim"
+  doAssert relativeTo("/Users///me/bar//z.nim", "//Users/") == "me/bar/z.nim"
+  doAssert relativeTo("/Users/me/bar/z.nim", "/Users/me") == "bar/z.nim"
+  doAssert relativeTo("", "/users/moo") == ""
+  doAssert relativeTo("foo", "") == "foo"
+
+  doAssert AbsoluteDir"/Users/me///" / RelativeFile"z.nim" == AbsoluteFile"/Users/me/z.nim"
+  doAssert relativeTo("/foo/bar.nim", "/foo/") == "bar.nim"
+
+when isMainModule and defined(windows):
+  let nasty = string(AbsoluteDir(r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\linkedPkgs\pkgB-#head\../../simplePkgs/pkgB-#head/") / RelativeFile"pkgA/module.nim")
+  doAssert nasty.replace('/', '\\') == r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\simplePkgs\pkgB-#head\pkgA\module.nim"
diff --git a/compiler/patterns.nim b/compiler/patterns.nim
index 859fe2a81..ebb3a7c1d 100644
--- a/compiler/patterns.nim
+++ b/compiler/patterns.nim
@@ -21,14 +21,17 @@ type
     formals: int
     c: PContext
     subMatch: bool       # subnode matches are special
+    mappingIsFull: bool
   PPatternContext = var TPatternContext
 
 proc getLazy(c: PPatternContext, sym: PSym): PNode =
-  if not isNil(c.mapping):
+  if c.mappingIsFull:
     result = c.mapping[sym.position]
 
 proc putLazy(c: PPatternContext, sym: PSym, n: PNode) =
-  if isNil(c.mapping): newSeq(c.mapping, c.formals)
+  if not c.mappingIsFull:
+    newSeq(c.mapping, c.formals)
+    c.mappingIsFull = true
   c.mapping[sym.position] = n
 
 proc matches(c: PPatternContext, p, n: PNode): bool
@@ -63,7 +66,7 @@ proc sameTrees(a, b: PNode): bool =
 
 proc inSymChoice(sc, x: PNode): bool =
   if sc.kind == nkClosedSymChoice:
-    for i in 0.. <sc.len:
+    for i in 0..<sc.len:
       if sc.sons[i].sym == x.sym: return true
   elif sc.kind == nkOpenSymChoice:
     # same name suffices for open sym choices!
@@ -77,13 +80,13 @@ proc checkTypes(c: PPatternContext, p: PSym, n: PNode): bool =
   if isNil(n.typ):
     result = p.typ.kind in {tyVoid, tyStmt}
   else:
-    result = sigmatch.argtypeMatches(c.c, p.typ, n.typ)
+    result = sigmatch.argtypeMatches(c.c, p.typ, n.typ, fromHlo = true)
 
 proc isPatternParam(c: PPatternContext, p: PNode): bool {.inline.} =
   result = p.kind == nkSym and p.sym.kind == skParam and p.sym.owner == c.owner
 
 proc matchChoice(c: PPatternContext, p, n: PNode): bool =
-  for i in 1 .. <p.len:
+  for i in 1 ..< p.len:
     if matches(c, p.sons[i], n): return true
 
 proc bindOrCheck(c: PPatternContext, param: PSym, n: PNode): bool =
@@ -115,7 +118,7 @@ proc matchNested(c: PPatternContext, p, n: PNode, rpn: bool): bool =
       if rpn: arglist.add(n.sons[0])
     elif n.kind == nkHiddenStdConv and n.sons[1].kind == nkBracket:
       let n = n.sons[1]
-      for i in 0.. <n.len:
+      for i in 0..<n.len:
         if not matchStarAux(c, op, n[i], arglist, rpn): return false
     elif checkTypes(c, p.sons[2].sym, n):
       add(arglist, n)
@@ -150,7 +153,7 @@ proc matches(c: PPatternContext, p, n: PNode): bool =
     of "*": result = matchNested(c, p, n, rpn=false)
     of "**": result = matchNested(c, p, n, rpn=true)
     of "~": result = not matches(c, p.sons[1], n)
-    else: internalError(p.info, "invalid pattern")
+    else: doAssert(false, "invalid pattern")
     # template {add(a, `&` * b)}(a: string{noalias}, b: varargs[string]) =
     #   add(a, b)
   elif p.kind == nkCurlyExpr:
@@ -186,7 +189,7 @@ proc matches(c: PPatternContext, p, n: PNode): bool =
             # unpack varargs:
             let n = lastSon(n).sons[1]
             arglist = newNodeI(nkArgList, n.info, n.len)
-            for i in 0.. <n.len: arglist.sons[i] = n.sons[i]
+            for i in 0..<n.len: arglist.sons[i] = n.sons[i]
           else:
             arglist = newNodeI(nkArgList, n.info, sonsLen(n) - plen + 1)
             # f(1, 2, 3)
@@ -206,10 +209,14 @@ proc matches(c: PPatternContext, p, n: PNode): bool =
 
 proc matchStmtList(c: PPatternContext, p, n: PNode): PNode =
   proc matchRange(c: PPatternContext, p, n: PNode, i: int): bool =
-    for j in 0 .. <p.len:
+    for j in 0 ..< p.len:
       if not matches(c, p.sons[j], n.sons[i+j]):
         # we need to undo any bindings:
-        if not isNil(c.mapping): c.mapping = nil
+        when defined(nimNoNilSeqs):
+          c.mapping = @[]
+          c.mappingIsFull = false
+        else:
+          if not isNil(c.mapping): c.mapping = nil
         return false
     result = true
 
@@ -229,7 +236,7 @@ proc matchStmtList(c: PPatternContext, p, n: PNode): PNode =
 
 proc aliasAnalysisRequested(params: PNode): bool =
   if params.len >= 2:
-    for i in 1 .. < params.len:
+    for i in 1 ..< params.len:
       let param = params.sons[i].sym
       if whichAlias(param) != aqNone: return true
 
@@ -237,7 +244,7 @@ proc addToArgList(result, n: PNode) =
   if n.typ != nil and n.typ.kind != tyStmt:
     if n.kind != nkArgList: result.add(n)
     else:
-      for i in 0 .. <n.len: result.add(n.sons[i])
+      for i in 0 ..< n.len: result.add(n.sons[i])
 
 proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
   ## returns a tree to semcheck if the rule triggered; nil otherwise
@@ -256,7 +263,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
   var args: PNode
   if requiresAA:
     args = newNodeI(nkArgList, n.info)
-  for i in 1 .. < params.len:
+  for i in 1 ..< params.len:
     let param = params.sons[i].sym
     let x = getLazy(ctx, param)
     # couldn't bind parameter:
@@ -265,7 +272,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
     if requiresAA: addToArgList(args, x)
   # perform alias analysis here:
   if requiresAA:
-    for i in 1 .. < params.len:
+    for i in 1 ..< params.len:
       var rs = result.sons[i]
       let param = params.sons[i].sym
       case whichAlias(param)
@@ -289,7 +296,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
         # constraint not fulfilled:
         if not ok: return nil
 
-  markUsed(n.info, s, c.graph.usageSym)
+  markUsed(c.config, n.info, s, c.graph.usageSym)
   if ctx.subMatch:
     assert m.len == 3
     m.sons[1] = result
diff --git a/compiler/pbraces.nim b/compiler/pbraces.nim
deleted file mode 100644
index b59fbc6cf..000000000
--- a/compiler/pbraces.nim
+++ /dev/null
@@ -1,1780 +0,0 @@
-#
-#
-#           The Nim Compiler
-#        (c) Copyright 2017 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-# This module implements the parser of the braces Nim syntax.
-
-import
-  llstream, lexer, idents, strutils, ast, astalgo, msgs
-
-from parser import TParser
-
-proc getTok(p: var TParser) =
-  ## Get the next token from the parser's lexer, and store it in the parser's
-  ## `tok` member.
-  rawGetTok(p.lex, p.tok)
-
-proc openParser*(p: var TParser, fileIdx: int32, inputStream: PLLStream;
-                 cache: IdentCache) =
-  ## Open a parser, using the given arguments to set up its internal state.
-  ##
-  initToken(p.tok)
-  openLexer(p.lex, fileIdx, inputStream, cache)
-  getTok(p)                   # read the first token
-  p.lex.allowTabs = true
-
-proc openParser*(p: var TParser, filename: string, inputStream: PLLStream;
-                 cache: IdentCache) =
-  openParser(p, filename.fileInfoIdx, inputStream, cache)
-
-proc closeParser*(p: var TParser) =
-  ## Close a parser, freeing up its resources.
-  closeLexer(p.lex)
-
-proc parMessage(p: TParser, msg: TMsgKind, arg = "") =
-  ## Produce and emit the parser message `arg` to output.
-  lexMessageTok(p.lex, msg, p.tok, arg)
-
-proc parMessage(p: TParser, msg: TMsgKind, tok: TToken) =
-  ## Produce and emit a parser message to output about the token `tok`
-  parMessage(p, msg, prettyTok(tok))
-
-proc rawSkipComment(p: var TParser, node: PNode) =
-  if p.tok.tokType == tkComment:
-    if node != nil:
-      if node.comment == nil: node.comment = ""
-      add(node.comment, p.tok.literal)
-    else:
-      parMessage(p, errInternal, "skipComment")
-    getTok(p)
-
-proc skipComment(p: var TParser, node: PNode) =
-  rawSkipComment(p, node)
-
-proc flexComment(p: var TParser, node: PNode) =
-  rawSkipComment(p, node)
-
-proc skipInd(p: var TParser) = discard
-proc optPar(p: var TParser) = discard
-
-proc optInd(p: var TParser, n: PNode) =
-  skipComment(p, n)
-
-proc getTokNoInd(p: var TParser) =
-  getTok(p)
-
-proc expectIdentOrKeyw(p: TParser) =
-  if p.tok.tokType != tkSymbol and not isKeyword(p.tok.tokType):
-    lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok))
-
-proc expectIdent(p: TParser) =
-  if p.tok.tokType != tkSymbol:
-    lexMessage(p.lex, errIdentifierExpected, prettyTok(p.tok))
-
-proc eat(p: var TParser, tokType: TTokType) =
-  ## Move the parser to the next token if the current token is of type
-  ## `tokType`, otherwise error.
-  if p.tok.tokType == tokType:
-    getTok(p)
-  else:
-    lexMessageTok(p.lex, errTokenExpected, p.tok, TokTypeToStr[tokType])
-
-proc parLineInfo(p: TParser): TLineInfo =
-  ## Retrieve the line information associated with the parser's current state.
-  result = getLineInfo(p.lex, p.tok)
-
-proc indAndComment(p: var TParser, n: PNode) =
-  rawSkipComment(p, n)
-
-proc newNodeP(kind: TNodeKind, p: TParser): PNode =
-  result = newNodeI(kind, parLineInfo(p))
-
-proc newIntNodeP(kind: TNodeKind, intVal: BiggestInt, p: TParser): PNode =
-  result = newNodeP(kind, p)
-  result.intVal = intVal
-
-proc newFloatNodeP(kind: TNodeKind, floatVal: BiggestFloat,
-                   p: TParser): PNode =
-  result = newNodeP(kind, p)
-  result.floatVal = floatVal
-
-proc newStrNodeP(kind: TNodeKind, strVal: string, p: TParser): PNode =
-  result = newNodeP(kind, p)
-  result.strVal = strVal
-
-proc newIdentNodeP(ident: PIdent, p: TParser): PNode =
-  result = newNodeP(nkIdent, p)
-  result.ident = ident
-
-proc parseExpr(p: var TParser): PNode
-proc parseStmt(p: var TParser): PNode
-proc parseTypeDesc(p: var TParser): PNode
-proc parseDoBlocks(p: var TParser, call: PNode)
-proc parseParamList(p: var TParser, retColon = true): PNode
-proc parseStmtPragma(p: var TParser): PNode
-proc parseCase(p: var TParser): PNode
-proc parseTry(p: var TParser): PNode
-
-proc isSigilLike(tok: TToken): bool {.inline.} =
-  result = tok.tokType == tkOpr and tok.ident.s[0] == '@'
-
-proc isAt(tok: TToken): bool {.inline.} =
-  tok.tokType == tkOpr and tok.ident.s == "@" and tok.strongSpaceB == 0
-
-proc isRightAssociative(tok: TToken): bool {.inline.} =
-  ## Determines whether the token is right assocative.
-  result = tok.tokType == tkOpr and tok.ident.s[0] == '^'
-  # or (let L = tok.ident.s.len; L > 1 and tok.ident.s[L-1] == '>'))
-
-proc getPrecedence(tok: TToken): int =
-  ## Calculates the precedence of the given token.
-  template considerStrongSpaces(x): untyped = x
-
-  case tok.tokType
-  of tkOpr:
-    let L = tok.ident.s.len
-    let relevantChar = tok.ident.s[0]
-
-    # arrow like?
-    if L > 1 and tok.ident.s[L-1] == '>' and
-      tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1)
-
-    template considerAsgn(value: untyped) =
-      result = if tok.ident.s[L-1] == '=': 1 else: value
-
-    case relevantChar
-    of '$', '^': considerAsgn(10)
-    of '*', '%', '/', '\\': considerAsgn(9)
-    of '~': result = 8
-    of '+', '-', '|': considerAsgn(8)
-    of '&': considerAsgn(7)
-    of '=', '<', '>', '!': result = 5
-    of '.': considerAsgn(6)
-    of '?': result = 2
-    else: considerAsgn(2)
-  of tkDiv, tkMod, tkShl, tkShr: result = 9
-  of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5
-  of tkDotDot: result = 6
-  of tkAnd: result = 4
-  of tkOr, tkXor, tkPtr, tkRef: result = 3
-  else: return -10
-  result = considerStrongSpaces(result)
-
-proc isOperator(tok: TToken): bool =
-  ## Determines if the given token is an operator type token.
-  tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
-                  tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor}
-
-proc isUnary(p: TParser): bool =
-  ## Check if the current parser token is a unary operator
-  if p.tok.tokType in {tkOpr, tkDotDot}:
-      result = true
-
-proc checkBinary(p: TParser) {.inline.} =
-  ## Check if the current parser token is a binary operator.
-  # we don't check '..' here as that's too annoying
-  discard
-
-#| module = stmt ^* (';' / IND{=})
-#|
-#| comma = ',' COMMENT?
-#| semicolon = ';' COMMENT?
-#| colon = ':' COMMENT?
-#| colcom = ':' COMMENT?
-#|
-#| operator =  OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9
-#|          | 'or' | 'xor' | 'and'
-#|          | 'is' | 'isnot' | 'in' | 'notin' | 'of'
-#|          | 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..'
-#|
-#| prefixOperator = operator
-#|
-#| optInd = COMMENT?
-#| optPar = (IND{>} | IND{=})?
-#|
-#| simpleExpr = arrowExpr (OP0 optInd arrowExpr)*
-#| arrowExpr = assignExpr (OP1 optInd assignExpr)*
-#| assignExpr = orExpr (OP2 optInd orExpr)*
-#| orExpr = andExpr (OP3 optInd andExpr)*
-#| andExpr = cmpExpr (OP4 optInd cmpExpr)*
-#| cmpExpr = sliceExpr (OP5 optInd sliceExpr)*
-#| sliceExpr = ampExpr (OP6 optInd ampExpr)*
-#| ampExpr = plusExpr (OP7 optInd plusExpr)*
-#| plusExpr = mulExpr (OP8 optInd mulExpr)*
-#| mulExpr = dollarExpr (OP9 optInd dollarExpr)*
-#| dollarExpr = primary (OP10 optInd primary)*
-
-proc colcom(p: var TParser, n: PNode) =
-  skipComment(p, n)
-
-proc parseSymbol(p: var TParser, allowNil = false): PNode =
-  #| symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`'
-  #|        | IDENT | 'addr' | 'type'
-  case p.tok.tokType
-  of tkSymbol, tkAddr, tkType:
-    result = newIdentNodeP(p.tok.ident, p)
-    getTok(p)
-  of tkAccent:
-    result = newNodeP(nkAccQuoted, p)
-    getTok(p)
-    while true:
-      case p.tok.tokType
-      of tkAccent:
-        if result.len == 0:
-          parMessage(p, errIdentifierExpected, p.tok)
-        break
-      of tkOpr, tkDot, tkDotDot, tkEquals, tkParLe..tkParDotRi:
-        var accm = ""
-        while p.tok.tokType in {tkOpr, tkDot, tkDotDot, tkEquals,
-                                tkParLe..tkParDotRi}:
-          accm.add(tokToStr(p.tok))
-          getTok(p)
-        result.add(newIdentNodeP(p.lex.cache.getIdent(accm), p))
-      of tokKeywordLow..tokKeywordHigh, tkSymbol, tkIntLit..tkCharLit:
-        result.add(newIdentNodeP(p.lex.cache.getIdent(tokToStr(p.tok)), p))
-        getTok(p)
-      else:
-        parMessage(p, errIdentifierExpected, p.tok)
-        break
-    eat(p, tkAccent)
-  else:
-    if allowNil and p.tok.tokType == tkNil:
-      result = newNodeP(nkNilLit, p)
-      getTok(p)
-    else:
-      parMessage(p, errIdentifierExpected, p.tok)
-      # BUGFIX: We must consume a token here to prevent endless loops!
-      # But: this really sucks for idetools and keywords, so we don't do it
-      # if it is a keyword:
-      if not isKeyword(p.tok.tokType): getTok(p)
-      result = ast.emptyNode
-
-proc colonOrEquals(p: var TParser, a: PNode): PNode =
-  if p.tok.tokType == tkColon:
-    result = newNodeP(nkExprColonExpr, p)
-    getTok(p)
-    #optInd(p, result)
-    addSon(result, a)
-    addSon(result, parseExpr(p))
-  elif p.tok.tokType == tkEquals:
-    result = newNodeP(nkExprEqExpr, p)
-    getTok(p)
-    #optInd(p, result)
-    addSon(result, a)
-    addSon(result, parseExpr(p))
-  else:
-    result = a
-
-proc exprColonEqExpr(p: var TParser): PNode =
-  #| exprColonEqExpr = expr (':'|'=' expr)?
-  var a = parseExpr(p)
-  result = colonOrEquals(p, a)
-
-proc exprList(p: var TParser, endTok: TTokType, result: PNode) =
-  #| exprList = expr ^+ comma
-  getTok(p)
-  optInd(p, result)
-  while (p.tok.tokType != endTok) and (p.tok.tokType != tkEof):
-    var a = parseExpr(p)
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    optInd(p, a)
-
-proc dotExpr(p: var TParser, a: PNode): PNode =
-  #| dotExpr = expr '.' optInd symbol
-  var info = p.parLineInfo
-  getTok(p)
-  result = newNodeI(nkDotExpr, info)
-  optInd(p, result)
-  addSon(result, a)
-  addSon(result, parseSymbol(p))
-
-proc qualifiedIdent(p: var TParser): PNode =
-  #| qualifiedIdent = symbol ('.' optInd symbol)?
-  result = parseSymbol(p)
-  if p.tok.tokType == tkDot: result = dotExpr(p, result)
-
-proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) =
-  assert(endTok in {tkCurlyLe, tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi})
-  getTok(p)
-  optInd(p, result)
-  while p.tok.tokType != endTok and p.tok.tokType != tkEof:
-    var a = exprColonEqExpr(p)
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    skipComment(p, a)
-  optPar(p)
-  eat(p, endTok)
-
-proc exprColonEqExprList(p: var TParser, kind: TNodeKind,
-                         endTok: TTokType): PNode =
-  #| exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)?
-  result = newNodeP(kind, p)
-  exprColonEqExprListAux(p, endTok, result)
-
-proc setOrTableConstr(p: var TParser): PNode =
-  result = newNodeP(nkCurly, p)
-  getTok(p)
-  optInd(p, result)
-  if p.tok.tokType == tkColon:
-    getTok(p) # skip ':'
-    result.kind = nkTableConstr
-  else:
-    while p.tok.tokType notin {tkBracketDotRi, tkEof}:
-      var a = exprColonEqExpr(p)
-      if a.kind == nkExprColonExpr: result.kind = nkTableConstr
-      addSon(result, a)
-      if p.tok.tokType != tkComma: break
-      getTok(p)
-      skipComment(p, a)
-  optPar(p)
-  eat(p, tkBracketDotRi)
-
-proc parseCast(p: var TParser): PNode =
-  #| castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')'
-  result = newNodeP(nkCast, p)
-  getTok(p)
-  eat(p, tkBracketLe)
-  optInd(p, result)
-  addSon(result, parseTypeDesc(p))
-  optPar(p)
-  eat(p, tkBracketRi)
-  eat(p, tkParLe)
-  optInd(p, result)
-  addSon(result, parseExpr(p))
-  optPar(p)
-  eat(p, tkParRi)
-
-proc setBaseFlags(n: PNode, base: TNumericalBase) =
-  case base
-  of base10: discard
-  of base2: incl(n.flags, nfBase2)
-  of base8: incl(n.flags, nfBase8)
-  of base16: incl(n.flags, nfBase16)
-
-proc parseGStrLit(p: var TParser, a: PNode): PNode =
-  case p.tok.tokType
-  of tkGStrLit:
-    result = newNodeP(nkCallStrLit, p)
-    addSon(result, a)
-    addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p))
-    getTok(p)
-  of tkGTripleStrLit:
-    result = newNodeP(nkCallStrLit, p)
-    addSon(result, a)
-    addSon(result, newStrNodeP(nkTripleStrLit, p.tok.literal, p))
-    getTok(p)
-  else:
-    result = a
-
-type
-  TPrimaryMode = enum pmNormal, pmTypeDesc, pmTypeDef, pmSkipSuffix
-
-proc complexOrSimpleStmt(p: var TParser): PNode
-proc simpleExpr(p: var TParser, mode = pmNormal): PNode
-
-proc semiStmtList(p: var TParser, result: PNode) =
-  inc p.inSemiStmtList
-  result.add(complexOrSimpleStmt(p))
-  while p.tok.tokType == tkSemiColon:
-    getTok(p)
-    optInd(p, result)
-    result.add(complexOrSimpleStmt(p))
-  dec p.inSemiStmtList
-  result.kind = nkStmtListExpr
-
-proc parsePar(p: var TParser): PNode =
-  #| parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try'
-  #|         | 'finally' | 'except' | 'for' | 'block' | 'const' | 'let'
-  #|         | 'when' | 'var' | 'mixin'
-  #| par = '(' optInd
-  #|           ( &parKeyw complexOrSimpleStmt ^+ ';'
-  #|           | ';' complexOrSimpleStmt ^+ ';'
-  #|           | pragmaStmt
-  #|           | simpleExpr ( ('=' expr (';' complexOrSimpleStmt ^+ ';' )? )
-  #|                        | (':' expr (',' exprColonEqExpr     ^+ ',' )? ) ) )
-  #|           optPar ')'
-  #
-  # unfortunately it's ambiguous: (expr: expr) vs (exprStmt); however a
-  # leading ';' could be used to enforce a 'stmt' context ...
-  result = newNodeP(nkPar, p)
-  getTok(p)
-  optInd(p, result)
-  if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase,
-                       tkTry, tkDefer, tkFinally, tkExcept, tkFor, tkBlock,
-                       tkConst, tkLet, tkWhen, tkVar,
-                       tkMixin}:
-    # XXX 'bind' used to be an expression, so we exclude it here;
-    # tests/reject/tbind2 fails otherwise.
-    semiStmtList(p, result)
-  elif p.tok.tokType == tkSemiColon:
-    # '(;' enforces 'stmt' context:
-    getTok(p)
-    optInd(p, result)
-    semiStmtList(p, result)
-  elif p.tok.tokType == tkCurlyDotLe:
-    result.add(parseStmtPragma(p))
-  elif p.tok.tokType != tkParRi:
-    var a = simpleExpr(p)
-    if p.tok.tokType == tkEquals:
-      # special case: allow assignments
-      getTok(p)
-      optInd(p, result)
-      let b = parseExpr(p)
-      let asgn = newNodeI(nkAsgn, a.info, 2)
-      asgn.sons[0] = a
-      asgn.sons[1] = b
-      result.add(asgn)
-      if p.tok.tokType == tkSemiColon:
-        semiStmtList(p, result)
-    elif p.tok.tokType == tkSemiColon:
-      # stmt context:
-      result.add(a)
-      semiStmtList(p, result)
-    else:
-      a = colonOrEquals(p, a)
-      result.add(a)
-      if p.tok.tokType == tkComma:
-        getTok(p)
-        skipComment(p, a)
-        while p.tok.tokType != tkParRi and p.tok.tokType != tkEof:
-          var a = exprColonEqExpr(p)
-          addSon(result, a)
-          if p.tok.tokType != tkComma: break
-          getTok(p)
-          skipComment(p, a)
-  optPar(p)
-  eat(p, tkParRi)
-
-proc identOrLiteral(p: var TParser, mode: TPrimaryMode): PNode =
-  #| literal = | INT_LIT | INT8_LIT | INT16_LIT | INT32_LIT | INT64_LIT
-  #|           | UINT_LIT | UINT8_LIT | UINT16_LIT | UINT32_LIT | UINT64_LIT
-  #|           | FLOAT_LIT | FLOAT32_LIT | FLOAT64_LIT
-  #|           | STR_LIT | RSTR_LIT | TRIPLESTR_LIT
-  #|           | CHAR_LIT
-  #|           | NIL
-  #| generalizedLit = GENERALIZED_STR_LIT | GENERALIZED_TRIPLESTR_LIT
-  #| identOrLiteral = generalizedLit | symbol | literal
-  #|                | par | arrayConstr | setOrTableConstr
-  #|                | castExpr
-  #| tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')'
-  #| arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']'
-  case p.tok.tokType
-  of tkSymbol, tkType, tkAddr:
-    result = newIdentNodeP(p.tok.ident, p)
-    getTok(p)
-    result = parseGStrLit(p, result)
-  of tkAccent:
-    result = parseSymbol(p)       # literals
-  of tkIntLit:
-    result = newIntNodeP(nkIntLit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkInt8Lit:
-    result = newIntNodeP(nkInt8Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkInt16Lit:
-    result = newIntNodeP(nkInt16Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkInt32Lit:
-    result = newIntNodeP(nkInt32Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkInt64Lit:
-    result = newIntNodeP(nkInt64Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkUIntLit:
-    result = newIntNodeP(nkUIntLit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkUInt8Lit:
-    result = newIntNodeP(nkUInt8Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkUInt16Lit:
-    result = newIntNodeP(nkUInt16Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkUInt32Lit:
-    result = newIntNodeP(nkUInt32Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkUInt64Lit:
-    result = newIntNodeP(nkUInt64Lit, p.tok.iNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkFloatLit:
-    result = newFloatNodeP(nkFloatLit, p.tok.fNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkFloat32Lit:
-    result = newFloatNodeP(nkFloat32Lit, p.tok.fNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkFloat64Lit:
-    result = newFloatNodeP(nkFloat64Lit, p.tok.fNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkFloat128Lit:
-    result = newFloatNodeP(nkFloat128Lit, p.tok.fNumber, p)
-    setBaseFlags(result, p.tok.base)
-    getTok(p)
-  of tkStrLit:
-    result = newStrNodeP(nkStrLit, p.tok.literal, p)
-    getTok(p)
-  of tkRStrLit:
-    result = newStrNodeP(nkRStrLit, p.tok.literal, p)
-    getTok(p)
-  of tkTripleStrLit:
-    result = newStrNodeP(nkTripleStrLit, p.tok.literal, p)
-    getTok(p)
-  of tkCharLit:
-    result = newIntNodeP(nkCharLit, ord(p.tok.literal[0]), p)
-    getTok(p)
-  of tkNil:
-    result = newNodeP(nkNilLit, p)
-    getTok(p)
-  of tkParLe:
-    # () constructor
-    if mode in {pmTypeDesc, pmTypeDef}:
-      result = exprColonEqExprList(p, nkPar, tkParRi)
-    else:
-      result = parsePar(p)
-  of tkBracketDotLe:
-    # {} constructor
-    result = setOrTableConstr(p)
-  of tkBracketLe:
-    # [] constructor
-    result = exprColonEqExprList(p, nkBracket, tkBracketRi)
-  of tkCast:
-    result = parseCast(p)
-  else:
-    parMessage(p, errExprExpected, p.tok)
-    getTok(p)  # we must consume a token here to prevend endless loops!
-    result = ast.emptyNode
-
-proc namedParams(p: var TParser, callee: PNode,
-                 kind: TNodeKind, endTok: TTokType): PNode =
-  let a = callee
-  result = newNodeP(kind, p)
-  addSon(result, a)
-  exprColonEqExprListAux(p, endTok, result)
-
-proc parseMacroColon(p: var TParser, x: PNode): PNode
-proc primarySuffix(p: var TParser, r: PNode): PNode =
-  #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' doBlocks?
-  #|       | doBlocks
-  #|       | '.' optInd symbol generalizedLit?
-  #|       | '[' optInd indexExprList optPar ']'
-  #|       | '{' optInd indexExprList optPar '}'
-  #|       | &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax
-  result = r
-
-  template somePar() = discard
-  while p.tok.indent < 0:
-    case p.tok.tokType
-    of tkParLe:
-      somePar()
-      result = namedParams(p, result, nkCall, tkParRi)
-      if result.len > 1 and result.sons[1].kind == nkExprColonExpr:
-        result.kind = nkObjConstr
-      else:
-        parseDoBlocks(p, result)
-    of tkDo:
-      var a = result
-      result = newNodeP(nkCall, p)
-      addSon(result, a)
-      parseDoBlocks(p, result)
-    of tkDot:
-      result = dotExpr(p, result)
-      result = parseGStrLit(p, result)
-    of tkBracketLe:
-      somePar()
-      result = namedParams(p, result, nkBracketExpr, tkBracketRi)
-    of tkBracketDotLe:
-      somePar()
-      result = namedParams(p, result, nkCurlyExpr, tkBracketDotRi)
-    of tkSymbol, tkAccent, tkIntLit..tkCharLit, tkNil, tkCast, tkAddr, tkType:
-      if p.inPragma == 0:
-        # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet
-        # solution, but pragmas.nim can't handle that
-        let a = result
-        result = newNodeP(nkCommand, p)
-        addSon(result, a)
-        when true:
-          addSon result, parseExpr(p)
-        else:
-          while p.tok.tokType != tkEof:
-            let x = parseExpr(p)
-            addSon(result, x)
-            if p.tok.tokType != tkComma: break
-            getTok(p)
-            optInd(p, x)
-          if p.tok.tokType == tkDo:
-            parseDoBlocks(p, result)
-          else:
-            result = parseMacroColon(p, result)
-      break
-    else:
-      break
-
-proc primary(p: var TParser, mode: TPrimaryMode): PNode
-proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode
-
-proc parseOperators(p: var TParser, headNode: PNode,
-                    limit: int, mode: TPrimaryMode): PNode =
-  result = headNode
-  # expand while operators have priorities higher than 'limit'
-  var opPrec = getPrecedence(p.tok)
-  let modeB = if mode == pmTypeDef: pmTypeDesc else: mode
-  # the operator itself must not start on a new line:
-  while opPrec >= limit and p.tok.indent < 0 and not isAt(p.tok):
-    checkBinary(p)
-    var leftAssoc = 1-ord(isRightAssociative(p.tok))
-    var a = newNodeP(nkInfix, p)
-    var opNode = newIdentNodeP(p.tok.ident, p) # skip operator:
-    getTok(p)
-    optInd(p, a)
-    # read sub-expression with higher priority:
-    var b = simpleExprAux(p, opPrec + leftAssoc, modeB)
-    addSon(a, opNode)
-    addSon(a, result)
-    addSon(a, b)
-    result = a
-    opPrec = getPrecedence(p.tok)
-
-proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
-  result = primary(p, mode)
-  result = parseOperators(p, result, limit, mode)
-
-proc simpleExpr(p: var TParser, mode = pmNormal): PNode =
-  result = simpleExprAux(p, -1, mode)
-
-proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode =
-  #| condExpr = expr colcom expr optInd
-  #|         ('elif' expr colcom expr optInd)*
-  #|          'else' colcom expr
-  #| ifExpr = 'if' condExpr
-  #| whenExpr = 'when' condExpr
-  result = newNodeP(kind, p)
-  while true:
-    getTok(p)                 # skip `if`, `elif`
-    var branch = newNodeP(nkElifExpr, p)
-    addSon(branch, parseExpr(p))
-    colcom(p, branch)
-    addSon(branch, parseExpr(p))
-    optInd(p, branch)
-    addSon(result, branch)
-    if p.tok.tokType != tkElif: break
-  var branch = newNodeP(nkElseExpr, p)
-  eat(p, tkElse)
-  colcom(p, branch)
-  addSon(branch, parseExpr(p))
-  addSon(result, branch)
-
-proc parsePragma(p: var TParser): PNode =
-  result = newNodeP(nkPragma, p)
-  inc p.inPragma
-  if isAt(p.tok):
-    while isAt(p.tok):
-      getTok(p)
-      var a = parseExpr(p)
-      optInd(p, a)
-      if a.kind in nkCallKinds and a.len == 2:
-        let repaired = newNodeI(nkExprColonExpr, a.info)
-        repaired.add a[0]
-        repaired.add a[1]
-        a = repaired
-      addSon(result, a)
-      skipComment(p, a)
-  else:
-    getTok(p)
-    optInd(p, result)
-    while p.tok.tokType notin {tkCurlyDotRi, tkCurlyRi, tkEof}:
-      var a = exprColonEqExpr(p)
-      addSon(result, a)
-      if p.tok.tokType == tkComma:
-        getTok(p)
-        skipComment(p, a)
-    optPar(p)
-    if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: getTok(p)
-    else: parMessage(p, errTokenExpected, ".}")
-  dec p.inPragma
-
-proc identVis(p: var TParser; allowDot=false): PNode =
-  #| identVis = symbol opr?  # postfix position
-  #| identVisDot = symbol '.' optInd symbol opr?
-  var a = parseSymbol(p)
-  if p.tok.tokType == tkOpr:
-    result = newNodeP(nkPostfix, p)
-    addSon(result, newIdentNodeP(p.tok.ident, p))
-    addSon(result, a)
-    getTok(p)
-  elif p.tok.tokType == tkDot and allowDot:
-    result = dotExpr(p, a)
-  else:
-    result = a
-
-proc identWithPragma(p: var TParser; allowDot=false): PNode =
-  #| identWithPragma = identVis pragma?
-  #| identWithPragmaDot = identVisDot pragma?
-  var a = identVis(p, allowDot)
-  if p.tok.tokType == tkCurlyDotLe or isAt(p.tok):
-    result = newNodeP(nkPragmaExpr, p)
-    addSon(result, a)
-    addSon(result, parsePragma(p))
-  else:
-    result = a
-
-type
-  TDeclaredIdentFlag = enum
-    withPragma,               # identifier may have pragma
-    withBothOptional          # both ':' and '=' parts are optional
-  TDeclaredIdentFlags = set[TDeclaredIdentFlag]
-
-proc parseIdentColonEquals(p: var TParser, flags: TDeclaredIdentFlags): PNode =
-  #| declColonEquals = identWithPragma (comma identWithPragma)* comma?
-  #|                   (':' optInd typeDesc)? ('=' optInd expr)?
-  #| identColonEquals = ident (comma ident)* comma?
-  #|      (':' optInd typeDesc)? ('=' optInd expr)?)
-  var a: PNode
-  result = newNodeP(nkIdentDefs, p)
-  while true:
-    case p.tok.tokType
-    of tkSymbol, tkAccent:
-      if withPragma in flags: a = identWithPragma(p)
-      else: a = parseSymbol(p)
-      if a.kind == nkEmpty: return
-    else: break
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    optInd(p, a)
-  if p.tok.tokType == tkColon:
-    getTok(p)
-    optInd(p, result)
-    addSon(result, parseTypeDesc(p))
-  else:
-    addSon(result, ast.emptyNode)
-    if p.tok.tokType != tkEquals and withBothOptional notin flags:
-      parMessage(p, errColonOrEqualsExpected, p.tok)
-  if p.tok.tokType == tkEquals:
-    getTok(p)
-    optInd(p, result)
-    addSon(result, parseExpr(p))
-  else:
-    addSon(result, ast.emptyNode)
-
-proc parseTuple(p: var TParser): PNode =
-  result = newNodeP(nkTupleTy, p)
-  getTok(p)
-  if p.tok.tokType in {tkBracketLe, tkCurlyLe}:
-    let usedCurly = p.tok.tokType == tkCurlyLe
-    getTok(p)
-    optInd(p, result)
-    while p.tok.tokType in {tkSymbol, tkAccent}:
-      var a = parseIdentColonEquals(p, {})
-      addSon(result, a)
-      if p.tok.tokType notin {tkComma, tkSemiColon}: break
-      getTok(p)
-      skipComment(p, a)
-    optPar(p)
-    if usedCurly: eat(p, tkCurlyRi)
-    else: eat(p, tkBracketRi)
-  else:
-    result = newNodeP(nkTupleClassTy, p)
-
-proc parseParamList(p: var TParser, retColon = true): PNode =
-  #| paramList = '(' declColonEquals ^* (comma/semicolon) ')'
-  #| paramListArrow = paramList? ('->' optInd typeDesc)?
-  #| paramListColon = paramList? (':' optInd typeDesc)?
-  var a: PNode
-  result = newNodeP(nkFormalParams, p)
-  addSon(result, ast.emptyNode) # return type
-  let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0
-  if hasParLe:
-    getTok(p)
-    optInd(p, result)
-    while true:
-      case p.tok.tokType
-      of tkSymbol, tkAccent:
-        a = parseIdentColonEquals(p, {withBothOptional, withPragma})
-      of tkParRi:
-        break
-      else:
-        parMessage(p, errTokenExpected, ")")
-        break
-      addSon(result, a)
-      if p.tok.tokType notin {tkComma, tkSemiColon}: break
-      getTok(p)
-      skipComment(p, a)
-    optPar(p)
-    eat(p, tkParRi)
-  let hasRet = if retColon: p.tok.tokType == tkColon
-               else: p.tok.tokType == tkOpr and p.tok.ident.s == "->"
-  if hasRet and p.tok.indent < 0:
-    getTok(p)
-    optInd(p, result)
-    result.sons[0] = parseTypeDesc(p)
-  elif not retColon and not hasParle:
-    # Mark as "not there" in order to mark for deprecation in the semantic pass:
-    result = ast.emptyNode
-
-proc optPragmas(p: var TParser): PNode =
-  if p.tok.tokType == tkCurlyDotLe or isAt(p.tok):
-    result = parsePragma(p)
-  else:
-    result = ast.emptyNode
-
-proc parseDoBlock(p: var TParser): PNode =
-  #| doBlock = 'do' paramListArrow pragmas? colcom stmt
-  let info = parLineInfo(p)
-  getTok(p)
-  let params = parseParamList(p, retColon=false)
-  let pragmas = optPragmas(p)
-  colcom(p, result)
-  result = newProcNode(nkDo, info, parseStmt(p),
-                       params = params,
-                       pragmas = pragmas)
-
-proc parseDoBlocks(p: var TParser, call: PNode) =
-  #| doBlocks = doBlock ^* IND{=}
-  while p.tok.tokType == tkDo:
-    addSon(call, parseDoBlock(p))
-
-proc parseCurlyStmt(p: var TParser): PNode =
-  result = newNodeP(nkStmtList, p)
-  eat(p, tkCurlyLe)
-  result.add parseStmt(p)
-  while p.tok.tokType notin {tkEof, tkCurlyRi}:
-    if p.tok.tokType == tkSemicolon: getTok(p)
-    elif p.tok.indent < 0: break
-    result.add parseStmt(p)
-  eat(p, tkCurlyRi)
-
-proc parseProcExpr(p: var TParser, isExpr: bool): PNode =
-  #| procExpr = 'proc' paramListColon pragmas? ('=' COMMENT? stmt)?
-  # either a proc type or a anonymous proc
-  let info = parLineInfo(p)
-  getTok(p)
-  let hasSignature = p.tok.tokType in {tkParLe, tkColon} and p.tok.indent < 0
-  let params = parseParamList(p)
-  let pragmas = optPragmas(p)
-  if p.tok.tokType == tkCurlyLe and isExpr:
-    result = newProcNode(nkLambda, info, parseCurlyStmt(p),
-                         params = params,
-                         pragmas = pragmas)
-  else:
-    result = newNodeI(nkProcTy, info)
-    if hasSignature:
-      addSon(result, params)
-      addSon(result, pragmas)
-
-proc isExprStart(p: TParser): bool =
-  case p.tok.tokType
-  of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf,
-     tkProc, tkIterator, tkBind, tkAddr,
-     tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr,
-     tkTuple, tkObject, tkType, tkWhen, tkCase, tkOut:
-    result = true
-  else: result = false
-
-proc parseSymbolList(p: var TParser, result: PNode, allowNil = false) =
-  while true:
-    var s = parseSymbol(p, allowNil)
-    if s.kind == nkEmpty: break
-    addSon(result, s)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    optInd(p, s)
-
-proc parseTypeDescKAux(p: var TParser, kind: TNodeKind,
-                       mode: TPrimaryMode): PNode =
-  #| distinct = 'distinct' optInd typeDesc
-  result = newNodeP(kind, p)
-  getTok(p)
-  optInd(p, result)
-  if not isOperator(p.tok) and isExprStart(p):
-    addSon(result, primary(p, mode))
-  if kind == nkDistinctTy and p.tok.tokType in {tkWith, tkWithout}:
-    let nodeKind = if p.tok.tokType == tkWith: nkWith
-                   else: nkWithout
-    getTok(p)
-    let list = newNodeP(nodeKind, p)
-    result.addSon list
-    parseSymbolList(p, list, allowNil = true)
-
-proc parseExpr(p: var TParser): PNode =
-  #| expr = (ifExpr
-  #|       | whenExpr
-  #|       | caseExpr
-  #|       | tryExpr)
-  #|       / simpleExpr
-  case p.tok.tokType:
-  of tkIf: result = parseIfExpr(p, nkIfExpr)
-  of tkWhen: result = parseIfExpr(p, nkWhenExpr)
-  of tkCase: result = parseCase(p)
-  of tkTry: result = parseTry(p)
-  else: result = simpleExpr(p)
-
-proc parseEnum(p: var TParser): PNode
-proc parseObject(p: var TParser): PNode
-proc parseTypeClass(p: var TParser): PNode
-
-proc primary(p: var TParser, mode: TPrimaryMode): PNode =
-  #| typeKeyw = 'var' | 'out' | 'ref' | 'ptr' | 'shared' | 'tuple'
-  #|          | 'proc' | 'iterator' | 'distinct' | 'object' | 'enum'
-  #| primary = typeKeyw typeDescK
-  #|         /  prefixOperator* identOrLiteral primarySuffix*
-  #|         / 'static' primary
-  #|         / 'bind' primary
-  if isOperator(p.tok):
-    let isSigil = isSigilLike(p.tok)
-    result = newNodeP(nkPrefix, p)
-    var a = newIdentNodeP(p.tok.ident, p)
-    addSon(result, a)
-    getTok(p)
-    optInd(p, a)
-    if isSigil:
-      #XXX prefix operators
-      addSon(result, primary(p, pmSkipSuffix))
-      result = primarySuffix(p, result)
-    else:
-      addSon(result, primary(p, pmNormal))
-    return
-
-  case p.tok.tokType:
-  of tkTuple: result = parseTuple(p)
-  of tkProc: result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef})
-  of tkIterator:
-    result = parseProcExpr(p, mode notin {pmTypeDesc, pmTypeDef})
-    if result.kind == nkLambda: result.kind = nkIteratorDef
-    else: result.kind = nkIteratorTy
-  of tkEnum:
-    if mode == pmTypeDef:
-      result = parseEnum(p)
-    else:
-      result = newNodeP(nkEnumTy, p)
-      getTok(p)
-  of tkObject:
-    if mode == pmTypeDef:
-      result = parseObject(p)
-    else:
-      result = newNodeP(nkObjectTy, p)
-      getTok(p)
-  of tkConcept:
-    if mode == pmTypeDef:
-      result = parseTypeClass(p)
-    else:
-      parMessage(p, errInvalidToken, p.tok)
-  of tkStatic:
-    let info = parLineInfo(p)
-    getTokNoInd(p)
-    let next = primary(p, pmNormal)
-    if next.kind == nkBracket and next.sonsLen == 1:
-      result = newNode(nkStaticTy, info, @[next.sons[0]])
-    else:
-      result = newNode(nkStaticExpr, info, @[next])
-  of tkBind:
-    result = newNodeP(nkBind, p)
-    getTok(p)
-    optInd(p, result)
-    addSon(result, primary(p, pmNormal))
-  of tkVar: result = parseTypeDescKAux(p, nkVarTy, mode)
-  of tkOut: result = parseTypeDescKAux(p, nkVarTy, mode)
-  of tkRef: result = parseTypeDescKAux(p, nkRefTy, mode)
-  of tkPtr: result = parseTypeDescKAux(p, nkPtrTy, mode)
-  of tkDistinct: result = parseTypeDescKAux(p, nkDistinctTy, mode)
-  else:
-    result = identOrLiteral(p, mode)
-    if mode != pmSkipSuffix:
-      result = primarySuffix(p, result)
-
-proc parseTypeDesc(p: var TParser): PNode =
-  #| typeDesc = simpleExpr
-  result = simpleExpr(p, pmTypeDesc)
-
-proc parseTypeDefAux(p: var TParser): PNode =
-  #| typeDefAux = simpleExpr
-  #|            | 'concept' typeClass
-  result = simpleExpr(p, pmTypeDef)
-
-proc makeCall(n: PNode): PNode =
-  ## Creates a call if the given node isn't already a call.
-  if n.kind in nkCallKinds:
-    result = n
-  else:
-    result = newNodeI(nkCall, n.info)
-    result.add n
-
-proc parseMacroColon(p: var TParser, x: PNode): PNode =
-  #| macroColon = ':' stmt? ( IND{=} 'of' exprList ':' stmt
-  #|                        | IND{=} 'elif' expr ':' stmt
-  #|                        | IND{=} 'except' exprList ':' stmt
-  #|                        | IND{=} 'else' ':' stmt )*
-  result = x
-  if p.tok.tokType == tkColon and p.tok.indent < 0:
-    result = makeCall(result)
-    getTok(p)
-    skipComment(p, result)
-    let stmtList = newNodeP(nkStmtList, p)
-    if p.tok.tokType notin {tkOf, tkElif, tkElse, tkExcept}:
-      let body = parseStmt(p)
-      stmtList.add body
-      #addSon(result, makeStmtList(body))
-    while true:
-      var b: PNode
-      case p.tok.tokType
-      of tkOf:
-        b = newNodeP(nkOfBranch, p)
-        exprList(p, tkCurlyLe, b)
-      of tkElif:
-        b = newNodeP(nkElifBranch, p)
-        getTok(p)
-        optInd(p, b)
-        addSon(b, parseExpr(p))
-      of tkExcept:
-        b = newNodeP(nkExceptBranch, p)
-        exprList(p, tkCurlyLe, b)
-      of tkElse:
-        b = newNodeP(nkElse, p)
-        getTok(p)
-      else: break
-      addSon(b, parseCurlyStmt(p))
-      addSon(stmtList, b)
-      if b.kind == nkElse: break
-    if stmtList.len == 1 and stmtList[0].kind == nkStmtList:
-      # to keep backwards compatibility (see tests/vm/tstringnil)
-      result.add stmtList[0]
-    else:
-      result.add stmtList
-
-proc parseExprStmt(p: var TParser): PNode =
-  #| exprStmt = simpleExpr
-  #|          (( '=' optInd expr )
-  #|          / ( expr ^+ comma
-  #|              doBlocks
-  #|               / macroColon
-  #|            ))?
-  var a = simpleExpr(p)
-  if p.tok.tokType == tkEquals:
-    getTok(p)
-    optInd(p, result)
-    var b = parseExpr(p)
-    result = newNodeI(nkAsgn, a.info)
-    addSon(result, a)
-    addSon(result, b)
-  else:
-    # simpleExpr parsed 'p a' from 'p a, b'?
-    if p.tok.indent < 0 and p.tok.tokType == tkComma and a.kind == nkCommand:
-      result = a
-      while true:
-        getTok(p)
-        optInd(p, result)
-        var e = parseExpr(p)
-        addSon(result, e)
-        if p.tok.tokType != tkComma: break
-    elif p.tok.indent < 0 and isExprStart(p):
-      if a.kind == nkCommand:
-        result = a
-      else:
-        result = newNode(nkCommand, a.info, @[a])
-      while true:
-        var e = parseExpr(p)
-        addSon(result, e)
-        if p.tok.tokType != tkComma: break
-        getTok(p)
-        optInd(p, result)
-    else:
-      result = a
-    if p.tok.tokType == tkDo and p.tok.indent < 0:
-      result = makeCall(result)
-      parseDoBlocks(p, result)
-      return result
-    result = parseMacroColon(p, result)
-
-proc parseModuleName(p: var TParser, kind: TNodeKind): PNode =
-  result = parseExpr(p)
-
-proc parseImport(p: var TParser, kind: TNodeKind): PNode =
-  #| importStmt = 'import' optInd expr
-  #|               ((comma expr)*
-  #|               / 'except' optInd (expr ^+ comma))
-  result = newNodeP(kind, p)
-  getTok(p)                   # skip `import` or `export`
-  optInd(p, result)
-  var a = parseModuleName(p, kind)
-  addSon(result, a)
-  if p.tok.tokType in {tkComma, tkExcept}:
-    if p.tok.tokType == tkExcept:
-      result.kind = succ(kind)
-    getTok(p)
-    optInd(p, result)
-    while true:
-      # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}:
-      a = parseModuleName(p, kind)
-      if a.kind == nkEmpty: break
-      addSon(result, a)
-      if p.tok.tokType != tkComma: break
-      getTok(p)
-      optInd(p, a)
-  #expectNl(p)
-
-proc parseIncludeStmt(p: var TParser): PNode =
-  #| includeStmt = 'include' optInd expr ^+ comma
-  result = newNodeP(nkIncludeStmt, p)
-  getTok(p)                   # skip `import` or `include`
-  optInd(p, result)
-  while true:
-    # was: while p.tok.tokType notin {tkEof, tkSad, tkDed}:
-    var a = parseExpr(p)
-    if a.kind == nkEmpty: break
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    optInd(p, a)
-  #expectNl(p)
-
-proc parseFromStmt(p: var TParser): PNode =
-  #| fromStmt = 'from' moduleName 'import' optInd expr (comma expr)*
-  result = newNodeP(nkFromStmt, p)
-  getTok(p)                   # skip `from`
-  optInd(p, result)
-  var a = parseModuleName(p, nkImportStmt)
-  addSon(result, a)           #optInd(p, a);
-  eat(p, tkImport)
-  optInd(p, result)
-  while true:
-    # p.tok.tokType notin {tkEof, tkSad, tkDed}:
-    a = parseExpr(p)
-    if a.kind == nkEmpty: break
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    optInd(p, a)
-  #expectNl(p)
-
-proc parseReturnOrRaise(p: var TParser, kind: TNodeKind): PNode =
-  #| returnStmt = 'return' optInd expr?
-  #| raiseStmt = 'raise' optInd expr?
-  #| yieldStmt = 'yield' optInd expr?
-  #| discardStmt = 'discard' optInd expr?
-  #| breakStmt = 'break' optInd expr?
-  #| continueStmt = 'break' optInd expr?
-  result = newNodeP(kind, p)
-  getTok(p)
-  if p.tok.tokType == tkComment:
-    skipComment(p, result)
-    addSon(result, ast.emptyNode)
-  elif p.tok.indent >= 0 or not isExprStart(p):
-    # NL terminates:
-    addSon(result, ast.emptyNode)
-  else:
-    addSon(result, parseExpr(p))
-
-proc parseIfOrWhen(p: var TParser, kind: TNodeKind): PNode =
-  #| condStmt = expr colcom stmt COMMENT?
-  #|            (IND{=} 'elif' expr colcom stmt)*
-  #|            (IND{=} 'else' colcom stmt)?
-  #| ifStmt = 'if' condStmt
-  #| whenStmt = 'when' condStmt
-  result = newNodeP(kind, p)
-  while true:
-    getTok(p)                 # skip `if`, `when`, `elif`
-    var branch = newNodeP(nkElifBranch, p)
-    optInd(p, branch)
-    addSon(branch, parseExpr(p))
-    colcom(p, branch)
-    addSon(branch, parseCurlyStmt(p))
-    skipComment(p, branch)
-    addSon(result, branch)
-    if p.tok.tokType != tkElif: break
-  if p.tok.tokType == tkElse:
-    var branch = newNodeP(nkElse, p)
-    eat(p, tkElse)
-    addSon(branch, parseCurlyStmt(p))
-    addSon(result, branch)
-
-proc parseWhile(p: var TParser): PNode =
-  #| whileStmt = 'while' expr colcom stmt
-  result = newNodeP(nkWhileStmt, p)
-  getTok(p)
-  optInd(p, result)
-  addSon(result, parseExpr(p))
-  colcom(p, result)
-  addSon(result, parseCurlyStmt(p))
-
-proc parseCase(p: var TParser): PNode =
-  #| ofBranch = 'of' exprList colcom stmt
-  #| ofBranches = ofBranch (IND{=} ofBranch)*
-  #|                       (IND{=} 'elif' expr colcom stmt)*
-  #|                       (IND{=} 'else' colcom stmt)?
-  #| caseStmt = 'case' expr ':'? COMMENT?
-  #|             (IND{>} ofBranches DED
-  #|             | IND{=} ofBranches)
-  var
-    b: PNode
-    inElif= false
-  result = newNodeP(nkCaseStmt, p)
-  getTok(p)
-  addSon(result, parseExpr(p))
-  eat(p, tkCurlyLe)
-  skipComment(p, result)
-
-  while true:
-    case p.tok.tokType
-    of tkOf:
-      if inElif: break
-      b = newNodeP(nkOfBranch, p)
-      exprList(p, tkCurlyLe, b)
-    of tkElif:
-      inElif = true
-      b = newNodeP(nkElifBranch, p)
-      getTok(p)
-      optInd(p, b)
-      addSon(b, parseExpr(p))
-    of tkElse:
-      b = newNodeP(nkElse, p)
-      getTok(p)
-    else: break
-    skipComment(p, b)
-    addSon(b, parseCurlyStmt(p))
-    addSon(result, b)
-    if b.kind == nkElse: break
-  eat(p, tkCurlyRi)
-
-proc parseTry(p: var TParser): PNode =
-  #| tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally')
-  #|            (IND{=}? 'except' exprList colcom stmt)*
-  #|            (IND{=}? 'finally' colcom stmt)?
-  #| tryExpr = 'try' colcom stmt &(optInd 'except'|'finally')
-  #|            (optInd 'except' exprList colcom stmt)*
-  #|            (optInd 'finally' colcom stmt)?
-  result = newNodeP(nkTryStmt, p)
-  getTok(p)
-  colcom(p, result)
-  addSon(result, parseCurlyStmt(p))
-  var b: PNode = nil
-  while true:
-    case p.tok.tokType
-    of tkExcept:
-      b = newNodeP(nkExceptBranch, p)
-      exprList(p, tkCurlyLe, b)
-    of tkFinally:
-      b = newNodeP(nkFinally, p)
-      getTok(p)
-    else: break
-    skipComment(p, b)
-    addSon(b, parseCurlyStmt(p))
-    addSon(result, b)
-    if b.kind == nkFinally: break
-  if b == nil: parMessage(p, errTokenExpected, "except")
-
-proc parseFor(p: var TParser): PNode =
-  #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt
-  result = newNodeP(nkForStmt, p)
-  getTokNoInd(p)
-  var a = identWithPragma(p)
-  addSon(result, a)
-  while p.tok.tokType == tkComma:
-    getTok(p)
-    optInd(p, a)
-    a = identWithPragma(p)
-    addSon(result, a)
-  eat(p, tkIn)
-  addSon(result, parseExpr(p))
-  colcom(p, result)
-  addSon(result, parseCurlyStmt(p))
-
-proc parseBlock(p: var TParser): PNode =
-  #| blockStmt = 'block' symbol? colcom stmt
-  result = newNodeP(nkBlockStmt, p)
-  getTokNoInd(p)
-  if p.tok.tokType == tkCurlyLe: addSon(result, ast.emptyNode)
-  else: addSon(result, parseSymbol(p))
-  colcom(p, result)
-  addSon(result, parseCurlyStmt(p))
-
-proc parseStaticOrDefer(p: var TParser; k: TNodeKind): PNode =
-  #| staticStmt = 'static' colcom stmt
-  #| deferStmt = 'defer' colcom stmt
-  result = newNodeP(k, p)
-  getTok(p)
-  colcom(p, result)
-  addSon(result, parseCurlyStmt(p))
-
-proc parseAsm(p: var TParser): PNode =
-  #| asmStmt = 'asm' pragma? (STR_LIT | RSTR_LIT | TRIPLE_STR_LIT)
-  result = newNodeP(nkAsmStmt, p)
-  getTokNoInd(p)
-  if p.tok.tokType == tkCurlyDotLe or isAt(p.tok): addSon(result, parsePragma(p))
-  else: addSon(result, ast.emptyNode)
-  case p.tok.tokType
-  of tkStrLit: addSon(result, newStrNodeP(nkStrLit, p.tok.literal, p))
-  of tkRStrLit: addSon(result, newStrNodeP(nkRStrLit, p.tok.literal, p))
-  of tkTripleStrLit: addSon(result,
-                            newStrNodeP(nkTripleStrLit, p.tok.literal, p))
-  else:
-    parMessage(p, errStringLiteralExpected)
-    addSon(result, ast.emptyNode)
-    return
-  getTok(p)
-
-proc parseGenericParam(p: var TParser): PNode =
-  #| genericParam = symbol (comma symbol)* (colon expr)? ('=' optInd expr)?
-  var a: PNode
-  result = newNodeP(nkIdentDefs, p)
-  while true:
-    case p.tok.tokType
-    of tkSymbol, tkAccent:
-      a = parseSymbol(p)
-      if a.kind == nkEmpty: return
-    else: break
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    optInd(p, a)
-  if p.tok.tokType == tkColon:
-    getTok(p)
-    optInd(p, result)
-    addSon(result, parseExpr(p))
-  else:
-    addSon(result, ast.emptyNode)
-  if p.tok.tokType == tkEquals:
-    getTok(p)
-    optInd(p, result)
-    addSon(result, parseExpr(p))
-  else:
-    addSon(result, ast.emptyNode)
-
-proc parseGenericParamList(p: var TParser): PNode =
-  #| genericParamList = '[' optInd
-  #|   genericParam ^* (comma/semicolon) optPar ']'
-  result = newNodeP(nkGenericParams, p)
-  getTok(p)
-  optInd(p, result)
-  while p.tok.tokType in {tkSymbol, tkAccent}:
-    var a = parseGenericParam(p)
-    addSon(result, a)
-    if p.tok.tokType notin {tkComma, tkSemiColon}: break
-    getTok(p)
-    skipComment(p, a)
-  optPar(p)
-  eat(p, tkBracketRi)
-
-proc parsePattern(p: var TParser): PNode =
-  eat(p, tkBracketDotLe)
-  result = parseStmt(p)
-  eat(p, tkBracketDotRi)
-
-proc validInd(p: TParser): bool = p.tok.indent < 0
-
-proc parseRoutine(p: var TParser, kind: TNodeKind): PNode =
-  #| indAndComment = (IND{>} COMMENT)? | COMMENT?
-  #| routine = optInd identVis pattern? genericParamList?
-  #|   paramListColon pragma? ('=' COMMENT? stmt)? indAndComment
-  result = newNodeP(kind, p)
-  getTok(p)
-  optInd(p, result)
-  addSon(result, identVis(p))
-  if p.tok.tokType == tkBracketDotLe and p.validInd:
-    addSon(result, p.parsePattern)
-  else:
-    addSon(result, ast.emptyNode)
-  if p.tok.tokType == tkBracketLe and p.validInd:
-    result.add(p.parseGenericParamList)
-  else:
-    addSon(result, ast.emptyNode)
-  addSon(result, p.parseParamList)
-  if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd:
-    addSon(result, p.parsePragma)
-  else:
-    addSon(result, ast.emptyNode)
-  # empty exception tracking:
-  addSon(result, ast.emptyNode)
-  if p.tok.tokType == tkCurlyLe:
-    addSon(result, parseCurlyStmt(p))
-  else:
-    addSon(result, ast.emptyNode)
-  indAndComment(p, result)
-
-proc newCommentStmt(p: var TParser): PNode =
-  #| commentStmt = COMMENT
-  result = newNodeP(nkCommentStmt, p)
-  result.comment = p.tok.literal
-  getTok(p)
-
-type
-  TDefParser = proc (p: var TParser): PNode {.nimcall.}
-
-proc parseSection(p: var TParser, kind: TNodeKind,
-                  defparser: TDefParser): PNode =
-  #| section(p) = COMMENT? p / (IND{>} (p / COMMENT)^+IND{=} DED)
-  result = newNodeP(kind, p)
-  if kind != nkTypeSection: getTok(p)
-  skipComment(p, result)
-  if p.tok.tokType == tkParLe:
-    getTok(p)
-    skipComment(p, result)
-    while true:
-      case p.tok.tokType
-      of tkSymbol, tkAccent, tkParLe:
-        var a = defparser(p)
-        skipComment(p, a)
-        addSon(result, a)
-      of tkComment:
-        var a = newCommentStmt(p)
-        addSon(result, a)
-      of tkParRi: break
-      else:
-        parMessage(p, errIdentifierExpected, p.tok)
-        break
-    eat(p, tkParRi)
-    if result.len == 0: parMessage(p, errIdentifierExpected, p.tok)
-  elif p.tok.tokType in {tkSymbol, tkAccent, tkBracketLe}:
-    # tkBracketLe is allowed for ``var [x, y] = ...`` tuple parsing
-    addSon(result, defparser(p))
-  else:
-    parMessage(p, errIdentifierExpected, p.tok)
-
-proc parseConstant(p: var TParser): PNode =
-  #| constant = identWithPragma (colon typedesc)? '=' optInd expr indAndComment
-  result = newNodeP(nkConstDef, p)
-  addSon(result, identWithPragma(p))
-  if p.tok.tokType == tkColon:
-    getTok(p)
-    optInd(p, result)
-    addSon(result, parseTypeDesc(p))
-  else:
-    addSon(result, ast.emptyNode)
-  eat(p, tkEquals)
-  optInd(p, result)
-  addSon(result, parseExpr(p))
-  indAndComment(p, result)
-
-proc parseEnum(p: var TParser): PNode =
-  #| enum = 'enum' optInd (symbol optInd ('=' optInd expr COMMENT?)? comma?)+
-  result = newNodeP(nkEnumTy, p)
-  getTok(p)
-  addSon(result, ast.emptyNode)
-  optInd(p, result)
-  flexComment(p, result)
-  eat(p, tkCurlyLe)
-  optInd(p, result)
-  while p.tok.tokType notin {tkEof, tkCurlyRi}:
-    var a = parseSymbol(p)
-    if a.kind == nkEmpty: return
-    if p.tok.tokType == tkEquals:
-      getTok(p)
-      optInd(p, a)
-      var b = a
-      a = newNodeP(nkEnumFieldDef, p)
-      addSon(a, b)
-      addSon(a, parseExpr(p))
-      if p.tok.indent < 0:
-        rawSkipComment(p, a)
-    if p.tok.tokType == tkComma:
-      getTok(p)
-      rawSkipComment(p, a)
-    addSon(result, a)
-  eat(p, tkCurlyRi)
-  if result.len <= 1:
-    lexMessageTok(p.lex, errIdentifierExpected, p.tok, prettyTok(p.tok))
-
-proc parseObjectPart(p: var TParser; needsCurly: bool): PNode
-proc parseObjectWhen(p: var TParser): PNode =
-  result = newNodeP(nkRecWhen, p)
-  while true:
-    getTok(p)                 # skip `when`, `elif`
-    var branch = newNodeP(nkElifBranch, p)
-    optInd(p, branch)
-    addSon(branch, parseExpr(p))
-    colcom(p, branch)
-    addSon(branch, parseObjectPart(p, true))
-    flexComment(p, branch)
-    addSon(result, branch)
-    if p.tok.tokType != tkElif: break
-  if p.tok.tokType == tkElse:
-    var branch = newNodeP(nkElse, p)
-    eat(p, tkElse)
-    colcom(p, branch)
-    addSon(branch, parseObjectPart(p, true))
-    flexComment(p, branch)
-    addSon(result, branch)
-
-proc parseObjectCase(p: var TParser): PNode =
-  result = newNodeP(nkRecCase, p)
-  getTokNoInd(p)
-  var a = newNodeP(nkIdentDefs, p)
-  addSon(a, identWithPragma(p))
-  eat(p, tkColon)
-  addSon(a, parseTypeDesc(p))
-  addSon(a, ast.emptyNode)
-  addSon(result, a)
-  eat(p, tkCurlyLe)
-  flexComment(p, result)
-  while true:
-    var b: PNode
-    case p.tok.tokType
-    of tkOf:
-      b = newNodeP(nkOfBranch, p)
-      exprList(p, tkColon, b)
-    of tkElse:
-      b = newNodeP(nkElse, p)
-      getTok(p)
-    else: break
-    colcom(p, b)
-    var fields = parseObjectPart(p, true)
-    if fields.kind == nkEmpty:
-      parMessage(p, errIdentifierExpected, p.tok)
-      fields = newNodeP(nkNilLit, p) # don't break further semantic checking
-    addSon(b, fields)
-    addSon(result, b)
-    if b.kind == nkElse: break
-  eat(p, tkCurlyRi)
-
-proc parseObjectPart(p: var TParser; needsCurly: bool): PNode =
-  if p.tok.tokType == tkCurlyLe:
-    result = newNodeP(nkRecList, p)
-    getTok(p)
-    rawSkipComment(p, result)
-    while true:
-      case p.tok.tokType
-      of tkCase, tkWhen, tkSymbol, tkAccent, tkNil, tkDiscard:
-        addSon(result, parseObjectPart(p, false))
-      of tkCurlyRi: break
-      else:
-        parMessage(p, errIdentifierExpected, p.tok)
-        break
-    eat(p, tkCurlyRi)
-  else:
-    if needsCurly:
-      parMessage(p, errTokenExpected, "{")
-    case p.tok.tokType
-    of tkWhen:
-      result = parseObjectWhen(p)
-    of tkCase:
-      result = parseObjectCase(p)
-    of tkSymbol, tkAccent:
-      result = parseIdentColonEquals(p, {withPragma})
-      if p.tok.indent < 0: rawSkipComment(p, result)
-    of tkNil, tkDiscard:
-      result = newNodeP(nkNilLit, p)
-      getTok(p)
-    else:
-      result = ast.emptyNode
-
-proc parseObject(p: var TParser): PNode =
-  result = newNodeP(nkObjectTy, p)
-  getTok(p)
-  if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd:
-    addSon(result, parsePragma(p))
-  else:
-    addSon(result, ast.emptyNode)
-  if p.tok.tokType == tkOf and p.tok.indent < 0:
-    var a = newNodeP(nkOfInherit, p)
-    getTok(p)
-    addSon(a, parseTypeDesc(p))
-    addSon(result, a)
-  else:
-    addSon(result, ast.emptyNode)
-  skipComment(p, result)
-  # an initial IND{>} HAS to follow:
-  addSon(result, parseObjectPart(p, true))
-
-proc parseTypeClassParam(p: var TParser): PNode =
-  if p.tok.tokType in {tkOut, tkVar}:
-    result = newNodeP(nkVarTy, p)
-    getTok(p)
-    result.addSon(p.parseSymbol)
-  else:
-    result = p.parseSymbol
-
-proc parseTypeClass(p: var TParser): PNode =
-  #| typeClassParam = ('var' | 'out')? symbol
-  #| typeClass = typeClassParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
-  #|               &IND{>} stmt
-  result = newNodeP(nkTypeClassTy, p)
-  getTok(p)
-  var args = newNodeP(nkArgList, p)
-  addSon(result, args)
-  addSon(args, p.parseTypeClassParam)
-  while p.tok.tokType == tkComma:
-    getTok(p)
-    addSon(args, p.parseTypeClassParam)
-  if (p.tok.tokType == tkCurlyDotLe or isAt(p.tok)) and p.validInd:
-    addSon(result, parsePragma(p))
-  else:
-    addSon(result, ast.emptyNode)
-  if p.tok.tokType == tkOf and p.tok.indent < 0:
-    var a = newNodeP(nkOfInherit, p)
-    getTok(p)
-    while true:
-      addSon(a, parseTypeDesc(p))
-      if p.tok.tokType != tkComma: break
-      getTok(p)
-    addSon(result, a)
-  else:
-    addSon(result, ast.emptyNode)
-  if p.tok.tokType == tkComment:
-    skipComment(p, result)
-  addSon(result, parseCurlyStmt(p))
-
-proc parseTypeDef(p: var TParser): PNode =
-  #|
-  #| typeDef = identWithPragmaDot genericParamList? '=' optInd typeDefAux
-  #|             indAndComment?
-  result = newNodeP(nkTypeDef, p)
-  addSon(result, identWithPragma(p, allowDot=true))
-  if p.tok.tokType == tkBracketLe and p.validInd:
-    addSon(result, parseGenericParamList(p))
-  else:
-    addSon(result, ast.emptyNode)
-  if p.tok.tokType == tkEquals:
-    getTok(p)
-    optInd(p, result)
-    addSon(result, parseTypeDefAux(p))
-  else:
-    addSon(result, ast.emptyNode)
-  indAndComment(p, result)    # special extension!
-
-proc parseVarTuple(p: var TParser): PNode =
-  #| varTuple = '(' optInd identWithPragma ^+ comma optPar ')' '=' optInd expr
-  result = newNodeP(nkVarTuple, p)
-  getTok(p)                   # skip '('
-  optInd(p, result)
-  while p.tok.tokType in {tkSymbol, tkAccent}:
-    var a = identWithPragma(p)
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    skipComment(p, a)
-  addSon(result, ast.emptyNode)         # no type desc
-  optPar(p)
-  eat(p, tkBracketRi)
-  eat(p, tkEquals)
-  optInd(p, result)
-  addSon(result, parseExpr(p))
-
-proc parseVariable(p: var TParser): PNode =
-  #| variable = (varTuple / identColonEquals) indAndComment
-  if p.tok.tokType == tkBracketLe: result = parseVarTuple(p)
-  else: result = parseIdentColonEquals(p, {withPragma})
-  indAndComment(p, result)
-
-proc parseBind(p: var TParser, k: TNodeKind): PNode =
-  #| bindStmt = 'bind' optInd qualifiedIdent ^+ comma
-  #| mixinStmt = 'mixin' optInd qualifiedIdent ^+ comma
-  result = newNodeP(k, p)
-  getTok(p)
-  optInd(p, result)
-  while true:
-    var a = qualifiedIdent(p)
-    addSon(result, a)
-    if p.tok.tokType != tkComma: break
-    getTok(p)
-    optInd(p, a)
-
-proc parseStmtPragma(p: var TParser): PNode =
-  result = parsePragma(p)
-  if p.tok.tokType == tkCurlyLe:
-    let a = result
-    result = newNodeI(nkPragmaBlock, a.info)
-    getTok(p)
-    skipComment(p, result)
-    result.add a
-    result.add parseStmt(p)
-    eat(p, tkCurlyRi)
-
-proc simpleStmt(p: var TParser): PNode =
-  case p.tok.tokType
-  of tkReturn: result = parseReturnOrRaise(p, nkReturnStmt)
-  of tkRaise: result = parseReturnOrRaise(p, nkRaiseStmt)
-  of tkYield: result = parseReturnOrRaise(p, nkYieldStmt)
-  of tkDiscard: result = parseReturnOrRaise(p, nkDiscardStmt)
-  of tkBreak: result = parseReturnOrRaise(p, nkBreakStmt)
-  of tkContinue: result = parseReturnOrRaise(p, nkContinueStmt)
-  of tkCurlyDotLe: result = parseStmtPragma(p)
-  of tkImport: result = parseImport(p, nkImportStmt)
-  of tkExport: result = parseImport(p, nkExportStmt)
-  of tkFrom: result = parseFromStmt(p)
-  of tkInclude: result = parseIncludeStmt(p)
-  of tkComment: result = newCommentStmt(p)
-  else:
-    if isExprStart(p): result = parseExprStmt(p)
-    else: result = ast.emptyNode
-  if result.kind notin {nkEmpty, nkCommentStmt}: skipComment(p, result)
-
-proc complexOrSimpleStmt(p: var TParser): PNode =
-  case p.tok.tokType
-  of tkIf: result = parseIfOrWhen(p, nkIfStmt)
-  of tkWhile: result = parseWhile(p)
-  of tkCase: result = parseCase(p)
-  of tkTry: result = parseTry(p)
-  of tkFor: result = parseFor(p)
-  of tkBlock: result = parseBlock(p)
-  of tkStatic: result = parseStaticOrDefer(p, nkStaticStmt)
-  of tkDefer: result = parseStaticOrDefer(p, nkDefer)
-  of tkAsm: result = parseAsm(p)
-  of tkProc: result = parseRoutine(p, nkProcDef)
-  of tkMethod: result = parseRoutine(p, nkMethodDef)
-  of tkIterator: result = parseRoutine(p, nkIteratorDef)
-  of tkMacro: result = parseRoutine(p, nkMacroDef)
-  of tkTemplate: result = parseRoutine(p, nkTemplateDef)
-  of tkConverter: result = parseRoutine(p, nkConverterDef)
-  of tkType:
-    getTok(p)
-    if p.tok.tokType == tkBracketLe:
-      getTok(p)
-      result = newNodeP(nkTypeOfExpr, p)
-      result.addSon(primary(p, pmTypeDesc))
-      eat(p, tkBracketRi)
-      result = parseOperators(p, result, -1, pmNormal)
-    else:
-      result = parseSection(p, nkTypeSection, parseTypeDef)
-  of tkConst: result = parseSection(p, nkConstSection, parseConstant)
-  of tkLet: result = parseSection(p, nkLetSection, parseVariable)
-  of tkWhen: result = parseIfOrWhen(p, nkWhenStmt)
-  of tkVar: result = parseSection(p, nkVarSection, parseVariable)
-  of tkBind: result = parseBind(p, nkBindStmt)
-  of tkMixin: result = parseBind(p, nkMixinStmt)
-  of tkUsing: result = parseSection(p, nkUsingStmt, parseVariable)
-  else: result = simpleStmt(p)
-
-proc parseStmt(p: var TParser): PNode =
-  result = complexOrSimpleStmt(p)
-
-proc parseAll*(p: var TParser): PNode =
-  ## Parses the rest of the input stream held by the parser into a PNode.
-  result = newNodeP(nkStmtList, p)
-  while p.tok.tokType != tkEof:
-    var a = complexOrSimpleStmt(p)
-    if a.kind != nkEmpty:
-      addSon(result, a)
-    else:
-      parMessage(p, errExprExpected, p.tok)
-      # bugfix: consume a token here to prevent an endless loop:
-      getTok(p)
-
-proc parseTopLevelStmt*(p: var TParser): PNode =
-  ## Implements an iterator which, when called repeatedly, returns the next
-  ## top-level statement or emptyNode if end of stream.
-  result = ast.emptyNode
-  while true:
-    case p.tok.tokType
-    of tkSemiColon: getTok(p)
-    of tkEof: break
-    else:
-      result = complexOrSimpleStmt(p)
-      if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok)
-      break
diff --git a/compiler/platform.nim b/compiler/platform.nim
index 19d0d5853..ef1d00e08 100644
--- a/compiler/platform.nim
+++ b/compiler/platform.nim
@@ -21,8 +21,8 @@ type
                     # conditionals to condsyms (end of module).
     osNone, osDos, osWindows, osOs2, osLinux, osMorphos, osSkyos, osSolaris,
     osIrix, osNetbsd, osFreebsd, osOpenbsd, osDragonfly, osAix, osPalmos, osQnx,
-    osAmiga, osAtari, osNetware, osMacos, osMacosx, osHaiku, osVxworks,
-    osJS, osNimrodVM, osStandalone
+    osAmiga, osAtari, osNetware, osMacos, osMacosx, osHaiku, osAndroid, osVxworks
+    osGenode, osJS, osNimVM, osStandalone, osNintendoSwitch
 
 type
   TInfoOSProp* = enum
@@ -143,30 +143,45 @@ const
       objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
       scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".",
       props: {ospNeedsPIC, ospPosix, ospLacksThreadVars}),
+     (name: "Android", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
+      objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
+      scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".",
+      props: {ospNeedsPIC, ospPosix}),
      (name: "VxWorks", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
       objExt: ".o", newLine: "\x0A", pathSep: ";", dirSep: "\\",
       scriptExt: ".sh", curDir: ".", exeExt: ".vxe", extSep: ".",
       props: {ospNeedsPIC, ospPosix, ospLacksThreadVars}),
+     (name: "Genode", pardir: "..", dllFrmt: "$1.lib.so", altDirSep: "/",
+      objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
+      scriptExt: "", curDir: "/", exeExt: "", extSep: ".",
+      props: {ospNeedsPIC, ospLacksThreadVars}),
+
      (name: "JS", parDir: "..",
       dllFrmt: "lib$1.so", altDirSep: "/",
       objExt: ".o", newLine: "\x0A",
       pathSep: ":", dirSep: "/",
       scriptExt: ".sh", curDir: ".",
       exeExt: "", extSep: ".", props: {}),
-     (name: "NimrodVM", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
+     (name: "NimVM", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
       objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
       scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".", props: {}),
      (name: "Standalone", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
       objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
       scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".",
-      props: {})]
+      props: {}),
+     (name: "NintendoSwitch", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
+      objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
+      scriptExt: ".sh", curDir: ".", exeExt: ".elf", extSep: ".",
+      props: {ospNeedsPIC, ospPosix}),
+     ]
 
 type
   TSystemCPU* = enum # Also add CPU for in initialization section and
                      # alias conditionals to condsyms (end of module).
     cpuNone, cpuI386, cpuM68k, cpuAlpha, cpuPowerpc, cpuPowerpc64,
     cpuPowerpc64el, cpuSparc, cpuVm, cpuIa64, cpuAmd64, cpuMips, cpuMipsel,
-    cpuArm, cpuArm64, cpuJS, cpuNimrodVM, cpuAVR, cpuMSP430, cpuSparc64
+    cpuArm, cpuArm64, cpuJS, cpuNimVM, cpuAVR, cpuMSP430, cpuSparc64,
+    cpuMips64, cpuMips64el, cpuRiscV64
 
 type
   TEndian* = enum
@@ -192,49 +207,47 @@ const
     (name: "arm", intSize: 32, endian: littleEndian, floatSize: 64, bit: 32),
     (name: "arm64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64),
     (name: "js", intSize: 32, endian: bigEndian,floatSize: 64,bit: 32),
-    (name: "nimrodvm", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32),
+    (name: "nimvm", intSize: 32, endian: bigEndian, floatSize: 64, bit: 32),
     (name: "avr", intSize: 16, endian: littleEndian, floatSize: 32, bit: 16),
     (name: "msp430", intSize: 16, endian: littleEndian, floatSize: 32, bit: 16),
-    (name: "sparc64", intSize: 64, endian: bigEndian, floatSize: 64, bit: 64)]
+    (name: "sparc64", intSize: 64, endian: bigEndian, floatSize: 64, bit: 64),
+    (name: "mips64", intSize: 64, endian: bigEndian, floatSize: 64, bit: 64),
+    (name: "mips64el", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64),
+    (name: "riscv64", intSize: 64, endian: littleEndian, floatSize: 64, bit: 64)]
 
-var
-  targetCPU*, hostCPU*: TSystemCPU
-  targetOS*, hostOS*: TSystemOS
-
-proc nameToOS*(name: string): TSystemOS
-proc nameToCPU*(name: string): TSystemCPU
-
-var
-  intSize*: int
-  floatSize*: int
-  ptrSize*: int
-  tnl*: string                # target newline
-
-proc setTarget*(o: TSystemOS, c: TSystemCPU) =
+type
+  Target* = object
+    targetCPU*, hostCPU*: TSystemCPU
+    targetOS*, hostOS*: TSystemOS
+    intSize*: int
+    floatSize*: int
+    ptrSize*: int
+    tnl*: string                # target newline
+
+proc setTarget*(t: var Target; o: TSystemOS, c: TSystemCPU) =
   assert(c != cpuNone)
   assert(o != osNone)
   #echo "new Target: OS: ", o, " CPU: ", c
-  targetCPU = c
-  targetOS = o
-  intSize = CPU[c].intSize div 8
-  floatSize = CPU[c].floatSize div 8
-  ptrSize = CPU[c].bit div 8
-  tnl = OS[o].newLine
-
-proc nameToOS(name: string): TSystemOS =
+  t.targetCPU = c
+  t.targetOS = o
+  t.intSize = CPU[c].intSize div 8
+  t.floatSize = CPU[c].floatSize div 8
+  t.ptrSize = CPU[c].bit div 8
+  t.tnl = OS[o].newLine
+
+proc nameToOS*(name: string): TSystemOS =
   for i in countup(succ(osNone), high(TSystemOS)):
     if cmpIgnoreStyle(name, OS[i].name) == 0:
       return i
   result = osNone
 
-proc nameToCPU(name: string): TSystemCPU =
+proc nameToCPU*(name: string): TSystemCPU =
   for i in countup(succ(cpuNone), high(TSystemCPU)):
     if cmpIgnoreStyle(name, CPU[i].name) == 0:
       return i
   result = cpuNone
 
-hostCPU = nameToCPU(system.hostCPU)
-hostOS = nameToOS(system.hostOS)
-
-setTarget(hostOS, hostCPU) # assume no cross-compiling
-
+proc setTargetFromSystem*(t: var Target) =
+  t.hostOS = nameToOS(system.hostOS)
+  t.hostCPU = nameToCPU(system.hostCPU)
+  t.setTarget(t.hostOS, t.hostCPU)
diff --git a/compiler/plugins/active.nim b/compiler/plugins/active.nim
index 7b6411178..19c320aae 100644
--- a/compiler/plugins/active.nim
+++ b/compiler/plugins/active.nim
@@ -10,4 +10,15 @@
 ## Include file that imports all plugins that are active.
 
 import
-  locals.locals, itersgen
+  ".." / [pluginsupport, idents, ast], locals, itersgen
+
+const
+  plugins: array[2, Plugin] = [
+    ("stdlib", "system", "iterToProc", iterToProcImpl),
+    ("stdlib", "system", "locals", semLocals)
+  ]
+
+proc getPlugin*(ic: IdentCache; fn: PSym): Transformation =
+  for p in plugins:
+    if pluginMatches(ic, p, fn): return p.t
+  return nil
diff --git a/compiler/plugins/itersgen.nim b/compiler/plugins/itersgen.nim
index f44735b77..440d2e081 100644
--- a/compiler/plugins/itersgen.nim
+++ b/compiler/plugins/itersgen.nim
@@ -9,30 +9,29 @@
 
 ## Plugin to transform an inline iterator into a data structure.
 
-import compiler/pluginsupport, compiler/ast, compiler/astalgo,
-  compiler/magicsys, compiler/lookups, compiler/semdata,
-  compiler/lambdalifting, compiler/rodread, compiler/msgs
+import ".." / [ast, astalgo,
+  magicsys, lookups, semdata,
+  lambdalifting, msgs]
 
-
-proc iterToProcImpl(c: PContext, n: PNode): PNode =
+proc iterToProcImpl*(c: PContext, n: PNode): PNode =
   result = newNodeI(nkStmtList, n.info)
   let iter = n[1]
   if iter.kind != nkSym or iter.sym.kind != skIterator:
-    localError(iter.info, "first argument needs to be an iterator")
+    localError(c.config, iter.info, "first argument needs to be an iterator")
     return
   if n[2].typ.isNil:
-    localError(n[2].info, "second argument needs to be a type")
+    localError(c.config, n[2].info, "second argument needs to be a type")
     return
   if n[3].kind != nkIdent:
-    localError(n[3].info, "third argument needs to be an identifier")
+    localError(c.config, n[3].info, "third argument needs to be an identifier")
     return
 
   let t = n[2].typ.skipTypes({tyTypeDesc, tyGenericInst})
   if t.kind notin {tyRef, tyPtr} or t.lastSon.kind != tyObject:
-    localError(n[2].info,
+    localError(c.config, n[2].info,
         "type must be a non-generic ref|ptr to object with state field")
     return
-  let body = liftIterToProc(iter.sym, iter.sym.getBody, t)
+  let body = liftIterToProc(c.graph, iter.sym, iter.sym.getBody, t)
 
   let prc = newSym(skProc, n[3].ident, iter.sym.owner, iter.sym.info)
   prc.typ = copyType(iter.sym.typ, prc, false)
@@ -41,11 +40,9 @@ proc iterToProcImpl(c: PContext, n: PNode): PNode =
   prc.typ.rawAddSon t
   let orig = iter.sym.ast
   prc.ast = newProcNode(nkProcDef, n.info,
-                        name = newSymNode(prc),
-                        params = orig[paramsPos],
-                        pragmas = orig[pragmasPos],
-                        body = body)
+              body = body, params = orig[paramsPos], name = newSymNode(prc),
+              pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode,
+              pragmas = orig[pragmasPos], exceptions = c.graph.emptyNode)
+
   prc.ast.add iter.sym.ast.sons[resultPos]
   addInterfaceDecl(c, prc)
-
-registerPlugin("stdlib", "system", "iterToProc", iterToProcImpl)
diff --git a/compiler/plugins/locals/locals.nim b/compiler/plugins/locals.nim
index 338e7bcac..4dd3e8f9c 100644
--- a/compiler/plugins/locals/locals.nim
+++ b/compiler/plugins/locals.nim
@@ -9,10 +9,10 @@
 
 ## The builtin 'system.locals' implemented as a plugin.
 
-import compiler/pluginsupport, compiler/ast, compiler/astalgo,
-  compiler/magicsys, compiler/lookups, compiler/semdata, compiler/lowerings
+import ".." / [pluginsupport, ast, astalgo,
+  magicsys, lookups, semdata, lowerings]
 
-proc semLocals(c: PContext, n: PNode): PNode =
+proc semLocals*(c: PContext, n: PNode): PNode =
   var counter = 0
   var tupleType = newTypeS(tyTuple, c)
   result = newNodeIT(nkPar, n.info, tupleType)
@@ -29,7 +29,7 @@ proc semLocals(c: PContext, n: PNode): PNode =
             {tyVarargs, tyOpenArray, tyTypeDesc, tyStatic, tyExpr, tyStmt, tyEmpty}:
 
         var field = newSym(skField, it.name, getCurrOwner(c), n.info)
-        field.typ = it.typ.skipTypes({tyGenericInst, tyVar})
+        field.typ = it.typ.skipTypes({tyVar})
         field.position = counter
         inc(counter)
 
@@ -39,5 +39,3 @@ proc semLocals(c: PContext, n: PNode): PNode =
         var a = newSymNode(it, result.info)
         if it.typ.skipTypes({tyGenericInst}).kind == tyVar: a = newDeref(a)
         result.add(a)
-
-registerPlugin("stdlib", "system", "locals", semLocals)
diff --git a/compiler/pluginsupport.nim b/compiler/pluginsupport.nim
index 19a0bc84d..a44436f11 100644
--- a/compiler/pluginsupport.nim
+++ b/compiler/pluginsupport.nim
@@ -7,40 +7,27 @@
 #    distribution, for details about the copyright.
 #
 
-## Plugin support for the Nim compiler. Right now they
-## need to be build with the compiler, no DLL support.
+## Plugin support for the Nim compiler. Right now plugins
+## need to be built with the compiler only: plugins using
+## DLLs or the FFI will not work.
 
 import ast, semdata, idents
 
 type
   Transformation* = proc (c: PContext; n: PNode): PNode {.nimcall.}
-  Plugin = ref object
-    fn, module, package: PIdent
+  Plugin* = tuple
+    package, module, fn: string
     t: Transformation
-    next: Plugin
 
-proc pluginMatches(p: Plugin; s: PSym): bool =
-  if s.name.id != p.fn.id:
+proc pluginMatches*(ic: IdentCache; p: Plugin; s: PSym): bool =
+  if s.name.id != ic.getIdent(p.fn).id:
     return false
   let module = s.skipGenericOwner
   if module == nil or module.kind != skModule or
-      module.name.id != p.module.id:
+      module.name.id != ic.getIdent(p.module).id:
     return false
   let package = module.owner
   if package == nil or package.kind != skPackage or
-      package.name.id != p.package.id:
+      package.name.id != ic.getIdent(p.package).id:
     return false
   return true
-
-var head: Plugin
-
-proc getPlugin*(fn: PSym): Transformation =
-  var it = head
-  while it != nil:
-    if pluginMatches(it, fn): return it.t
-    it = it.next
-
-proc registerPlugin*(package, module, fn: string; t: Transformation) =
-  let oldHead = head
-  head = Plugin(fn: getIdent(fn), module: getIdent(module),
-                 package: getIdent(package), t: t, next: oldHead)
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 387738f6d..f67e74f11 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -12,7 +12,7 @@
 import
   os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer,
   wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees,
-  rodread, types, lookups
+  types, lookups, lineinfos, pathutils
 
 const
   FirstCallConv* = wNimcall
@@ -21,76 +21,100 @@ const
 const
   procPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl,
     wMagic, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader,
-    wCompilerproc, wProcVar, wDeprecated, wVarargs, wCompileTime, wMerge,
+    wCompilerProc, wCore, 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}
+    wAsmNoStackFrame, wError, wDiscardable, wNoInit, wCodegenDecl,
+    wGensym, wInject, wRaises, wTags, wLocks, wDelegator, wGcSafe, wOverride,
+    wConstructor, wExportNims, wUsed, wLiftLocals, wStacktrace, wLinetrace}
   converterPragmas* = procPragmas
   methodPragmas* = procPragmas+{wBase}-{wImportCpp}
   templatePragmas* = {wImmediate, wDeprecated, wError, wGensym, wInject, wDirty,
-    wDelegator, wExportNims, wUsed}
+    wDelegator, wExportNims, wUsed, wPragma}
   macroPragmas* = {FirstCallConv..LastCallConv, wImmediate, wImportc, wExportc,
-    wNodecl, wMagic, wNosideeffect, wCompilerproc, wDeprecated, wExtern,
+    wNodecl, wMagic, wNosideeffect, wCompilerProc, wCore, 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}
+  exprPragmas* = {wLine, wLocks, wNoRewrite, wGcSafe, wNosideeffect}
   stmtPragmas* = {wChecks, wObjChecks, wFieldChecks, wRangechecks,
-    wBoundchecks, wOverflowchecks, wNilchecks, wAssertions, wWarnings, wHints,
+    wBoundchecks, wOverflowchecks, wNilchecks, wMovechecks, wAssertions,
+    wWarnings, wHints,
     wLinedir, wStacktrace, wLinetrace, wOptimization, wHint, wWarning, wError,
     wFatal, wDefine, wUndef, wCompile, wLink, wLinksys, wPure, wPush, wPop,
-    wBreakpoint, wWatchPoint, wPassl, wPassc, wDeadCodeElim, wDeprecated,
+    wBreakpoint, wWatchPoint, wPassl, wPassc,
+    wDeadCodeElimUnused,  # deprecated, always on
+    wDeprecated,
     wFloatchecks, wInfChecks, wNanChecks, wPragma, wEmit, wUnroll,
-    wLinearScanEnd, wPatterns, wEffects, wNoForward, wComputedGoto,
+    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}
+    wRaises, wLocks, wTags, wGcSafe, wCodegenDecl}
   typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl,
-    wPure, wHeader, wCompilerproc, wFinal, wSize, wExtern, wShallow,
+    wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wExtern, wShallow,
     wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef,
     wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
-    wBorrow, wGcSafe, wExportNims, wPartial, wUsed}
+    wBorrow, wGcSafe, wExportNims, wPartial, wUsed, wExplain, wPackage}
   fieldPragmas* = {wImportc, wExportc, wDeprecated, wExtern,
     wImportCpp, wImportObjC, wError, wGuard, wBitsize, wUsed}
   varPragmas* = {wImportc, wExportc, wVolatile, wRegister, wThreadVar, wNodecl,
-    wMagic, wHeader, wDeprecated, wCompilerproc, wDynlib, wExtern,
+    wMagic, wHeader, wDeprecated, wCompilerProc, wCore, 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}
+    wIntDefine, wStrDefine, wUsed, wCompilerProc, wCore}
   letPragmas* = varPragmas
   procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNosideeffect,
                       wThread, wRaises, wLocks, wTags, wGcSafe}
+  forVarPragmas* = {wInject, wGensym}
   allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
 
+proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode =
+  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)
-# implementation
 
-proc invalidPragma(n: PNode) =
-  localError(n.info, errInvalidPragmaX, renderTree(n, {renderNoComments}))
+proc recordPragma(c: PContext; n: PNode; key, val: string; val2 = "") =
+  var recorded = newNodeI(nkCommentStmt, n.info)
+  recorded.add newStrNode(key, n.info)
+  recorded.add newStrNode(val, n.info)
+  if val2.len > 0: recorded.add newStrNode(val2, n.info)
+  c.graph.recordStmt(c.graph, c.module, 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) =
+  localError(c.config, n.info, "cannot attach a custom pragma to '" & s.name.s & "'")
 
 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:
+      if it.kind in nkPragmaCallKinds and it.len == 2 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(c, it)
+        else: invalidPragma(c, it)
       else:
-        invalidPragma(it)
+        invalidPragma(c, it)
 
-proc setExternName(s: PSym, extname: string, info: TLineInfo) =
+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)
@@ -100,75 +124,76 @@ proc setExternName(s: PSym, extname: string, info: TLineInfo) =
     try:
       s.loc.r = rope(extname % s.name.s)
     except ValueError:
-      localError(info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)")
-  if gCmd == cmdPretty and '$' notin extname:
+      localError(c.config, info, "invalid extern name: '" & extname & "'. (Forgot to escape '$'?)")
+  if c.config.cmd == cmdPretty and '$' notin extname:
     # note that '{.importc.}' is transformed into '{.importc: "$1".}'
     s.loc.flags.incl(lfFullExternalName)
 
-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)
-  let m = s.getModule()
-  incl(m.flags, sfCompileToCpp)
-  extccomp.gMixedMode = true
+  if c.config.cmd == cmdCompileToC:
+    let m = s.getModule()
+    incl(m.flags, sfCompileToCpp)
+  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)
 
-proc newEmptyStrNode(n: PNode): PNode {.noinline.} =
-  result = newNodeIT(nkStrLit, n.info, getSysType(tyString))
+proc newEmptyStrNode(c: PContext; n: PNode): PNode {.noinline.} =
+  result = newNodeIT(nkStrLit, n.info, getSysType(c.graph, n.info, tyString))
   result.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]
     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)
+  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)
+    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 processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
@@ -177,8 +202,8 @@ proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
 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
@@ -187,7 +212,7 @@ proc processMagic(c: PContext, n: PNode, s: PSym) =
     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
@@ -195,33 +220,38 @@ proc wordToCallConv(sw: TSpecialWord): TCallingConvention =
   result = TCallingConvention(ord(ccDefault) + ord(sw) - ord(wNimcall))
 
 proc isTurnedOn(c: PContext, n: PNode): bool =
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     let x = c.semConstBoolExpr(c, n.sons[1])
     n.sons[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) =
-  if isTurnedOn(c, n): gOptions = gOptions + op
-  else: gOptions = gOptions - op
+proc onOff(c: PContext, n: PNode, op: TOptions, resOptions: var TOptions) =
+  if isTurnedOn(c, n): resOptions = resOptions + op
+  else: resOptions = resOptions - op
 
-proc pragmaDeadCodeElim(c: PContext, n: PNode) =
-  if isTurnedOn(c, n): incl(c.module.flags, sfDeadCodeElim)
-  else: excl(c.module.flags, sfDeadCodeElim)
+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 pragmaNoForward(c: PContext, n: PNode) =
-  if isTurnedOn(c, n): incl(c.module.flags, sfNoForward)
-  else: excl(c.module.flags, sfNoForward)
+  # deprecated as of 0.18.1
+  message(c.config, n.info, warnDeprecated,
+          "use {.experimental: \"codeReordering.\".} instead; " &
+          (if flag == sfNoForward: "{.noForward.}" else: "{.reorder.}"))
 
 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.sons[1].kind == nkIdent:
+    let sw = whichKeyword(n.sons[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:
@@ -232,13 +262,13 @@ 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.isOverriden = 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(...).}
@@ -246,8 +276,8 @@ proc expectDynlibNode(c: PContext, n: PNode): PNode =
     if result.kind == nkSym and result.sym.kind == skConst:
       result = result.sym.ast # look it up
     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):
@@ -255,7 +285,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
     if not lib.isOverriden:
       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:
         addToLib(lib, sym)
@@ -270,184 +300,221 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) =
       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):
+  if n.kind in nkPragmaCallKinds and len(n) == 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.sons[0].sons[0].ident)
+    case whichKeyword(n[0][0].ident)
     of wHint:
-      var x = findStr(msgs.HintsToStr, n.sons[0].sons[1].ident.s)
+      var x = findStr(HintsToStr, n[0][1].ident.s)
       if x >= 0: nk = TNoteKind(x + ord(hintMin))
-      else: invalidPragma(n); return
+      else: invalidPragma(c, n); return
     of wWarning:
-      var x = findStr(msgs.WarningsToStr, n.sons[0].sons[1].ident.s)
+      var x = findStr(WarningsToStr, n[0][1].ident.s)
       if x >= 0: nk = TNoteKind(x + ord(warnMin))
-      else: invalidPragma(n); return
+      else: invalidPragma(c, n); return
     else:
-      invalidPragma(n)
+      invalidPragma(c, n)
       return
 
-    let x = c.semConstBoolExpr(c, n.sons[1])
+    let x = c.semConstBoolExpr(c, n[1])
     n.sons[1] = x
-    if x.kind == nkIntLit and x.intVal != 0: incl(gNotes, nk)
-    else: excl(gNotes, nk)
+    if x.kind == nkIntLit and x.intVal != 0: incl(c.config.notes, nk)
+    else: excl(c.config.notes, nk)
+  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 wNilchecks: {optNilCheck}
+  of wFloatchecks: {optNaNCheck, optInfCheck}
+  of wNanChecks: {optNaNCheck}
+  of wInfChecks: {optInfCheck}
+  of wMovechecks: {optMoveCheck}
+  of wAssertions: {optAssert}
+  of wWarnings: {optWarns}
+  of wHints: {optHints}
+  of wLinedir: {optLineDir}
+  of wStacktrace: {optStackTrace}
+  of wLinetrace: {optLineTrace}
+  of wDebugger: {optEndb}
+  of wProfiler: {optProfiler, optMemTracker}
+  of wMemTracker: {optMemTracker}
+  of wByRef: {optByRef}
+  of wImplicitStatic: {optImplicitStatic}
+  of wPatterns: {optPatterns}
+  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
+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.sons[0].kind == nkBracketExpr: processNote(c, n)
-  elif n.sons[0].kind != nkIdent: result = true
+  elif n.sons[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.sons[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.sons[1].kind != nkIdent:
+          invalidPragma(c, n)
+        else:
+          case n.sons[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 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()
+  if n.sons[start-1].kind in nkPragmaCallKinds:
+    localError(c.config, n.info, "'push' cannot have arguments")
+  var x = newOptionEntry(c.config)
   var y = c.optionStack[^1]
-  x.options = gOptions
+  x.options = c.config.options
   x.defaultCC = y.defaultCC
   x.dynlib = y.dynlib
-  x.notes = gNotes
+  x.notes = c.config.notes
+  x.features = c.features
   c.optionStack.add(x)
   for i in countup(start, sonsLen(n) - 1):
-    if processOption(c, n.sons[i]):
+    if not tryProcessOption(c, n.sons[i], c.config.options):
       # simply store it somewhere:
       if x.otherPragmas.isNil:
         x.otherPragmas = newNodeI(nkPragma, n.info)
       x.otherPragmas.add n.sons[i]
-    #localError(n.info, errOptionExpected)
+    #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)
 
 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.config.options = c.optionStack[^1].options
+    c.config.notes = c.optionStack[^1].notes
+    c.features = c.optionStack[^1].features
     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")
+  if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent):
+    defineSymbol(c.config.symbols, n[1].ident.s)
+    message(c.config, n.info, warnDeprecated, "define")
   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)
+    message(c.config, n.info, warnDeprecated, "undef")
   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) =
+  proc docompile(c: PContext; it: PNode; src, dest: AbsoluteFile) =
+    var cf = Cfile(cname: src, obj: dest, flags: {CfileFlag.External})
+    extccomp.addExternalFileToCompile(c.config, cf)
+    recordPragma(c, it, "compile", src.string, dest.string)
 
   proc getStrLit(c: PContext, n: PNode; i: int): string =
-    n.sons[i] = c.semConstExpr(c, n.sons[i])
-    case n.sons[i].kind
+    n.sons[i] = c.semConstExpr(c, n[i])
+    case n[i].kind
     of nkStrLit, nkRStrLit, nkTripleStrLit:
-      shallowCopy(result, n.sons[i].strVal)
+      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.sons[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 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")
+        found = findFile(c.config, s)
+        if found.isEmpty: found = AbsoluteFile s
+    let obj = toObjFile(c.config, completeCFilePath(c.config, found, false))
+    docompile(c, it, found, obj)
+
+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 pragmaBreakpoint(c: PContext, n: PNode) =
   discard getOptionalStr(c, n, "")
 
 proc pragmaWatchpoint(c: PContext, n: PNode) =
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     n.sons[1] = c.semExpr(c, n.sons[1])
   else:
-    invalidPragma(n)
+    invalidPragma(c, n)
 
 proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
   case n.sons[1].kind
@@ -455,7 +522,7 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
     result = newNode(if n.kind == nkAsmStmt: nkAsmStmt else: nkArgList, n.info)
     var str = n.sons[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
@@ -468,9 +535,10 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
       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 e = searchInScopes(con, getIdent(con.cache, sub))
         if e != nil:
-          if e.kind == skStub: loadStub(e)
+          when false:
+            if e.kind == skStub: loadStub(e)
           incl(e.flags, sfUsed)
           addSon(result, newSymNode(e))
         else:
@@ -481,12 +549,12 @@ proc semAsmOrEmit*(con: PContext, n: PNode, marker: char): PNode =
       if c < 0: break
       a = c + 1
   else:
-    illFormedAstLocal(n)
+    illFormedAstLocal(n, con.config)
     result = newNode(nkAsmStmt, n.info)
 
 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:
@@ -500,136 +568,139 @@ proc pragmaEmit(c: PContext, n: PNode) =
       of nkStrLit, nkRStrLit, nkTripleStrLit:
         n.sons[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)
     else:
-      invalidPragma(n)
+      invalidPragma(c, n)
 
 proc pragmaLine(c: PContext, n: PNode) =
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     n.sons[1] = c.semConstExpr(c, n.sons[1])
     let a = n.sons[1]
-    if a.kind == nkPar:
+    if a.kind in {nkPar, nkTupleConstr}:
+      # unpack the tuple
       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 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
+  let it = n[i]
+  if it.kind notin nkPragmaCallKinds and it.len == 2: invalidPragma(c, n)
+  elif it[0].kind != nkIdent: invalidPragma(c, n)
+  elif it[1].kind != nkIdent: invalidPragma(c, n)
+
+  var userPragma = newSym(skTemplate, it[1].ident, nil, it.info, c.config.options)
+  userPragma.ast = newNode(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")
+      localError(c.config, x.info, errGenerated, "invalid type for raises/tags list")
     x.typ = t
 
-  if n.kind == nkExprColonExpr:
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     let it = n.sons[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")
+      localError(c.config, n.info, errGenerated, "locks pragma takes a list of expressions")
     else:
-      for i in 0 .. <n.len:
+      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)
+  if it.kind notin nkPragmaCallKinds or it.len != 2:
+    invalidPragma(c, 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, it[1].info, "invalid string literal for locks pragma (only allowed string is \"unknown\")")
     else:
       let x = expectIntLit(c, it)
       if x < 0 or x > MaxLockLevel:
-        localError(it[1].info, "integer must be within 0.." & $MaxLockLevel)
+        localError(c.config, it[1].info, "integer must be within 0.." & $MaxLockLevel)
       else:
         result = TLockLevel(x)
 
-proc typeBorrow(sym: PSym, n: PNode) =
-  if n.kind == nkExprColonExpr:
+proc typeBorrow(c: PContext; sym: PSym, n: PNode) =
+  if n.kind in nkPragmaCallKinds and n.len == 2:
     let it = n.sons[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) =
+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)
+  registerCompilerProc(c.graph, s)
 
-proc deprecatedStmt(c: PContext; pragma: PNode) =
-  let pragma = pragma[1]
+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
+    localError(c.config, pragma.info, "list of key:value pairs expected"); return
   for n in pragma:
-    if n.kind in {nkExprColonExpr, nkExprEqExpr}:
+    if n.kind in nkPragmaCallKinds and n.len == 2:
       let dest = qualifiedLookUp(c, n[1], {checkUndeclared})
-      assert dest != nil
-      let src = considerQuotedIdent(n[0])
-      let alias = newSym(skAlias, src, dest, n[0].info)
+      if dest == nil or dest.kind in routineKinds:
+        localError(c.config, n.info, warnUser, "the .deprecated pragma is unreliable for routines")
+      let src = considerQuotedIdent(c, n[0])
+      let alias = newSym(skAlias, src, dest, n[0].info, c.config.options)
       incl(alias.flags, sfExported)
-      if sfCompilerProc in dest.flags: markCompilerProc(alias)
+      if sfCompilerProc in dest.flags: markCompilerProc(c, alias)
       addInterfaceDecl(c, alias)
       n.sons[1] = newSymNode(dest)
     else:
-      localError(n.info, "key:value pair expected")
+      localError(c.config, n.info, "key:value pair expected")
 
 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
@@ -641,104 +712,136 @@ 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), nil, n.info,
+        c.config.options)
   else:
     result = qualifiedLookUp(c, n, {checkUndeclared})
 
-proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
+proc semCustomPragma(c: PContext, n: PNode): PNode =
+  if n.kind == nkIdent:
+    result = newTree(nkCall, n)
+  elif n.kind == nkExprColonExpr:
+    # pragma: arg -> pragma(arg)
+    result = newTree(nkCall, n[0], n[1])
+  elif n.kind in nkPragmaCallKinds:
+    result = n
+  else:
+    invalidPragma(c, n)
+    return n
+
+  let r = c.semOverloadedCall(c, result, n, {skTemplate}, {efNoUndeclared})
+  if r.isNil or sfCustomPragma notin r[0].sym.flags:
+    invalidPragma(c, n)
+  else:
+    result = r
+    if n.kind == nkIdent:
+      result = result[0]
+    elif n.kind == nkExprColonExpr:
+      result.kind = n.kind # pragma(arg) -> pragma: arg
+
+proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
                   validPragmas: TSpecialWords): bool =
   var it = n.sons[i]
-  var key = if it.kind == nkExprColonExpr: it.sons[0] else: it
+  var key = if it.kind in nkPragmaCallKinds and it.len > 1: it.sons[0] else: it
   if key.kind == nkBracketExpr:
     processNote(c, it)
     return
-  let ident = considerQuotedIdent(key)
+  elif key.kind notin nkIdentKinds:
+    n.sons[i] = semCustomPragma(c, it)
+    return
+  let ident = considerQuotedIdent(c, key)
   var userPragma = strTableGet(c.userPragmas, ident)
   if userPragma != nil:
+    # number of pragmas increase/decrease with user pragma expansion
     inc c.instCounter
     if c.instCounter > 100:
-      globalError(it.info, errRecursiveDependencyX, userPragma.name.s)
+      globalError(c.config, it.info, "recursive dependency: " & 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
+    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
     dec c.instCounter
   else:
-    var k = whichKeyword(ident)
+    let k = whichKeyword(ident)
     if k in validPragmas:
       case k
       of wExportc:
-        makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info)
+        makeExternExport(c, sym, getOptionalStr(c, it, "$1"), it.info)
         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)
+        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 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)
+          message(c.config, n.info, warnDeprecated, "use 'untyped' parameters instead; immediate")
+        else: invalidPragma(c, it)
       of wDirty:
         if sym.kind == skTemplate: incl(sym.flags, sfDirty)
-        else: invalidPragma(it)
+        else: invalidPragma(c, it)
       of wImportCpp:
-        processImportCpp(sym, getOptionalStr(c, it, "$1"), it.info)
+        processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
       of wImportObjC:
-        processImportObjC(sym, getOptionalStr(c, it, "$1"), it.info)
+        processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info)
       of wAlign:
-        if sym.typ == nil: invalidPragma(it)
+        if sym.typ == nil: invalidPragma(c, it)
         var align = expectIntLit(c, it)
         if (not isPowerOfTwo(align) and align != 0) or align >% high(int16):
-          localError(it.info, errPowerOfTwoExpected)
+          localError(c.config, it.info, "power of two expected")
         else:
           sym.typ.align = align.int16
       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)
+          localError(c.config, it.info, "size may only be 1, 2, 4 or 8")
         else:
           sym.typ.size = size
+          # TODO, this is not correct
+          sym.typ.align = int16(size)
       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 wRegister:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfRegister)
       of wThreadVar:
-        noVal(it)
-        incl(sym.flags, sfThread)
-      of wDeadCodeElim: pragmaDeadCodeElim(c, it)
+        noVal(c, it)
+        incl(sym.flags, {sfThread, sfGlobal})
+      of wDeadCodeElimUnused: discard  # 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(it)
+        noVal(c, it)
         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)
+        noVal(c, it)
       of wConstructor:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfConstructor)
       of wHeader:
         var lib = getLib(c, libHeader, getStrLitNode(c, it))
@@ -748,100 +851,130 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         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)
+        noVal(c, it)
+        if sym != nil:
+          incl(sym.flags, sfNoSideEffect)
+          if sym.typ != nil: incl(sym.typ.flags, tfNoSideEffect)
       of wSideeffect:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfSideEffect)
       of wNoreturn:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfNoReturn)
+        if sym.typ[0] != nil:
+          localError(c.config, sym.ast[paramsPos][0].info,
+            ".noreturn with return type not allowed")
       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 wProcVar:
-        noVal(it)
+        noVal(c, it)
         incl(sym.flags, sfProcvar)
+      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}:
+          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
       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 wUsed in validPragmas:
           # 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))
+          let s = expectStrLit(c, it)
+          recordPragma(c, it, "error", s)
+          localError(c.config, it.info, errUser, s)
+      of wFatal: fatal(c.config, it.info, errUser, expectStrLit(c, it))
       of wDefine: processDefine(c, it)
       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 wLink: processLink(c, it)
+      of wPassl:
+        let s = expectStrLit(c, it)
+        extccomp.addLinkOption(c.config, s)
+        recordPragma(c, it, "passl", s)
+      of wPassc:
+        let s = expectStrLit(c, it)
+        extccomp.addCompileOption(c.config, s)
+        recordPragma(c, it, "passc", s)
       of wBreakpoint: pragmaBreakpoint(c, it)
       of wWatchPoint: pragmaWatchpoint(c, it)
       of wPush:
@@ -849,129 +982,148 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int,
         result = true
       of wPop: processPop(c, it)
       of wPragma:
-        processPragma(c, n, i)
-        result = true
+        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)
+         wLinedir, wOptimization, wMovechecks, wCallconv, wDebugger, wProfiler,
+         wFloatchecks, wNanChecks, wInfChecks, wPatterns:
+        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)
+        if sym.typ == nil: invalidPragma(c, it)
         else: sym.typ.callConv = wordToCallConv(k)
       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 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)
+        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)
+        noVal(c, it)
+        if sym.typ == nil: invalidPragma(c, it)
         else: incl(sym.typ.flags, tfNeedsInit)
       of wByRef:
-        noVal(it)
+        noVal(c, it)
         if sym == nil or sym.typ == nil:
-          if processOption(c, it): localError(it.info, errOptionExpected)
+          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 != 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 wLocks:
         if sym == nil: pragmaLockStmt(c, it)
-        elif sym.typ == nil: invalidPragma(it)
+        elif sym.typ == nil: invalidPragma(c, it)
         else: sym.typ.lockLevel = pragmaLocks(c, it)
       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)
+        if sym == nil: invalidPragma(c, it)
+        else: magicsys.registerNimScriptSymbol(c.graph, sym)
       of wInjectStmt:
-        if it.kind != nkExprColonExpr:
-          localError(it.info, errExprExpected)
+        if it.kind notin nkPragmaCallKinds or it.len != 2:
+          localError(c.config, it.info, "expression expected")
         else:
           it.sons[1] = c.semExpr(c, it.sons[1])
       of wExperimental:
-        noVal(it)
-        if isTopLevel(c):
-          c.module.flags.incl sfExperimental
-        else:
-          localError(it.info, "'experimental' pragma only valid as toplevel statement")
+        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 wThis:
-        if it.kind == nkExprColonExpr:
-          c.selfName = considerQuotedIdent(it[1])
+        if it.kind in nkPragmaCallKinds and it.len == 2:
+          c.selfName = considerQuotedIdent(c, it[1])
+          message(c.config, n.info, warnDeprecated, "the '.this' pragma")
+        elif it.kind == nkIdent or it.len == 1:
+          c.selfName = getIdent(c.cache, "self")
+          message(c.config, n.info, warnDeprecated, "the '.this' pragma")
         else:
-          c.selfName = getIdent("self")
+          localError(c.config, it.info, "'this' pragma is allowed to have zero or one arguments")
       of wNoRewrite:
-        noVal(it)
+        noVal(c, it)
       of wBase:
-        noVal(it)
+        noVal(c, it)
         sym.flags.incl sfBase
       of wIntDefine:
         sym.magic = mIntDefine
       of wStrDefine:
         sym.magic = mStrDefine
       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)
+      of wLiftLocals: discard
+      else: invalidPragma(c, it)
+    elif sym == nil or (sym != nil and sym.kind in {skVar, skLet, skParam, 
+                      skField, skProc, skFunc, skConverter, skMethod, skType}):
+      n.sons[i] = semCustomPragma(c, it)
+    elif sym != nil:
+      illegalCustomPragma(c, it, sym)
+    else:
+      invalidPragma(c, it)
+
+proc mergePragmas(n, pragmas: PNode) =
+  if n[pragmasPos].kind == nkEmpty:
+    n[pragmasPos] = pragmas
+  else:
+    for p in pragmas: n.sons[pragmasPos].add p
 
 proc implicitPragmas*(c: PContext, sym: PSym, n: PNode,
                       validPragmas: TSpecialWords) =
@@ -979,14 +1131,17 @@ proc implicitPragmas*(c: PContext, sym: PSym, n: PNode,
     for it in c.optionStack:
       let o = it.otherPragmas
       if not o.isNil:
-        pushInfoContext(n.info)
-        for i in countup(0, sonsLen(o) - 1):
+        pushInfoContext(c.config, n.info)
+        var i = 0
+        while i < o.len:
           if singlePragma(c, sym, o, i, validPragmas):
-            internalError(n.info, "implicitPragmas")
-        popInfoContext()
+            internalError(c.config, n.info, "implicitPragmas")
+          inc i
+        popInfoContext(c.config)
+        if sym.kind in routineKinds and sym.ast != nil: mergePragmas(sym.ast, o)
 
     if lfExportLib in sym.loc.flags and sfExportc notin sym.flags:
-      localError(n.info, errDynlibRequiresExportc)
+      localError(c.config, n.info, ".dynlib requires .exportc")
     var lib = c.optionStack[^1].dynlib
     if {lfDynamicLib, lfHeader} * sym.loc.flags == {} and
         sfImportc in sym.flags and lib != nil:
@@ -995,11 +1150,10 @@ proc implicitPragmas*(c: PContext, sym: PSym, n: PNode,
       if sym.loc.r == nil: sym.loc.r = 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
 
@@ -1007,9 +1161,10 @@ proc hasPragma*(n: PNode, pragma: TSpecialWord): bool =
 
 proc pragmaRec(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) =
   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
+  var i = 0
+  while i < n.len:
+    if singlePragma(c, sym, n, i, validPragmas): break
+    inc i
 
 proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) =
   if n == nil: return
diff --git a/compiler/prefixmatches.nim b/compiler/prefixmatches.nim
index 00e2c537d..246d1ae5e 100644
--- a/compiler/prefixmatches.nim
+++ b/compiler/prefixmatches.nim
@@ -24,7 +24,7 @@ proc prefixMatch*(p, s: string): PrefixMatch =
   # check for prefix/contains:
   while i < L:
     if s[i] == '_': inc i
-    if eq(s[i], p[0]):
+    if i < L and eq(s[i], p[0]):
       var ii = i+1
       var jj = 1
       while ii < L and jj < p.len:
@@ -43,10 +43,10 @@ proc prefixMatch*(p, s: string): PrefixMatch =
     i = 1
     var j = 1
     while i < s.len:
-      if s[i] == '_' and i < s.len-1:
+      if i < s.len-1 and s[i] == '_':
         if j < p.len and eq(p[j], s[i+1]): inc j
         else: return PrefixMatch.None
-      if s[i] in {'A'..'Z'} and s[i-1] notin {'A'..'Z'}:
+      if i < s.len and s[i] in {'A'..'Z'} and s[i-1] notin {'A'..'Z'}:
         if j < p.len and eq(p[j], s[i]): inc j
         else: return PrefixMatch.None
       inc i
diff --git a/compiler/procfind.nim b/compiler/procfind.nim
index 137765ddb..3f47e7e8a 100644
--- a/compiler/procfind.nim
+++ b/compiler/procfind.nim
@@ -14,14 +14,12 @@ import
   ast, astalgo, msgs, semdata, types, trees, strutils
 
 proc equalGenericParams(procA, procB: PNode): bool =
-  if sonsLen(procA) != sonsLen(procB): return
+  if sonsLen(procA) != sonsLen(procB): return false
   for i in countup(0, sonsLen(procA) - 1):
     if procA.sons[i].kind != nkSym:
-      internalError(procA.info, "equalGenericParams")
-      return
+      return false
     if procB.sons[i].kind != nkSym:
-      internalError(procB.info, "equalGenericParams")
-      return
+      return false
     let a = procA.sons[i].sym
     let b = procB.sons[i].sym
     if a.name.id != b.name.id or
@@ -57,7 +55,7 @@ proc searchForProcOld*(c: PContext, scope: PScope, fn: PSym): PSym =
         of paramsEqual:
           return
         of paramsIncompatible:
-          localError(fn.info, errNotOverloadable, fn.name.s)
+          localError(c.config, fn.info, "overloaded '$1' leads to ambiguous calls" % fn.name.s)
           return
         of paramsNotEqual:
           discard
@@ -66,30 +64,25 @@ proc searchForProcOld*(c: PContext, scope: PScope, fn: PSym): PSym =
 proc searchForProcNew(c: PContext, scope: PScope, fn: PSym): PSym =
   const flags = {ExactGenericParams, ExactTypeDescValues,
                  ExactConstraints, IgnoreCC}
-
   var it: TIdentIter
-
   result = initIdentIter(it, scope.symbols, fn.name)
   while result != nil:
-    if result.kind == fn.kind and sameType(result.typ, fn.typ, flags):
+    if result.kind == fn.kind: #and sameType(result.typ, fn.typ, flags):
       case equalParams(result.typ.n, fn.typ.n)
       of paramsEqual:
         if (sfExported notin result.flags) and (sfExported in fn.flags):
           let message = ("public implementation '$1' has non-public " &
                          "forward declaration in $2") %
-                        [getProcHeader(result), $result.info]
-          localError(fn.info, errGenerated, message)
+                        [getProcHeader(c.config, result), c.config$result.info]
+          localError(c.config, fn.info, message)
         return
       of paramsIncompatible:
-        localError(fn.info, errNotOverloadable, fn.name.s)
+        localError(c.config, fn.info, "overloaded '$1' leads to ambiguous calls" % fn.name.s)
         return
       of paramsNotEqual:
         discard
-
     result = nextIdentIter(it, scope.symbols)
 
-  return nil
-
 proc searchForProc*(c: PContext, scope: PScope, fn: PSym): PSym =
   result = searchForProcNew(c, scope, fn)
   when false:
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index badcaea66..ce47cf219 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -10,19 +10,20 @@
 # This module implements the renderer of the standard Nim representation.
 
 import
-  lexer, options, idents, strutils, ast, msgs
+  lexer, options, idents, strutils, ast, msgs, lineinfos
 
 type
   TRenderFlag* = enum
     renderNone, renderNoBody, renderNoComments, renderDocComments,
-    renderNoPragmas, renderIds, renderNoProcDefs
+    renderNoPragmas, renderIds, renderNoProcDefs, renderSyms
   TRenderFlags* = set[TRenderFlag]
-  TRenderTok*{.final.} = object
+  TRenderTok* = object
     kind*: TTokType
     length*: int16
+    sym*: PSym
 
   TRenderTokSeq* = seq[TRenderTok]
-  TSrcGen*{.final.} = object
+  TSrcGen* = object
     indent*: int
     lineLen*: int
     pos*: int              # current position for iteration over the buffer
@@ -34,17 +35,14 @@ type
     pendingWhitespace: int
     comStack*: seq[PNode]  # comment stack
     flags*: TRenderFlags
+    inGenericParams: bool
     checkAnon: bool        # we're in a context that can contain sfAnon
     inPragma: int
+    when defined(nimpretty):
+      pendingNewlineCount: int
+    fid*: FileIndex
+    config*: ConfigRef
 
-
-proc renderModule*(n: PNode, filename: string, renderFlags: TRenderFlags = {})
-proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string
-proc initTokRender*(r: var TSrcGen, n: PNode, renderFlags: TRenderFlags = {})
-proc getNextTok*(r: var TSrcGen, kind: var TTokType, literal: var string)
-
-proc `$`*(n: PNode): string = n.renderTree
-# implementation
 # We render the source code in a two phases: The first
 # determines how long the subtree will likely be, the second
 # phase appends to a buffer that will be the output.
@@ -66,13 +64,35 @@ proc renderDefinitionName*(s: PSym, noQuotes = false): string =
   else:
     result = '`' & x & '`'
 
+when not defined(nimpretty):
+  const
+    IndentWidth = 2
+    longIndentWid = IndentWidth * 2
+else:
+  template IndentWidth: untyped = lexer.gIndentationWidth
+  template longIndentWid: untyped = IndentWidth() * 2
+
+  proc minmaxLine(n: PNode): (int, int) =
+    case n.kind
+    of nkTripleStrLit:
+      result = (n.info.line.int, n.info.line.int + countLines(n.strVal))
+    of nkCommentStmt:
+      result = (n.info.line.int, n.info.line.int + countLines(n.comment))
+    else:
+      result = (n.info.line.int, n.info.line.int)
+    for i in 0 ..< safeLen(n):
+      let (currMin, currMax) = minmaxLine(n[i])
+      if currMin < result[0]: result[0] = currMin
+      if currMax > result[1]: result[1] = currMax
+
+  proc lineDiff(a, b: PNode): int =
+    result = minmaxLine(b)[0] - minmaxLine(a)[1]
+
 const
-  IndentWidth = 2
-  longIndentWid = 4
   MaxLineLen = 80
   LineCommentColumn = 30
 
-proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) =
+proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags; config: ConfigRef) =
   g.comStack = @[]
   g.tokens = @[]
   g.indent = 0
@@ -83,18 +103,24 @@ proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags) =
   g.flags = renderFlags
   g.pendingNL = -1
   g.pendingWhitespace = -1
-  g.checkAnon = false
+  g.inGenericParams = false
+  g.config = config
 
-proc addTok(g: var TSrcGen, kind: TTokType, s: string) =
+proc addTok(g: var TSrcGen, kind: TTokType, s: string; sym: PSym = nil) =
   var length = len(g.tokens)
   setLen(g.tokens, length + 1)
   g.tokens[length].kind = kind
   g.tokens[length].length = int16(len(s))
+  g.tokens[length].sym = sym
   add(g.buf, s)
 
 proc addPendingNL(g: var TSrcGen) =
   if g.pendingNL >= 0:
-    addTok(g, tkSpaces, "\n" & spaces(g.pendingNL))
+    when defined(nimpretty):
+      let newlines = repeat("\n", clamp(g.pendingNewlineCount, 1, 3))
+    else:
+      const newlines = "\n"
+    addTok(g, tkSpaces, newlines & spaces(g.pendingNL))
     g.lineLen = g.pendingNL
     g.pendingNL = - 1
     g.pendingWhitespace = -1
@@ -118,11 +144,17 @@ proc putNL(g: var TSrcGen) =
 
 proc optNL(g: var TSrcGen, indent: int) =
   g.pendingNL = indent
-  g.lineLen = indent          # BUGFIX
+  g.lineLen = indent
+  when defined(nimpretty): g.pendingNewlineCount = 0
 
 proc optNL(g: var TSrcGen) =
   optNL(g, g.indent)
 
+proc optNL(g: var TSrcGen; a, b: PNode) =
+  g.pendingNL = g.indent
+  g.lineLen = g.indent
+  when defined(nimpretty): g.pendingNewlineCount = lineDiff(a, b)
+
 proc indentNL(g: var TSrcGen) =
   inc(g.indent, IndentWidth)
   g.pendingNL = g.indent
@@ -135,34 +167,23 @@ proc dedent(g: var TSrcGen) =
     dec(g.pendingNL, IndentWidth)
     dec(g.lineLen, IndentWidth)
 
-proc put(g: var TSrcGen, kind: TTokType, s: string) =
+proc put(g: var TSrcGen, kind: TTokType, s: string; sym: PSym = nil) =
   if kind != tkSpaces:
     addPendingNL(g)
     if len(s) > 0:
-      addTok(g, kind, s)
+      addTok(g, kind, s, sym)
       inc(g.lineLen, len(s))
   else:
     g.pendingWhitespace = s.len
 
-proc toNimChar(c: char): string =
-  case c
-  of '\0': result = "\\0"
-  of '\x01'..'\x1F', '\x80'..'\xFF': result = "\\x" & strutils.toHex(ord(c), 2)
-  of '\'', '\"', '\\': result = '\\' & c
-  else: result = c & ""
-
-proc makeNimString(s: string): string =
-  result = "\""
-  for i in countup(0, len(s)-1): add(result, toNimChar(s[i]))
-  add(result, '\"')
-
 proc putComment(g: var TSrcGen, s: string) =
-  if s.isNil: return
+  if s.len == 0: return
   var i = 0
+  let hi = len(s) - 1
   var isCode = (len(s) >= 2) and (s[1] != ' ')
   var ind = g.lineLen
   var com = "## "
-  while true:
+  while i <= hi:
     case s[i]
     of '\0':
       break
@@ -170,7 +191,7 @@ proc putComment(g: var TSrcGen, s: string) =
       put(g, tkComment, com)
       com = "## "
       inc(i)
-      if s[i] == '\x0A': inc(i)
+      if i <= hi and s[i] == '\x0A': inc(i)
       optNL(g, ind)
     of '\x0A':
       put(g, tkComment, com)
@@ -185,28 +206,29 @@ proc putComment(g: var TSrcGen, s: string) =
       # gets too long:
       # compute length of the following word:
       var j = i
-      while s[j] > ' ': inc(j)
+      while j <= hi and s[j] > ' ': inc(j)
       if not isCode and (g.lineLen + (j - i) > MaxLineLen):
         put(g, tkComment, com)
         optNL(g, ind)
         com = "## "
-      while s[i] > ' ':
+      while i <= hi and s[i] > ' ':
         add(com, s[i])
         inc(i)
   put(g, tkComment, com)
   optNL(g)
 
 proc maxLineLength(s: string): int =
-  if s.isNil: return 0
+  if s.len == 0: return 0
   var i = 0
+  let hi = len(s) - 1
   var lineLen = 0
-  while true:
+  while i <= hi:
     case s[i]
     of '\0':
       break
     of '\x0D':
       inc(i)
-      if s[i] == '\x0A': inc(i)
+      if i <= hi and s[i] == '\x0A': inc(i)
       result = max(result, lineLen)
       lineLen = 0
     of '\x0A':
@@ -219,7 +241,7 @@ proc maxLineLength(s: string): int =
 
 proc putRawStr(g: var TSrcGen, kind: TTokType, s: string) =
   var i = 0
-  var hi = len(s) - 1
+  let hi = len(s) - 1
   var str = ""
   while i <= hi:
     case s[i]
@@ -227,7 +249,7 @@ proc putRawStr(g: var TSrcGen, kind: TTokType, s: string) =
       put(g, kind, str)
       str = ""
       inc(i)
-      if (i <= hi) and (s[i] == '\x0A'): inc(i)
+      if i <= hi and s[i] == '\x0A': inc(i)
       optNL(g, 0)
     of '\x0A':
       put(g, kind, str)
@@ -261,7 +283,7 @@ const
 
 proc shouldRenderComment(g: var TSrcGen, n: PNode): bool =
   result = false
-  if n.comment != nil:
+  if n.comment.len > 0:
     result = (renderNoComments notin g.flags) or
         (renderDocComments in g.flags)
 
@@ -283,197 +305,220 @@ proc gcoms(g: var TSrcGen) =
   for i in countup(0, high(g.comStack)): gcom(g, g.comStack[i])
   popAllComs(g)
 
-proc lsub(n: PNode): int
-proc litAux(n: PNode, x: BiggestInt, size: int): string =
+proc lsub(g: TSrcGen; n: PNode): int
+proc litAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string =
   proc skip(t: PType): PType =
     result = t
-    while result.kind in {tyGenericInst, tyRange, tyVar, tyDistinct,
-                          tyOrdinal, tyAlias}:
+    while result != nil and result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct,
+                          tyOrdinal, tyAlias, tySink}:
       result = lastSon(result)
-  if n.typ != nil and n.typ.skip.kind in {tyBool, tyEnum}:
-    let enumfields = n.typ.skip.n
+  let typ = n.typ.skip
+  if typ != nil and typ.kind in {tyBool, tyEnum}:
+    if sfPure in typ.sym.flags:
+      result = typ.sym.name.s & '.'
+    let enumfields = typ.n
     # we need a slow linear search because of enums with holes:
     for e in items(enumfields):
-      if e.sym.position == x: return e.sym.name.s
+      if e.sym.position == x:
+        result &= e.sym.name.s
+        return
 
   if nfBase2 in n.flags: result = "0b" & toBin(x, size * 8)
   elif nfBase8 in n.flags: result = "0o" & toOct(x, size * 3)
   elif nfBase16 in n.flags: result = "0x" & toHex(x, size * 2)
   else: result = $x
 
-proc ulitAux(n: PNode, x: BiggestInt, size: int): string =
+proc ulitAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string =
   if nfBase2 in n.flags: result = "0b" & toBin(x, size * 8)
   elif nfBase8 in n.flags: result = "0o" & toOct(x, size * 3)
   elif nfBase16 in n.flags: result = "0x" & toHex(x, size * 2)
   else: result = $x
   # XXX proper unsigned output!
 
-proc atom(n: PNode): string =
+proc atom(g: TSrcGen; n: PNode): string =
+  when defined(nimpretty):
+    doAssert g.config != nil, "g.config not initialized!"
+    let comment = if n.info.commentOffsetA < n.info.commentOffsetB:
+                    " " & fileSection(g.config, g.fid, n.info.commentOffsetA, n.info.commentOffsetB)
+                  else:
+                    ""
+    if n.info.offsetA <= n.info.offsetB:
+      # for some constructed tokens this can not be the case and we're better
+      # off to not mess with the offset then.
+      return fileSection(g.config, g.fid, n.info.offsetA, n.info.offsetB) & comment
   var f: float32
   case n.kind
   of nkEmpty: result = ""
   of nkIdent: result = n.ident.s
   of nkSym: result = n.sym.name.s
-  of nkStrLit: result = makeNimString(n.strVal)
-  of nkRStrLit: result = "r\"" & replace(n.strVal, "\"", "\"\"")  & '\"'
+  of nkStrLit: result = ""; result.addQuoted(n.strVal)
+  of nkRStrLit: result = "r\"" & replace(n.strVal, "\"", "\"\"") & '\"'
   of nkTripleStrLit: result = "\"\"\"" & n.strVal & "\"\"\""
-  of nkCharLit: result = '\'' & toNimChar(chr(int(n.intVal))) & '\''
-  of nkIntLit: result = litAux(n, n.intVal, 4)
-  of nkInt8Lit: result = litAux(n, n.intVal, 1) & "\'i8"
-  of nkInt16Lit: result = litAux(n, n.intVal, 2) & "\'i16"
-  of nkInt32Lit: result = litAux(n, n.intVal, 4) & "\'i32"
-  of nkInt64Lit: result = litAux(n, n.intVal, 8) & "\'i64"
-  of nkUIntLit: result = ulitAux(n, n.intVal, 4) & "\'u"
-  of nkUInt8Lit: result = ulitAux(n, n.intVal, 1) & "\'u8"
-  of nkUInt16Lit: result = ulitAux(n, n.intVal, 2) & "\'u16"
-  of nkUInt32Lit: result = ulitAux(n, n.intVal, 4) & "\'u32"
-  of nkUInt64Lit: result = ulitAux(n, n.intVal, 8) & "\'u64"
+  of nkCharLit:
+    result = "\'"
+    result.addEscapedChar(chr(int(n.intVal)));
+    result.add '\''
+  of nkIntLit: result = litAux(g, n, n.intVal, 4)
+  of nkInt8Lit: result = litAux(g, n, n.intVal, 1) & "\'i8"
+  of nkInt16Lit: result = litAux(g, n, n.intVal, 2) & "\'i16"
+  of nkInt32Lit: result = litAux(g, n, n.intVal, 4) & "\'i32"
+  of nkInt64Lit: result = litAux(g, n, n.intVal, 8) & "\'i64"
+  of nkUIntLit: result = ulitAux(g, n, n.intVal, 4) & "\'u"
+  of nkUInt8Lit: result = ulitAux(g, n, n.intVal, 1) & "\'u8"
+  of nkUInt16Lit: result = ulitAux(g, n, n.intVal, 2) & "\'u16"
+  of nkUInt32Lit: result = ulitAux(g, n, n.intVal, 4) & "\'u32"
+  of nkUInt64Lit: result = ulitAux(g, n, n.intVal, 8) & "\'u64"
   of nkFloatLit:
     if n.flags * {nfBase2, nfBase8, nfBase16} == {}: result = $(n.floatVal)
-    else: result = litAux(n, (cast[PInt64](addr(n.floatVal)))[] , 8)
+    else: result = litAux(g, n, (cast[PInt64](addr(n.floatVal)))[] , 8)
   of nkFloat32Lit:
     if n.flags * {nfBase2, nfBase8, nfBase16} == {}:
       result = $n.floatVal & "\'f32"
     else:
       f = n.floatVal.float32
-      result = litAux(n, (cast[PInt32](addr(f)))[], 4) & "\'f32"
+      result = litAux(g, n, (cast[PInt32](addr(f)))[], 4) & "\'f32"
   of nkFloat64Lit:
     if n.flags * {nfBase2, nfBase8, nfBase16} == {}:
       result = $n.floatVal & "\'f64"
     else:
-      result = litAux(n, (cast[PInt64](addr(n.floatVal)))[], 8) & "\'f64"
+      result = litAux(g, n, (cast[PInt64](addr(n.floatVal)))[], 8) & "\'f64"
   of nkNilLit: result = "nil"
   of nkType:
     if (n.typ != nil) and (n.typ.sym != nil): result = n.typ.sym.name.s
     else: result = "[type node]"
   else:
-    internalError("rnimsyn.atom " & $n.kind)
+    internalError(g.config, "rnimsyn.atom " & $n.kind)
     result = ""
 
-proc lcomma(n: PNode, start: int = 0, theEnd: int = - 1): int =
+proc lcomma(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): int =
   assert(theEnd < 0)
   result = 0
   for i in countup(start, sonsLen(n) + theEnd):
-    inc(result, lsub(n.sons[i]))
-    inc(result, 2)            # for ``, ``
+    let param = n.sons[i]
+    if nfDefaultParam notin param.flags:
+      inc(result, lsub(g, param))
+      inc(result, 2)          # for ``, ``
   if result > 0:
     dec(result, 2)            # last does not get a comma!
 
-proc lsons(n: PNode, start: int = 0, theEnd: int = - 1): int =
+proc lsons(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): int =
   assert(theEnd < 0)
   result = 0
-  for i in countup(start, sonsLen(n) + theEnd): inc(result, lsub(n.sons[i]))
+  for i in countup(start, sonsLen(n) + theEnd): inc(result, lsub(g, n.sons[i]))
 
-proc lsub(n: PNode): int =
+proc lsub(g: TSrcGen; n: PNode): int =
   # computes the length of a tree
   if isNil(n): return 0
-  if n.comment != nil: return MaxLineLen + 1
+  if n.comment.len > 0: return MaxLineLen + 1
   case n.kind
   of nkEmpty: result = 0
   of nkTripleStrLit:
     if containsNL(n.strVal): result = MaxLineLen + 1
-    else: result = len(atom(n))
+    else: result = len(atom(g, n))
   of succ(nkEmpty)..pred(nkTripleStrLit), succ(nkTripleStrLit)..nkNilLit:
-    result = len(atom(n))
+    result = len(atom(g, n))
   of nkCall, nkBracketExpr, nkCurlyExpr, nkConv, nkPattern, nkObjConstr:
-    result = lsub(n.sons[0]) + lcomma(n, 1) + 2
-  of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: result = lsub(n[1])
-  of nkCast: result = lsub(n.sons[0]) + lsub(n.sons[1]) + len("cast[]()")
-  of nkAddr: result = (if n.len>0: lsub(n.sons[0]) + len("addr()") else: 4)
-  of nkStaticExpr: result = lsub(n.sons[0]) + len("static_")
-  of nkHiddenAddr, nkHiddenDeref: result = lsub(n.sons[0])
-  of nkCommand: result = lsub(n.sons[0]) + lcomma(n, 1) + 1
-  of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(n) + 3
-  of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(n) + 2
-  of nkArgList: result = lcomma(n)
+    result = lsub(g, n.sons[0]) + lcomma(g, n, 1) + 2
+  of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: result = lsub(g, n[1])
+  of nkCast: result = lsub(g, n.sons[0]) + lsub(g, n.sons[1]) + len("cast[]()")
+  of nkAddr: result = (if n.len>0: lsub(g, n.sons[0]) + len("addr()") else: 4)
+  of nkStaticExpr: result = lsub(g, n.sons[0]) + len("static_")
+  of nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: result = lsub(g, n.sons[0])
+  of nkCommand: result = lsub(g, n.sons[0]) + lcomma(g, n, 1) + 1
+  of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(g, n) + 3
+  of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(g, n) + 2
+  of nkTupleConstr:
+    # assume the trailing comma:
+    result = lcomma(g, n) + 3
+  of nkArgList: result = lcomma(g, n)
   of nkTableConstr:
-    result = if n.len > 0: lcomma(n) + 2 else: len("{:}")
+    result = if n.len > 0: lcomma(g, n) + 2 else: len("{:}")
   of nkClosedSymChoice, nkOpenSymChoice:
-    result = lsons(n) + len("()") + sonsLen(n) - 1
-  of nkTupleTy: result = lcomma(n) + len("tuple[]")
+    result = lsons(g, n) + len("()") + sonsLen(n) - 1
+  of nkTupleTy: result = lcomma(g, n) + len("tuple[]")
   of nkTupleClassTy: result = len("tuple")
-  of nkDotExpr: result = lsons(n) + 1
-  of nkBind: result = lsons(n) + len("bind_")
-  of nkBindStmt: result = lcomma(n) + len("bind_")
-  of nkMixinStmt: result = lcomma(n) + len("mixin_")
-  of nkCheckedFieldExpr: result = lsub(n.sons[0])
-  of nkLambda: result = lsons(n) + len("proc__=_")
-  of nkDo: result = lsons(n) + len("do__:_")
+  of nkDotExpr: result = lsons(g, n) + 1
+  of nkBind: result = lsons(g, n) + len("bind_")
+  of nkBindStmt: result = lcomma(g, n) + len("bind_")
+  of nkMixinStmt: result = lcomma(g, n) + len("mixin_")
+  of nkCheckedFieldExpr: result = lsub(g, n.sons[0])
+  of nkLambda: result = lsons(g, n) + len("proc__=_")
+  of nkDo: result = lsons(g, n) + len("do__:_")
   of nkConstDef, nkIdentDefs:
-    result = lcomma(n, 0, - 3)
+    result = lcomma(g, n, 0, - 3)
     var L = sonsLen(n)
-    if n.sons[L - 2].kind != nkEmpty: result = result + lsub(n.sons[L - 2]) + 2
-    if n.sons[L - 1].kind != nkEmpty: result = result + lsub(n.sons[L - 1]) + 3
-  of nkVarTuple: result = lcomma(n, 0, - 3) + len("() = ") + lsub(lastSon(n))
-  of nkChckRangeF: result = len("chckRangeF") + 2 + lcomma(n)
-  of nkChckRange64: result = len("chckRange64") + 2 + lcomma(n)
-  of nkChckRange: result = len("chckRange") + 2 + lcomma(n)
-  of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString:
+    if n.sons[L - 2].kind != nkEmpty: result = result + lsub(g, n.sons[L - 2]) + 2
+    if n.sons[L - 1].kind != nkEmpty: result = result + lsub(g, n.sons[L - 1]) + 3
+  of nkVarTuple: result = lcomma(g, n, 0, - 3) + len("() = ") + lsub(g, lastSon(n))
+  of nkChckRangeF: result = len("chckRangeF") + 2 + lcomma(g, n)
+  of nkChckRange64: result = len("chckRange64") + 2 + lcomma(g, n)
+  of nkChckRange: result = len("chckRange") + 2 + lcomma(g, n)
+  of nkObjDownConv, nkObjUpConv:
     result = 2
-    if sonsLen(n) >= 1: result = result + lsub(n.sons[0])
-    result = result + lcomma(n, 1)
-  of nkExprColonExpr: result = lsons(n) + 2
-  of nkInfix: result = lsons(n) + 2
+    if sonsLen(n) >= 1: result = result + lsub(g, n.sons[0])
+    result = result + lcomma(g, n, 1)
+  of nkExprColonExpr: result = lsons(g, n) + 2
+  of nkInfix: result = lsons(g, n) + 2
   of nkPrefix:
-    result = lsons(n)+1+(if n.len > 0 and n.sons[1].kind == nkInfix: 2 else: 0)
-  of nkPostfix: result = lsons(n)
-  of nkCallStrLit: result = lsons(n)
-  of nkPragmaExpr: result = lsub(n.sons[0]) + lcomma(n, 1)
-  of nkRange: result = lsons(n) + 2
-  of nkDerefExpr: result = lsub(n.sons[0]) + 2
-  of nkAccQuoted: result = lsons(n) + 2
+    result = lsons(g, n)+1+(if n.len > 0 and n.sons[1].kind == nkInfix: 2 else: 0)
+  of nkPostfix: result = lsons(g, n)
+  of nkCallStrLit: result = lsons(g, n)
+  of nkPragmaExpr: result = lsub(g, n.sons[0]) + lcomma(g, n, 1)
+  of nkRange: result = lsons(g, n) + 2
+  of nkDerefExpr: result = lsub(g, n.sons[0]) + 2
+  of nkAccQuoted: result = lsons(g, n) + 2
   of nkIfExpr:
-    result = lsub(n.sons[0].sons[0]) + lsub(n.sons[0].sons[1]) + lsons(n, 1) +
+    result = lsub(g, n.sons[0].sons[0]) + lsub(g, n.sons[0].sons[1]) + lsons(g, n, 1) +
         len("if_:_")
-  of nkElifExpr: result = lsons(n) + len("_elif_:_")
-  of nkElseExpr: result = lsub(n.sons[0]) + len("_else:_") # type descriptions
-  of nkTypeOfExpr: result = (if n.len > 0: lsub(n.sons[0]) else: 0)+len("type()")
-  of nkRefTy: result = (if n.len > 0: lsub(n.sons[0])+1 else: 0) + len("ref")
-  of nkPtrTy: result = (if n.len > 0: lsub(n.sons[0])+1 else: 0) + len("ptr")
-  of nkVarTy: result = (if n.len > 0: lsub(n.sons[0])+1 else: 0) + len("var")
+  of nkElifExpr: result = lsons(g, n) + len("_elif_:_")
+  of nkElseExpr: result = lsub(g, n.sons[0]) + len("_else:_") # type descriptions
+  of nkTypeOfExpr: result = (if n.len > 0: lsub(g, n.sons[0]) else: 0)+len("type()")
+  of nkRefTy: result = (if n.len > 0: lsub(g, n.sons[0])+1 else: 0) + len("ref")
+  of nkPtrTy: result = (if n.len > 0: lsub(g, n.sons[0])+1 else: 0) + len("ptr")
+  of nkVarTy: result = (if n.len > 0: lsub(g, n.sons[0])+1 else: 0) + len("var")
   of nkDistinctTy:
-    result = len("distinct") + (if n.len > 0: lsub(n.sons[0])+1 else: 0)
+    result = len("distinct") + (if n.len > 0: lsub(g, n.sons[0])+1 else: 0)
     if n.len > 1:
       result += (if n[1].kind == nkWith: len("_with_") else: len("_without_"))
-      result += lcomma(n[1])
-  of nkStaticTy: result = (if n.len > 0: lsub(n.sons[0]) else: 0) +
+      result += lcomma(g, n[1])
+  of nkStaticTy: result = (if n.len > 0: lsub(g, n.sons[0]) else: 0) +
                                                          len("static[]")
-  of nkTypeDef: result = lsons(n) + 3
-  of nkOfInherit: result = lsub(n.sons[0]) + len("of_")
-  of nkProcTy: result = lsons(n) + len("proc_")
-  of nkIteratorTy: result = lsons(n) + len("iterator_")
-  of nkSharedTy: result = lsons(n) + len("shared_")
+  of nkTypeDef: result = lsons(g, n) + 3
+  of nkOfInherit: result = lsub(g, n.sons[0]) + len("of_")
+  of nkProcTy: result = lsons(g, n) + len("proc_")
+  of nkIteratorTy: result = lsons(g, n) + len("iterator_")
+  of nkSharedTy: result = lsons(g, n) + len("shared_")
   of nkEnumTy:
     if sonsLen(n) > 0:
-      result = lsub(n.sons[0]) + lcomma(n, 1) + len("enum_")
+      result = lsub(g, n.sons[0]) + lcomma(g, n, 1) + len("enum_")
     else:
       result = len("enum")
-  of nkEnumFieldDef: result = lsons(n) + 3
+  of nkEnumFieldDef: result = lsons(g, n) + 3
   of nkVarSection, nkLetSection:
     if sonsLen(n) > 1: result = MaxLineLen + 1
-    else: result = lsons(n) + len("var_")
+    else: result = lsons(g, n) + len("var_")
   of nkUsingStmt:
     if sonsLen(n) > 1: result = MaxLineLen + 1
-    else: result = lsons(n) + len("using_")
-  of nkReturnStmt: result = lsub(n.sons[0]) + len("return_")
-  of nkRaiseStmt: result = lsub(n.sons[0]) + len("raise_")
-  of nkYieldStmt: result = lsub(n.sons[0]) + len("yield_")
-  of nkDiscardStmt: result = lsub(n.sons[0]) + len("discard_")
-  of nkBreakStmt: result = lsub(n.sons[0]) + len("break_")
-  of nkContinueStmt: result = lsub(n.sons[0]) + len("continue_")
-  of nkPragma: result = lcomma(n) + 4
-  of nkCommentStmt: result = if n.comment.isNil: 0 else: len(n.comment)
-  of nkOfBranch: result = lcomma(n, 0, - 2) + lsub(lastSon(n)) + len("of_:_")
-  of nkImportAs: result = lsub(n.sons[0]) + len("_as_") + lsub(n.sons[1])
-  of nkElifBranch: result = lsons(n) + len("elif_:_")
-  of nkElse: result = lsub(n.sons[0]) + len("else:_")
-  of nkFinally: result = lsub(n.sons[0]) + len("finally:_")
-  of nkGenericParams: result = lcomma(n) + 2
+    else: result = lsons(g, n) + len("using_")
+  of nkReturnStmt: result = lsub(g, n.sons[0]) + len("return_")
+  of nkRaiseStmt: result = lsub(g, n.sons[0]) + len("raise_")
+  of nkYieldStmt: result = lsub(g, n.sons[0]) + len("yield_")
+  of nkDiscardStmt: result = lsub(g, n.sons[0]) + len("discard_")
+  of nkBreakStmt: result = lsub(g, n.sons[0]) + len("break_")
+  of nkContinueStmt: result = lsub(g, n.sons[0]) + len("continue_")
+  of nkPragma: result = lcomma(g, n) + 4
+  of nkCommentStmt: result = len(n.comment)
+  of nkOfBranch: result = lcomma(g, n, 0, - 2) + lsub(g, lastSon(n)) + len("of_:_")
+  of nkImportAs: result = lsub(g, n.sons[0]) + len("_as_") + lsub(g, n.sons[1])
+  of nkElifBranch: result = lsons(g, n) + len("elif_:_")
+  of nkElse: result = lsub(g, n.sons[0]) + len("else:_")
+  of nkFinally: result = lsub(g, n.sons[0]) + len("finally:_")
+  of nkGenericParams: result = lcomma(g, n) + 2
   of nkFormalParams:
-    result = lcomma(n, 1) + 2
-    if n.sons[0].kind != nkEmpty: result = result + lsub(n.sons[0]) + 2
+    result = lcomma(g, n, 1) + 2
+    if n.sons[0].kind != nkEmpty: result = result + lsub(g, n.sons[0]) + 2
   of nkExceptBranch:
-    result = lcomma(n, 0, -2) + lsub(lastSon(n)) + len("except_:_")
+    result = lcomma(g, n, 0, -2) + lsub(g, lastSon(n)) + len("except_:_")
   else: result = MaxLineLen + 1
 
 proc fits(g: TSrcGen, x: int): bool =
@@ -501,7 +546,7 @@ proc gsub(g: var TSrcGen, n: PNode) =
 proc hasCom(n: PNode): bool =
   result = false
   if n.isNil: return false
-  if n.comment != nil: return true
+  if n.comment.len > 0: return true
   case n.kind
   of nkEmpty..nkNilLit: discard
   else:
@@ -516,7 +561,7 @@ proc gcommaAux(g: var TSrcGen, n: PNode, ind: int, start: int = 0,
                theEnd: int = - 1, separator = tkComma) =
   for i in countup(start, sonsLen(n) + theEnd):
     var c = i < sonsLen(n) + theEnd
-    var sublen = lsub(n.sons[i]) + ord(c)
+    var sublen = lsub(g, n.sons[i]) + ord(c)
     if not fits(g, sublen) and (ind + sublen < MaxLineLen): optNL(g, ind)
     let oldLen = g.tokens.len
     gsub(g, n.sons[i])
@@ -563,12 +608,12 @@ proc gsection(g: var TSrcGen, n: PNode, c: TContext, kind: TTokType,
     gcoms(g)
   dedent(g)
 
-proc longMode(n: PNode, start: int = 0, theEnd: int = - 1): bool =
-  result = n.comment != nil
+proc longMode(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): bool =
+  result = n.comment.len > 0
   if not result:
     # check further
     for i in countup(start, sonsLen(n) + theEnd):
-      if (lsub(n.sons[i]) > MaxLineLen):
+      if (lsub(g, n.sons[i]) > MaxLineLen):
         result = true
         break
 
@@ -576,27 +621,39 @@ proc gstmts(g: var TSrcGen, n: PNode, c: TContext, doIndent=true) =
   if n.kind == nkEmpty: return
   if n.kind in {nkStmtList, nkStmtListExpr, nkStmtListType}:
     if doIndent: indentNL(g)
-    for i in countup(0, sonsLen(n) - 1):
-      optNL(g)
-      if n.sons[i].kind in {nkStmtList, nkStmtListExpr, nkStmtListType}:
-        gstmts(g, n.sons[i], c, doIndent=false)
+    let L = n.len
+    for i in 0 .. L-1:
+      if i > 0:
+        optNL(g, n[i-1], n[i])
       else:
-        gsub(g, n.sons[i])
+        optNL(g)
+      if n[i].kind in {nkStmtList, nkStmtListExpr, nkStmtListType}:
+        gstmts(g, n[i], c, doIndent=false)
+      else:
+        gsub(g, n[i])
       gcoms(g)
     if doIndent: dedent(g)
   else:
-    if rfLongMode in c.flags: indentNL(g)
+    indentNL(g)
     gsub(g, n)
     gcoms(g)
+    dedent(g)
     optNL(g)
-    if rfLongMode in c.flags: dedent(g)
+
+
+proc gcond(g: var TSrcGen, n: PNode) =
+  if n.kind == nkStmtListExpr:
+    put(g, tkParLe, "(")
+  gsub(g, n)
+  if n.kind == nkStmtListExpr:
+    put(g, tkParRi, ")")
 
 proc gif(g: var TSrcGen, n: PNode) =
   var c: TContext
-  gsub(g, n.sons[0].sons[0])
+  gcond(g, n.sons[0].sons[0])
   initContext(c)
   putWithSpace(g, tkColon, ":")
-  if longMode(n) or (lsub(n.sons[0].sons[1]) + g.lineLen > MaxLineLen):
+  if longMode(g, n) or (lsub(g, n.sons[0].sons[1]) + g.lineLen > MaxLineLen):
     incl(c.flags, rfLongMode)
   gcoms(g)                    # a good place for comments
   gstmts(g, n.sons[0].sons[1], c)
@@ -608,10 +665,10 @@ proc gif(g: var TSrcGen, n: PNode) =
 proc gwhile(g: var TSrcGen, n: PNode) =
   var c: TContext
   putWithSpace(g, tkWhile, "while")
-  gsub(g, n.sons[0])
+  gcond(g, n.sons[0])
   putWithSpace(g, tkColon, ":")
   initContext(c)
-  if longMode(n) or (lsub(n.sons[1]) + g.lineLen > MaxLineLen):
+  if longMode(g, n) or (lsub(g, n.sons[1]) + g.lineLen > MaxLineLen):
     incl(c.flags, rfLongMode)
   gcoms(g)                    # a good place for comments
   gstmts(g, n.sons[1], c)
@@ -620,7 +677,7 @@ proc gpattern(g: var TSrcGen, n: PNode) =
   var c: TContext
   put(g, tkCurlyLe, "{")
   initContext(c)
-  if longMode(n) or (lsub(n.sons[0]) + g.lineLen > MaxLineLen):
+  if longMode(g, n) or (lsub(g, n.sons[0]) + g.lineLen > MaxLineLen):
     incl(c.flags, rfLongMode)
   gcoms(g)                    # a good place for comments
   gstmts(g, n, c)
@@ -631,7 +688,7 @@ proc gpragmaBlock(g: var TSrcGen, n: PNode) =
   gsub(g, n.sons[0])
   putWithSpace(g, tkColon, ":")
   initContext(c)
-  if longMode(n) or (lsub(n.sons[1]) + g.lineLen > MaxLineLen):
+  if longMode(g, n) or (lsub(g, n.sons[1]) + g.lineLen > MaxLineLen):
     incl(c.flags, rfLongMode)
   gcoms(g)                    # a good place for comments
   gstmts(g, n.sons[1], c)
@@ -641,7 +698,7 @@ proc gtry(g: var TSrcGen, n: PNode) =
   put(g, tkTry, "try")
   putWithSpace(g, tkColon, ":")
   initContext(c)
-  if longMode(n) or (lsub(n.sons[0]) + g.lineLen > MaxLineLen):
+  if longMode(g, n) or (lsub(g, n.sons[0]) + g.lineLen > MaxLineLen):
     incl(c.flags, rfLongMode)
   gcoms(g)                    # a good place for comments
   gstmts(g, n.sons[0], c)
@@ -652,8 +709,8 @@ proc gfor(g: var TSrcGen, n: PNode) =
   var length = sonsLen(n)
   putWithSpace(g, tkFor, "for")
   initContext(c)
-  if longMode(n) or
-      (lsub(n.sons[length - 1]) + lsub(n.sons[length - 2]) + 6 + g.lineLen >
+  if longMode(g, n) or
+      (lsub(g, n.sons[length - 1]) + lsub(g, n.sons[length - 2]) + 6 + g.lineLen >
       MaxLineLen):
     incl(c.flags, rfLongMode)
   gcomma(g, n, c, 0, - 3)
@@ -668,16 +725,17 @@ proc gcase(g: var TSrcGen, n: PNode) =
   var c: TContext
   initContext(c)
   var length = sonsLen(n)
+  if length == 0: return
   var last = if n.sons[length-1].kind == nkElse: -2 else: -1
-  if longMode(n, 0, last): incl(c.flags, rfLongMode)
+  if longMode(g, n, 0, last): incl(c.flags, rfLongMode)
   putWithSpace(g, tkCase, "case")
-  gsub(g, n.sons[0])
+  gcond(g, n.sons[0])
   gcoms(g)
   optNL(g)
   gsons(g, n, c, 1, last)
   if last == - 2:
     initContext(c)
-    if longMode(n.sons[length - 1]): incl(c.flags, rfLongMode)
+    if longMode(g, n.sons[length - 1]): incl(c.flags, rfLongMode)
     gsub(g, n.sons[length - 1], c)
 
 proc gproc(g: var TSrcGen, n: PNode) =
@@ -692,16 +750,17 @@ proc gproc(g: var TSrcGen, n: PNode) =
 
   if n.sons[patternPos].kind != nkEmpty:
     gpattern(g, n.sons[patternPos])
-  let oldCheckAnon = g.checkAnon
-  g.checkAnon = true
+  let oldInGenericParams = g.inGenericParams
+  g.inGenericParams = true
   if renderNoBody in g.flags and n[miscPos].kind != nkEmpty and
       n[miscPos][1].kind != nkEmpty:
     gsub(g, n[miscPos][1])
   else:
     gsub(g, n.sons[genericParamsPos])
-  g.checkAnon = oldCheckAnon
+  g.inGenericParams = oldInGenericParams
   gsub(g, n.sons[paramsPos])
-  gsub(g, n.sons[pragmasPos])
+  if renderNoPragmas notin g.flags:
+    gsub(g, n.sons[pragmasPos])
   if renderNoBody notin g.flags:
     if n.sons[bodyPos].kind != nkEmpty:
       put(g, tkSpaces, Space)
@@ -739,20 +798,17 @@ proc gblock(g: var TSrcGen, n: PNode) =
   else:
     put(g, tkBlock, "block")
   putWithSpace(g, tkColon, ":")
-  if longMode(n) or (lsub(n.sons[1]) + g.lineLen > MaxLineLen):
+  if longMode(g, n) or (lsub(g, n.sons[1]) + g.lineLen > MaxLineLen):
     incl(c.flags, rfLongMode)
   gcoms(g)
-  # XXX I don't get why this is needed here! gstmts should already handle this!
-  indentNL(g)
   gstmts(g, n.sons[1], c)
-  dedent(g)
 
 proc gstaticStmt(g: var TSrcGen, n: PNode) =
   var c: TContext
   putWithSpace(g, tkStatic, "static")
   putWithSpace(g, tkColon, ":")
   initContext(c)
-  if longMode(n) or (lsub(n.sons[0]) + g.lineLen > MaxLineLen):
+  if longMode(g, n) or (lsub(g, n.sons[0]) + g.lineLen > MaxLineLen):
     incl(c.flags, rfLongMode)
   gcoms(g)                    # a good place for comments
   gstmts(g, n.sons[0], c)
@@ -765,11 +821,14 @@ proc gasm(g: var TSrcGen, n: PNode) =
     gsub(g, n.sons[1])
 
 proc gident(g: var TSrcGen, n: PNode) =
-  if g.checkAnon and n.kind == nkSym and sfAnon in n.sym.flags: return
+  if g.inGenericParams and n.kind == nkSym:
+    if sfAnon in n.sym.flags or
+      (n.typ != nil and tfImplicitTypeParam in n.typ.flags): return
+
   var t: TTokType
-  var s = atom(n)
-  if (s[0] in lexer.SymChars):
-    if (n.kind == nkIdent):
+  var s = atom(g, n)
+  if s.len > 0 and s[0] in lexer.SymChars:
+    if n.kind == nkIdent:
       if (n.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or
           (n.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)):
         t = tkSymbol
@@ -779,9 +838,12 @@ proc gident(g: var TSrcGen, n: PNode) =
       t = tkSymbol
   else:
     t = tkOpr
-  put(g, t, s)
+  put(g, t, s, if n.kind == nkSym and renderSyms in g.flags: n.sym else: nil)
   if n.kind == nkSym and (renderIds in g.flags or sfGenSym in n.sym.flags):
-    put(g, tkIntLit, $n.sym.id)
+    when defined(debugMagics):
+      put(g, tkIntLit, $n.sym.id & $n.sym.magic)
+    else:
+      put(g, tkIntLit, $n.sym.id)
 
 proc doParamsAux(g: var TSrcGen, params: PNode) =
   if params.len > 1:
@@ -806,51 +868,100 @@ proc isBracket*(n: PNode): bool =
   of nkSym: result = n.sym.name.s == "[]"
   else: result = false
 
+proc skipHiddenNodes(n: PNode): PNode =
+  result = n
+  while result != nil:
+    if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and result.len > 1:
+      result = result[1]
+    elif result.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString} and
+        result.len > 0:
+      result = result[0]
+    else: break
+
+proc accentedName(g: var TSrcGen, n: PNode) =
+  if n == nil: return
+  let isOperator =
+    if n.kind == nkIdent and n.ident.s.len > 0 and n.ident.s[0] in OpChars: true
+    elif n.kind == nkSym and n.sym.name.s.len > 0 and n.sym.name.s[0] in OpChars: true
+    else: false
+
+  if isOperator:
+    put(g, tkAccent, "`")
+    gident(g, n)
+    put(g, tkAccent, "`")
+  else:
+    gsub(g, n)
+
+proc infixArgument(g: var TSrcGen, n: PNode, i: int) =
+  if i >= n.len: return
+
+  var needsParenthesis = false
+  let n_next = n[i].skipHiddenNodes
+  if n_next.kind == nkInfix:
+    if n_next[0].kind in {nkSym, nkIdent} and n[0].kind in {nkSym, nkIdent}:
+      let nextId = if n_next[0].kind == nkSym: n_next[0].sym.name else: n_next[0].ident
+      let nnId = if n[0].kind == nkSym: n[0].sym.name else: n[0].ident
+      if getPrecedence(nextId) < getPrecedence(nnId):
+        needsParenthesis = true
+  if needsParenthesis:
+    put(g, tkParLe, "(")
+  gsub(g, n, i)
+  if needsParenthesis:
+    put(g, tkParRi, ")")
+
 proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   if isNil(n): return
   var
     a: TContext
-  if n.comment != nil: pushCom(g, n)
+  if n.comment.len > 0: pushCom(g, n)
   case n.kind                 # atoms:
-  of nkTripleStrLit: putRawStr(g, tkTripleStrLit, n.strVal)
+  of nkTripleStrLit: put(g, tkTripleStrLit, atom(g, n))
   of nkEmpty: discard
-  of nkType: put(g, tkInvalid, atom(n))
+  of nkType: put(g, tkInvalid, atom(g, n))
   of nkSym, nkIdent: gident(g, n)
-  of nkIntLit: put(g, tkIntLit, atom(n))
-  of nkInt8Lit: put(g, tkInt8Lit, atom(n))
-  of nkInt16Lit: put(g, tkInt16Lit, atom(n))
-  of nkInt32Lit: put(g, tkInt32Lit, atom(n))
-  of nkInt64Lit: put(g, tkInt64Lit, atom(n))
-  of nkUIntLit: put(g, tkUIntLit, atom(n))
-  of nkUInt8Lit: put(g, tkUInt8Lit, atom(n))
-  of nkUInt16Lit: put(g, tkUInt16Lit, atom(n))
-  of nkUInt32Lit: put(g, tkUInt32Lit, atom(n))
-  of nkUInt64Lit: put(g, tkUInt64Lit, atom(n))
-  of nkFloatLit: put(g, tkFloatLit, atom(n))
-  of nkFloat32Lit: put(g, tkFloat32Lit, atom(n))
-  of nkFloat64Lit: put(g, tkFloat64Lit, atom(n))
-  of nkFloat128Lit: put(g, tkFloat128Lit, atom(n))
-  of nkStrLit: put(g, tkStrLit, atom(n))
-  of nkRStrLit: put(g, tkRStrLit, atom(n))
-  of nkCharLit: put(g, tkCharLit, atom(n))
-  of nkNilLit: put(g, tkNil, atom(n))    # complex expressions
+  of nkIntLit: put(g, tkIntLit, atom(g, n))
+  of nkInt8Lit: put(g, tkInt8Lit, atom(g, n))
+  of nkInt16Lit: put(g, tkInt16Lit, atom(g, n))
+  of nkInt32Lit: put(g, tkInt32Lit, atom(g, n))
+  of nkInt64Lit: put(g, tkInt64Lit, atom(g, n))
+  of nkUIntLit: put(g, tkUIntLit, atom(g, n))
+  of nkUInt8Lit: put(g, tkUInt8Lit, atom(g, n))
+  of nkUInt16Lit: put(g, tkUInt16Lit, atom(g, n))
+  of nkUInt32Lit: put(g, tkUInt32Lit, atom(g, n))
+  of nkUInt64Lit: put(g, tkUInt64Lit, atom(g, n))
+  of nkFloatLit: put(g, tkFloatLit, atom(g, n))
+  of nkFloat32Lit: put(g, tkFloat32Lit, atom(g, n))
+  of nkFloat64Lit: put(g, tkFloat64Lit, atom(g, n))
+  of nkFloat128Lit: put(g, tkFloat128Lit, atom(g, n))
+  of nkStrLit: put(g, tkStrLit, atom(g, n))
+  of nkRStrLit: put(g, tkRStrLit, atom(g, n))
+  of nkCharLit: put(g, tkCharLit, atom(g, n))
+  of nkNilLit: put(g, tkNil, atom(g, n))    # complex expressions
   of nkCall, nkConv, nkDotCall, nkPattern, nkObjConstr:
     if n.len > 0 and isBracket(n[0]):
       gsub(g, n, 1)
       put(g, tkBracketLe, "[")
       gcomma(g, n, 2)
       put(g, tkBracketRi, "]")
+    elif n.len > 1 and n.lastSon.kind == nkStmtList:
+      accentedName(g, n[0])
+      if n.len > 2:
+        put(g, tkParLe, "(")
+        gcomma(g, n, 1, -2)
+        put(g, tkParRi, ")")
+      put(g, tkColon, ":")
+      gsub(g, n, n.len-1)
     else:
-      if sonsLen(n) >= 1: gsub(g, n.sons[0])
+      if sonsLen(n) >= 1: accentedName(g, n[0])
       put(g, tkParLe, "(")
       gcomma(g, n, 1)
       put(g, tkParRi, ")")
   of nkCallStrLit:
-    gsub(g, n, 0)
+    if n.len > 0: accentedName(g, n[0])
     if n.len > 1 and n.sons[1].kind == nkRStrLit:
       put(g, tkRStrLit, '\"' & replace(n[1].strVal, "\"", "\"\"") & '\"')
     else:
-      gsub(g, n.sons[1])
+      gsub(g, n, 1)
   of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv:
     if n.len >= 2:
       gsub(g, n.sons[1])
@@ -888,7 +999,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     gsub(g, n, 0)
     gcomma(g, n, 1)
   of nkCommand:
-    gsub(g, n, 0)
+    accentedName(g, n[0])
     put(g, tkSpaces, Space)
     gcomma(g, n, 1)
   of nkExprEqExpr, nkAsgn, nkFastAsgn:
@@ -911,7 +1022,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     put(g, tkParLe, "(")
     gcomma(g, n)
     put(g, tkParRi, ")")
-  of nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString:
+  of nkObjDownConv, nkObjUpConv:
     if sonsLen(n) >= 1: gsub(g, n.sons[0])
     put(g, tkParLe, "(")
     gcomma(g, n, 1)
@@ -936,6 +1047,11 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     put(g, tkParLe, "(")
     gcomma(g, n, c)
     put(g, tkParRi, ")")
+  of nkTupleConstr:
+    put(g, tkParLe, "(")
+    gcomma(g, n, c)
+    if n.len == 1: put(g, tkComma, ",")
+    put(g, tkParRi, ")")
   of nkCurly:
     put(g, tkCurlyLe, "{")
     gcomma(g, n, c)
@@ -958,7 +1074,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   of nkBind:
     putWithSpace(g, tkBind, "bind")
     gsub(g, n, 0)
-  of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref:
+  of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString:
     gsub(g, n, 0)
   of nkLambda:
     putWithSpace(g, tkProc, "proc")
@@ -996,19 +1112,25 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     putWithSpace(g, tkColon, ":")
     gsub(g, n, 1)
   of nkInfix:
-    gsub(g, n, 1)
+    infixArgument(g, n, 1)
     put(g, tkSpaces, Space)
     gsub(g, n, 0)        # binary operator
-    if not fits(g, lsub(n.sons[2]) + lsub(n.sons[0]) + 1):
+    if n.len == 3 and not fits(g, lsub(g, n.sons[2]) + lsub(g, n.sons[0]) + 1):
       optNL(g, g.indent + longIndentWid)
     else:
       put(g, tkSpaces, Space)
-    gsub(g, n, 2)
+    infixArgument(g, n, 2)
   of nkPrefix:
     gsub(g, n, 0)
     if n.len > 1:
-      put(g, tkSpaces, Space)
-      if n.sons[1].kind == nkInfix:
+      let opr = if n[0].kind == nkIdent: n[0].ident
+                elif n[0].kind == nkSym: n[0].sym.name
+                elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name
+                else: nil
+      let n_next = skipHiddenNodes(n[1])
+      if n_next.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)):
+        put(g, tkSpaces, Space)
+      if n_next.kind == nkInfix:
         put(g, tkParLe, "(")
         gsub(g, n.sons[1])
         put(g, tkParRi, ")")
@@ -1027,19 +1149,19 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   of nkAccQuoted:
     put(g, tkAccent, "`")
     if n.len > 0: gsub(g, n.sons[0])
-    for i in 1 .. <n.len:
+    for i in 1 ..< n.len:
       put(g, tkSpaces, Space)
       gsub(g, n.sons[i])
     put(g, tkAccent, "`")
   of nkIfExpr:
     putWithSpace(g, tkIf, "if")
-    if n.len > 0: gsub(g, n.sons[0], 0)
+    if n.len > 0: gcond(g, n.sons[0].sons[0])
     putWithSpace(g, tkColon, ":")
     if n.len > 0: gsub(g, n.sons[0], 1)
     gsons(g, n, emptyContext, 1)
   of nkElifExpr:
     putWithSpace(g, tkElif, " elif")
-    gsub(g, n, 0)
+    gcond(g, n[0])
     putWithSpace(g, tkColon, ":")
     gsub(g, n, 1)
   of nkElseExpr:
@@ -1075,9 +1197,9 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
       gsub(g, n.sons[0])
       if n.len > 1:
         if n[1].kind == nkWith:
-          putWithSpace(g, tkWith, " with")
+          putWithSpace(g, tkSymbol, " with")
         else:
-          putWithSpace(g, tkWithout, " without")
+          putWithSpace(g, tkSymbol, " without")
         gcomma(g, n[1])
     else:
       put(g, tkDistinct, "distinct")
@@ -1162,6 +1284,9 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
   of nkProcDef:
     if renderNoProcDefs notin g.flags: putWithSpace(g, tkProc, "proc")
     gproc(g, n)
+  of nkFuncDef:
+    if renderNoProcDefs notin g.flags: putWithSpace(g, tkFunc, "func")
+    gproc(g, n)
   of nkConverterDef:
     if renderNoProcDefs notin g.flags: putWithSpace(g, tkConverter, "converter")
     gproc(g, n)
@@ -1218,17 +1343,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     putWithSpace(g, tkContinue, "continue")
     gsub(g, n, 0)
   of nkPragma:
-    if renderNoPragmas notin g.flags:
-      if g.inPragma <= 0:
-        inc g.inPragma
-        #if not previousNL(g):
-        put(g, tkSpaces, Space)
-        put(g, tkCurlyDotLe, "{.")
-        gcomma(g, n, emptyContext)
-        put(g, tkCurlyDotRi, ".}")
-        dec g.inPragma
-      else:
-        gcomma(g, n, emptyContext)
+    if g.inPragma <= 0:
+      inc g.inPragma
+      #if not previousNL(g):
+      put(g, tkSpaces, Space)
+      put(g, tkCurlyDotLe, "{.")
+      gcomma(g, n, emptyContext)
+      put(g, tkCurlyDotRi, ".}")
+      dec g.inPragma
+    else:
+      gcomma(g, n, emptyContext)
   of nkImportStmt, nkExportStmt:
     if n.kind == nkImportStmt:
       putWithSpace(g, tkImport, "import")
@@ -1309,15 +1433,25 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     gstmts(g, n.sons[0], c)
   of nkExceptBranch:
     optNL(g)
-    putWithSpace(g, tkExcept, "except")
-    gcomma(g, n, 0, - 2)
+    if n.len != 1:
+      putWithSpace(g, tkExcept, "except")
+    else:
+      put(g, tkExcept, "except")
+    gcomma(g, n, 0, -2)
     putWithSpace(g, tkColon, ":")
     gcoms(g)
     gstmts(g, lastSon(n), c)
   of nkGenericParams:
-    put(g, tkBracketLe, "[")
-    gcomma(g, n)
-    put(g, tkBracketRi, "]")
+    proc hasExplicitParams(gp: PNode): bool =
+      for p in gp:
+        if p.typ == nil or tfImplicitTypeParam notin p.typ.flags:
+          return true
+      return false
+
+    if n.hasExplicitParams:
+      put(g, tkBracketLe, "[")
+      gsemicolon(g, n)
+      put(g, tkBracketRi, "]")
   of nkFormalParams:
     put(g, tkParLe, "(")
     gsemicolon(g, n, 1)
@@ -1332,35 +1466,56 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
     put(g, tkBracketRi, "]")
   of nkTupleClassTy:
     put(g, tkTuple, "tuple")
-  of nkMetaNode_Obsolete:
-    put(g, tkParLe, "(META|")
+  of nkComesFrom:
+    put(g, tkParLe, "(ComesFrom|")
     gsub(g, n, 0)
     put(g, tkParRi, ")")
-  of nkGotoState, nkState:
+  of nkGotoState:
     var c: TContext
     initContext c
-    putWithSpace g, tkSymbol, if n.kind == nkState: "state" else: "goto"
+    putWithSpace g, tkSymbol, "goto"
     gsons(g, n, c)
+  of nkState:
+    var c: TContext
+    initContext c
+    putWithSpace g, tkSymbol, "state"
+    gsub(g, n[0], c)
+    putWithSpace(g, tkColon, ":")
+    indentNL(g)
+    gsons(g, n, c, 1)
+    dedent(g)
+
   of nkBreakState:
     put(g, tkTuple, "breakstate")
   of nkTypeClassTy:
     gTypeClassTy(g, n)
   else:
     #nkNone, nkExplicitTypeListCall:
-    internalError(n.info, "rnimsyn.gsub(" & $n.kind & ')')
+    internalError(g.config, n.info, "rnimsyn.gsub(" & $n.kind & ')')
 
-proc renderTree(n: PNode, renderFlags: TRenderFlags = {}): string =
+proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string =
   var g: TSrcGen
-  initSrcGen(g, renderFlags)
-  gsub(g, n)
+  initSrcGen(g, renderFlags, newPartialConfigRef())
+  # do not indent the initial statement list so that
+  # writeFile("file.nim", repr n)
+  # produces working Nim code:
+  if n.kind in {nkStmtList, nkStmtListExpr, nkStmtListType}:
+    gstmts(g, n, emptyContext, doIndent = false)
+  else:
+    gsub(g, n)
   result = g.buf
 
-proc renderModule(n: PNode, filename: string,
-                  renderFlags: TRenderFlags = {}) =
+proc `$`*(n: PNode): string = n.renderTree
+
+proc renderModule*(n: PNode, infile, outfile: string,
+                   renderFlags: TRenderFlags = {};
+                   fid = FileIndex(-1);
+                   conf: ConfigRef = nil) =
   var
     f: File
     g: TSrcGen
-  initSrcGen(g, renderFlags)
+  initSrcGen(g, renderFlags, conf)
+  g.fid = fid
   for i in countup(0, sonsLen(n) - 1):
     gsub(g, n.sons[i])
     optNL(g)
@@ -1369,19 +1524,17 @@ proc renderModule(n: PNode, filename: string,
        nkCommentStmt: putNL(g)
     else: discard
   gcoms(g)
-  if optStdout in gGlobalOptions:
-    write(stdout, g.buf)
-  elif open(f, filename, fmWrite):
+  if open(f, outfile, fmWrite):
     write(f, g.buf)
     close(f)
   else:
-    rawMessage(errCannotOpenFile, filename)
+    rawMessage(g.config, errGenerated, "cannot open file: " & outfile)
 
-proc initTokRender(r: var TSrcGen, n: PNode, renderFlags: TRenderFlags = {}) =
-  initSrcGen(r, renderFlags)
+proc initTokRender*(r: var TSrcGen, n: PNode, renderFlags: TRenderFlags = {}) =
+  initSrcGen(r, renderFlags, newPartialConfigRef())
   gsub(r, n)
 
-proc getNextTok(r: var TSrcGen, kind: var TTokType, literal: var string) =
+proc getNextTok*(r: var TSrcGen, kind: var TTokType, literal: var string) =
   if r.idx < len(r.tokens):
     kind = r.tokens[r.idx].kind
     var length = r.tokens[r.idx].length.int
@@ -1390,3 +1543,9 @@ proc getNextTok(r: var TSrcGen, kind: var TTokType, literal: var string) =
     inc(r.idx)
   else:
     kind = tkEof
+
+proc getTokSym*(r: TSrcGen): PSym =
+  if r.idx > 0 and r.idx <= len(r.tokens):
+    result = r.tokens[r.idx-1].sym
+  else:
+    result = nil
diff --git a/compiler/reorder.nim b/compiler/reorder.nim
new file mode 100644
index 000000000..8c4d0d307
--- /dev/null
+++ b/compiler/reorder.nim
@@ -0,0 +1,444 @@
+
+import
+  intsets, ast, idents, algorithm, renderer, parser, os, strutils,
+  sequtils, msgs, modulegraphs, syntaxes, options, modulepaths, tables,
+  lineinfos
+
+type
+  DepN = ref object
+    pnode: PNode
+    id, idx, lowLink: int
+    onStack: bool
+    kids: seq[DepN]
+    hAQ, hIS, hB, hCmd: int
+    when defined(debugReorder):
+      expls: seq[string]
+  DepG = seq[DepN]
+
+when defined(debugReorder):
+  var idNames = newTable[int, string]()
+
+proc newDepN(id: int, pnode: PNode): DepN =
+  new(result)
+  result.id = id
+  result.pnode = pnode
+  result.idx = -1
+  result.lowLink = -1
+  result.onStack = false
+  result.kids = @[]
+  result.hAQ = -1
+  result.hIS = -1
+  result.hB = -1
+  result.hCmd = -1
+  when defined(debugReorder):
+    result.expls = @[]
+
+proc accQuoted(cache: IdentCache; n: PNode): PIdent =
+  var id = ""
+  for i in 0 ..< n.len:
+    let x = n[i]
+    case x.kind
+    of nkIdent: id.add(x.ident.s)
+    of nkSym: id.add(x.sym.name.s)
+    else: discard
+  result = getIdent(cache, id)
+
+proc addDecl(cache: IdentCache; n: PNode; declares: var IntSet) =
+  case n.kind
+  of nkPostfix: addDecl(cache, n[1], declares)
+  of nkPragmaExpr: addDecl(cache, n[0], declares)
+  of nkIdent:
+    declares.incl n.ident.id
+    when defined(debugReorder):
+      idNames[n.ident.id] = n.ident.s
+  of nkSym:
+    declares.incl n.sym.name.id
+    when defined(debugReorder):
+      idNames[n.sym.name.id] = n.sym.name.s
+  of nkAccQuoted:
+    let a = accQuoted(cache, n)
+    declares.incl a.id
+    when defined(debugReorder):
+      idNames[a.id] = a.s
+  of nkEnumFieldDef:
+    addDecl(cache, n[0], declares)
+  else: discard
+
+proc computeDeps(cache: IdentCache; n: PNode, declares, uses: var IntSet; topLevel: bool) =
+  template deps(n) = computeDeps(cache, n, declares, uses, false)
+  template decl(n) =
+    if topLevel: addDecl(cache, n, declares)
+  case n.kind
+  of procDefs, nkMacroDef, nkTemplateDef:
+    decl(n[0])
+    for i in 1..bodyPos: deps(n[i])
+  of nkLetSection, nkVarSection, nkUsingStmt:
+    for a in n:
+      if a.kind in {nkIdentDefs, nkVarTuple}:
+        for j in countup(0, a.len-3): decl(a[j])
+        for j in a.len-2..a.len-1: deps(a[j])
+  of nkConstSection, nkTypeSection:
+    for a in n:
+      if a.len >= 3:
+        decl(a[0])
+        for i in 1..<a.len:
+          if a[i].kind == nkEnumTy:
+            # declare enum members
+            for b in a[i]:
+              decl(b)
+          else:
+            deps(a[i])
+  of nkIdentDefs:
+    for i in 1..<n.len: # avoid members identifiers in object definition
+      deps(n[i])
+  of nkIdent: uses.incl n.ident.id
+  of nkSym: uses.incl n.sym.name.id
+  of nkAccQuoted: uses.incl accQuoted(cache, n).id
+  of nkOpenSymChoice, nkClosedSymChoice:
+    uses.incl n.sons[0].sym.name.id
+  of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
+    for i in 0..<len(n): computeDeps(cache, n[i], declares, uses, topLevel)
+  of nkPragma:
+    let a = n.sons[0]
+    if a.kind == nkExprColonExpr and a.sons[0].kind == nkIdent and
+       a.sons[0].ident.s == "pragma":
+        # user defined pragma
+        decl(a.sons[1])
+    else:
+      for i in 0..<safeLen(n): deps(n[i])
+  else:
+    for i in 0..<safeLen(n): deps(n[i])
+
+proc cleanPath(s: string): string =
+  # Here paths may have the form A / B or "A/B"
+  result = ""
+  for c in s:
+    if c != ' ' and c != '\"':
+      result.add c
+
+proc joinPath(parts: seq[string]): string =
+  let nb = parts.len
+  assert nb > 0
+  if nb == 1:
+    return parts[0]
+  result = parts[0] / parts[1]
+  for i in 2..<parts.len:
+    result = result / parts[i]
+
+proc getIncludePath(n: PNode, modulePath: string): string =
+  let istr = n.renderTree.cleanPath
+  let (pdir, _) = modulePath.splitPath
+  let p = istr.split('/').joinPath.addFileExt("nim")
+  result = pdir / p
+
+proc hasIncludes(n:PNode): bool =
+  for a in n:
+    if a.kind == nkIncludeStmt:
+      return true
+
+proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode {.procvar.} =
+  result = syntaxes.parseFile(fileIdx, graph.cache, graph.config)
+  graph.addDep(s, fileIdx)
+  graph.addIncludeDep(FileIndex s.position, fileIdx)
+
+proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode,
+                    modulePath: string, includedFiles: var IntSet): PNode =
+  # Parses includes and injects them in the current tree
+  if not n.hasIncludes:
+    return n
+  result = newNodeI(nkStmtList, n.info)
+  for a in n:
+    if a.kind == nkIncludeStmt:
+      for i in 0..<a.len:
+        var f = checkModuleName(graph.config, a.sons[i])
+        if f != InvalidFileIDX:
+          if containsOrIncl(includedFiles, f.int):
+            localError(graph.config, a.info, "recursive dependency: '$1'" %
+              toFilename(graph.config, f))
+          else:
+            let nn = includeModule(graph, module, f)
+            let nnn = expandIncludes(graph, module, nn, modulePath,
+                                      includedFiles)
+            excl(includedFiles, f.int)
+            for b in nnn:
+              result.add b
+    else:
+      result.add a
+
+proc splitSections(n: PNode): PNode =
+  # Split typeSections and ConstSections into
+  # sections that contain only one definition
+  assert n.kind == nkStmtList
+  result = newNodeI(nkStmtList, n.info)
+  for a in n:
+    if a.kind in {nkTypeSection, nkConstSection} and a.len > 1:
+      for b in a:
+        var s = newNode(a.kind)
+        s.info = b.info
+        s.add b
+        result.add s
+    else:
+      result.add a
+
+proc haveSameKind(dns: seq[DepN]): bool =
+  # Check if all the nodes in a strongly connected
+  # component have the same kind
+  result = true
+  let kind = dns[0].pnode.kind
+  for dn in dns:
+    if dn.pnode.kind != kind:
+      return false
+
+proc mergeSections(conf: ConfigRef; comps: seq[seq[DepN]], res: PNode) =
+  # Merges typeSections and ConstSections when they form
+  # a strong component (ex: circular type definition)
+  for c in comps:
+    assert c.len > 0
+    if c.len == 1:
+      res.add c[0].pnode
+    else:
+      let fstn = c[0].pnode
+      let kind = fstn.kind
+      # always return to the original order when we got circular dependencies
+      let cs = c.sortedByIt(it.id)
+      if kind in {nkTypeSection, nkConstSection} and haveSameKind(cs):
+        # Circular dependency between type or const sections, we just
+        # need to merge them
+        var sn = newNode(kind)
+        for dn in cs:
+          sn.add dn.pnode.sons[0]
+        res.add sn
+      else:
+          # Problematic circular dependency, we arrange the nodes into
+          # their original relative order and make sure to re-merge
+          # consecutive type and const sections
+          var wmsg = "Circular dependency detected. reorder pragma may not be able to" &
+            " reorder some nodes properely"
+          when defined(debugReorder):
+            wmsg &= ":\n"
+            for i in 0..<cs.len-1:
+                for j in i..<cs.len:
+                  for ci in 0..<cs[i].kids.len:
+                    if cs[i].kids[ci].id == cs[j].id:
+                      wmsg &= "line " & $cs[i].pnode.info.line &
+                        " depends on line " & $cs[j].pnode.info.line &
+                        ": " & cs[i].expls[ci] & "\n"
+            for j in 0..<cs.len-1:
+                for ci in 0..<cs[^1].kids.len:
+                  if cs[^1].kids[ci].id == cs[j].id:
+                    wmsg &= "line " & $cs[^1].pnode.info.line &
+                      " depends on line " & $cs[j].pnode.info.line &
+                      ": " & cs[^1].expls[ci] & "\n"
+          message(conf, cs[0].pnode.info, warnUser, wmsg)
+
+          var i = 0
+          while i < cs.len:
+            if cs[i].pnode.kind in {nkTypeSection, nkConstSection}:
+              let ckind = cs[i].pnode.kind
+              var sn = newNode(ckind)
+              sn.add cs[i].pnode[0]
+              inc i
+              while i < cs.len and cs[i].pnode.kind == ckind:
+                sn.add cs[i].pnode[0]
+                inc i
+              res.add sn
+            else:
+              res.add cs[i].pnode
+              inc i
+
+proc hasImportStmt(n: PNode): bool =
+  # Checks if the node is an import statement or
+  # i it contains one
+  case n.kind
+  of nkImportStmt, nkFromStmt, nkImportExceptStmt:
+    return true
+  of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
+    for a in n:
+      if a.hasImportStmt:
+        return true
+  else:
+    result = false
+
+proc hasImportStmt(n: DepN): bool =
+  if n.hIS < 0:
+    n.hIS = ord(n.pnode.hasImportStmt)
+  result = bool(n.hIS)
+
+proc hasCommand(n: PNode): bool =
+  # Checks if the node is a command or a call
+  # or if it contains one
+  case n.kind
+  of nkCommand, nkCall:
+    result = true
+  of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse,
+      nkStaticStmt, nkLetSection, nkConstSection, nkVarSection,
+      nkIdentDefs:
+    for a in n:
+      if a.hasCommand:
+        return true
+  else:
+    return false
+
+proc hasCommand(n: DepN): bool =
+  if n.hCmd < 0:
+    n.hCmd = ord(n.pnode.hasCommand)
+  result = bool(n.hCmd)
+
+proc hasAccQuoted(n: PNode): bool =
+  if n.kind == nkAccQuoted:
+    return true
+  for a in n:
+    if hasAccQuoted(a):
+      return true
+
+const extandedProcDefs = procDefs + {nkMacroDef,  nkTemplateDef}
+
+proc hasAccQuotedDef(n: PNode): bool =
+  # Checks if the node is a function, macro, template ...
+  # with a quoted name or if it contains one
+  case n.kind
+  of extandedProcDefs:
+    result = n[0].hasAccQuoted
+  of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
+    for a in n:
+      if a.hasAccQuotedDef:
+        return true
+  else:
+    result = false
+
+proc hasAccQuotedDef(n: DepN): bool =
+  if n.hAQ < 0:
+    n.hAQ = ord(n.pnode.hasAccQuotedDef)
+  result = bool(n.hAQ)
+
+proc hasBody(n: PNode): bool =
+  # Checks if the node is a function, macro, template ...
+  # with a body or if it contains one
+  case n.kind
+  of nkCommand, nkCall:
+    result = true
+  of extandedProcDefs:
+    result = n[^1].kind == nkStmtList
+  of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
+    for a in n:
+      if a.hasBody:
+        return true
+  else:
+    result = false
+
+proc hasBody(n: DepN): bool =
+  if n.hB < 0:
+    n.hB = ord(n.pnode.hasBody)
+  result = bool(n.hB)
+
+proc intersects(s1, s2: IntSet): bool =
+  for a in s1:
+    if s2.contains(a):
+      return true
+
+proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG =
+  # Build a dependency graph
+  result = newSeqOfCap[DepN](deps.len)
+  for i in 0..<deps.len:
+    result.add newDepN(i, n.sons[i])
+  for i in 0..<deps.len:
+    var ni = result[i]
+    let uses = deps[i][1]
+    let niHasBody = ni.hasBody
+    let niHasCmd = ni.hasCommand
+    for j in 0..<deps.len:
+      if i == j: continue
+      var nj = result[j]
+      let declares = deps[j][0]
+      if j < i and nj.hasCommand and niHasCmd:
+        # Preserve order for commands and calls
+        ni.kids.add nj
+        when defined(debugReorder):
+          ni.expls.add "both have commands and one comes after the other"
+      elif j < i and nj.hasImportStmt:
+        # Every node that comes after an import statement must
+        # depend on that import
+        ni.kids.add nj
+        when defined(debugReorder):
+          ni.expls.add "parent is, or contains, an import statement and child comes after it"
+      elif j < i and niHasBody and nj.hasAccQuotedDef:
+        # Every function, macro, template... with a body depends
+        # on precedent function declarations that have quoted names.
+        # That's because it is hard to detect the use of functions
+        # like "[]=", "[]", "or" ... in their bodies.
+        ni.kids.add nj
+        when defined(debugReorder):
+          ni.expls.add "one declares a quoted identifier and the other has a body and comes after it"
+      elif j < i and niHasBody and not nj.hasBody and
+        intersects(deps[i][0], declares):
+          # Keep function declaration before function definition
+          ni.kids.add nj
+          when defined(debugReorder):
+            for dep in deps[i][0]:
+              if dep in declares:
+                ni.expls.add "one declares \"" & idNames[dep] & "\" and the other defines it"
+      else:
+        for d in declares:
+          if uses.contains(d):
+            ni.kids.add nj
+            when defined(debugReorder):
+              ni.expls.add "one declares \"" & idNames[d] & "\" and the other uses it"
+
+proc strongConnect(v: var DepN, idx: var int, s: var seq[DepN],
+                   res: var seq[seq[DepN]]) =
+  # Recursive part of trajan's algorithm
+  v.idx = idx
+  v.lowLink = idx
+  inc idx
+  s.add v
+  v.onStack = true
+  for w in v.kids.mitems:
+    if w.idx < 0:
+      strongConnect(w, idx, s, res)
+      v.lowLink = min(v.lowLink, w.lowLink)
+    elif w.onStack:
+      v.lowLink = min(v.lowLink, w.idx)
+  if v.lowLink == v.idx:
+    var comp = newSeq[DepN]()
+    while true:
+      var w = s.pop
+      w.onStack = false
+      comp.add w
+      if w.id == v.id: break
+    res.add comp
+
+proc getStrongComponents(g: var DepG): seq[seq[DepN]] =
+  ## Tarjan's algorithm. Performs a topological sort
+  ## and detects strongly connected components.
+  result = newSeq[seq[DepN]]()
+  var s = newSeq[DepN]()
+  var idx = 0
+  for v in g.mitems:
+    if v.idx < 0:
+      strongConnect(v, idx, s, result)
+
+proc hasForbiddenPragma(n: PNode): bool =
+  # Checks if the tree node has some pragmas that do not
+  # play well with reordering, like the push/pop pragma
+  for a in n:
+    if a.kind == nkPragma and a[0].kind == nkIdent and
+        a[0].ident.s == "push":
+          return true
+
+proc reorder*(graph: ModuleGraph, n: PNode, module: PSym): PNode =
+  if n.hasForbiddenPragma:
+    return n
+  var includedFiles = initIntSet()
+  let mpath = toFullPath(graph.config, module.fileIdx)
+  let n = expandIncludes(graph, module, n, mpath,
+                          includedFiles).splitSections
+  result = newNodeI(nkStmtList, n.info)
+  var deps = newSeq[(IntSet, IntSet)](n.len)
+  for i in 0..<n.len:
+    deps[i][0] = initIntSet()
+    deps[i][1] = initIntSet()
+    computeDeps(graph.cache, n[i], deps[i][0], deps[i][1], true)
+
+  var g = buildGraph(n, deps)
+  let comps = getStrongComponents(g)
+  mergeSections(graph.config, comps, result)
diff --git a/compiler/rod.nim b/compiler/rod.nim
new file mode 100644
index 000000000..bbd2f0c6c
--- /dev/null
+++ b/compiler/rod.nim
@@ -0,0 +1,31 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2017 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the canonalization for the various caching mechanisms.
+
+import ast, idgen, lineinfos, msgs, incremental, modulegraphs, pathutils
+
+when not nimIncremental:
+  template setupModuleCache*(g: ModuleGraph) = discard
+  template storeNode*(g: ModuleGraph; module: PSym; n: PNode) = discard
+  template loadNode*(g: ModuleGraph; module: PSym): PNode = newNode(nkStmtList)
+
+  proc loadModuleSym*(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): (PSym, int) {.inline.} = (nil, getID())
+
+  template addModuleDep*(g: ModuleGraph; module, fileIdx: FileIndex; isIncludeFile: bool) = discard
+
+  template storeRemaining*(g: ModuleGraph; module: PSym) = discard
+
+  template registerModule*(g: ModuleGraph; module: PSym) = discard
+
+else:
+  include rodimpl
+
+  # idea for testing all this logic: *Always* load the AST from the DB, whether
+  # we already have it in RAM or not!
diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim
new file mode 100644
index 000000000..147e8c3d6
--- /dev/null
+++ b/compiler/rodimpl.nim
@@ -0,0 +1,946 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the new compilation cache.
+
+import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types,
+  renderer, rodutils, idents, astalgo, btrees, magicsys, cgmeth, extccomp,
+  btrees, trees, condsyms, nversion, pathutils
+
+## Todo:
+## - Dependency computation should use *signature* hashes in order to
+##   avoid recompiling dependent modules.
+## - Patch the rest of the compiler to do lazy loading of proc bodies.
+## - Patch the C codegen to cache proc bodies and maybe types.
+
+template db(): DbConn = g.incr.db
+
+proc encodeConfig(g: ModuleGraph): string =
+  result = newStringOfCap(100)
+  result.add RodFileVersion
+  for d in definedSymbolNames(g.config.symbols):
+    result.add ' '
+    result.add d
+
+  template serialize(field) =
+    result.add ' '
+    result.add($g.config.field)
+
+  depConfigFields(serialize)
+
+proc needsRecompile(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile;
+                    cycleCheck: var IntSet): bool =
+  let root = db.getRow(sql"select id, fullhash from filenames where fullpath = ?",
+    fullpath.string)
+  if root[0].len == 0: return true
+  if root[1] != hashFileCached(g.config, fileIdx, fullpath):
+    return true
+  # cycle detection: assume "not changed" is correct.
+  if cycleCheck.containsOrIncl(int fileIdx):
+    return false
+  # check dependencies (recursively):
+  for row in db.fastRows(sql"select fullpath from filenames where id in (select dependency from deps where module = ?)",
+                         root[0]):
+    let dep = AbsoluteFile row[0]
+    if needsRecompile(g, g.config.fileInfoIdx(dep), dep, cycleCheck):
+      return true
+  return false
+
+proc getModuleId(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): int =
+  ## Analyse the known dependency graph.
+  if g.config.symbolFiles == disabledSf: return getID()
+  when false:
+    if g.config.symbolFiles in {disabledSf, writeOnlySf} or
+      g.incr.configChanged:
+      return getID()
+  let module = g.incr.db.getRow(
+    sql"select id, fullHash, nimid from modules where fullpath = ?", string fullpath)
+  let currentFullhash = hashFileCached(g.config, fileIdx, fullpath)
+  if module[0].len == 0:
+    result = getID()
+    db.exec(sql"insert into modules(fullpath, interfHash, fullHash, nimid) values (?, ?, ?, ?)",
+      string fullpath, "", currentFullhash, result)
+  else:
+    result = parseInt(module[2])
+    if currentFullhash == module[1]:
+      # not changed, so use the cached AST:
+      doAssert(result != 0)
+      var cycleCheck = initIntSet()
+      if not needsRecompile(g, fileIdx, fullpath, cycleCheck) and not g.incr.configChanged:
+        echo "cached successfully! ", string fullpath
+        return -result
+    db.exec(sql"update modules set fullHash = ? where id = ?", currentFullhash, module[0])
+    db.exec(sql"delete from deps where module = ?", module[0])
+    db.exec(sql"delete from types where module = ?", module[0])
+    db.exec(sql"delete from syms where module = ?", module[0])
+    db.exec(sql"delete from toplevelstmts where module = ?", module[0])
+    db.exec(sql"delete from statics where module = ?", module[0])
+
+proc loadModuleSym*(g: ModuleGraph; fileIdx: FileIndex; fullpath: AbsoluteFile): (PSym, int) =
+  let id = getModuleId(g, fileIdx, fullpath)
+  result = (g.incr.r.syms.getOrDefault(abs id), id)
+
+proc pushType(w: var Writer, t: PType) =
+  if not containsOrIncl(w.tmarks, t.uniqueId):
+    w.tstack.add(t)
+
+proc pushSym(w: var Writer, s: PSym) =
+  if not containsOrIncl(w.smarks, s.id):
+    w.sstack.add(s)
+
+template w: untyped = g.incr.w
+
+proc encodeNode(g: ModuleGraph; fInfo: TLineInfo, n: PNode,
+                result: var string) =
+  if n == nil:
+    # nil nodes have to be stored too:
+    result.add("()")
+    return
+  result.add('(')
+  encodeVInt(ord(n.kind), result)
+  # we do not write comments for now
+  # Line information takes easily 20% or more of the filesize! Therefore we
+  # omit line information if it is the same as the parent's line information:
+  if fInfo.fileIndex != n.info.fileIndex:
+    result.add('?')
+    encodeVInt(n.info.col, result)
+    result.add(',')
+    encodeVInt(int n.info.line, result)
+    result.add(',')
+    #encodeVInt(toDbFileId(g.incr, g.config, n.info.fileIndex), result)
+    encodeVInt(n.info.fileIndex.int, result)
+  elif fInfo.line != n.info.line:
+    result.add('?')
+    encodeVInt(n.info.col, result)
+    result.add(',')
+    encodeVInt(int n.info.line, result)
+  elif fInfo.col != n.info.col:
+    result.add('?')
+    encodeVInt(n.info.col, result)
+  # No need to output the file index, as this is the serialization of one
+  # file.
+  let f = n.flags * PersistentNodeFlags
+  if f != {}:
+    result.add('$')
+    encodeVInt(cast[int32](f), result)
+  if n.typ != nil:
+    result.add('^')
+    encodeVInt(n.typ.uniqueId, result)
+    pushType(w, n.typ)
+  case n.kind
+  of nkCharLit..nkUInt64Lit:
+    if n.intVal != 0:
+      result.add('!')
+      encodeVBiggestInt(n.intVal, result)
+  of nkFloatLit..nkFloat64Lit:
+    if n.floatVal != 0.0:
+      result.add('!')
+      encodeStr($n.floatVal, result)
+  of nkStrLit..nkTripleStrLit:
+    if n.strVal != "":
+      result.add('!')
+      encodeStr(n.strVal, result)
+  of nkIdent:
+    result.add('!')
+    encodeStr(n.ident.s, result)
+  of nkSym:
+    result.add('!')
+    encodeVInt(n.sym.id, result)
+    pushSym(w, n.sym)
+  else:
+    for i in countup(0, sonsLen(n) - 1):
+      encodeNode(g, n.info, n.sons[i], result)
+  add(result, ')')
+
+proc encodeLoc(g: ModuleGraph; loc: TLoc, result: var string) =
+  var oldLen = result.len
+  result.add('<')
+  if loc.k != low(loc.k): encodeVInt(ord(loc.k), result)
+  if loc.storage != low(loc.storage):
+    add(result, '*')
+    encodeVInt(ord(loc.storage), result)
+  if loc.flags != {}:
+    add(result, '$')
+    encodeVInt(cast[int32](loc.flags), result)
+  if loc.lode != nil:
+    add(result, '^')
+    encodeNode(g, unknownLineInfo(), loc.lode, result)
+  if loc.r != nil:
+    add(result, '!')
+    encodeStr($loc.r, result)
+  if oldLen + 1 == result.len:
+    # no data was necessary, so remove the '<' again:
+    setLen(result, oldLen)
+  else:
+    add(result, '>')
+
+proc encodeType(g: ModuleGraph, t: PType, result: var string) =
+  if t == nil:
+    # nil nodes have to be stored too:
+    result.add("[]")
+    return
+  # we need no surrounding [] here because the type is in a line of its own
+  if t.kind == tyForward: internalError(g.config, "encodeType: tyForward")
+  # for the new rodfile viewer we use a preceding [ so that the data section
+  # can easily be disambiguated:
+  add(result, '[')
+  encodeVInt(ord(t.kind), result)
+  add(result, '+')
+  encodeVInt(t.uniqueId, result)
+  if t.id != t.uniqueId:
+    add(result, '+')
+    encodeVInt(t.id, result)
+  if t.n != nil:
+    encodeNode(g, unknownLineInfo(), t.n, result)
+  if t.flags != {}:
+    add(result, '$')
+    encodeVInt(cast[int32](t.flags), result)
+  if t.callConv != low(t.callConv):
+    add(result, '?')
+    encodeVInt(ord(t.callConv), result)
+  if t.owner != nil:
+    add(result, '*')
+    encodeVInt(t.owner.id, result)
+    pushSym(w, t.owner)
+  if t.sym != nil:
+    add(result, '&')
+    encodeVInt(t.sym.id, result)
+    pushSym(w, t.sym)
+  if t.size != - 1:
+    add(result, '/')
+    encodeVBiggestInt(t.size, result)
+  if t.align != 2:
+    add(result, '=')
+    encodeVInt(t.align, result)
+  if t.lockLevel.ord != UnspecifiedLockLevel.ord:
+    add(result, '\14')
+    encodeVInt(t.lockLevel.int16, result)
+  if t.destructor != nil and t.destructor.id != 0:
+    add(result, '\15')
+    encodeVInt(t.destructor.id, result)
+    pushSym(w, t.destructor)
+  if t.deepCopy != nil:
+    add(result, '\16')
+    encodeVInt(t.deepcopy.id, result)
+    pushSym(w, t.deepcopy)
+  if t.assignment != nil:
+    add(result, '\17')
+    encodeVInt(t.assignment.id, result)
+    pushSym(w, t.assignment)
+  if t.sink != nil:
+    add(result, '\18')
+    encodeVInt(t.sink.id, result)
+    pushSym(w, t.sink)
+  for i, s in items(t.methods):
+    add(result, '\19')
+    encodeVInt(i, result)
+    add(result, '\20')
+    encodeVInt(s.id, result)
+    pushSym(w, s)
+  encodeLoc(g, t.loc, result)
+  if t.typeInst != nil:
+    add(result, '\21')
+    encodeVInt(t.typeInst.uniqueId, result)
+    pushType(w, t.typeInst)
+  for i in countup(0, sonsLen(t) - 1):
+    if t.sons[i] == nil:
+      add(result, "^()")
+    else:
+      add(result, '^')
+      encodeVInt(t.sons[i].uniqueId, result)
+      pushType(w, t.sons[i])
+
+proc encodeLib(g: ModuleGraph, lib: PLib, info: TLineInfo, result: var string) =
+  add(result, '|')
+  encodeVInt(ord(lib.kind), result)
+  add(result, '|')
+  encodeStr($lib.name, result)
+  add(result, '|')
+  encodeNode(g, info, lib.path, result)
+
+proc encodeInstantiations(g: ModuleGraph; s: seq[PInstantiation];
+                          result: var string) =
+  for t in s:
+    result.add('\15')
+    encodeVInt(t.sym.id, result)
+    pushSym(w, t.sym)
+    for tt in t.concreteTypes:
+      result.add('\17')
+      encodeVInt(tt.uniqueId, result)
+      pushType(w, tt)
+    result.add('\20')
+    encodeVInt(t.compilesId, result)
+
+proc encodeSym(g: ModuleGraph, s: PSym, result: var string) =
+  if s == nil:
+    # nil nodes have to be stored too:
+    result.add("{}")
+    return
+  # we need no surrounding {} here because the symbol is in a line of its own
+  encodeVInt(ord(s.kind), result)
+  result.add('+')
+  encodeVInt(s.id, result)
+  result.add('&')
+  encodeStr(s.name.s, result)
+  if s.typ != nil:
+    result.add('^')
+    encodeVInt(s.typ.uniqueId, result)
+    pushType(w, s.typ)
+  result.add('?')
+  if s.info.col != -1'i16: encodeVInt(s.info.col, result)
+  result.add(',')
+  encodeVInt(int s.info.line, result)
+  result.add(',')
+  #encodeVInt(toDbFileId(g.incr, g.config, s.info.fileIndex), result)
+  encodeVInt(s.info.fileIndex.int, result)
+  if s.owner != nil:
+    result.add('*')
+    encodeVInt(s.owner.id, result)
+    pushSym(w, s.owner)
+  if s.flags != {}:
+    result.add('$')
+    encodeVInt(cast[int32](s.flags), result)
+  if s.magic != mNone:
+    result.add('@')
+    encodeVInt(ord(s.magic), result)
+  result.add('!')
+  encodeVInt(cast[int32](s.options), result)
+  if s.position != 0:
+    result.add('%')
+    encodeVInt(s.position, result)
+  if s.offset != - 1:
+    result.add('`')
+    encodeVInt(s.offset, result)
+  encodeLoc(g, s.loc, result)
+  if s.annex != nil: encodeLib(g, s.annex, s.info, result)
+  if s.constraint != nil:
+    add(result, '#')
+    encodeNode(g, unknownLineInfo(), s.constraint, result)
+  case s.kind
+  of skType, skGenericParam:
+    for t in s.typeInstCache:
+      result.add('\14')
+      encodeVInt(t.uniqueId, result)
+      pushType(w, t)
+  of routineKinds:
+    encodeInstantiations(g, s.procInstCache, result)
+    if s.gcUnsafetyReason != nil:
+      result.add('\16')
+      encodeVInt(s.gcUnsafetyReason.id, result)
+      pushSym(w, s.gcUnsafetyReason)
+    if s.transformedBody != nil:
+      result.add('\24')
+      encodeNode(g, s.info, s.transformedBody, result)
+  of skModule, skPackage:
+    encodeInstantiations(g, s.usedGenerics, result)
+    # we don't serialize:
+    #tab*: TStrTable         # interface table for modules
+  of skLet, skVar, skField, skForVar:
+    if s.guard != nil:
+      result.add('\18')
+      encodeVInt(s.guard.id, result)
+      pushSym(w, s.guard)
+    if s.bitsize != 0:
+      result.add('\19')
+      encodeVInt(s.bitsize, result)
+  else: discard
+  # lazy loading will soon reload the ast lazily, so the ast needs to be
+  # the last entry of a symbol:
+  if s.ast != nil:
+    # we used to attempt to save space here by only storing a dummy AST if
+    # it is not necessary, but Nim's heavy compile-time evaluation features
+    # make that unfeasible nowadays:
+    encodeNode(g, s.info, s.ast, result)
+
+proc storeSym(g: ModuleGraph; s: PSym) =
+  if sfForward in s.flags and s.kind != skModule:
+    w.forwardedSyms.add s
+    return
+  var buf = newStringOfCap(160)
+  encodeSym(g, s, buf)
+  # XXX only store the name for exported symbols in order to speed up lookup
+  # times once we enable the skStub logic.
+  let m = getModule(s)
+  let mid = if m == nil: 0 else: abs(m.id)
+  db.exec(sql"insert into syms(nimid, module, name, data, exported) values (?, ?, ?, ?, ?)",
+    s.id, mid, s.name.s, buf, ord(sfExported in s.flags))
+
+proc storeType(g: ModuleGraph; t: PType) =
+  var buf = newStringOfCap(160)
+  encodeType(g, t, buf)
+  let m = if t.owner != nil: getModule(t.owner) else: nil
+  let mid = if m == nil: 0 else: abs(m.id)
+  db.exec(sql"insert into types(nimid, module, data) values (?, ?, ?)",
+    t.uniqueId, mid, buf)
+
+proc transitiveClosure(g: ModuleGraph) =
+  var i = 0
+  while true:
+    if i > 100_000:
+      doAssert false, "loop never ends!"
+    if w.sstack.len > 0:
+      let s = w.sstack.pop()
+      when false:
+        echo "popped ", s.name.s, " ", s.id
+      storeSym(g, s)
+    elif w.tstack.len > 0:
+      let t = w.tstack.pop()
+      storeType(g, t)
+      when false:
+        echo "popped type ", typeToString(t), " ", t.uniqueId
+    else:
+      break
+    inc i
+
+proc storeNode*(g: ModuleGraph; module: PSym; n: PNode) =
+  if g.config.symbolFiles == disabledSf: return
+  var buf = newStringOfCap(160)
+  encodeNode(g, module.info, n, buf)
+  db.exec(sql"insert into toplevelstmts(module, position, data) values (?, ?, ?)",
+    abs(module.id), module.offset, buf)
+  inc module.offset
+  transitiveClosure(g)
+
+proc recordStmt*(g: ModuleGraph; module: PSym; n: PNode) =
+  storeNode(g, module, n)
+
+proc storeFilename(g: ModuleGraph; fullpath: AbsoluteFile; fileIdx: FileIndex) =
+  let id = db.getValue(sql"select id from filenames where fullpath = ?", fullpath.string)
+  if id.len == 0:
+    let fullhash = hashFileCached(g.config, fileIdx, fullpath)
+    db.exec(sql"insert into filenames(nimid, fullpath, fullhash) values (?, ?, ?)",
+        int(fileIdx), fullpath.string, fullhash)
+
+proc storeRemaining*(g: ModuleGraph; module: PSym) =
+  if g.config.symbolFiles == disabledSf: return
+  var stillForwarded: seq[PSym] = @[]
+  for s in w.forwardedSyms:
+    if sfForward notin s.flags:
+      storeSym(g, s)
+    else:
+      stillForwarded.add s
+  swap w.forwardedSyms, stillForwarded
+  transitiveClosure(g)
+  var nimid = 0
+  for x in items(g.config.m.fileInfos):
+    # don't store the "command line" entry:
+    if nimid != 0:
+      storeFilename(g, x.fullPath, FileIndex(nimid))
+    inc nimid
+
+# ---------------- decoder -----------------------------------
+
+type
+  BlobReader = object
+    s: string
+    pos: int
+
+using
+  b: var BlobReader
+  g: ModuleGraph
+
+proc loadSym(g; id: int, info: TLineInfo): PSym
+proc loadType(g; id: int, info: TLineInfo): PType
+
+proc decodeLineInfo(g; b; info: var TLineInfo) =
+  if b.s[b.pos] == '?':
+    inc(b.pos)
+    if b.s[b.pos] == ',': info.col = -1'i16
+    else: info.col = int16(decodeVInt(b.s, b.pos))
+    if b.s[b.pos] == ',':
+      inc(b.pos)
+      if b.s[b.pos] == ',': info.line = 0'u16
+      else: info.line = uint16(decodeVInt(b.s, b.pos))
+      if b.s[b.pos] == ',':
+        inc(b.pos)
+        #info.fileIndex = fromDbFileId(g.incr, g.config, decodeVInt(b.s, b.pos))
+        info.fileIndex = FileIndex decodeVInt(b.s, b.pos)
+
+proc skipNode(b) =
+  # ')' itself cannot be part of a string literal so that this is correct.
+  assert b.s[b.pos] == '('
+  var par = 0
+  var pos = b.pos+1
+  while true:
+    case b.s[pos]
+    of ')':
+      if par == 0: break
+      dec par
+    of '(': inc par
+    else: discard
+    inc pos
+  b.pos = pos+1 # skip ')'
+
+proc decodeNodeLazyBody(g; b; fInfo: TLineInfo,
+                        belongsTo: PSym): PNode =
+  result = nil
+  if b.s[b.pos] == '(':
+    inc(b.pos)
+    if b.s[b.pos] == ')':
+      inc(b.pos)
+      return                  # nil node
+    result = newNodeI(TNodeKind(decodeVInt(b.s, b.pos)), fInfo)
+    decodeLineInfo(g, b, result.info)
+    if b.s[b.pos] == '$':
+      inc(b.pos)
+      result.flags = cast[TNodeFlags](int32(decodeVInt(b.s, b.pos)))
+    if b.s[b.pos] == '^':
+      inc(b.pos)
+      var id = decodeVInt(b.s, b.pos)
+      result.typ = loadType(g, id, result.info)
+    case result.kind
+    of nkCharLit..nkUInt64Lit:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        result.intVal = decodeVBiggestInt(b.s, b.pos)
+    of nkFloatLit..nkFloat64Lit:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        var fl = decodeStr(b.s, b.pos)
+        result.floatVal = parseFloat(fl)
+    of nkStrLit..nkTripleStrLit:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        result.strVal = decodeStr(b.s, b.pos)
+      else:
+        result.strVal = ""
+    of nkIdent:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        var fl = decodeStr(b.s, b.pos)
+        result.ident = g.cache.getIdent(fl)
+      else:
+        internalError(g.config, result.info, "decodeNode: nkIdent")
+    of nkSym:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        var id = decodeVInt(b.s, b.pos)
+        result.sym = loadSym(g, id, result.info)
+      else:
+        internalError(g.config, result.info, "decodeNode: nkSym")
+    else:
+      var i = 0
+      while b.s[b.pos] != ')':
+        when false:
+          if belongsTo != nil and i == bodyPos:
+            addSonNilAllowed(result, nil)
+            belongsTo.offset = b.pos
+            skipNode(b)
+          else:
+            discard
+        addSonNilAllowed(result, decodeNodeLazyBody(g, b, result.info, nil))
+        inc i
+    if b.s[b.pos] == ')': inc(b.pos)
+    else: internalError(g.config, result.info, "decodeNode: ')' missing")
+  else:
+    internalError(g.config, fInfo, "decodeNode: '(' missing " & $b.pos)
+
+proc decodeNode(g; b; fInfo: TLineInfo): PNode =
+  result = decodeNodeLazyBody(g, b, fInfo, nil)
+
+proc decodeLoc(g; b; loc: var TLoc, info: TLineInfo) =
+  if b.s[b.pos] == '<':
+    inc(b.pos)
+    if b.s[b.pos] in {'0'..'9', 'a'..'z', 'A'..'Z'}:
+      loc.k = TLocKind(decodeVInt(b.s, b.pos))
+    else:
+      loc.k = low(loc.k)
+    if b.s[b.pos] == '*':
+      inc(b.pos)
+      loc.storage = TStorageLoc(decodeVInt(b.s, b.pos))
+    else:
+      loc.storage = low(loc.storage)
+    if b.s[b.pos] == '$':
+      inc(b.pos)
+      loc.flags = cast[TLocFlags](int32(decodeVInt(b.s, b.pos)))
+    else:
+      loc.flags = {}
+    if b.s[b.pos] == '^':
+      inc(b.pos)
+      loc.lode = decodeNode(g, b, info)
+      # rrGetType(b, decodeVInt(b.s, b.pos), info)
+    else:
+      loc.lode = nil
+    if b.s[b.pos] == '!':
+      inc(b.pos)
+      loc.r = rope(decodeStr(b.s, b.pos))
+    else:
+      loc.r = nil
+    if b.s[b.pos] == '>': inc(b.pos)
+    else: internalError(g.config, info, "decodeLoc " & b.s[b.pos])
+
+proc loadBlob(g; query: SqlQuery; id: int): BlobReader =
+  let blob = db.getValue(query, id)
+  if blob.len == 0:
+    internalError(g.config, "symbolfiles: cannot find ID " & $ id)
+  result = BlobReader(pos: 0)
+  shallowCopy(result.s, blob)
+  # ensure we can read without index checks:
+  result.s.add '\0'
+
+proc loadType(g; id: int; info: TLineInfo): PType =
+  result = g.incr.r.types.getOrDefault(id)
+  if result != nil: return result
+  var b = loadBlob(g, sql"select data from types where nimid = ?", id)
+
+  if b.s[b.pos] == '[':
+    inc(b.pos)
+    if b.s[b.pos] == ']':
+      inc(b.pos)
+      return                  # nil type
+  new(result)
+  result.kind = TTypeKind(decodeVInt(b.s, b.pos))
+  if b.s[b.pos] == '+':
+    inc(b.pos)
+    result.uniqueId = decodeVInt(b.s, b.pos)
+    setId(result.uniqueId)
+    #if debugIds: registerID(result)
+  else:
+    internalError(g.config, info, "decodeType: no id")
+  if b.s[b.pos] == '+':
+    inc(b.pos)
+    result.id = decodeVInt(b.s, b.pos)
+  else:
+    result.id = result.uniqueId
+  # here this also avoids endless recursion for recursive type
+  g.incr.r.types.add(result.uniqueId, result)
+  if b.s[b.pos] == '(': result.n = decodeNode(g, b, unknownLineInfo())
+  if b.s[b.pos] == '$':
+    inc(b.pos)
+    result.flags = cast[TTypeFlags](int32(decodeVInt(b.s, b.pos)))
+  if b.s[b.pos] == '?':
+    inc(b.pos)
+    result.callConv = TCallingConvention(decodeVInt(b.s, b.pos))
+  if b.s[b.pos] == '*':
+    inc(b.pos)
+    result.owner = loadSym(g, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '&':
+    inc(b.pos)
+    result.sym = loadSym(g, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '/':
+    inc(b.pos)
+    result.size = decodeVInt(b.s, b.pos)
+  else:
+    result.size = -1
+  if b.s[b.pos] == '=':
+    inc(b.pos)
+    result.align = decodeVInt(b.s, b.pos).int16
+  else:
+    result.align = 2
+
+  if b.s[b.pos] == '\14':
+    inc(b.pos)
+    result.lockLevel = decodeVInt(b.s, b.pos).TLockLevel
+  else:
+    result.lockLevel = UnspecifiedLockLevel
+
+  if b.s[b.pos] == '\15':
+    inc(b.pos)
+    result.destructor = loadSym(g, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '\16':
+    inc(b.pos)
+    result.deepCopy = loadSym(g, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '\17':
+    inc(b.pos)
+    result.assignment = loadSym(g, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '\18':
+    inc(b.pos)
+    result.sink = loadSym(g, decodeVInt(b.s, b.pos), info)
+  while b.s[b.pos] == '\19':
+    inc(b.pos)
+    let x = decodeVInt(b.s, b.pos)
+    doAssert b.s[b.pos] == '\20'
+    inc(b.pos)
+    let y = loadSym(g, decodeVInt(b.s, b.pos), info)
+    result.methods.add((x, y))
+  decodeLoc(g, b, result.loc, info)
+  if b.s[b.pos] == '\21':
+    inc(b.pos)
+    let d = decodeVInt(b.s, b.pos)
+    result.typeInst = loadType(g, d, info)
+  while b.s[b.pos] == '^':
+    inc(b.pos)
+    if b.s[b.pos] == '(':
+      inc(b.pos)
+      if b.s[b.pos] == ')': inc(b.pos)
+      else: internalError(g.config, info, "decodeType ^(" & b.s[b.pos])
+      rawAddSon(result, nil)
+    else:
+      let d = decodeVInt(b.s, b.pos)
+      rawAddSon(result, loadType(g, d, info))
+
+proc decodeLib(g; b; info: TLineInfo): PLib =
+  result = nil
+  if b.s[b.pos] == '|':
+    new(result)
+    inc(b.pos)
+    result.kind = TLibKind(decodeVInt(b.s, b.pos))
+    if b.s[b.pos] != '|': internalError(g.config, "decodeLib: 1")
+    inc(b.pos)
+    result.name = rope(decodeStr(b.s, b.pos))
+    if b.s[b.pos] != '|': internalError(g.config, "decodeLib: 2")
+    inc(b.pos)
+    result.path = decodeNode(g, b, info)
+
+proc decodeInstantiations(g; b; info: TLineInfo;
+                          s: var seq[PInstantiation]) =
+  while b.s[b.pos] == '\15':
+    inc(b.pos)
+    var ii: PInstantiation
+    new ii
+    ii.sym = loadSym(g, decodeVInt(b.s, b.pos), info)
+    ii.concreteTypes = @[]
+    while b.s[b.pos] == '\17':
+      inc(b.pos)
+      ii.concreteTypes.add loadType(g, decodeVInt(b.s, b.pos), info)
+    if b.s[b.pos] == '\20':
+      inc(b.pos)
+      ii.compilesId = decodeVInt(b.s, b.pos)
+    s.add ii
+
+proc loadSymFromBlob(g; b; info: TLineInfo): PSym =
+  if b.s[b.pos] == '{':
+    inc(b.pos)
+    if b.s[b.pos] == '}':
+      inc(b.pos)
+      return                  # nil sym
+  var k = TSymKind(decodeVInt(b.s, b.pos))
+  var id: int
+  if b.s[b.pos] == '+':
+    inc(b.pos)
+    id = decodeVInt(b.s, b.pos)
+    setId(id)
+  else:
+    internalError(g.config, info, "decodeSym: no id")
+  var ident: PIdent
+  if b.s[b.pos] == '&':
+    inc(b.pos)
+    ident = g.cache.getIdent(decodeStr(b.s, b.pos))
+  else:
+    internalError(g.config, info, "decodeSym: no ident")
+  #echo "decoding: {", ident.s
+  new(result)
+  result.id = id
+  result.kind = k
+  result.name = ident         # read the rest of the symbol description:
+  g.incr.r.syms.add(result.id, result)
+  if b.s[b.pos] == '^':
+    inc(b.pos)
+    result.typ = loadType(g, decodeVInt(b.s, b.pos), info)
+  decodeLineInfo(g, b, result.info)
+  if b.s[b.pos] == '*':
+    inc(b.pos)
+    result.owner = loadSym(g, decodeVInt(b.s, b.pos), result.info)
+  if b.s[b.pos] == '$':
+    inc(b.pos)
+    result.flags = cast[TSymFlags](int32(decodeVInt(b.s, b.pos)))
+  if b.s[b.pos] == '@':
+    inc(b.pos)
+    result.magic = TMagic(decodeVInt(b.s, b.pos))
+  if b.s[b.pos] == '!':
+    inc(b.pos)
+    result.options = cast[TOptions](int32(decodeVInt(b.s, b.pos)))
+  if b.s[b.pos] == '%':
+    inc(b.pos)
+    result.position = decodeVInt(b.s, b.pos)
+  if b.s[b.pos] == '`':
+    inc(b.pos)
+    result.offset = decodeVInt(b.s, b.pos)
+  else:
+    result.offset = -1
+  decodeLoc(g, b, result.loc, result.info)
+  result.annex = decodeLib(g, b, info)
+  if b.s[b.pos] == '#':
+    inc(b.pos)
+    result.constraint = decodeNode(g, b, unknownLineInfo())
+  case result.kind
+  of skType, skGenericParam:
+    while b.s[b.pos] == '\14':
+      inc(b.pos)
+      result.typeInstCache.add loadType(g, decodeVInt(b.s, b.pos), result.info)
+  of routineKinds:
+    decodeInstantiations(g, b, result.info, result.procInstCache)
+    if b.s[b.pos] == '\16':
+      inc(b.pos)
+      result.gcUnsafetyReason = loadSym(g, decodeVInt(b.s, b.pos), result.info)
+    if b.s[b.pos] == '\24':
+      inc b.pos
+      result.transformedBody = decodeNode(g, b, result.info)
+  of skModule, skPackage:
+    decodeInstantiations(g, b, result.info, result.usedGenerics)
+  of skLet, skVar, skField, skForVar:
+    if b.s[b.pos] == '\18':
+      inc(b.pos)
+      result.guard = loadSym(g, decodeVInt(b.s, b.pos), result.info)
+    if b.s[b.pos] == '\19':
+      inc(b.pos)
+      result.bitsize = decodeVInt(b.s, b.pos).int16
+  else: discard
+
+  if b.s[b.pos] == '(':
+    #if result.kind in routineKinds:
+    #  result.ast = decodeNodeLazyBody(b, result.info, result)
+    #else:
+    result.ast = decodeNode(g, b, result.info)
+  if sfCompilerProc in result.flags:
+    registerCompilerProc(g, result)
+    #echo "loading ", result.name.s
+
+proc loadSym(g; id: int; info: TLineInfo): PSym =
+  result = g.incr.r.syms.getOrDefault(id)
+  if result != nil: return result
+  var b = loadBlob(g, sql"select data from syms where nimid = ?", id)
+  result = loadSymFromBlob(g, b, info)
+  doAssert id == result.id, "symbol ID is not consistent!"
+
+proc registerModule*(g; module: PSym) =
+  g.incr.r.syms.add(abs module.id, module)
+
+proc loadModuleSymTab(g; module: PSym) =
+  ## goal: fill  module.tab
+  g.incr.r.syms.add(module.id, module)
+  for row in db.fastRows(sql"select nimid, data from syms where module = ? and exported = 1", abs(module.id)):
+    let id = parseInt(row[0])
+    var s = g.incr.r.syms.getOrDefault(id)
+    if s == nil:
+      var b = BlobReader(pos: 0)
+      shallowCopy(b.s, row[1])
+      # ensure we can read without index checks:
+      b.s.add '\0'
+      s = loadSymFromBlob(g, b, module.info)
+    assert s != nil
+    if s.kind != skField:
+      strTableAdd(module.tab, s)
+  if sfSystemModule in module.flags:
+    g.systemModule = module
+
+proc replay(g: ModuleGraph; module: PSym; n: PNode) =
+  # XXX check if we need to replay nkStaticStmt here.
+  case n.kind
+  #of nkStaticStmt:
+    #evalStaticStmt(module, g, n[0], module)
+    #of nkVarSection, nkLetSection:
+    #  nkVarSections are already covered by the vmgen which produces nkStaticStmt
+  of nkMethodDef:
+    methodDef(g, n[namePos].sym, fromCache=true)
+  of nkCommentStmt:
+    # pragmas are complex and can be user-overriden via templates. So
+    # instead of using the original ``nkPragma`` nodes, we rely on the
+    # fact that pragmas.nim was patched to produce specialized recorded
+    # statements for us in the form of ``nkCommentStmt`` with (key, value)
+    # pairs. Ordinary nkCommentStmt nodes never have children so this is
+    # not ambiguous.
+    # Fortunately only a tiny subset of the available pragmas need to
+    # be replayed here. This is always a subset of ``pragmas.stmtPragmas``.
+    if n.len >= 2:
+      internalAssert g.config, n[0].kind == nkStrLit and n[1].kind == nkStrLit
+      case n[0].strVal
+      of "hint": message(g.config, n.info, hintUser, n[1].strVal)
+      of "warning": message(g.config, n.info, warnUser, n[1].strVal)
+      of "error": localError(g.config, n.info, errUser, n[1].strVal)
+      of "compile":
+        internalAssert g.config, n.len == 3 and n[2].kind == nkStrLit
+        var cf = Cfile(cname: AbsoluteFile n[1].strVal, obj: AbsoluteFile n[2].strVal,
+                       flags: {CfileFlag.External})
+        extccomp.addExternalFileToCompile(g.config, cf)
+      of "link":
+        extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal)
+      of "passl":
+        extccomp.addLinkOption(g.config, n[1].strVal)
+      of "passc":
+        extccomp.addCompileOption(g.config, n[1].strVal)
+      of "cppdefine":
+        options.cppDefine(g.config, n[1].strVal)
+      of "inc":
+        let destKey = n[1].strVal
+        let by = n[2].intVal
+        let v = getOrDefault(g.cacheCounters, destKey)
+        g.cacheCounters[destKey] = v+by
+      of "put":
+        let destKey = n[1].strVal
+        let key = n[2].strVal
+        let val = n[3]
+        if not contains(g.cacheTables, destKey):
+          g.cacheTables[destKey] = initBTree[string, PNode]()
+        if not contains(g.cacheTables[destKey], key):
+          g.cacheTables[destKey].add(key, val)
+        else:
+          internalError(g.config, n.info, "key already exists: " & key)
+      of "incl":
+        let destKey = n[1].strVal
+        let val = n[2]
+        if not contains(g.cacheSeqs, destKey):
+          g.cacheSeqs[destKey] = newTree(nkStmtList, val)
+        else:
+          block search:
+            for existing in g.cacheSeqs[destKey]:
+              if exprStructuralEquivalent(existing, val, strictSymEquality=true):
+                break search
+            g.cacheSeqs[destKey].add val
+      of "add":
+        let destKey = n[1].strVal
+        let val = n[2]
+        if not contains(g.cacheSeqs, destKey):
+          g.cacheSeqs[destKey] = newTree(nkStmtList, val)
+        else:
+          g.cacheSeqs[destKey].add val
+      else:
+        internalAssert g.config, false
+  of nkImportStmt:
+    for x in n:
+      internalAssert g.config, x.kind == nkSym
+      let modpath = AbsoluteFile toFullPath(g.config, x.sym.info)
+      let imported = g.importModuleCallback(g, module, fileInfoIdx(g.config, modpath))
+      internalAssert g.config, imported.id < 0
+  of nkStmtList, nkStmtListExpr:
+    for x in n: replay(g, module, x)
+  else: discard "nothing to do for this node"
+
+proc loadNode*(g: ModuleGraph; module: PSym): PNode =
+  loadModuleSymTab(g, module)
+  result = newNodeI(nkStmtList, module.info)
+  for row in db.rows(sql"select data from toplevelstmts where module = ? order by position asc",
+                        abs module.id):
+    var b = BlobReader(pos: 0)
+    # ensure we can read without index checks:
+    b.s = row[0] & '\0'
+    result.add decodeNode(g, b, module.info)
+  db.exec(sql"insert into controlblock(idgen) values (?)", gFrontEndId)
+  replay(g, module, result)
+
+proc setupModuleCache*(g: ModuleGraph) =
+  if g.config.symbolFiles == disabledSf: return
+  g.recordStmt = recordStmt
+  let dbfile = getNimcacheDir(g.config) / RelativeFile"rodfiles.db"
+  if g.config.symbolFiles == writeOnlySf:
+    removeFile(dbfile)
+  createDir getNimcacheDir(g.config)
+  let ec = encodeConfig(g)
+  if not fileExists(dbfile):
+    db = open(connection=string dbfile, user="nim", password="",
+              database="nim")
+    createDb(db)
+    db.exec(sql"insert into config(config) values (?)", ec)
+  else:
+    db = open(connection=string dbfile, user="nim", password="",
+              database="nim")
+    let oldConfig = db.getValue(sql"select config from config")
+    g.incr.configChanged = oldConfig != ec
+    # ensure the filename IDs stay consistent:
+    for row in db.rows(sql"select fullpath, nimid from filenames order by nimid"):
+      let id = fileInfoIdx(g.config, AbsoluteFile row[0])
+      doAssert id.int == parseInt(row[1])
+    db.exec(sql"update config set config = ?", ec)
+  db.exec(sql"pragma journal_mode=off")
+  # This MUST be turned off, otherwise it's way too slow even for testing purposes:
+  db.exec(sql"pragma SYNCHRONOUS=off")
+  db.exec(sql"pragma LOCKING_MODE=exclusive")
+  let lastId = db.getValue(sql"select max(idgen) from controlblock")
+  if lastId.len > 0:
+    idgen.setId(parseInt lastId)
diff --git a/compiler/rodread.nim b/compiler/rodread.nim
deleted file mode 100644
index f7e5a0f84..000000000
--- a/compiler/rodread.nim
+++ /dev/null
@@ -1,1236 +0,0 @@
-#
-#
-#           The Nim Compiler
-#        (c) Copyright 2013 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-# This module is responsible for loading of rod files.
-#
-# Reading and writing binary files are really hard to debug. Therefore we use
-# a "creative" text/binary hybrid format. ROD-files are more efficient
-# to process because symbols can be loaded on demand.
-#
-# A ROD file consists of:
-#
-#  - a header:
-#    NIM:$fileversion\n
-#  - the module's id (even if the module changed, its ID will not!):
-#    ID:Ax3\n
-#  - HASH value of this module:
-#    HASH:HASH-val\n
-#  - a section containing the compiler options and defines this
-#    module has been compiled with:
-#    OPTIONS:options\n
-#    GOPTIONS:options\n # global options
-#    CMD:command\n
-#    DEFINES:defines\n
-#  - FILES(
-#    myfile.inc
-#    lib/mymodA
-#    )
-#  - an include file dependency section:
-#    INCLUDES(
-#    <fileidx> <Hash of myfile.inc>\n # fileidx is the LINE in the file section!
-#    )
-#  - a module dependency section:
-#    DEPS: <fileidx> <fileidx>\n
-#  - an interface section:
-#    INTERF(
-#    identifier1 id\n # id is the symbol's id
-#    identifier2 id\n
-#    )
-#  - a compiler proc section:
-#    COMPILERPROCS(
-#    identifier1 id\n # id is the symbol's id
-#    )
-#  - an index consisting of (ID, linenumber)-pairs:
-#    INDEX(
-#    id-diff idx-diff\n
-#    id-diff idx-diff\n
-#    )
-#
-#    Since the whole index has to be read in advance, we compress it by
-#    storing the integer differences to the last entry instead of using the
-#    real numbers.
-#
-#  - an import index consisting of (ID, moduleID)-pairs:
-#    IMPORTS(
-#    id-diff moduleID-diff\n
-#    id-diff moduleID-diff\n
-#    )
-#  - a list of all exported type converters because they are needed for correct
-#    semantic checking:
-#    CONVERTERS:id id\n   # symbol ID
-#
-#    This is a misnomer now; it's really a "load unconditionally" section as
-#    it is also used for pattern templates.
-#
-#  - a list of all (private or exported) methods because they are needed for
-#    correct dispatcher generation:
-#    METHODS: id id\n   # symbol ID
-#  - an AST section that contains the module's AST:
-#    INIT(
-#    idx\n  # position of the node in the DATA section
-#    idx\n
-#    )
-#  - a data section, where each type, symbol or AST is stored.
-#    DATA(
-#    type
-#    (node)
-#    sym
-#    )
-#
-#    The data section MUST be the last section of the file, because processing
-#    stops immediately after ``DATA(`` and the rest is only loaded on demand
-#    by using a mem'mapped file.
-#
-
-import
-  os, options, strutils, nversion, ast, astalgo, msgs, platform, condsyms,
-  ropes, idents, securehash, idgen, types, rodutils, memfiles, tables
-
-type
-  TReasonForRecompile* = enum ## all the reasons that can trigger recompilation
-    rrEmpty,                  # dependencies not yet computed
-    rrNone,                   # no need to recompile
-    rrRodDoesNotExist,        # rod file does not exist
-    rrRodInvalid,             # rod file is invalid
-    rrHashChange,             # file has been edited since last recompilation
-    rrDefines,                # defines have changed
-    rrOptions,                # options have changed
-    rrInclDeps,               # an include has changed
-    rrModDeps                 # a module this module depends on has been changed
-
-const
-  reasonToFrmt*: array[TReasonForRecompile, string] = ["",
-    "no need to recompile: $1", "symbol file for $1 does not exist",
-    "symbol file for $1 has the wrong version",
-    "file edited since last compilation: $1",
-    "list of conditional symbols changed for: $1",
-    "list of options changed for: $1",
-    "an include file edited: $1",
-    "a module $1 depends on has changed"]
-
-type
-  TIndex*{.final.} = object   # an index with compression
-    lastIdxKey*, lastIdxVal*: int
-    tab*: TIITable
-    r*: string                # writers use this
-    offset*: int              # readers use this
-
-  TRodReader* = object of RootObj
-    pos: int                 # position; used for parsing
-    s: cstring               # mmap'ed file contents
-    options: TOptions
-    reason: TReasonForRecompile
-    modDeps: seq[int32]
-    files: seq[int32]
-    dataIdx: int             # offset of start of data section
-    convertersIdx: int       # offset of start of converters section
-    initIdx, interfIdx, compilerProcsIdx, methodsIdx: int
-    filename: string
-    index, imports: TIndex
-    readerIndex: int
-    line: int            # only used for debugging, but is always in the code
-    moduleID: int
-    syms: Table[int, PSym]       # already processed symbols
-    memfile: MemFile     # unfortunately there is no point in time where we
-                         # can close this! XXX
-    methods*: TSymSeq
-    origFile: string
-    inViewMode: bool
-    cache*: IdentCache
-
-  PRodReader* = ref TRodReader
-
-var rodCompilerprocs*: TStrTable # global because this is needed by magicsys
-
-proc rawLoadStub(s: PSym)
-
-var gTypeTable: TIdTable
-
-proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym
-  # `info` is only used for debugging purposes
-proc rrGetType(r: PRodReader, id: int, info: TLineInfo): PType
-
-proc decodeLineInfo(r: PRodReader, info: var TLineInfo) =
-  if r.s[r.pos] == '?':
-    inc(r.pos)
-    if r.s[r.pos] == ',': info.col = -1'i16
-    else: info.col = int16(decodeVInt(r.s, r.pos))
-    if r.s[r.pos] == ',':
-      inc(r.pos)
-      if r.s[r.pos] == ',': info.line = -1'i16
-      else: info.line = int16(decodeVInt(r.s, r.pos))
-      if r.s[r.pos] == ',':
-        inc(r.pos)
-        info = newLineInfo(r.files[decodeVInt(r.s, r.pos)], info.line, info.col)
-
-proc skipNode(r: PRodReader) =
-  assert r.s[r.pos] == '('
-  var par = 0
-  var pos = r.pos+1
-  while true:
-    case r.s[pos]
-    of ')':
-      if par == 0: break
-      dec par
-    of '(': inc par
-    else: discard
-    inc pos
-  r.pos = pos+1 # skip ')'
-
-proc decodeNodeLazyBody(r: PRodReader, fInfo: TLineInfo,
-                        belongsTo: PSym): PNode =
-  result = nil
-  if r.s[r.pos] == '(':
-    inc(r.pos)
-    if r.s[r.pos] == ')':
-      inc(r.pos)
-      return                  # nil node
-    result = newNodeI(TNodeKind(decodeVInt(r.s, r.pos)), fInfo)
-    decodeLineInfo(r, result.info)
-    if r.s[r.pos] == '$':
-      inc(r.pos)
-      result.flags = cast[TNodeFlags](int32(decodeVInt(r.s, r.pos)))
-    if r.s[r.pos] == '^':
-      inc(r.pos)
-      var id = decodeVInt(r.s, r.pos)
-      result.typ = rrGetType(r, id, result.info)
-    case result.kind
-    of nkCharLit..nkUInt64Lit:
-      if r.s[r.pos] == '!':
-        inc(r.pos)
-        result.intVal = decodeVBiggestInt(r.s, r.pos)
-    of nkFloatLit..nkFloat64Lit:
-      if r.s[r.pos] == '!':
-        inc(r.pos)
-        var fl = decodeStr(r.s, r.pos)
-        result.floatVal = parseFloat(fl)
-    of nkStrLit..nkTripleStrLit:
-      if r.s[r.pos] == '!':
-        inc(r.pos)
-        result.strVal = decodeStr(r.s, r.pos)
-      else:
-        result.strVal = ""    # BUGFIX
-    of nkIdent:
-      if r.s[r.pos] == '!':
-        inc(r.pos)
-        var fl = decodeStr(r.s, r.pos)
-        result.ident = r.cache.getIdent(fl)
-      else:
-        internalError(result.info, "decodeNode: nkIdent")
-    of nkSym:
-      if r.s[r.pos] == '!':
-        inc(r.pos)
-        var id = decodeVInt(r.s, r.pos)
-        result.sym = rrGetSym(r, id, result.info)
-      else:
-        internalError(result.info, "decodeNode: nkSym")
-    else:
-      var i = 0
-      while r.s[r.pos] != ')':
-        if belongsTo != nil and i == bodyPos:
-          addSonNilAllowed(result, nil)
-          belongsTo.offset = r.pos
-          skipNode(r)
-        else:
-          addSonNilAllowed(result, decodeNodeLazyBody(r, result.info, nil))
-        inc i
-    if r.s[r.pos] == ')': inc(r.pos)
-    else: internalError(result.info, "decodeNode: ')' missing")
-  else:
-    internalError(fInfo, "decodeNode: '(' missing " & $r.pos)
-
-proc decodeNode(r: PRodReader, fInfo: TLineInfo): PNode =
-  result = decodeNodeLazyBody(r, fInfo, nil)
-
-proc decodeLoc(r: PRodReader, loc: var TLoc, info: TLineInfo) =
-  if r.s[r.pos] == '<':
-    inc(r.pos)
-    if r.s[r.pos] in {'0'..'9', 'a'..'z', 'A'..'Z'}:
-      loc.k = TLocKind(decodeVInt(r.s, r.pos))
-    else:
-      loc.k = low(loc.k)
-    if r.s[r.pos] == '*':
-      inc(r.pos)
-      loc.s = TStorageLoc(decodeVInt(r.s, r.pos))
-    else:
-      loc.s = low(loc.s)
-    if r.s[r.pos] == '$':
-      inc(r.pos)
-      loc.flags = cast[TLocFlags](int32(decodeVInt(r.s, r.pos)))
-    else:
-      loc.flags = {}
-    if r.s[r.pos] == '^':
-      inc(r.pos)
-      loc.t = rrGetType(r, decodeVInt(r.s, r.pos), info)
-    else:
-      loc.t = nil
-    if r.s[r.pos] == '!':
-      inc(r.pos)
-      loc.r = rope(decodeStr(r.s, r.pos))
-    else:
-      loc.r = nil
-    if r.s[r.pos] == '>': inc(r.pos)
-    else: internalError(info, "decodeLoc " & r.s[r.pos])
-
-proc decodeType(r: PRodReader, info: TLineInfo): PType =
-  result = nil
-  if r.s[r.pos] == '[':
-    inc(r.pos)
-    if r.s[r.pos] == ']':
-      inc(r.pos)
-      return                  # nil type
-  new(result)
-  result.kind = TTypeKind(decodeVInt(r.s, r.pos))
-  if r.s[r.pos] == '+':
-    inc(r.pos)
-    result.id = decodeVInt(r.s, r.pos)
-    setId(result.id)
-    if debugIds: registerID(result)
-  else:
-    internalError(info, "decodeType: no id")
-  # here this also avoids endless recursion for recursive type
-  idTablePut(gTypeTable, result, result)
-  if r.s[r.pos] == '(': result.n = decodeNode(r, unknownLineInfo())
-  if r.s[r.pos] == '$':
-    inc(r.pos)
-    result.flags = cast[TTypeFlags](int32(decodeVInt(r.s, r.pos)))
-  if r.s[r.pos] == '?':
-    inc(r.pos)
-    result.callConv = TCallingConvention(decodeVInt(r.s, r.pos))
-  if r.s[r.pos] == '*':
-    inc(r.pos)
-    result.owner = rrGetSym(r, decodeVInt(r.s, r.pos), info)
-  if r.s[r.pos] == '&':
-    inc(r.pos)
-    result.sym = rrGetSym(r, decodeVInt(r.s, r.pos), info)
-  if r.s[r.pos] == '/':
-    inc(r.pos)
-    result.size = decodeVInt(r.s, r.pos)
-  else:
-    result.size = - 1
-  if r.s[r.pos] == '=':
-    inc(r.pos)
-    result.align = decodeVInt(r.s, r.pos).int16
-  else:
-    result.align = 2
-
-  if r.s[r.pos] == '\14':
-    inc(r.pos)
-    result.lockLevel = decodeVInt(r.s, r.pos).TLockLevel
-  else:
-    result.lockLevel = UnspecifiedLockLevel
-
-  if r.s[r.pos] == '\15':
-    inc(r.pos)
-    result.destructor = rrGetSym(r, decodeVInt(r.s, r.pos), info)
-  if r.s[r.pos] == '\16':
-    inc(r.pos)
-    result.deepCopy = rrGetSym(r, decodeVInt(r.s, r.pos), info)
-  if r.s[r.pos] == '\17':
-    inc(r.pos)
-    result.assignment = rrGetSym(r, decodeVInt(r.s, r.pos), info)
-  while r.s[r.pos] == '\18':
-    inc(r.pos)
-    let x = decodeVInt(r.s, r.pos)
-    doAssert r.s[r.pos] == '\19'
-    inc(r.pos)
-    let y = rrGetSym(r, decodeVInt(r.s, r.pos), info)
-    result.methods.safeAdd((x, y))
-  decodeLoc(r, result.loc, info)
-  while r.s[r.pos] == '^':
-    inc(r.pos)
-    if r.s[r.pos] == '(':
-      inc(r.pos)
-      if r.s[r.pos] == ')': inc(r.pos)
-      else: internalError(info, "decodeType ^(" & r.s[r.pos])
-      rawAddSon(result, nil)
-    else:
-      var d = decodeVInt(r.s, r.pos)
-      rawAddSon(result, rrGetType(r, d, info))
-
-proc decodeLib(r: PRodReader, info: TLineInfo): PLib =
-  result = nil
-  if r.s[r.pos] == '|':
-    new(result)
-    inc(r.pos)
-    result.kind = TLibKind(decodeVInt(r.s, r.pos))
-    if r.s[r.pos] != '|': internalError("decodeLib: 1")
-    inc(r.pos)
-    result.name = rope(decodeStr(r.s, r.pos))
-    if r.s[r.pos] != '|': internalError("decodeLib: 2")
-    inc(r.pos)
-    result.path = decodeNode(r, info)
-
-proc decodeInstantiations(r: PRodReader; info: TLineInfo;
-                          s: var seq[PInstantiation]) =
-  while r.s[r.pos] == '\15':
-    inc(r.pos)
-    var ii: PInstantiation
-    new ii
-    ii.sym = rrGetSym(r, decodeVInt(r.s, r.pos), info)
-    ii.concreteTypes = @[]
-    while r.s[r.pos] == '\17':
-      inc(r.pos)
-      ii.concreteTypes.add rrGetType(r, decodeVInt(r.s, r.pos), info)
-    if r.s[r.pos] == '\20':
-      inc(r.pos)
-      ii.compilesId = decodeVInt(r.s, r.pos)
-    s.safeAdd ii
-
-proc decodeSym(r: PRodReader, info: TLineInfo): PSym =
-  var
-    id: int
-    ident: PIdent
-  result = nil
-  if r.s[r.pos] == '{':
-    inc(r.pos)
-    if r.s[r.pos] == '}':
-      inc(r.pos)
-      return                  # nil sym
-  var k = TSymKind(decodeVInt(r.s, r.pos))
-  if r.s[r.pos] == '+':
-    inc(r.pos)
-    id = decodeVInt(r.s, r.pos)
-    setId(id)
-  else:
-    internalError(info, "decodeSym: no id")
-  if r.s[r.pos] == '&':
-    inc(r.pos)
-    ident = r.cache.getIdent(decodeStr(r.s, r.pos))
-  else:
-    internalError(info, "decodeSym: no ident")
-  #echo "decoding: {", ident.s
-  result = r.syms.getOrDefault(id)
-  if result == nil:
-    new(result)
-    result.id = id
-    r.syms[result.id] = result
-    if debugIds: registerID(result)
-  elif result.id != id:
-    internalError(info, "decodeSym: wrong id")
-  elif result.kind != skStub and not r.inViewMode:
-    # we already loaded the symbol
-    return
-  else:
-    reset(result[])
-    result.id = id
-  result.kind = k
-  result.name = ident         # read the rest of the symbol description:
-  if r.s[r.pos] == '^':
-    inc(r.pos)
-    result.typ = rrGetType(r, decodeVInt(r.s, r.pos), info)
-  decodeLineInfo(r, result.info)
-  if r.s[r.pos] == '*':
-    inc(r.pos)
-    result.owner = rrGetSym(r, decodeVInt(r.s, r.pos), result.info)
-  if r.s[r.pos] == '$':
-    inc(r.pos)
-    result.flags = cast[TSymFlags](int32(decodeVInt(r.s, r.pos)))
-  if r.s[r.pos] == '@':
-    inc(r.pos)
-    result.magic = TMagic(decodeVInt(r.s, r.pos))
-  if r.s[r.pos] == '!':
-    inc(r.pos)
-    result.options = cast[TOptions](int32(decodeVInt(r.s, r.pos)))
-  else:
-    result.options = r.options
-  if r.s[r.pos] == '%':
-    inc(r.pos)
-    result.position = decodeVInt(r.s, r.pos)
-  elif result.kind notin routineKinds + {skModule}:
-    result.position = 0
-    # this may have been misused as reader index! But we still
-    # need it for routines as the body is loaded lazily.
-  if r.s[r.pos] == '`':
-    inc(r.pos)
-    result.offset = decodeVInt(r.s, r.pos)
-  else:
-    result.offset = - 1
-  decodeLoc(r, result.loc, result.info)
-  result.annex = decodeLib(r, info)
-  if r.s[r.pos] == '#':
-    inc(r.pos)
-    result.constraint = decodeNode(r, unknownLineInfo())
-  case result.kind
-  of skType, skGenericParam:
-    while r.s[r.pos] == '\14':
-      inc(r.pos)
-      result.typeInstCache.safeAdd rrGetType(r, decodeVInt(r.s, r.pos), result.info)
-  of routineKinds:
-    decodeInstantiations(r, result.info, result.procInstCache)
-    if r.s[r.pos] == '\16':
-      inc(r.pos)
-      result.gcUnsafetyReason = rrGetSym(r, decodeVInt(r.s, r.pos), result.info)
-  of skModule, skPackage:
-    decodeInstantiations(r, result.info, result.usedGenerics)
-  of skLet, skVar, skField, skForVar:
-    if r.s[r.pos] == '\18':
-      inc(r.pos)
-      result.guard = rrGetSym(r, decodeVInt(r.s, r.pos), result.info)
-    if r.s[r.pos] == '\19':
-      inc(r.pos)
-      result.bitsize = decodeVInt(r.s, r.pos).int16
-  else: discard
-
-  if r.s[r.pos] == '(':
-    if result.kind in routineKinds:
-      result.ast = decodeNodeLazyBody(r, result.info, result)
-      # since we load the body lazily, we need to set the reader to
-      # be able to reload:
-      result.position = r.readerIndex
-    else:
-      result.ast = decodeNode(r, result.info)
-  #echo "decoded: ", ident.s, "}"
-
-proc skipSection(r: PRodReader) =
-  if r.s[r.pos] == ':':
-    while r.s[r.pos] > '\x0A': inc(r.pos)
-  elif r.s[r.pos] == '(':
-    var c = 0                 # count () pairs
-    inc(r.pos)
-    while true:
-      case r.s[r.pos]
-      of '\x0A': inc(r.line)
-      of '(': inc(c)
-      of ')':
-        if c == 0:
-          inc(r.pos)
-          break
-        elif c > 0:
-          dec(c)
-      of '\0': break          # end of file
-      else: discard
-      inc(r.pos)
-  else:
-    internalError("skipSection " & $r.line)
-
-proc rdWord(r: PRodReader): string =
-  result = ""
-  while r.s[r.pos] in {'A'..'Z', '_', 'a'..'z', '0'..'9'}:
-    add(result, r.s[r.pos])
-    inc(r.pos)
-
-proc newStub(r: PRodReader, name: string, id: int): PSym =
-  new(result)
-  result.kind = skStub
-  result.id = id
-  result.name = r.cache.getIdent(name)
-  result.position = r.readerIndex
-  setId(id)                   #MessageOut(result.name.s);
-  if debugIds: registerID(result)
-
-proc processInterf(r: PRodReader, module: PSym) =
-  if r.interfIdx == 0: internalError("processInterf")
-  r.pos = r.interfIdx
-  while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'):
-    var w = decodeStr(r.s, r.pos)
-    inc(r.pos)
-    var key = decodeVInt(r.s, r.pos)
-    inc(r.pos)                # #10
-    var s = newStub(r, w, key)
-    s.owner = module
-    strTableAdd(module.tab, s)
-    r.syms[s.id] = s
-
-proc processCompilerProcs(r: PRodReader, module: PSym) =
-  if r.compilerProcsIdx == 0: internalError("processCompilerProcs")
-  r.pos = r.compilerProcsIdx
-  while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'):
-    var w = decodeStr(r.s, r.pos)
-    inc(r.pos)
-    var key = decodeVInt(r.s, r.pos)
-    inc(r.pos)                # #10
-    var s = r.syms.getOrDefault(key)
-    if s == nil:
-      s = newStub(r, w, key)
-      s.owner = module
-      r.syms[s.id] = s
-    strTableAdd(rodCompilerprocs, s)
-
-proc processIndex(r: PRodReader; idx: var TIndex; outf: File = nil) =
-  var key, val, tmp: int
-  inc(r.pos, 2)               # skip "(\10"
-  inc(r.line)
-  while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'):
-    tmp = decodeVInt(r.s, r.pos)
-    if r.s[r.pos] == ' ':
-      inc(r.pos)
-      key = idx.lastIdxKey + tmp
-      val = decodeVInt(r.s, r.pos) + idx.lastIdxVal
-    else:
-      key = idx.lastIdxKey + 1
-      val = tmp + idx.lastIdxVal
-    iiTablePut(idx.tab, key, val)
-    if not outf.isNil: outf.write(key, " ", val, "\n")
-    idx.lastIdxKey = key
-    idx.lastIdxVal = val
-    setId(key)                # ensure that this id will not be used
-    if r.s[r.pos] == '\x0A':
-      inc(r.pos)
-      inc(r.line)
-  if r.s[r.pos] == ')': inc(r.pos)
-
-proc cmdChangeTriggersRecompilation(old, new: TCommands): bool =
-  if old == new: return false
-  # we use a 'case' statement without 'else' so that addition of a
-  # new command forces us to consider it here :-)
-  case old
-  of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
-      cmdCompileToJS, cmdCompileToPHP, cmdCompileToLLVM:
-    if new in {cmdDoc, cmdCheck, cmdIdeTools, cmdPretty, cmdDef,
-               cmdInteractive}:
-      return false
-  of cmdNone, cmdDoc, cmdInterpret, cmdPretty, cmdGenDepend, cmdDump,
-      cmdCheck, cmdParse, cmdScan, cmdIdeTools, cmdDef,
-      cmdRst2html, cmdRst2tex, cmdInteractive, cmdRun:
-    discard
-  # else: trigger recompilation:
-  result = true
-
-proc processRodFile(r: PRodReader, hash: SecureHash) =
-  var
-    w: string
-    d: int
-  var inclHash: SecureHash
-  while r.s[r.pos] != '\0':
-    var section = rdWord(r)
-    if r.reason != rrNone:
-      break                   # no need to process this file further
-    case section
-    of "HASH":
-      inc(r.pos)              # skip ':'
-      if hash != parseSecureHash(decodeStr(r.s, r.pos)):
-        r.reason = rrHashChange
-    of "ID":
-      inc(r.pos)              # skip ':'
-      r.moduleID = decodeVInt(r.s, r.pos)
-      setId(r.moduleID)
-    of "ORIGFILE":
-      inc(r.pos)
-      r.origFile = decodeStr(r.s, r.pos)
-    of "OPTIONS":
-      inc(r.pos)              # skip ':'
-      r.options = cast[TOptions](int32(decodeVInt(r.s, r.pos)))
-      if options.gOptions != r.options: r.reason = rrOptions
-    of "GOPTIONS":
-      inc(r.pos)              # skip ':'
-      var dep = cast[TGlobalOptions](int32(decodeVInt(r.s, r.pos)))
-      if gGlobalOptions-harmlessOptions != dep-harmlessOptions:
-        r.reason = rrOptions
-    of "CMD":
-      inc(r.pos)              # skip ':'
-      var dep = cast[TCommands](int32(decodeVInt(r.s, r.pos)))
-      if cmdChangeTriggersRecompilation(dep, gCmd): r.reason = rrOptions
-    of "DEFINES":
-      inc(r.pos)              # skip ':'
-      d = 0
-      while r.s[r.pos] > '\x0A':
-        w = decodeStr(r.s, r.pos)
-        inc(d)
-        if not condsyms.isDefined(r.cache.getIdent(w)):
-          r.reason = rrDefines #MessageOut('not defined, but should: ' + w);
-        if r.s[r.pos] == ' ': inc(r.pos)
-      if d != countDefinedSymbols(): r.reason = rrDefines
-    of "FILES":
-      inc(r.pos, 2)           # skip "(\10"
-      inc(r.line)
-      while r.s[r.pos] != ')':
-        let finalPath = decodeStr(r.s, r.pos)
-        #let resolvedPath = relativePath.findModule(r.origFile)
-        #let finalPath = if resolvedPath.len > 0: resolvedPath else: relativePath
-        r.files.add(finalPath.fileInfoIdx)
-        inc(r.pos)            # skip #10
-        inc(r.line)
-      if r.s[r.pos] == ')': inc(r.pos)
-    of "INCLUDES":
-      inc(r.pos, 2)           # skip "(\10"
-      inc(r.line)
-      while r.s[r.pos] != ')':
-        w = r.files[decodeVInt(r.s, r.pos)].toFullPath
-        inc(r.pos)            # skip ' '
-        inclHash = parseSecureHash(decodeStr(r.s, r.pos))
-        if r.reason == rrNone:
-          if not existsFile(w) or (inclHash != secureHashFile(w)):
-            r.reason = rrInclDeps
-        if r.s[r.pos] == '\x0A':
-          inc(r.pos)
-          inc(r.line)
-      if r.s[r.pos] == ')': inc(r.pos)
-    of "DEPS":
-      inc(r.pos)              # skip ':'
-      while r.s[r.pos] > '\x0A':
-        r.modDeps.add(r.files[int32(decodeVInt(r.s, r.pos))])
-        if r.s[r.pos] == ' ': inc(r.pos)
-    of "INTERF":
-      r.interfIdx = r.pos + 2
-      skipSection(r)
-    of "COMPILERPROCS":
-      r.compilerProcsIdx = r.pos + 2
-      skipSection(r)
-    of "INDEX":
-      processIndex(r, r.index)
-    of "IMPORTS":
-      processIndex(r, r.imports)
-    of "CONVERTERS":
-      r.convertersIdx = r.pos + 1
-      skipSection(r)
-    of "METHODS":
-      r.methodsIdx = r.pos + 1
-      skipSection(r)
-    of "DATA":
-      r.dataIdx = r.pos + 2 # "(\10"
-      # We do not read the DATA section here! We read the needed objects on
-      # demand. And the DATA section comes last in the file, so we stop here:
-      break
-    of "INIT":
-      r.initIdx = r.pos + 2   # "(\10"
-      skipSection(r)
-    else:
-      internalError("invalid section: '" & section &
-                    "' at " & $r.line & " in " & r.filename)
-      #MsgWriteln("skipping section: " & section &
-      #           " at " & $r.line & " in " & r.filename)
-      skipSection(r)
-    if r.s[r.pos] == '\x0A':
-      inc(r.pos)
-      inc(r.line)
-
-
-proc startsWith(buf: cstring, token: string, pos = 0): bool =
-  var s = 0
-  while s < token.len and buf[pos+s] == token[s]: inc s
-  result = s == token.len
-
-proc newRodReader(modfilename: string, hash: SecureHash,
-                  readerIndex: int; cache: IdentCache): PRodReader =
-  new(result)
-  result.cache = cache
-  try:
-    result.memfile = memfiles.open(modfilename)
-  except OSError:
-    return nil
-  result.files = @[]
-  result.modDeps = @[]
-  result.methods = @[]
-  var r = result
-  r.reason = rrNone
-  r.pos = 0
-  r.line = 1
-  r.readerIndex = readerIndex
-  r.filename = modfilename
-  r.syms = initTable[int, PSym]()
-  # we terminate the file explicitly with ``\0``, so the cast to `cstring`
-  # is safe:
-  r.s = cast[cstring](r.memfile.mem)
-  if startsWith(r.s, "NIM:"):
-    initIiTable(r.index.tab)
-    initIiTable(r.imports.tab) # looks like a ROD file
-    inc(r.pos, 4)
-    var version = ""
-    while r.s[r.pos] notin {'\0', '\x0A'}:
-      add(version, r.s[r.pos])
-      inc(r.pos)
-    if r.s[r.pos] == '\x0A': inc(r.pos)
-    if version != RodFileVersion:
-      # since ROD files are only for caching, no backwards compatibility is
-      # needed
-      #echo "expected version ", version, " ", RodFileVersion
-      result.memfile.close
-      result = nil
-  else:
-    result.memfile.close
-    result = nil
-
-proc rrGetType(r: PRodReader, id: int, info: TLineInfo): PType =
-  result = PType(idTableGet(gTypeTable, id))
-  if result == nil:
-    # load the type:
-    var oldPos = r.pos
-    var d = iiTableGet(r.index.tab, id)
-    if d == InvalidKey: internalError(info, "rrGetType")
-    r.pos = d + r.dataIdx
-    result = decodeType(r, info)
-    r.pos = oldPos
-
-type
-  TFileModuleRec{.final.} = object
-    filename*: string
-    reason*: TReasonForRecompile
-    rd*: PRodReader
-    hash*: SecureHash
-    hashDone*: bool
-
-  TFileModuleMap = seq[TFileModuleRec]
-
-var gMods*: TFileModuleMap = @[]
-
-proc decodeSymSafePos(rd: PRodReader, offset: int, info: TLineInfo): PSym =
-  # all compiled modules
-  if rd.dataIdx == 0: internalError(info, "dataIdx == 0")
-  var oldPos = rd.pos
-  rd.pos = offset + rd.dataIdx
-  result = decodeSym(rd, info)
-  rd.pos = oldPos
-
-proc findSomeWhere(id: int) =
-  for i in countup(0, high(gMods)):
-    var rd = gMods[i].rd
-    if rd != nil:
-      var d = iiTableGet(rd.index.tab, id)
-      if d != InvalidKey:
-        echo "found id ", id, " in ", gMods[i].filename
-
-proc getReader(moduleId: int): PRodReader =
-  # we can't index 'gMods' here as it's indexed by a *file index* which is not
-  # the module ID! We could introduce a mapping ID->PRodReader but I'll leave
-  # this for later versions if benchmarking shows the linear search causes
-  # problems:
-  for i in 0 .. <gMods.len:
-    result = gMods[i].rd
-    if result != nil and result.moduleID == moduleId: return result
-  return nil
-
-proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym =
-  result = r.syms.getOrDefault(id)
-  if result == nil:
-    # load the symbol:
-    var d = iiTableGet(r.index.tab, id)
-    if d == InvalidKey:
-      # import from other module:
-      var moduleID = iiTableGet(r.imports.tab, id)
-      if moduleID < 0:
-        var x = ""
-        encodeVInt(id, x)
-        internalError(info, "missing from both indexes: +" & x)
-      var rd = getReader(moduleID)
-      d = iiTableGet(rd.index.tab, id)
-      if d != InvalidKey:
-        result = decodeSymSafePos(rd, d, info)
-      else:
-        var x = ""
-        encodeVInt(id, x)
-        when false: findSomeWhere(id)
-        internalError(info, "rrGetSym: no reader found: +" & x)
-    else:
-      # own symbol:
-      result = decodeSymSafePos(r, d, info)
-  if result != nil and result.kind == skStub: rawLoadStub(result)
-
-proc loadInitSection*(r: PRodReader): PNode =
-  if r.initIdx == 0 or r.dataIdx == 0: internalError("loadInitSection")
-  var oldPos = r.pos
-  r.pos = r.initIdx
-  result = newNode(nkStmtList)
-  while r.s[r.pos] > '\x0A' and r.s[r.pos] != ')':
-    var d = decodeVInt(r.s, r.pos)
-    inc(r.pos)                # #10
-    var p = r.pos
-    r.pos = d + r.dataIdx
-    addSon(result, decodeNode(r, unknownLineInfo()))
-    r.pos = p
-  r.pos = oldPos
-
-proc loadConverters(r: PRodReader) =
-  # We have to ensure that no exported converter is a stub anymore, and the
-  # import mechanism takes care of the rest.
-  if r.convertersIdx == 0 or r.dataIdx == 0:
-    internalError("importConverters")
-  r.pos = r.convertersIdx
-  while r.s[r.pos] > '\x0A':
-    var d = decodeVInt(r.s, r.pos)
-    discard rrGetSym(r, d, unknownLineInfo())
-    if r.s[r.pos] == ' ': inc(r.pos)
-
-proc loadMethods(r: PRodReader) =
-  if r.methodsIdx == 0 or r.dataIdx == 0:
-    internalError("loadMethods")
-  r.pos = r.methodsIdx
-  while r.s[r.pos] > '\x0A':
-    var d = decodeVInt(r.s, r.pos)
-    r.methods.add(rrGetSym(r, d, unknownLineInfo()))
-    if r.s[r.pos] == ' ': inc(r.pos)
-
-proc getHash*(fileIdx: int32): SecureHash =
-  internalAssert fileIdx >= 0 and fileIdx < gMods.len
-
-  if gMods[fileIdx].hashDone:
-    return gMods[fileIdx].hash
-
-  result = secureHashFile(fileIdx.toFullPath)
-  gMods[fileIdx].hash = result
-
-template growCache*(cache, pos) =
-  if cache.len <= pos: cache.setLen(pos+1)
-
-proc checkDep(fileIdx: int32; cache: IdentCache): TReasonForRecompile =
-  assert fileIdx != InvalidFileIDX
-  growCache gMods, fileIdx
-  if gMods[fileIdx].reason != rrEmpty:
-    # reason has already been computed for this module:
-    return gMods[fileIdx].reason
-  let filename = fileIdx.toFilename
-  var hash = getHash(fileIdx)
-  gMods[fileIdx].reason = rrNone  # we need to set it here to avoid cycles
-  result = rrNone
-  var rodfile = toGeneratedFile(filename.withPackageName, RodExt)
-  var r = newRodReader(rodfile, hash, fileIdx, cache)
-  if r == nil:
-    result = (if existsFile(rodfile): rrRodInvalid else: rrRodDoesNotExist)
-  else:
-    processRodFile(r, hash)
-    result = r.reason
-    if result == rrNone:
-      # check modules it depends on
-      # NOTE: we need to process the entire module graph so that no ID will
-      # be used twice! However, compilation speed does not suffer much from
-      # this, since results are cached.
-      var res = checkDep(systemFileIdx, cache)
-      if res != rrNone: result = rrModDeps
-      for i in countup(0, high(r.modDeps)):
-        res = checkDep(r.modDeps[i], cache)
-        if res != rrNone:
-          result = rrModDeps
-          # we cannot break here, because of side-effects of `checkDep`
-  if result != rrNone:
-    rawMessage(hintProcessing, reasonToFrmt[result] % filename)
-  if result != rrNone or optForceFullMake in gGlobalOptions:
-    # recompilation is necessary:
-    if r != nil: memfiles.close(r.memfile)
-    r = nil
-  gMods[fileIdx].rd = r
-  gMods[fileIdx].reason = result  # now we know better
-
-proc handleSymbolFile*(module: PSym; cache: IdentCache): PRodReader =
-  let fileIdx = module.fileIdx
-  if optSymbolFiles notin gGlobalOptions:
-    module.id = getID()
-    return nil
-  idgen.loadMaxIds(options.gProjectPath / options.gProjectName)
-
-  discard checkDep(fileIdx, cache)
-  if gMods[fileIdx].reason == rrEmpty: internalError("handleSymbolFile")
-  result = gMods[fileIdx].rd
-  if result != nil:
-    module.id = result.moduleID
-    result.syms[module.id] = module
-    processInterf(result, module)
-    processCompilerProcs(result, module)
-    loadConverters(result)
-    loadMethods(result)
-  else:
-    module.id = getID()
-
-proc rawLoadStub(s: PSym) =
-  if s.kind != skStub: internalError("loadStub")
-  var rd = gMods[s.position].rd
-  var theId = s.id                # used for later check
-  var d = iiTableGet(rd.index.tab, s.id)
-  if d == InvalidKey: internalError("loadStub: invalid key")
-  var rs = decodeSymSafePos(rd, d, unknownLineInfo())
-  if rs != s:
-    #echo "rs: ", toHex(cast[int](rs.position), int.sizeof * 2),
-    #     "\ns:  ", toHex(cast[int](s.position), int.sizeof * 2)
-    internalError(rs.info, "loadStub: wrong symbol")
-  elif rs.id != theId:
-    internalError(rs.info, "loadStub: wrong ID")
-  #MessageOut('loaded stub: ' + s.name.s);
-
-proc loadStub*(s: PSym) =
-  ## loads the stub symbol `s`.
-
-  # deactivate the GC here because we do a deep recursion and generate no
-  # garbage when restoring parts of the object graph anyway.
-  # Since we die with internal errors if this fails, no try-finally is
-  # necessary.
-  GC_disable()
-  rawLoadStub(s)
-  GC_enable()
-
-proc getBody*(s: PSym): PNode =
-  ## retrieves the AST's body of `s`. If `s` has been loaded from a rod-file
-  ## it may perform an expensive reload operation. Otherwise it's a simple
-  ## accessor.
-  assert s.kind in routineKinds
-  # prevent crashes due to incorrect macro transformations (bug #2377)
-  if s.ast.isNil or bodyPos >= s.ast.len: return ast.emptyNode
-  result = s.ast.sons[bodyPos]
-  if result == nil:
-    assert s.offset != 0
-    var r = gMods[s.position].rd
-    var oldPos = r.pos
-    r.pos = s.offset
-    result = decodeNode(r, s.info)
-    r.pos = oldPos
-    s.ast.sons[bodyPos] = result
-    s.offset = 0
-
-initIdTable(gTypeTable)
-initStrTable(rodCompilerprocs)
-
-# viewer:
-proc writeNode(f: File; n: PNode) =
-  f.write("(")
-  if n != nil:
-    f.write($n.kind)
-    if n.typ != nil:
-      f.write('^')
-      f.write(n.typ.id)
-    case n.kind
-    of nkCharLit..nkUInt64Lit:
-      if n.intVal != 0:
-        f.write('!')
-        f.write(n.intVal)
-    of nkFloatLit..nkFloat64Lit:
-      if n.floatVal != 0.0:
-        f.write('!')
-        f.write($n.floatVal)
-    of nkStrLit..nkTripleStrLit:
-      if n.strVal != "":
-        f.write('!')
-        f.write(n.strVal.escape)
-    of nkIdent:
-      f.write('!')
-      f.write(n.ident.s)
-    of nkSym:
-      f.write('!')
-      f.write(n.sym.id)
-    else:
-      for i in countup(0, sonsLen(n) - 1):
-        writeNode(f, n.sons[i])
-  f.write(")")
-
-proc writeSym(f: File; s: PSym) =
-  if s == nil:
-    f.write("{}\n")
-    return
-  f.write("{")
-  f.write($s.kind)
-  f.write('+')
-  f.write(s.id)
-  f.write('&')
-  f.write(s.name.s)
-  if s.typ != nil:
-    f.write('^')
-    f.write(s.typ.id)
-  if s.owner != nil:
-    f.write('*')
-    f.write(s.owner.id)
-  if s.flags != {}:
-    f.write('$')
-    f.write($s.flags)
-  if s.magic != mNone:
-    f.write('@')
-    f.write($s.magic)
-  if s.options != gOptions:
-    f.write('!')
-    f.write($s.options)
-  if s.position != 0:
-    f.write('%')
-    f.write($s.position)
-  if s.offset != -1:
-    f.write('`')
-    f.write($s.offset)
-  if s.constraint != nil:
-    f.write('#')
-    f.writeNode(s.constraint)
-  if s.ast != nil:
-    f.writeNode(s.ast)
-  f.write("}\n")
-
-proc writeType(f: File; t: PType) =
-  if t == nil:
-    f.write("[]\n")
-    return
-  f.write('[')
-  f.write($t.kind)
-  f.write('+')
-  f.write($t.id)
-  if t.n != nil:
-    f.writeNode(t.n)
-  if t.flags != {}:
-    f.write('$')
-    f.write($t.flags)
-  if t.callConv != low(t.callConv):
-    f.write('?')
-    f.write($t.callConv)
-  if t.owner != nil:
-    f.write('*')
-    f.write($t.owner.id)
-  if t.sym != nil:
-    f.write('&')
-    f.write(t.sym.id)
-  if t.size != -1:
-    f.write('/')
-    f.write($t.size)
-  if t.align != 2:
-    f.write('=')
-    f.write($t.align)
-  for i in countup(0, sonsLen(t) - 1):
-    if t.sons[i] == nil:
-      f.write("^()")
-    else:
-      f.write('^')
-      f.write($t.sons[i].id)
-  f.write("]\n")
-
-proc viewFile(rodfile: string) =
-  var r = newRodReader(rodfile, secureHash(""), 0, newIdentCache())
-  if r == nil:
-    rawMessage(errGenerated, "cannot open file (or maybe wrong version):" &
-       rodfile)
-    return
-  r.inViewMode = true
-  var outf = system.open(rodfile.changeFileExt(".rod.txt"), fmWrite)
-  while r.s[r.pos] != '\0':
-    let section = rdWord(r)
-    case section
-    of "HASH":
-      inc(r.pos)              # skip ':'
-      outf.writeLine("HASH:", $decodeVInt(r.s, r.pos))
-    of "ID":
-      inc(r.pos)              # skip ':'
-      r.moduleID = decodeVInt(r.s, r.pos)
-      setId(r.moduleID)
-      outf.writeLine("ID:", $r.moduleID)
-    of "ORIGFILE":
-      inc(r.pos)
-      r.origFile = decodeStr(r.s, r.pos)
-      outf.writeLine("ORIGFILE:", r.origFile)
-    of "OPTIONS":
-      inc(r.pos)              # skip ':'
-      r.options = cast[TOptions](int32(decodeVInt(r.s, r.pos)))
-      outf.writeLine("OPTIONS:", $r.options)
-    of "GOPTIONS":
-      inc(r.pos)              # skip ':'
-      let dep = cast[TGlobalOptions](int32(decodeVInt(r.s, r.pos)))
-      outf.writeLine("GOPTIONS:", $dep)
-    of "CMD":
-      inc(r.pos)              # skip ':'
-      let dep = cast[TCommands](int32(decodeVInt(r.s, r.pos)))
-      outf.writeLine("CMD:", $dep)
-    of "DEFINES":
-      inc(r.pos)              # skip ':'
-      var d = 0
-      outf.write("DEFINES:")
-      while r.s[r.pos] > '\x0A':
-        let w = decodeStr(r.s, r.pos)
-        inc(d)
-        outf.write(" ", w)
-        if r.s[r.pos] == ' ': inc(r.pos)
-      outf.write("\n")
-    of "FILES":
-      inc(r.pos, 2)           # skip "(\10"
-      inc(r.line)
-      outf.write("FILES(\n")
-      while r.s[r.pos] != ')':
-        let relativePath = decodeStr(r.s, r.pos)
-        let resolvedPath = relativePath.findModule(r.origFile)
-        let finalPath = if resolvedPath.len > 0: resolvedPath else: relativePath
-        r.files.add(finalPath.fileInfoIdx)
-        inc(r.pos)            # skip #10
-        inc(r.line)
-        outf.writeLine finalPath
-      if r.s[r.pos] == ')': inc(r.pos)
-      outf.write(")\n")
-    of "INCLUDES":
-      inc(r.pos, 2)           # skip "(\10"
-      inc(r.line)
-      outf.write("INCLUDES(\n")
-      while r.s[r.pos] != ')':
-        let w = r.files[decodeVInt(r.s, r.pos)]
-        inc(r.pos)            # skip ' '
-        let inclHash = decodeVInt(r.s, r.pos)
-        if r.s[r.pos] == '\x0A':
-          inc(r.pos)
-          inc(r.line)
-        outf.write(w, " ", inclHash, "\n")
-      if r.s[r.pos] == ')': inc(r.pos)
-      outf.write(")\n")
-    of "DEPS":
-      inc(r.pos)              # skip ':'
-      outf.write("DEPS:")
-      while r.s[r.pos] > '\x0A':
-        let v = int32(decodeVInt(r.s, r.pos))
-        r.modDeps.add(r.files[v])
-        if r.s[r.pos] == ' ': inc(r.pos)
-        outf.write(" ", r.files[v])
-      outf.write("\n")
-    of "INTERF",  "COMPILERPROCS":
-      inc r.pos, 2
-      if section == "INTERF": r.interfIdx = r.pos
-      else: r.compilerProcsIdx = r.pos
-      outf.write(section, "(\n")
-      while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'):
-        let w = decodeStr(r.s, r.pos)
-        inc(r.pos)
-        let key = decodeVInt(r.s, r.pos)
-        inc(r.pos)                # #10
-        outf.write(w, " ", key, "\n")
-      if r.s[r.pos] == ')': inc r.pos
-      outf.write(")\n")
-    of "INDEX":
-      outf.write(section, "(\n")
-      processIndex(r, r.index, outf)
-      outf.write(")\n")
-    of "IMPORTS":
-      outf.write(section, "(\n")
-      processIndex(r, r.imports, outf)
-      outf.write(")\n")
-    of "CONVERTERS",  "METHODS":
-      inc r.pos
-      if section == "METHODS": r.methodsIdx = r.pos
-      else: r.convertersIdx = r.pos
-      outf.write(section, ":")
-      while r.s[r.pos] > '\x0A':
-        let d = decodeVInt(r.s, r.pos)
-        outf.write(" ", $d)
-        if r.s[r.pos] == ' ': inc(r.pos)
-      outf.write("\n")
-    of "DATA":
-      inc(r.pos, 2)
-      r.dataIdx = r.pos
-      outf.write("DATA(\n")
-      while r.s[r.pos] != ')':
-        if r.s[r.pos] == '(':
-          outf.writeNode decodeNode(r, unknownLineInfo())
-          outf.write("\n")
-        elif r.s[r.pos] == '[':
-          outf.writeType decodeType(r, unknownLineInfo())
-        else:
-          outf.writeSym decodeSym(r, unknownLineInfo())
-        if r.s[r.pos] == '\x0A':
-          inc(r.pos)
-          inc(r.line)
-      if r.s[r.pos] == ')': inc r.pos
-      outf.write(")\n")
-    of "INIT":
-      outf.write("INIT(\n")
-      inc r.pos, 2
-      r.initIdx = r.pos
-      while r.s[r.pos] > '\x0A' and r.s[r.pos] != ')':
-        let d = decodeVInt(r.s, r.pos)
-        inc(r.pos)                # #10
-        #let p = r.pos
-        #r.pos = d + r.dataIdx
-        #outf.writeNode decodeNode(r, UnknownLineInfo())
-        #outf.write("\n")
-        #r.pos = p
-      if r.s[r.pos] == ')': inc r.pos
-      outf.write("<not supported by viewer>)\n")
-    else:
-      internalError("invalid section: '" & section &
-                    "' at " & $r.line & " in " & r.filename)
-      skipSection(r)
-    if r.s[r.pos] == '\x0A':
-      inc(r.pos)
-      inc(r.line)
-  outf.close
-
-when isMainModule:
-  viewFile(paramStr(1).addFileExt(rodExt))
diff --git a/compiler/rodutils.nim b/compiler/rodutils.nim
index d24e8f560..90431999a 100644
--- a/compiler/rodutils.nim
+++ b/compiler/rodutils.nim
@@ -8,22 +8,52 @@
 #
 
 ## Serialization utilities for the compiler.
-import strutils
-
-proc c_sprintf(buf, frmt: cstring) {.importc: "sprintf", header: "<stdio.h>", nodecl, varargs.}
-
-proc toStrMaxPrecision*(f: BiggestFloat): string =
-  if f != f:
+import strutils, math
+
+# bcc on windows doesn't have C99 functions
+when defined(windows) and defined(bcc):
+  {.emit: """#if defined(_MSC_VER) && _MSC_VER < 1900
+  #include <stdarg.h>
+  static int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) {
+    int count = -1;
+    if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
+    if (count == -1) count = _vscprintf(format, ap);
+    return count;
+  }
+  int snprintf(char *outBuf, size_t size, const char *format, ...) {
+    int count;
+    va_list ap;
+    va_start(ap, format);
+    count = c99_vsnprintf(outBuf, size, format, ap);
+    va_end(ap);
+    return count;
+  }
+  #endif
+  """.}
+
+proc c_snprintf(s: cstring; n:uint; frmt: cstring): cint {.importc: "snprintf", header: "<stdio.h>", nodecl, varargs.}
+
+proc toStrMaxPrecision*(f: BiggestFloat, literalPostfix = ""): string =
+  case classify(f)
+  of fcNaN:
     result = "NAN"
-  elif f == 0.0:
-    result = "0.0"
-  elif f == 0.5 * f:
-    if f > 0.0: result = "INF"
-    else: result = "-INF"
+  of fcNegZero:
+    result = "-0.0" & literalPostfix
+  of fcZero:
+    result = "0.0" & literalPostfix
+  of fcInf:
+    result = "INF"
+  of fcNegInf:
+    result = "-INF"
   else:
-    var buf: array[0..80, char]
-    c_sprintf(buf, "%#.16e", f)
-    result = $buf
+    when defined(nimNoArrayToCstringConversion):
+      result = newString(81)
+      let n = c_snprintf(result.cstring, result.len.uint, "%#.16e%s", f, literalPostfix.cstring)
+      setLen(result, n)
+    else:
+      var buf: array[0..80, char]
+      discard c_snprintf(buf.cstring, buf.len.uint, "%#.16e%s", f, literalPostfix.cstring)
+      result = $buf.cstring
 
 proc encodeStr*(s: string, result: var string) =
   for i in countup(0, len(s) - 1):
@@ -58,6 +88,8 @@ proc decodeStr*(s: cstring, pos: var int): string =
 const
   chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
+{.push overflowChecks: off.}
+
 # since negative numbers require a leading '-' they use up 1 byte. Thus we
 # subtract/add `vintDelta` here to save space for small negative numbers
 # which are common in ROD files:
@@ -122,6 +154,8 @@ proc decodeVInt*(s: cstring, pos: var int): int =
 proc decodeVBiggestInt*(s: cstring, pos: var int): BiggestInt =
   decodeIntImpl()
 
+{.pop.}
+
 iterator decodeVIntArray*(s: cstring): int =
   var i = 0
   while s[i] != '\0':
@@ -133,4 +167,3 @@ iterator decodeStrArray*(s: cstring): string =
   while s[i] != '\0':
     yield decodeStr(s, i)
     if s[i] == ' ': inc i
-
diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim
deleted file mode 100644
index d61d817dd..000000000
--- a/compiler/rodwrite.nim
+++ /dev/null
@@ -1,651 +0,0 @@
-#
-#
-#           The Nim Compiler
-#        (c) Copyright 2012 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-# This module is responsible for writing of rod files. Note that writing of
-# rod files is a pass, reading of rod files is not! This is why reading and
-# writing of rod files is split into two different modules.
-
-import
-  intsets, os, options, strutils, nversion, ast, astalgo, msgs, platform,
-  condsyms, ropes, idents, securehash, rodread, passes, importer, idgen,
-  rodutils
-
-from modulegraphs import ModuleGraph
-
-type
-  TRodWriter = object of TPassContext
-    module: PSym
-    hash: SecureHash
-    options: TOptions
-    defines: string
-    inclDeps: string
-    modDeps: string
-    interf: string
-    compilerProcs: string
-    index, imports: TIndex
-    converters, methods: string
-    init: string
-    data: string
-    sstack: TSymSeq          # a stack of symbols to process
-    tstack: TTypeSeq         # a stack of types to process
-    files: TStringSeq
-    origFile: string
-    cache: IdentCache
-
-  PRodWriter = ref TRodWriter
-
-proc getDefines(): string =
-  result = ""
-  for d in definedSymbolNames():
-    if result.len != 0: add(result, " ")
-    add(result, d)
-
-proc fileIdx(w: PRodWriter, filename: string): int =
-  for i in countup(0, high(w.files)):
-    if w.files[i] == filename:
-      return i
-  result = len(w.files)
-  setLen(w.files, result + 1)
-  w.files[result] = filename
-
-template filename*(w: PRodWriter): string =
-  w.module.filename
-
-proc newRodWriter(hash: SecureHash, module: PSym; cache: IdentCache): PRodWriter =
-  new(result)
-  result.sstack = @[]
-  result.tstack = @[]
-  initIiTable(result.index.tab)
-  initIiTable(result.imports.tab)
-  result.index.r = ""
-  result.imports.r = ""
-  result.hash = hash
-  result.module = module
-  result.defines = getDefines()
-  result.options = options.gOptions
-  result.files = @[]
-  result.inclDeps = ""
-  result.modDeps = ""
-  result.interf = newStringOfCap(2_000)
-  result.compilerProcs = ""
-  result.converters = ""
-  result.methods = ""
-  result.init = ""
-  result.origFile = module.info.toFullPath
-  result.data = newStringOfCap(12_000)
-  result.cache = cache
-
-proc addModDep(w: PRodWriter, dep: string; info: TLineInfo) =
-  if w.modDeps.len != 0: add(w.modDeps, ' ')
-  let resolved = dep.findModule(info.toFullPath)
-  encodeVInt(fileIdx(w, resolved), w.modDeps)
-
-const
-  rodNL = "\x0A"
-
-proc addInclDep(w: PRodWriter, dep: string; info: TLineInfo) =
-  let resolved = dep.findModule(info.toFullPath)
-  encodeVInt(fileIdx(w, resolved), w.inclDeps)
-  add(w.inclDeps, " ")
-  encodeStr($secureHashFile(resolved), w.inclDeps)
-  add(w.inclDeps, rodNL)
-
-proc pushType(w: PRodWriter, t: PType) =
-  # check so that the stack does not grow too large:
-  if iiTableGet(w.index.tab, t.id) == InvalidKey:
-    w.tstack.add(t)
-
-proc pushSym(w: PRodWriter, s: PSym) =
-  # check so that the stack does not grow too large:
-  if iiTableGet(w.index.tab, s.id) == InvalidKey:
-    when false:
-      if s.kind == skMethod:
-        echo "encoding ", s.id, " ", s.name.s
-        writeStackTrace()
-    w.sstack.add(s)
-
-proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode,
-                result: var string) =
-  if n == nil:
-    # nil nodes have to be stored too:
-    result.add("()")
-    return
-  result.add('(')
-  encodeVInt(ord(n.kind), result)
-  # we do not write comments for now
-  # Line information takes easily 20% or more of the filesize! Therefore we
-  # omit line information if it is the same as the father's line information:
-  if fInfo.fileIndex != n.info.fileIndex:
-    result.add('?')
-    encodeVInt(n.info.col, result)
-    result.add(',')
-    encodeVInt(n.info.line, result)
-    result.add(',')
-    encodeVInt(fileIdx(w, toFullPath(n.info)), result)
-  elif fInfo.line != n.info.line:
-    result.add('?')
-    encodeVInt(n.info.col, result)
-    result.add(',')
-    encodeVInt(n.info.line, result)
-  elif fInfo.col != n.info.col:
-    result.add('?')
-    encodeVInt(n.info.col, result)
-  # No need to output the file index, as this is the serialization of one
-  # file.
-  var f = n.flags * PersistentNodeFlags
-  if f != {}:
-    result.add('$')
-    encodeVInt(cast[int32](f), result)
-  if n.typ != nil:
-    result.add('^')
-    encodeVInt(n.typ.id, result)
-    pushType(w, n.typ)
-  case n.kind
-  of nkCharLit..nkUInt64Lit:
-    if n.intVal != 0:
-      result.add('!')
-      encodeVBiggestInt(n.intVal, result)
-  of nkFloatLit..nkFloat64Lit:
-    if n.floatVal != 0.0:
-      result.add('!')
-      encodeStr($n.floatVal, result)
-  of nkStrLit..nkTripleStrLit:
-    if n.strVal != "":
-      result.add('!')
-      encodeStr(n.strVal, result)
-  of nkIdent:
-    result.add('!')
-    encodeStr(n.ident.s, result)
-  of nkSym:
-    result.add('!')
-    encodeVInt(n.sym.id, result)
-    pushSym(w, n.sym)
-  else:
-    for i in countup(0, sonsLen(n) - 1):
-      encodeNode(w, n.info, n.sons[i], result)
-  add(result, ')')
-
-proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) =
-  var oldLen = result.len
-  result.add('<')
-  if loc.k != low(loc.k): encodeVInt(ord(loc.k), result)
-  if loc.s != low(loc.s):
-    add(result, '*')
-    encodeVInt(ord(loc.s), result)
-  if loc.flags != {}:
-    add(result, '$')
-    encodeVInt(cast[int32](loc.flags), result)
-  if loc.t != nil:
-    add(result, '^')
-    encodeVInt(cast[int32](loc.t.id), result)
-    pushType(w, loc.t)
-  if loc.r != nil:
-    add(result, '!')
-    encodeStr($loc.r, result)
-  if oldLen + 1 == result.len:
-    # no data was necessary, so remove the '<' again:
-    setLen(result, oldLen)
-  else:
-    add(result, '>')
-
-proc encodeType(w: PRodWriter, t: PType, result: var string) =
-  if t == nil:
-    # nil nodes have to be stored too:
-    result.add("[]")
-    return
-  # we need no surrounding [] here because the type is in a line of its own
-  if t.kind == tyForward: internalError("encodeType: tyForward")
-  # for the new rodfile viewer we use a preceding [ so that the data section
-  # can easily be disambiguated:
-  add(result, '[')
-  encodeVInt(ord(t.kind), result)
-  add(result, '+')
-  encodeVInt(t.id, result)
-  if t.n != nil:
-    encodeNode(w, unknownLineInfo(), t.n, result)
-  if t.flags != {}:
-    add(result, '$')
-    encodeVInt(cast[int32](t.flags), result)
-  if t.callConv != low(t.callConv):
-    add(result, '?')
-    encodeVInt(ord(t.callConv), result)
-  if t.owner != nil:
-    add(result, '*')
-    encodeVInt(t.owner.id, result)
-    pushSym(w, t.owner)
-  if t.sym != nil:
-    add(result, '&')
-    encodeVInt(t.sym.id, result)
-    pushSym(w, t.sym)
-  if t.size != - 1:
-    add(result, '/')
-    encodeVBiggestInt(t.size, result)
-  if t.align != 2:
-    add(result, '=')
-    encodeVInt(t.align, result)
-  if t.lockLevel.ord != UnspecifiedLockLevel.ord:
-    add(result, '\14')
-    encodeVInt(t.lockLevel.int16, result)
-  if t.destructor != nil and t.destructor.id != 0:
-    add(result, '\15')
-    encodeVInt(t.destructor.id, result)
-    pushSym(w, t.destructor)
-  if t.deepCopy != nil:
-    add(result, '\16')
-    encodeVInt(t.deepcopy.id, result)
-    pushSym(w, t.deepcopy)
-  if t.assignment != nil:
-    add(result, '\17')
-    encodeVInt(t.assignment.id, result)
-    pushSym(w, t.assignment)
-  for i, s in items(t.methods):
-    add(result, '\18')
-    encodeVInt(i, result)
-    add(result, '\19')
-    encodeVInt(s.id, result)
-    pushSym(w, s)
-  encodeLoc(w, t.loc, result)
-  for i in countup(0, sonsLen(t) - 1):
-    if t.sons[i] == nil:
-      add(result, "^()")
-    else:
-      add(result, '^')
-      encodeVInt(t.sons[i].id, result)
-      pushType(w, t.sons[i])
-
-proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) =
-  add(result, '|')
-  encodeVInt(ord(lib.kind), result)
-  add(result, '|')
-  encodeStr($lib.name, result)
-  add(result, '|')
-  encodeNode(w, info, lib.path, result)
-
-proc encodeInstantiations(w: PRodWriter; s: seq[PInstantiation];
-                          result: var string) =
-  for t in s:
-    result.add('\15')
-    encodeVInt(t.sym.id, result)
-    pushSym(w, t.sym)
-    for tt in t.concreteTypes:
-      result.add('\17')
-      encodeVInt(tt.id, result)
-      pushType(w, tt)
-    result.add('\20')
-    encodeVInt(t.compilesId, result)
-
-proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
-  if s == nil:
-    # nil nodes have to be stored too:
-    result.add("{}")
-    return
-  # we need no surrounding {} here because the symbol is in a line of its own
-  encodeVInt(ord(s.kind), result)
-  result.add('+')
-  encodeVInt(s.id, result)
-  result.add('&')
-  encodeStr(s.name.s, result)
-  if s.typ != nil:
-    result.add('^')
-    encodeVInt(s.typ.id, result)
-    pushType(w, s.typ)
-  result.add('?')
-  if s.info.col != -1'i16: encodeVInt(s.info.col, result)
-  result.add(',')
-  if s.info.line != -1'i16: encodeVInt(s.info.line, result)
-  result.add(',')
-  encodeVInt(fileIdx(w, toFullPath(s.info)), result)
-  if s.owner != nil:
-    result.add('*')
-    encodeVInt(s.owner.id, result)
-    pushSym(w, s.owner)
-  if s.flags != {}:
-    result.add('$')
-    encodeVInt(cast[int32](s.flags), result)
-  if s.magic != mNone:
-    result.add('@')
-    encodeVInt(ord(s.magic), result)
-  if s.options != w.options:
-    result.add('!')
-    encodeVInt(cast[int32](s.options), result)
-  if s.position != 0:
-    result.add('%')
-    encodeVInt(s.position, result)
-  if s.offset != - 1:
-    result.add('`')
-    encodeVInt(s.offset, result)
-  encodeLoc(w, s.loc, result)
-  if s.annex != nil: encodeLib(w, s.annex, s.info, result)
-  if s.constraint != nil:
-    add(result, '#')
-    encodeNode(w, unknownLineInfo(), s.constraint, result)
-  case s.kind
-  of skType, skGenericParam:
-    for t in s.typeInstCache:
-      result.add('\14')
-      encodeVInt(t.id, result)
-      pushType(w, t)
-  of routineKinds:
-    encodeInstantiations(w, s.procInstCache, result)
-    if s.gcUnsafetyReason != nil:
-      result.add('\16')
-      encodeVInt(s.gcUnsafetyReason.id, result)
-      pushSym(w, s.gcUnsafetyReason)
-  of skModule, skPackage:
-    encodeInstantiations(w, s.usedGenerics, result)
-    # we don't serialize:
-    #tab*: TStrTable         # interface table for modules
-  of skLet, skVar, skField, skForVar:
-    if s.guard != nil:
-      result.add('\18')
-      encodeVInt(s.guard.id, result)
-      pushSym(w, s.guard)
-    if s.bitsize != 0:
-      result.add('\19')
-      encodeVInt(s.bitsize, result)
-  else: discard
-  # lazy loading will soon reload the ast lazily, so the ast needs to be
-  # the last entry of a symbol:
-  if s.ast != nil:
-    # we used to attempt to save space here by only storing a dummy AST if
-    # it is not necessary, but Nim's heavy compile-time evaluation features
-    # make that unfeasible nowadays:
-    encodeNode(w, s.info, s.ast, result)
-
-proc addToIndex(w: var TIndex, key, val: int) =
-  if key - w.lastIdxKey == 1:
-    # we do not store a key-diff of 1 to safe space
-    encodeVInt(val - w.lastIdxVal, w.r)
-  else:
-    encodeVInt(key - w.lastIdxKey, w.r)
-    add(w.r, ' ')
-    encodeVInt(val - w.lastIdxVal, w.r)
-  add(w.r, rodNL)
-  w.lastIdxKey = key
-  w.lastIdxVal = val
-  iiTablePut(w.tab, key, val)
-
-const debugWrittenIds = false
-
-when debugWrittenIds:
-  var debugWritten = initIntSet()
-
-proc symStack(w: PRodWriter): int =
-  var i = 0
-  while i < len(w.sstack):
-    var s = w.sstack[i]
-    if sfForward in s.flags:
-      w.sstack[result] = s
-      inc result
-    elif iiTableGet(w.index.tab, s.id) == InvalidKey:
-      var m = getModule(s)
-      if m == nil and s.kind != skPackage and sfGenSym notin s.flags:
-        internalError("symStack: module nil: " & s.name.s)
-      if s.kind == skPackage or {sfFromGeneric, sfGenSym} * s.flags != {} or m.id == w.module.id:
-        # put definition in here
-        var L = w.data.len
-        addToIndex(w.index, s.id, L)
-        when debugWrittenIds: incl(debugWritten, s.id)
-        encodeSym(w, s, w.data)
-        add(w.data, rodNL)
-        # put into interface section if appropriate:
-        if {sfExported, sfFromGeneric} * s.flags == {sfExported} and
-            s.kind in ExportableSymKinds:
-          encodeStr(s.name.s, w.interf)
-          add(w.interf, ' ')
-          encodeVInt(s.id, w.interf)
-          add(w.interf, rodNL)
-        if sfCompilerProc in s.flags:
-          encodeStr(s.name.s, w.compilerProcs)
-          add(w.compilerProcs, ' ')
-          encodeVInt(s.id, w.compilerProcs)
-          add(w.compilerProcs, rodNL)
-        if s.kind == skConverter or hasPattern(s):
-          if w.converters.len != 0: add(w.converters, ' ')
-          encodeVInt(s.id, w.converters)
-        if s.kind == skMethod and sfDispatcher notin s.flags:
-          if w.methods.len != 0: add(w.methods, ' ')
-          encodeVInt(s.id, w.methods)
-      elif iiTableGet(w.imports.tab, s.id) == InvalidKey:
-        addToIndex(w.imports, s.id, m.id)
-        when debugWrittenIds:
-          if not contains(debugWritten, s.id):
-            echo(w.filename)
-            debug(s)
-            debug(s.owner)
-            debug(m)
-            internalError("Symbol referred to but never written")
-    inc(i)
-  setLen(w.sstack, result)
-
-proc typeStack(w: PRodWriter): int =
-  var i = 0
-  while i < len(w.tstack):
-    var t = w.tstack[i]
-    if t.kind == tyForward:
-      w.tstack[result] = t
-      inc result
-    elif iiTableGet(w.index.tab, t.id) == InvalidKey:
-      var L = w.data.len
-      addToIndex(w.index, t.id, L)
-      encodeType(w, t, w.data)
-      add(w.data, rodNL)
-    inc(i)
-  setLen(w.tstack, result)
-
-proc processStacks(w: PRodWriter, finalPass: bool) =
-  var oldS = 0
-  var oldT = 0
-  while true:
-    var slen = symStack(w)
-    var tlen = typeStack(w)
-    if slen == oldS and tlen == oldT: break
-    oldS = slen
-    oldT = tlen
-  if finalPass and (oldS != 0 or oldT != 0):
-    internalError("could not serialize some forwarded symbols/types")
-
-proc rawAddInterfaceSym(w: PRodWriter, s: PSym) =
-  pushSym(w, s)
-  processStacks(w, false)
-
-proc addInterfaceSym(w: PRodWriter, s: PSym) =
-  if w == nil: return
-  if s.kind in ExportableSymKinds and
-      {sfExported, sfCompilerProc} * s.flags != {}:
-    rawAddInterfaceSym(w, s)
-
-proc addStmt(w: PRodWriter, n: PNode) =
-  encodeVInt(w.data.len, w.init)
-  add(w.init, rodNL)
-  encodeNode(w, unknownLineInfo(), n, w.data)
-  add(w.data, rodNL)
-  processStacks(w, false)
-
-proc writeRod(w: PRodWriter) =
-  processStacks(w, true)
-  var f: File
-  if not open(f, completeGeneratedFilePath(changeFileExt(
-                      w.filename.withPackageName, RodExt)),
-              fmWrite):
-    #echo "couldn't write rod file for: ", w.filename
-    return
-  # write header:
-  f.write("NIM:")
-  f.write(RodFileVersion)
-  f.write(rodNL)
-  var id = "ID:"
-  encodeVInt(w.module.id, id)
-  f.write(id)
-  f.write(rodNL)
-
-  var orig = "ORIGFILE:"
-  encodeStr(w.origFile, orig)
-  f.write(orig)
-  f.write(rodNL)
-
-  var hash = "HASH:"
-  encodeStr($w.hash, hash)
-  f.write(hash)
-  f.write(rodNL)
-
-  var options = "OPTIONS:"
-  encodeVInt(cast[int32](w.options), options)
-  f.write(options)
-  f.write(rodNL)
-
-  var goptions = "GOPTIONS:"
-  encodeVInt(cast[int32](gGlobalOptions), goptions)
-  f.write(goptions)
-  f.write(rodNL)
-
-  var cmd = "CMD:"
-  encodeVInt(cast[int32](gCmd), cmd)
-  f.write(cmd)
-  f.write(rodNL)
-
-  f.write("DEFINES:")
-  f.write(w.defines)
-  f.write(rodNL)
-
-  var files = "FILES(" & rodNL
-  for i in countup(0, high(w.files)):
-    encodeStr(w.files[i], files)
-    files.add(rodNL)
-  f.write(files)
-  f.write(')' & rodNL)
-
-  f.write("INCLUDES(" & rodNL)
-  f.write(w.inclDeps)
-  f.write(')' & rodNL)
-
-  f.write("DEPS:")
-  f.write(w.modDeps)
-  f.write(rodNL)
-
-  f.write("INTERF(" & rodNL)
-  f.write(w.interf)
-  f.write(')' & rodNL)
-
-  f.write("COMPILERPROCS(" & rodNL)
-  f.write(w.compilerProcs)
-  f.write(')' & rodNL)
-
-  f.write("INDEX(" & rodNL)
-  f.write(w.index.r)
-  f.write(')' & rodNL)
-
-  f.write("IMPORTS(" & rodNL)
-  f.write(w.imports.r)
-  f.write(')' & rodNL)
-
-  f.write("CONVERTERS:")
-  f.write(w.converters)
-  f.write(rodNL)
-
-  f.write("METHODS:")
-  f.write(w.methods)
-  f.write(rodNL)
-
-  f.write("INIT(" & rodNL)
-  f.write(w.init)
-  f.write(')' & rodNL)
-
-  f.write("DATA(" & rodNL)
-  f.write(w.data)
-  f.write(')' & rodNL)
-  # write trailing zero which is necessary because we use memory mapped files
-  # for reading:
-  f.write("\0")
-  f.close()
-
-  #echo "interf: ", w.interf.len
-  #echo "index:  ", w.index.r.len
-  #echo "init:   ", w.init.len
-  #echo "data:   ", w.data.len
-
-proc process(c: PPassContext, n: PNode): PNode =
-  result = n
-  if c == nil: return
-  var w = PRodWriter(c)
-  case n.kind
-  of nkStmtList:
-    for i in countup(0, sonsLen(n) - 1): discard process(c, n.sons[i])
-    #var s = n.sons[namePos].sym
-    #addInterfaceSym(w, s)
-  of nkProcDef, nkIteratorDef, nkConverterDef,
-      nkTemplateDef, nkMacroDef:
-    let s = n.sons[namePos].sym
-    if s == nil: internalError(n.info, "rodwrite.process")
-    if n.sons[bodyPos] == nil:
-      internalError(n.info, "rodwrite.process: body is nil")
-    if n.sons[bodyPos].kind != nkEmpty or s.magic != mNone or
-        sfForward notin s.flags:
-      addInterfaceSym(w, s)
-  of nkMethodDef:
-    let s = n.sons[namePos].sym
-    if s == nil: internalError(n.info, "rodwrite.process")
-    if n.sons[bodyPos] == nil:
-      internalError(n.info, "rodwrite.process: body is nil")
-    if n.sons[bodyPos].kind != nkEmpty or s.magic != mNone or
-        sfForward notin s.flags:
-      pushSym(w, s)
-      processStacks(w, false)
-
-  of nkVarSection, nkLetSection, nkConstSection:
-    for i in countup(0, sonsLen(n) - 1):
-      var a = n.sons[i]
-      if a.kind == nkCommentStmt: continue
-      addInterfaceSym(w, a.sons[0].sym)
-  of nkTypeSection:
-    for i in countup(0, sonsLen(n) - 1):
-      var a = n.sons[i]
-      if a.kind == nkCommentStmt: continue
-      if a.sons[0].kind != nkSym: internalError(a.info, "rodwrite.process")
-      var s = a.sons[0].sym
-      addInterfaceSym(w, s)
-      # this takes care of enum fields too
-      # Note: The check for ``s.typ.kind = tyEnum`` is wrong for enum
-      # type aliasing! Otherwise the same enum symbol would be included
-      # several times!
-      #
-      #        if (a.sons[2] <> nil) and (a.sons[2].kind = nkEnumTy) then begin
-      #          a := s.typ.n;
-      #          for j := 0 to sonsLen(a)-1 do
-      #            addInterfaceSym(w, a.sons[j].sym);
-      #        end
-  of nkImportStmt:
-    for i in countup(0, sonsLen(n) - 1):
-      addModDep(w, getModuleName(n.sons[i]), n.info)
-    addStmt(w, n)
-  of nkFromStmt, nkImportExceptStmt:
-    addModDep(w, getModuleName(n.sons[0]), n.info)
-    addStmt(w, n)
-  of nkIncludeStmt:
-    for i in countup(0, sonsLen(n) - 1):
-      addInclDep(w, getModuleName(n.sons[i]), n.info)
-  of nkPragma:
-    addStmt(w, n)
-  else:
-    discard
-
-proc myOpen(g: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
-  if module.id < 0: internalError("rodwrite: module ID not set")
-  var w = newRodWriter(module.fileIdx.getHash, module, cache)
-  rawAddInterfaceSym(w, module)
-  result = w
-
-proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode =
-  result = process(c, n)
-  var w = PRodWriter(c)
-  writeRod(w)
-  idgen.saveMaxIds(options.gProjectPath / options.gProjectName)
-
-const rodwritePass* = makePass(open = myOpen, close = myClose, process = process)
-
diff --git a/compiler/ropes.nim b/compiler/ropes.nim
index d84b59f78..0d6d7d78f 100644
--- a/compiler/ropes.nim
+++ b/compiler/ropes.nim
@@ -56,7 +56,9 @@
 #  To cache them they are inserted in a `cache` array.
 
 import
-  platform, hashes
+  hashes
+
+from pathutils import AbsoluteFile
 
 type
   FormatStr* = string  # later we may change it to CString for better
@@ -66,43 +68,22 @@ type
   Rope* = ref RopeObj
   RopeObj*{.acyclic.} = object of RootObj # the empty rope is represented
                                           # by nil to safe space
-    left*, right*: Rope
-    length*: int
-    data*: string             # != nil if a leaf
-
-  RopeSeq* = seq[Rope]
-
-  RopesError* = enum
-    rCannotOpenFile
-    rInvalidFormatStr
-
-# implementation
-
-var errorHandler*: proc(err: RopesError, msg: string, useWarning = false)
-  # avoid dependency on msgs.nim
+    left, right: Rope
+    L: int                    # <= 0 if a leaf
+    data*: string
 
 proc len*(a: Rope): int =
   ## the rope's length
   if a == nil: result = 0
-  else: result = a.length
-
-proc newRope(data: string = nil): Rope =
-  new(result)
-  if data != nil:
-    result.length = len(data)
-    result.data = data
+  else: result = abs a.L
 
-proc newMutableRope*(capacity = 30): Rope =
-  ## creates a new rope that supports direct modifications of the rope's
-  ## 'data' and 'length' fields.
+proc newRope(data: string = ""): Rope =
   new(result)
-  result.data = newStringOfCap(capacity)
-
-proc freezeMutableRope*(r: Rope) {.inline.} =
-  r.length = r.data.len
+  result.L = -len(data)
+  result.data = data
 
 var
-  cache: array[0..2048*2 - 1, Rope]
+  cache: array[0..2048*2 - 1, Rope] # XXX Global here!
 
 proc resetRopeCache* =
   for i in low(cache)..high(cache):
@@ -158,7 +139,7 @@ proc `&`*(a, b: Rope): Rope =
     result = a
   else:
     result = newRope()
-    result.length = a.length + b.length
+    result.L = abs(a.L) + abs(b.L)
     result.left = a
     result.right = b
 
@@ -188,11 +169,11 @@ iterator leaves*(r: Rope): string =
     var stack = @[r]
     while stack.len > 0:
       var it = stack.pop
-      while isNil(it.data):
+      while it.left != nil:
+        assert it.right != nil
         stack.add(it.right)
         it = it.left
         assert(it != nil)
-      assert(it.data != nil)
       yield it.data
 
 iterator items*(r: Rope): char =
@@ -204,13 +185,14 @@ proc writeRope*(f: File, r: Rope) =
   ## writes a rope to a file.
   for s in leaves(r): write(f, s)
 
-proc writeRope*(head: Rope, filename: string, useWarning = false) =
+proc writeRope*(head: Rope, filename: AbsoluteFile): bool =
   var f: File
-  if open(f, filename, fmWrite):
+  if open(f, filename.string, fmWrite):
     if head != nil: writeRope(f, head)
     close(f)
+    result = true
   else:
-    errorHandler(rCannotOpenFile, filename, useWarning)
+    result = false
 
 proc `$`*(r: Rope): string =
   ## converts a rope back to a string.
@@ -225,10 +207,6 @@ proc ropeConcat*(a: varargs[Rope]): Rope =
 proc prepend*(a: var Rope, b: Rope) = a = b & a
 proc prepend*(a: var Rope, b: string) = a = b & a
 
-var
-  rnl* = tnl.newRope
-  softRnl* = tnl.newRope
-
 proc `%`*(frmt: FormatStr, args: openArray[Rope]): Rope =
   var i = 0
   var length = len(frmt)
@@ -250,10 +228,10 @@ proc `%`*(frmt: FormatStr, args: openArray[Rope]): Rope =
         while true:
           j = j * 10 + ord(frmt[i]) - ord('0')
           inc(i)
-          if frmt[i] notin {'0'..'9'}: break
+          if i >= frmt.len or frmt[i] notin {'0'..'9'}: break
         num = j
         if j > high(args) + 1:
-          errorHandler(rInvalidFormatStr, $(j))
+          doAssert false, "invalid format string: " & frmt
         else:
           add(result, args[j-1])
       of '{':
@@ -264,20 +242,21 @@ proc `%`*(frmt: FormatStr, args: openArray[Rope]): Rope =
           inc(i)
         num = j
         if frmt[i] == '}': inc(i)
-        else: errorHandler(rInvalidFormatStr, $(frmt[i]))
+        else:
+          doAssert false, "invalid format string: " & frmt
 
         if j > high(args) + 1:
-          errorHandler(rInvalidFormatStr, $(j))
+          doAssert false, "invalid format string: " & frmt
         else:
           add(result, args[j-1])
       of 'n':
-        add(result, softRnl)
+        add(result, "\n")
         inc(i)
       of 'N':
-        add(result, rnl)
+        add(result, "\n")
         inc(i)
       else:
-        errorHandler(rInvalidFormatStr, $(frmt[i]))
+        doAssert false, "invalid format string: " & frmt
     var start = i
     while i < length:
       if frmt[i] != '$': inc(i)
@@ -337,19 +316,18 @@ proc equalsFile*(r: Rope, f: File): bool =
   result = readBuffer(f, addr(buf[0]), 1) == 0 and
       btotal == rtotal # check that we've read all
 
-proc equalsFile*(r: Rope, filename: string): bool =
+proc equalsFile*(r: Rope, filename: AbsoluteFile): bool =
   ## returns true if the contents of the file `f` equal `r`. If `f` does not
   ## exist, false is returned.
   var f: File
-  result = open(f, filename)
+  result = open(f, filename.string)
   if result:
     result = equalsFile(r, f)
     close(f)
 
-proc writeRopeIfNotEqual*(r: Rope, filename: string): bool =
+proc writeRopeIfNotEqual*(r: Rope, filename: AbsoluteFile): bool =
   # returns true if overwritten
   if not equalsFile(r, filename):
-    writeRope(r, filename)
-    result = true
+    result = writeRope(r, filename)
   else:
     result = false
diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim
index 0c31eadbe..bfff86479 100644
--- a/compiler/scriptconfig.nim
+++ b/compiler/scriptconfig.nim
@@ -13,7 +13,7 @@
 import
   ast, modules, idents, passes, passaux, condsyms,
   options, nimconf, sem, semdata, llstream, vm, vmdef, commands, msgs,
-  os, times, osproc, wordrecg, strtabs, modulegraphs
+  os, times, osproc, wordrecg, strtabs, modulegraphs, lineinfos, pathutils
 
 # we support 'cmpIgnoreStyle' natively for efficiency:
 from strutils import cmpIgnoreStyle, contains
@@ -26,11 +26,12 @@ proc listDirs(a: VmArgs, filter: set[PathComponent]) =
   setResult(a, result)
 
 proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
-              config: ConfigRef = nil): PEvalContext =
+              graph: ModuleGraph): PEvalContext =
   # For Nimble we need to export 'setupVM'.
-  result = newCtx(module, cache)
+  result = newCtx(module, cache, graph)
   result.mode = emRepl
   registerAdditionalOps(result)
+  let conf = graph.config
 
   # captured vars:
   var errorMsg: string
@@ -44,7 +45,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
   template cbos(name, body) {.dirty.} =
     result.registerCallback "stdlib.system." & astToStr(name),
       proc (a: VmArgs) =
-        errorMsg = nil
+        errorMsg = ""
         try:
           body
         except OSError:
@@ -70,33 +71,47 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
     setResult(a, os.getCurrentDir())
   cbos moveFile:
     os.moveFile(getString(a, 0), getString(a, 1))
+  cbos moveDir:
+    os.moveDir(getString(a, 0), getString(a, 1))
   cbos copyFile:
     os.copyFile(getString(a, 0), getString(a, 1))
+  cbos copyDir:
+    os.copyDir(getString(a, 0), getString(a, 1))
   cbos getLastModificationTime:
-    setResult(a, toSeconds(getLastModificationTime(getString(a, 0))))
+    setResult(a, getLastModificationTime(getString(a, 0)).toUnix)
+  cbos findExe:
+    setResult(a, os.findExe(getString(a, 0)))
 
   cbos rawExec:
     setResult(a, osproc.execCmd getString(a, 0))
 
   cbconf getEnv:
-    setResult(a, os.getEnv(a.getString 0))
+    setResult(a, os.getEnv(a.getString 0, a.getString 1))
   cbconf existsEnv:
     setResult(a, os.existsEnv(a.getString 0))
+  cbconf putEnv:
+    os.putEnv(a.getString 0, a.getString 1)
   cbconf dirExists:
     setResult(a, os.dirExists(a.getString 0))
   cbconf fileExists:
     setResult(a, os.fileExists(a.getString 0))
 
+  cbconf projectName:
+    setResult(a, conf.projectName)
+  cbconf projectDir:
+    setResult(a, conf.projectPath.string)
+  cbconf projectPath:
+    setResult(a, conf.projectFull.string)
   cbconf thisDir:
     setResult(a, vthisDir)
   cbconf put:
-    options.setConfigVar(getString(a, 0), getString(a, 1))
+    options.setConfigVar(conf, getString(a, 0), getString(a, 1))
   cbconf get:
-    setResult(a, options.getConfigVar(a.getString 0))
+    setResult(a, options.getConfigVar(conf, a.getString 0))
   cbconf exists:
-    setResult(a, options.existsConfigVar(a.getString 0))
+    setResult(a, options.existsConfigVar(conf, a.getString 0))
   cbconf nimcacheDir:
-    setResult(a, options.getNimcacheDir())
+    setResult(a, options.getNimcacheDir(conf).string)
   cbconf paramStr:
     setResult(a, os.paramStr(int a.getInt 0))
   cbconf paramCount:
@@ -106,66 +121,69 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
   cbconf cmpIgnoreCase:
     setResult(a, strutils.cmpIgnoreCase(a.getString 0, a.getString 1))
   cbconf setCommand:
-    options.command = a.getString 0
+    conf.command = a.getString 0
     let arg = a.getString 1
+    incl(conf.globalOptions, optWasNimscript)
     if arg.len > 0:
-      gProjectName = arg
+      conf.projectName = arg
       let path =
-        if gProjectName.isAbsolute: gProjectName
-        else: gProjectPath / gProjectName
+        if conf.projectName.isAbsolute: AbsoluteFile(conf.projectName)
+        else: conf.projectPath / RelativeFile(conf.projectName)
       try:
-        gProjectFull = canonicalizePath(path)
+        conf.projectFull = canonicalizePath(conf, path)
       except OSError:
-        gProjectFull = path
+        conf.projectFull = path
   cbconf getCommand:
-    setResult(a, options.command)
+    setResult(a, conf.command)
   cbconf switch:
-    processSwitch(a.getString 0, a.getString 1, passPP, module.info)
+    processSwitch(a.getString 0, a.getString 1, passPP, module.info, conf)
   cbconf hintImpl:
     processSpecificNote(a.getString 0, wHint, passPP, module.info,
-      a.getString 1)
+      a.getString 1, conf)
   cbconf warningImpl:
     processSpecificNote(a.getString 0, wWarning, passPP, module.info,
-      a.getString 1)
+      a.getString 1, conf)
   cbconf patchFile:
     let key = a.getString(0) & "_" & a.getString(1)
     var val = a.getString(2).addFileExt(NimExt)
     if {'$', '~'} in val:
-      val = pathSubs(val, vthisDir)
+      val = pathSubs(conf, val, vthisDir)
     elif not isAbsolute(val):
       val = vthisDir / val
-    gModuleOverrides[key] = val
+    conf.moduleOverrides[key] = val
   cbconf selfExe:
     setResult(a, os.getAppFilename())
   cbconf cppDefine:
-    if config != nil:
-      options.cppDefine(config, a.getString(0))
+    options.cppDefine(conf, a.getString(0))
 
-proc runNimScript*(cache: IdentCache; scriptName: string;
-                   freshDefines=true; config: ConfigRef=nil) =
-  passes.gIncludeFile = includeModule
-  passes.gImportModule = importModule
-  let graph = newModuleGraph(config)
-  if freshDefines: initDefines()
+proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
+                   freshDefines=true; conf: ConfigRef) =
+  rawMessage(conf, hintConf, scriptName.string)
+  let oldSymbolFiles = conf.symbolFiles
+  conf.symbolFiles = disabledSf
 
-  defineSymbol("nimscript")
-  defineSymbol("nimconfig")
-  registerPass(semPass)
-  registerPass(evalPass)
+  let graph = newModuleGraph(cache, conf)
+  connectCallbacks(graph)
+  if freshDefines: initDefines(conf.symbols)
 
-  searchPaths.add(options.libpath)
+  defineSymbol(conf.symbols, "nimscript")
+  defineSymbol(conf.symbols, "nimconfig")
+  registerPass(graph, semPass)
+  registerPass(graph, evalPass)
+
+  conf.searchPaths.add(conf.libpath)
 
   var m = graph.makeModule(scriptName)
   incl(m.flags, sfMainModule)
-  vm.globalCtx = setupVM(m, cache, scriptName, config)
+  graph.vm = setupVM(m, cache, scriptName.string, graph)
 
-  graph.compileSystemModule(cache)
-  discard graph.processModule(m, llStreamOpen(scriptName, fmRead), nil, cache)
+  graph.compileSystemModule() # TODO: see why this unsets hintConf in conf.notes
+  discard graph.processModule(m, llStreamOpen(scriptName, fmRead))
 
   # ensure we load 'system.nim' again for the real non-config stuff!
-  resetSystemArtifacts()
-  vm.globalCtx = nil
+  resetSystemArtifacts(graph)
   # do not remove the defined symbols
   #initDefines()
-  undefSymbol("nimscript")
-  undefSymbol("nimconfig")
+  undefSymbol(conf.symbols, "nimscript")
+  undefSymbol(conf.symbols, "nimconfig")
+  conf.symbolFiles = oldSymbolFiles
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 6ad77e3fb..8332af346 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -12,16 +12,19 @@
 import
   ast, strutils, hashes, options, lexer, astalgo, trees, treetab,
   wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math,
-  magicsys, parser, nversion, nimsets, semfold, importer,
-  procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch,
+  magicsys, parser, nversion, nimsets, semfold, modulepaths, importer,
+  procfind, lookups, pragmas, passes, semdata, semtypinst, sigmatch,
   intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting,
-  evaltempl, patterns, parampatterns, sempass2, nimfix.pretty, semmacrosanity,
-  semparallel, lowerings, pluginsupport, plugins.active
+  evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity,
+  lowerings, pluginsupport, plugins/active, rod, lineinfos
 
-from modulegraphs import ModuleGraph
+from modulegraphs import ModuleGraph, PPassContext, onUse, onDef, onDefResolveForward
 
 when defined(nimfix):
-  import nimfix.prettybase
+  import nimfix/prettybase
+
+when not defined(leanCompiler):
+  import semparallel
 
 # implementation
 
@@ -33,11 +36,12 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
 proc semProcBody(c: PContext, n: PNode): PNode
 
 proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode
-proc changeType(n: PNode, newType: PType, check: bool)
+proc changeType(c: PContext; n: PNode, newType: PType, check: bool)
 
 proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode
 proc semTypeNode(c: PContext, n: PNode, prev: PType): PType
-proc semStmt(c: PContext, n: PNode): PNode
+proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode
+proc semOpAux(c: PContext, n: PNode)
 proc semParamList(c: PContext, n, genericParams: PNode, s: PSym)
 proc addParams(c: PContext, n: PNode, kind: TSymKind)
 proc maybeAddResult(c: PContext, s: PSym, n: PNode)
@@ -45,17 +49,20 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
 proc activate(c: PContext, n: PNode)
 proc semQuoteAst(c: PContext, n: PNode): PNode
 proc finishMethod(c: PContext, s: PSym)
-
+proc evalAtCompileTime(c: PContext, n: PNode): PNode
 proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode
-
+proc semStaticExpr(c: PContext, n: PNode): PNode
+proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType
+proc semTypeOf(c: PContext; n: PNode): PNode
+proc hasUnresolvedArgs(c: PContext, n: PNode): bool
 proc isArrayConstr(n: PNode): bool {.inline.} =
   result = n.kind == nkBracket and
     n.typ.skipTypes(abstractInst).kind == tyArray
 
-template semIdeForTemplateOrGenericCheck(n, requiresCheck) =
+template semIdeForTemplateOrGenericCheck(conf, n, requiresCheck) =
   # we check quickly if the node is where the cursor is
   when defined(nimsuggest):
-    if n.info.fileIndex == gTrackPos.fileIndex and n.info.line == gTrackPos.line:
+    if n.info.fileIndex == conf.m.trackPos.fileIndex and n.info.line == conf.m.trackPos.line:
       requiresCheck = true
 
 template semIdeForTemplateOrGeneric(c: PContext; n: PNode;
@@ -64,46 +71,50 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode;
   # templates perform some quick check whether the cursor is actually in
   # the generic or template.
   when defined(nimsuggest):
-    if gCmd == cmdIdeTools and requiresCheck:
+    if c.config.cmd == cmdIdeTools and requiresCheck:
       #if optIdeDebug in gGlobalOptions:
       #  echo "passing to safeSemExpr: ", renderTree(n)
       discard safeSemExpr(c, n)
 
+proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode =
+  result = arg
+  let x = result.skipConv
+  if x.kind in {nkPar, nkTupleConstr, nkCurly} and formal.kind != tyExpr:
+    changeType(c, x, formal, check=true)
+  else:
+    result = skipHiddenSubConv(result)
+    #result.typ = takeType(formal, arg.typ)
+    #echo arg.info, " picked ", result.typ.typeToString
+
 proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
   if arg.typ.isNil:
-    localError(arg.info, errExprXHasNoType,
+    localError(c.config, arg.info, "expression has no type: " &
                renderTree(arg, {renderNoComments}))
     # error correction:
-    result = copyNode(arg)
+    result = copyTree(arg)
     result.typ = formal
   else:
     result = indexTypesMatch(c, formal, arg.typ, arg)
     if result == nil:
-      typeMismatch(info, formal, arg.typ)
+      typeMismatch(c.config, info, formal, arg.typ)
       # error correction:
       result = copyTree(arg)
       result.typ = formal
     else:
-      let x = result.skipConv
-      if x.kind == nkPar and formal.kind != tyExpr:
-        changeType(x, formal, check=true)
-      else:
-        result = skipHiddenSubConv(result)
-        #result.typ = takeType(formal, arg.typ)
-        #echo arg.info, " picked ", result.typ.typeToString
+      result = fitNodePostMatch(c, formal, result)
 
 proc inferWithMetatype(c: PContext, formal: PType,
                        arg: PNode, coerceDistincts = false): PNode
 
-var commonTypeBegin = PType(kind: tyExpr)
+template commonTypeBegin*(): PType = PType(kind: tyExpr)
 
 proc commonType*(x, y: PType): PType =
   # new type relation that is used for array constructors,
   # if expressions, etc.:
   if x == nil: return x
   if y == nil: return y
-  var a = skipTypes(x, {tyGenericInst, tyAlias})
-  var b = skipTypes(y, {tyGenericInst, tyAlias})
+  var a = skipTypes(x, {tyGenericInst, tyAlias, tySink})
+  var b = skipTypes(y, {tyGenericInst, tyAlias, tySink})
   result = x
   if a.kind in {tyExpr, tyNil}: result = y
   elif b.kind in {tyExpr, tyNil}: result = x
@@ -111,7 +122,7 @@ proc commonType*(x, y: PType): PType =
   elif b.kind == tyStmt: result = b
   elif a.kind == tyTypeDesc:
     # turn any concrete typedesc into the abstract typedesc type
-    if a.sons == nil: result = a
+    if a.len == 0: result = a
     else:
       result = newType(tyTypeDesc, a.owner)
       rawAddSon(result, newType(tyNone, a.owner))
@@ -122,7 +133,7 @@ proc commonType*(x, y: PType): PType =
     if a.sons[idx].kind == tyEmpty: return y
   elif a.kind == tyTuple and b.kind == tyTuple and a.len == b.len:
     var nt: PType
-    for i in 0.. <a.len:
+    for i in 0..<a.len:
       let aEmpty = isEmptyContainer(a.sons[i])
       let bEmpty = isEmptyContainer(b.sons[i])
       if aEmpty != bEmpty:
@@ -153,20 +164,36 @@ proc commonType*(x, y: PType): PType =
     if a.kind in {tyRef, tyPtr}:
       k = a.kind
       if b.kind != a.kind: return x
-      a = a.lastSon
-      b = b.lastSon
+      # bug #7601, array construction of ptr generic
+      a = a.lastSon.skipTypes({tyGenericInst})
+      b = b.lastSon.skipTypes({tyGenericInst})
     if a.kind == tyObject and b.kind == tyObject:
       result = commonSuperclass(a, b)
       # this will trigger an error later:
       if result.isNil or result == a: return x
       if result == b: return y
-      if k != tyNone:
+      # bug #7906, tyRef/tyPtr + tyGenericInst of ref/ptr object ->
+      # ill-formed AST, no need for additional tyRef/tyPtr
+      if k != tyNone and x.kind != tyGenericInst:
         let r = result
         result = newType(k, r.owner)
         result.addSonSkipIntLit(r)
 
+proc endsInNoReturn(n: PNode): bool =
+  # check if expr ends in raise exception or call of noreturn proc
+  var it = n
+  while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0:
+    it = it.lastSon
+  result = it.kind == nkRaiseStmt or
+    it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags
+
+proc commonType*(x: PType, y: PNode): PType =
+  # ignore exception raising branches in case/if expressions
+  if endsInNoReturn(y): return x
+  commonType(x, y.typ)
+
 proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym =
-  result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info)
+  result = newSym(kind, considerQuotedIdent(c, n), getCurrOwner(c), n.info)
   when defined(nimsuggest):
     suggestDecl(c, n, result)
 
@@ -177,20 +204,21 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym =
   if n.kind == nkSym:
     # and sfGenSym in n.sym.flags:
     result = n.sym
-    if result.kind != kind:
-      localError(n.info, "cannot use symbol of kind '" &
+    if result.kind notin {kind, skTemp}:
+      localError(c.config, n.info, "cannot use symbol of kind '" &
                  $result.kind & "' as a '" & $kind & "'")
-    if sfGenSym in result.flags and result.kind notin {skTemplate, skMacro, skParam}:
-      # declarative context, so produce a fresh gensym:
-      result = copySym(result)
-      result.ast = n.sym.ast
-      put(c.p, n.sym, result)
+    when false:
+      if sfGenSym in result.flags and result.kind notin {skTemplate, skMacro, skParam}:
+        # declarative context, so produce a fresh gensym:
+        result = copySym(result)
+        result.ast = n.sym.ast
+        put(c.p, n.sym, result)
     # when there is a nested proc inside a template, semtmpl
     # will assign a wrong owner during the first pass over the
     # template; we must fix it here: see #909
     result.owner = getCurrOwner(c)
   else:
-    result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info)
+    result = newSym(kind, considerQuotedIdent(c, n), getCurrOwner(c), n.info)
   #if kind in {skForVar, skLet, skVar} and result.owner.kind == skModule:
   #  incl(result.flags, sfGlobal)
   when defined(nimsuggest):
@@ -202,33 +230,37 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
 proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode,
                         allowed: TSymFlags): PSym
 
-proc typeAllowedCheck(info: TLineInfo; typ: PType; kind: TSymKind) =
-  let t = typeAllowed(typ, kind)
+proc typeAllowedCheck(conf: ConfigRef; info: TLineInfo; typ: PType; kind: TSymKind;
+                      flags: TTypeAllowedFlags = {}) =
+  let t = typeAllowed(typ, kind, flags)
   if t != nil:
-    if t == typ: localError(info, "invalid type: '" & typeToString(typ) & "'")
-    else: localError(info, "invalid type: '" & typeToString(t) &
-                           "' in this context: '" & typeToString(typ) & "'")
+    if t == typ:
+      localError(conf, info, "invalid type: '" & typeToString(typ) &
+        "' for " & substr($kind, 2).toLowerAscii)
+    else:
+      localError(conf, info, "invalid type: '" & typeToString(t) &
+        "' in this context: '" & typeToString(typ) &
+        "' for " & substr($kind, 2).toLowerAscii)
 
 proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} =
-  typeAllowedCheck(typ.n.info, typ, skProc)
+  typeAllowedCheck(c.config, typ.n.info, typ, skProc)
 
 proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym
 proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode
 proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
-proc isOpImpl(c: PContext, n: PNode): PNode
 proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
                      flags: TExprFlags = {}): PNode
 proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
                   flags: TExprFlags = {}): PNode
 
-proc symFromType(t: PType, info: TLineInfo): PSym =
+proc symFromType(c: PContext; t: PType, info: TLineInfo): PSym =
   if t.sym != nil: return t.sym
-  result = newSym(skType, getIdent"AnonType", t.owner, info)
+  result = newSym(skType, getIdent(c.cache, "AnonType"), t.owner, info)
   result.flags.incl sfAnon
   result.typ = t
 
 proc symNodeFromType(c: PContext, t: PType, info: TLineInfo): PNode =
-  result = newSymNode(symFromType(t, info), info)
+  result = newSymNode(symFromType(c, t, info), info)
   result.typ = makeTypeDesc(c, t)
 
 when false:
@@ -264,10 +296,10 @@ proc fixupTypeAfterEval(c: PContext, evaluated, eOrig: PNode): PNode =
       result = evaluated
       let expectedType = eOrig.typ.skipTypes({tyStatic})
       if hasCycle(result):
-        globalError(eOrig.info, "the resulting AST is cyclic and cannot be processed further")
+        globalError(c.config, eOrig.info, "the resulting AST is cyclic and cannot be processed further")
         result = errorNode(c, eOrig)
       else:
-        semmacrosanity.annotateType(result, expectedType)
+        semmacrosanity.annotateType(result, expectedType, c.config)
   else:
     result = semExprWithType(c, evaluated)
     #result = fitNode(c, e.typ, result) inlined with special case:
@@ -284,18 +316,18 @@ proc tryConstExpr(c: PContext, n: PNode): PNode =
   var e = semExprWithType(c, n)
   if e == nil: return
 
-  result = getConstExpr(c.module, e)
+  result = getConstExpr(c.module, e, c.graph)
   if result != nil: return
 
-  let oldErrorCount = msgs.gErrorCounter
-  let oldErrorMax = msgs.gErrorMax
-  let oldErrorOutputs = errorOutputs
+  let oldErrorCount = c.config.errorCounter
+  let oldErrorMax = c.config.errorMax
+  let oldErrorOutputs = c.config.m.errorOutputs
 
-  errorOutputs = {}
-  msgs.gErrorMax = high(int)
+  c.config.m.errorOutputs = {}
+  c.config.errorMax = high(int)
 
   try:
-    result = evalConstExpr(c.module, c.cache, e)
+    result = evalConstExpr(c.module, c.graph, e)
     if result == nil or result.kind == nkEmpty:
       result = nil
     else:
@@ -304,31 +336,48 @@ proc tryConstExpr(c: PContext, n: PNode): PNode =
   except ERecoverableError:
     result = nil
 
-  msgs.gErrorCounter = oldErrorCount
-  msgs.gErrorMax = oldErrorMax
-  errorOutputs = oldErrorOutputs
+  c.config.errorCounter = oldErrorCount
+  c.config.errorMax = oldErrorMax
+  c.config.m.errorOutputs = oldErrorOutputs
+
+const
+  errConstExprExpected = "constant expression expected"
 
 proc semConstExpr(c: PContext, n: PNode): PNode =
   var e = semExprWithType(c, n)
   if e == nil:
-    localError(n.info, errConstExprExpected)
+    localError(c.config, n.info, errConstExprExpected)
     return n
-  result = getConstExpr(c.module, e)
+  result = getConstExpr(c.module, e, c.graph)
   if result == nil:
     #if e.kind == nkEmpty: globalError(n.info, errConstExprExpected)
-    result = evalConstExpr(c.module, c.cache, e)
+    result = evalConstExpr(c.module, c.graph, e)
     if result == nil or result.kind == nkEmpty:
       if e.info != n.info:
-        pushInfoContext(n.info)
-        localError(e.info, errConstExprExpected)
-        popInfoContext()
+        pushInfoContext(c.config, n.info)
+        localError(c.config, e.info, errConstExprExpected)
+        popInfoContext(c.config)
       else:
-        localError(e.info, errConstExprExpected)
+        localError(c.config, e.info, errConstExprExpected)
       # error correction:
       result = e
     else:
       result = fixupTypeAfterEval(c, result, e)
 
+proc semExprFlagDispatched(c: PContext, n: PNode, flags: TExprFlags): PNode =
+  if efNeedStatic in flags:
+    if efPreferNilResult in flags:
+      return tryConstExpr(c, n)
+    else:
+      return semConstExpr(c, n)
+  else:
+    result = semExprWithType(c, n, flags)
+    if efPreferStatic in flags:
+      var evaluated = getConstExpr(c.module, result, c.graph)
+      if evaluated != nil: return evaluated
+      evaluated = evalAtCompileTime(c, result)
+      if evaluated != nil: return evaluated
+
 include hlo, seminst, semcall
 
 when false:
@@ -338,23 +387,23 @@ when false:
     for i in 0 ..< n.safeLen:
       resetSemFlag(n[i])
 
-proc semAfterMacroCall(c: PContext, n: PNode, s: PSym,
-                       flags: TExprFlags): PNode =
+proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
+                       s: PSym, flags: TExprFlags): PNode =
   ## Semantically check the output of a macro.
   ## This involves processes such as re-checking the macro output for type
   ## coherence, making sure that variables declared with 'let' aren't
   ## reassigned, and binding the unbound identifiers that the macro output
   ## contains.
-  inc(evalTemplateCounter)
-  if evalTemplateCounter > 100:
-    globalError(s.info, errTemplateInstantiationTooNested)
+  inc(c.config.evalTemplateCounter)
+  if c.config.evalTemplateCounter > evalTemplateLimit:
+    globalError(c.config, s.info, "template instantiation too nested")
   c.friendModules.add(s.owner.getModule)
 
-  result = n
-  excl(n.flags, nfSem)
+  result = macroResult
+  excl(result.flags, nfSem)
   #resetSemFlag n
   if s.typ.sons[0] == nil:
-    result = semStmt(c, result)
+    result = semStmt(c, result, flags)
   else:
     case s.typ.sons[0].kind
     of tyExpr:
@@ -363,75 +412,97 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym,
       # semExprWithType(c, result)
       result = semExpr(c, result, flags)
     of tyStmt:
-      result = semStmt(c, result)
+      result = semStmt(c, result, flags)
     of tyTypeDesc:
-      if n.kind == nkStmtList: result.kind = nkStmtListType
+      if result.kind == nkStmtList: result.kind = nkStmtListType
       var typ = semTypeNode(c, result, nil)
-      result.typ = makeTypeDesc(c, typ)
+      if typ == nil:
+        localError(c.config, result.info, "expression has no type: " &
+                   renderTree(result, {renderNoComments}))
+        result = newSymNode(errorSym(c, result))
+      else:
+        result.typ = makeTypeDesc(c, typ)
       #result = symNodeFromType(c, typ, n.info)
     else:
+      var retType = s.typ.sons[0]
+      if s.ast[genericParamsPos] != nil and retType.isMetaType:
+        # The return type may depend on the Macro arguments
+        # e.g. template foo(T: typedesc): seq[T]
+        # We will instantiate the return type here, because
+        # we now know the supplied arguments
+        var paramTypes = newIdTable()
+        for param, value in genericParamsInMacroCall(s, call):
+          idTablePut(paramTypes, param.typ, value.typ)
+
+        retType = generateTypeInstance(c, paramTypes,
+                                       macroResult.info, retType)
+
       result = semExpr(c, result, flags)
-      result = fitNode(c, s.typ.sons[0], result, result.info)
-      #GlobalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0]))
-  dec(evalTemplateCounter)
+      result = fitNode(c, retType, result, result.info)
+      #globalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0]))
+  dec(c.config.evalTemplateCounter)
   discard c.friendModules.pop()
 
+const
+  errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters"
+
 proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
                   flags: TExprFlags = {}): PNode =
-  pushInfoContext(nOrig.info)
+  pushInfoContext(c.config, nOrig.info, sym.detailedInfo)
 
-  markUsed(n.info, sym, c.graph.usageSym)
-  styleCheckUse(n.info, sym)
+  markUsed(c.config, n.info, sym, c.graph.usageSym)
+  onUse(n.info, sym)
   if sym == c.p.owner:
-    globalError(n.info, errRecursiveDependencyX, sym.name.s)
+    globalError(c.config, n.info, "recursive dependency: '$1'" % sym.name.s)
 
   let genericParams = if sfImmediate in sym.flags: 0
                       else: sym.ast[genericParamsPos].len
   let suppliedParams = max(n.safeLen - 1, 0)
 
   if suppliedParams < genericParams:
-    globalError(n.info, errMissingGenericParamsForTemplate, n.renderTree)
+    globalError(c.config, n.info, errMissingGenericParamsForTemplate % n.renderTree)
 
   #if c.evalContext == nil:
   #  c.evalContext = c.createEvalContext(emStatic)
-  result = evalMacroCall(c.module, c.cache, n, nOrig, sym)
+  result = evalMacroCall(c.module, c.graph, n, nOrig, sym)
   if efNoSemCheck notin flags:
-    result = semAfterMacroCall(c, result, sym, flags)
-  result = wrapInComesFrom(nOrig.info, result)
-  popInfoContext()
+    result = semAfterMacroCall(c, n, result, sym, flags)
+  result = wrapInComesFrom(nOrig.info, sym, result)
+  popInfoContext(c.config)
 
 proc forceBool(c: PContext, n: PNode): PNode =
-  result = fitNode(c, getSysType(tyBool), n, n.info)
+  result = fitNode(c, getSysType(c.graph, n.info, tyBool), n, n.info)
   if result == nil: result = n
 
 proc semConstBoolExpr(c: PContext, n: PNode): PNode =
   let nn = semExprWithType(c, n)
-  result = fitNode(c, getSysType(tyBool), nn, nn.info)
+  result = fitNode(c, getSysType(c.graph, n.info, tyBool), nn, nn.info)
   if result == nil:
-    localError(n.info, errConstExprExpected)
+    localError(c.config, n.info, errConstExprExpected)
     return nn
-  result = getConstExpr(c.module, result)
+  result = getConstExpr(c.module, result, c.graph)
   if result == nil:
-    localError(n.info, errConstExprExpected)
+    localError(c.config, n.info, errConstExprExpected)
     result = nn
 
 proc semGenericStmt(c: PContext, n: PNode): PNode
+proc semConceptBody(c: PContext, n: PNode): PNode
 
 include semtypes, semtempl, semgnrc, semstmts, semexprs
 
 proc addCodeForGenerics(c: PContext, n: PNode) =
   for i in countup(c.lastGenericIdx, c.generics.len - 1):
     var prc = c.generics[i].inst.sym
-    if prc.kind in {skProc, skMethod, skConverter} and prc.magic == mNone:
+    if prc.kind in {skProc, skFunc, skMethod, skConverter} and prc.magic == mNone:
       if prc.ast == nil or prc.ast.sons[bodyPos] == nil:
-        internalError(prc.info, "no code for " & prc.name.s)
+        internalError(c.config, prc.info, "no code for " & prc.name.s)
       else:
         addSon(n, prc.ast)
   c.lastGenericIdx = c.generics.len
 
-proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
-  var c = newContext(graph, module, cache)
-  if c.p != nil: internalError(module.info, "sem.myOpen")
+proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
+  var c = newContext(graph, module)
+  if c.p != nil: internalError(graph.config, module.info, "sem.myOpen")
   c.semConstExpr = semConstExpr
   c.semExpr = semExpr
   c.semTryExpr = tryExpr
@@ -449,42 +520,48 @@ proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
   c.importTable = openScope(c)
   c.importTable.addSym(module) # a module knows itself
   if sfSystemModule in module.flags:
-    magicsys.systemModule = module # set global variable!
+    graph.systemModule = module
   c.topLevelScope = openScope(c)
   # don't be verbose unless the module belongs to the main package:
-  if module.owner.id == gMainPackageId:
-    gNotes = gMainPackageNotes
+  if module.owner.id == graph.config.mainPackageId:
+    graph.config.notes = graph.config.mainPackageNotes
   else:
-    if gMainPackageNotes == {}: gMainPackageNotes = gNotes
-    gNotes = ForeignPackageNotes
+    if graph.config.mainPackageNotes == {}: graph.config.mainPackageNotes = graph.config.notes
+    graph.config.notes = graph.config.foreignPackageNotes
   result = c
 
-proc myOpenCached(graph: ModuleGraph; module: PSym; rd: PRodReader): PPassContext =
-  result = myOpen(graph, module, rd.cache)
-  for m in items(rd.methods): methodDef(graph, m, true)
-
-proc isImportSystemStmt(n: PNode): bool =
-  if magicsys.systemModule == nil: return false
+proc isImportSystemStmt(g: ModuleGraph; n: PNode): bool =
+  if g.systemModule == nil: return false
   case n.kind
   of nkImportStmt:
     for x in n:
-      let f = checkModuleName(x, false)
-      if f == magicsys.systemModule.info.fileIndex:
-        return true
+      if x.kind == nkIdent:
+        let f = checkModuleName(g.config, x, false)
+        if f == g.systemModule.info.fileIndex:
+          return true
   of nkImportExceptStmt, nkFromStmt:
-    let f = checkModuleName(n[0], false)
-    if f == magicsys.systemModule.info.fileIndex:
-      return true
+    if n[0].kind == nkIdent:
+      let f = checkModuleName(g.config, n[0], false)
+      if f == g.systemModule.info.fileIndex:
+        return true
   else: discard
 
+proc isEmptyTree(n: PNode): bool =
+  case n.kind
+  of nkStmtList:
+    for it in n:
+      if not isEmptyTree(it): return false
+    result = true
+  of nkEmpty, nkCommentStmt: result = true
+  else: result = false
+
 proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
   if n.kind == nkDefer:
-    localError(n.info, "defer statement not supported at top level")
-  if c.topStmts == 0 and not isImportSystemStmt(n):
-    if sfSystemModule notin c.module.flags and
-        n.kind notin {nkEmpty, nkCommentStmt}:
-      c.importTable.addSym magicsys.systemModule # import the "System" identifier
-      importAllSymbols(c, magicsys.systemModule)
+    localError(c.config, n.info, "defer statement not supported at top level")
+  if c.topStmts == 0 and not isImportSystemStmt(c.graph, n):
+    if sfSystemModule notin c.module.flags and not isEmptyTree(n):
+      c.importTable.addSym c.graph.systemModule # import the "System" identifier
+      importAllSymbols(c, c.graph.systemModule)
       inc c.topStmts
   else:
     inc c.topStmts
@@ -492,21 +569,25 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
     result = semAllTypeSections(c, n)
   else:
     result = n
-  result = semStmt(c, result)
-  # BUGFIX: process newly generated generics here, not at the end!
-  if c.lastGenericIdx < c.generics.len:
-    var a = newNodeI(nkStmtList, n.info)
-    addCodeForGenerics(c, a)
-    if sonsLen(a) > 0:
-      # a generic has been added to `a`:
-      if result.kind != nkEmpty: addSon(a, result)
-      result = a
+  result = semStmt(c, result, {})
+  when false:
+    # Code generators are lazy now and can deal with undeclared procs, so these
+    # steps are not required anymore and actually harmful for the upcoming
+    # destructor support.
+    # BUGFIX: process newly generated generics here, not at the end!
+    if c.lastGenericIdx < c.generics.len:
+      var a = newNodeI(nkStmtList, n.info)
+      addCodeForGenerics(c, a)
+      if sonsLen(a) > 0:
+        # a generic has been added to `a`:
+        if result.kind != nkEmpty: addSon(a, result)
+        result = a
   result = hloStmt(c, result)
-  if gCmd == cmdInteractive and not isEmptyType(result.typ):
+  if c.config.cmd == cmdInteractive and not isEmptyType(result.typ):
     result = buildEchoStmt(c, result)
-  if gCmd == cmdIdeTools:
+  if c.config.cmd == cmdIdeTools:
     appendToModule(c.module, result)
-  result = transformStmt(c.module, result)
+  trackTopLevelStmt(c.graph, c.module, result)
 
 proc recoverContext(c: PContext) =
   # clean up in case of a semantic error: We clean up the stacks, etc. This is
@@ -519,37 +600,40 @@ proc recoverContext(c: PContext) =
 proc myProcess(context: PPassContext, n: PNode): PNode =
   var c = PContext(context)
   # no need for an expensive 'try' if we stop after the first error anyway:
-  if msgs.gErrorMax <= 1:
+  if c.config.errorMax <= 1:
     result = semStmtAndGenerateGenerics(c, n)
   else:
-    let oldContextLen = msgs.getInfoContextLen()
+    let oldContextLen = msgs.getInfoContextLen(c.config)
     let oldInGenericInst = c.inGenericInst
     try:
       result = semStmtAndGenerateGenerics(c, n)
     except ERecoverableError, ESuggestDone:
       recoverContext(c)
       c.inGenericInst = oldInGenericInst
-      msgs.setInfoContextLen(oldContextLen)
+      msgs.setInfoContextLen(c.config, oldContextLen)
       if getCurrentException() of ESuggestDone:
         c.suggestionsMade = true
         result = nil
       else:
-        result = ast.emptyNode
-      #if gCmd == cmdIdeTools: findSuggest(c, n)
+        result = newNodeI(nkEmpty, n.info)
+      #if c.config.cmd == cmdIdeTools: findSuggest(c, n)
+  rod.storeNode(c.graph, c.module, result)
 
 proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode =
   var c = PContext(context)
-  if gCmd == cmdIdeTools and not c.suggestionsMade:
+  if c.config.cmd == cmdIdeTools and not c.suggestionsMade:
     suggestSentinel(c)
   closeScope(c)         # close module's scope
   rawCloseScope(c)      # imported symbols; don't check for unused ones!
   result = newNode(nkStmtList)
   if n != nil:
-    internalError(n.info, "n is not nil") #result := n;
+    internalError(c.config, n.info, "n is not nil") #result := n;
   addCodeForGenerics(c, result)
   if c.module.ast != nil:
     result.add(c.module.ast)
   popOwner(c)
   popProcCon(c)
+  storeRemaining(c.graph, c.module)
 
-const semPass* = makePass(myOpen, myOpenCached, myProcess, myClose)
+const semPass* = makePass(myOpen, myProcess, myClose,
+                          isFrontend = true)
diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim
index 70765d087..5d676dc76 100644
--- a/compiler/semasgn.nim
+++ b/compiler/semasgn.nim
@@ -7,8 +7,8 @@
 #    distribution, for details about the copyright.
 #
 
-## This module implements lifting for assignments. Later versions of this code
-## will be able to also lift ``=deepCopy`` and ``=destroy``.
+## This module implements lifting for type-bound operations
+## (``=sink``, ``=``, ``=destroy``, ``=deepCopy``).
 
 # included from sem.nim
 
@@ -22,7 +22,8 @@ type
     recurse: bool
 
 proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode)
-proc liftBody(c: PContext; typ: PType; info: TLineInfo): PSym
+proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp;
+              info: TLineInfo): PSym {.discardable.}
 
 proc at(a, i: PNode, elemType: PType): PNode =
   result = newNodeI(nkBracketExpr, a.info, 2)
@@ -31,8 +32,8 @@ proc at(a, i: PNode, elemType: PType): PNode =
   result.typ = elemType
 
 proc liftBodyTup(c: var TLiftCtx; t: PType; body, x, y: PNode) =
-  for i in 0 .. <t.len:
-    let lit = lowerings.newIntLit(i)
+  for i in 0 ..< t.len:
+    let lit = lowerings.newIntLit(c.c.graph, x.info, i)
     liftBodyAux(c, t.sons[i], body, x.at(lit, t.sons[i]), y.at(lit, t.sons[i]))
 
 proc dotField(x: PNode, f: PSym): PNode =
@@ -57,7 +58,7 @@ proc liftBodyObj(c: var TLiftCtx; n, body, x, y: PNode) =
     var access = dotField(x, n[0].sym)
     caseStmt.add(access)
     # copy the branches over, but replace the fields with the for loop body:
-    for i in 1 .. <n.len:
+    for i in 1 ..< n.len:
       var branch = copyTree(n[i])
       let L = branch.len
       branch.sons[L-1] = newNodeI(nkStmtList, c.info)
@@ -65,23 +66,23 @@ proc liftBodyObj(c: var TLiftCtx; n, body, x, y: PNode) =
       liftBodyObj(c, n[i].lastSon, branch.sons[L-1], x, y)
       caseStmt.add(branch)
     body.add(caseStmt)
-    localError(c.info, "cannot lift assignment operator to 'case' object")
+    localError(c.c.config, c.info, "cannot lift assignment operator to 'case' object")
   of nkRecList:
     for t in items(n): liftBodyObj(c, t, body, x, y)
   else:
-    illFormedAstLocal(n)
+    illFormedAstLocal(n, c.c.config)
 
 proc genAddr(c: PContext; x: PNode): PNode =
   if x.kind == nkHiddenDeref:
-    checkSonsLen(x, 1)
+    checkSonsLen(x, 1, c.config)
     result = x.sons[0]
   else:
     result = newNodeIT(nkHiddenAddr, x.info, makeVarType(c, x.typ))
     addSon(result, x)
 
 proc newAsgnCall(c: PContext; op: PSym; x, y: PNode): PNode =
-  if sfError in op.flags:
-    localError(x.info, errWrongSymbolX, op.name.s)
+  #if sfError in op.flags:
+  #  localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s)
   result = newNodeI(nkCall, x.info)
   result.add newSymNode(op)
   result.add genAddr(c, x)
@@ -92,46 +93,60 @@ proc newAsgnStmt(le, ri: PNode): PNode =
   result.sons[0] = le
   result.sons[1] = ri
 
-proc newDestructorCall(op: PSym; x: PNode): PNode =
+proc newOpCall(op: PSym; x: PNode): PNode =
   result = newNodeIT(nkCall, x.info, op.typ.sons[0])
   result.add(newSymNode(op))
   result.add x
 
+proc destructorCall(c: PContext; op: PSym; x: PNode): PNode =
+  result = newNodeIT(nkCall, x.info, op.typ.sons[0])
+  result.add(newSymNode(op))
+  result.add genAddr(c, x)
+
 proc newDeepCopyCall(op: PSym; x, y: PNode): PNode =
-  result = newAsgnStmt(x, newDestructorCall(op, y))
+  result = newAsgnStmt(x, newOpCall(op, y))
+
+proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
+                        field: PSym): bool =
+  if tfHasAsgn in t.flags:
+    var op: PSym
+    if sameType(t, c.asgnForType):
+      # generate recursive call:
+      if c.recurse:
+        op = c.fn
+      else:
+        c.recurse = true
+        return false
+    else:
+      op = field
+      if op == nil:
+        op = liftBody(c.c, t, c.kind, c.info)
+    if sfError in op.flags:
+      incl c.fn.flags, sfError
+    else:
+      markUsed(c.c.config, c.info, op, c.c.graph.usageSym)
+    onUse(c.info, op)
+    body.add newAsgnCall(c.c, op, x, y)
+    result = true
 
 proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
   case c.kind
   of attachedDestructor:
     let op = t.destructor
     if op != nil:
-      markUsed(c.info, op, c.c.graph.usageSym)
-      styleCheckUse(c.info, op)
-      body.add newDestructorCall(op, x)
+      markUsed(c.c.config, c.info, op, c.c.graph.usageSym)
+      onUse(c.info, op)
+      body.add destructorCall(c.c, op, x)
       result = true
   of attachedAsgn:
-    if tfHasAsgn in t.flags:
-      var op: PSym
-      if sameType(t, c.asgnForType):
-        # generate recursive call:
-        if c.recurse:
-          op = c.fn
-        else:
-          c.recurse = true
-          return false
-      else:
-        op = t.assignment
-        if op == nil:
-          op = liftBody(c.c, t, c.info)
-      markUsed(c.info, op, c.c.graph.usageSym)
-      styleCheckUse(c.info, op)
-      body.add newAsgnCall(c.c, op, x, y)
-      result = true
+    result = considerAsgnOrSink(c, t, body, x, y, t.assignment)
+  of attachedSink:
+    result = considerAsgnOrSink(c, t, body, x, y, t.sink)
   of attachedDeepCopy:
     let op = t.deepCopy
     if op != nil:
-      markUsed(c.info, op, c.c.graph.usageSym)
-      styleCheckUse(c.info, op)
+      markUsed(c.c.config, c.info, op, c.c.graph.usageSym)
+      onUse(c.info, op)
       body.add newDeepCopyCall(op, x, y)
       result = true
 
@@ -142,65 +157,84 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
 proc addVar(father, v, value: PNode) =
   var vpart = newNodeI(nkIdentDefs, v.info, 3)
   vpart.sons[0] = v
-  vpart.sons[1] = ast.emptyNode
+  vpart.sons[1] = newNodeI(nkEmpty, v.info)
   vpart.sons[2] = value
   addSon(father, vpart)
 
 proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode =
-  var temp = newSym(skTemp, getIdent(lowerings.genPrefix), c.fn, c.info)
-  temp.typ = getSysType(tyInt)
+  var temp = newSym(skTemp, getIdent(c.c.cache, lowerings.genPrefix), c.fn, c.info)
+  temp.typ = getSysType(c.c.graph, body.info, tyInt)
   incl(temp.flags, sfFromGeneric)
 
   var v = newNodeI(nkVarSection, c.info)
   result = newSymNode(temp)
-  v.addVar(result, lowerings.newIntLit(first))
+  v.addVar(result, lowerings.newIntLit(c.c.graph, body.info, first))
   body.add v
 
-proc genBuiltin(magic: TMagic; name: string; i: PNode): PNode =
+proc genBuiltin(g: ModuleGraph; magic: TMagic; name: string; i: PNode): PNode =
   result = newNodeI(nkCall, i.info)
-  result.add createMagic(name, magic).newSymNode
+  result.add createMagic(g, name, magic).newSymNode
   result.add i
 
 proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode =
   result = newNodeI(nkWhileStmt, c.info, 2)
-  let cmp = genBuiltin(mLeI, "<=", i)
-  cmp.add genHigh(dest)
-  cmp.typ = getSysType(tyBool)
+  let cmp = genBuiltin(c.c.graph, mLeI, "<=", i)
+  cmp.add genHigh(c.c.graph, dest)
+  cmp.typ = getSysType(c.c.graph, c.info, tyBool)
   result.sons[0] = cmp
   result.sons[1] = newNodeI(nkStmtList, c.info)
 
-proc addIncStmt(body, i: PNode) =
-  let incCall = genBuiltin(mInc, "inc", i)
-  incCall.add lowerings.newIntLit(1)
+proc addIncStmt(c: var TLiftCtx; body, i: PNode) =
+  let incCall = genBuiltin(c.c.graph, mInc, "inc", i)
+  incCall.add lowerings.newIntLit(c.c.graph, c.info, 1)
   body.add incCall
 
 proc newSeqCall(c: PContext; x, y: PNode): PNode =
   # don't call genAddr(c, x) here:
-  result = genBuiltin(mNewSeq, "newSeq", x)
-  let lenCall = genBuiltin(mLengthSeq, "len", y)
-  lenCall.typ = getSysType(tyInt)
+  result = genBuiltin(c.graph, mNewSeq, "newSeq", x)
+  let lenCall = genBuiltin(c.graph, mLengthSeq, "len", y)
+  lenCall.typ = getSysType(c.graph, x.info, tyInt)
   result.add lenCall
 
 proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case t.kind
   of tyNone, tyEmpty, tyVoid: discard
   of tyPointer, tySet, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCString,
-      tyPtr, tyString, tyRef:
+      tyPtr, tyRef, tyOpt, tyUncheckedArray:
     defaultOp(c, t, body, x, y)
-  of tyArray, tySequence:
+  of tyArray:
     if tfHasAsgn in t.flags:
-      if t.kind == tySequence:
-        # XXX add 'nil' handling here
+      let i = declareCounter(c, body, firstOrd(c.c.config, t))
+      let whileLoop = genWhileLoop(c, i, x)
+      let elemType = t.lastSon
+      liftBodyAux(c, elemType, whileLoop.sons[1], x.at(i, elemType),
+                                                  y.at(i, elemType))
+      addIncStmt(c, whileLoop.sons[1], i)
+      body.add whileLoop
+    else:
+      defaultOp(c, t, body, x, y)
+  of tySequence:
+    # note that tfHasAsgn is propagated so we need the check on
+    # 'selectedGC' here to determine if we have the new runtime.
+    if c.c.config.selectedGC == gcDestructors:
+      discard considerOverloadedOp(c, t, body, x, y)
+    elif tfHasAsgn in t.flags:
+      if c.kind != attachedDestructor:
         body.add newSeqCall(c.c, x, y)
-      let i = declareCounter(c, body, firstOrd(t))
+      let i = declareCounter(c, body, firstOrd(c.c.config, t))
       let whileLoop = genWhileLoop(c, i, x)
       let elemType = t.lastSon
       liftBodyAux(c, elemType, whileLoop.sons[1], x.at(i, elemType),
                                                   y.at(i, elemType))
-      addIncStmt(whileLoop.sons[1], i)
+      addIncStmt(c, whileLoop.sons[1], i)
       body.add whileLoop
     else:
       defaultOp(c, t, body, x, y)
+  of tyString:
+    if tfHasAsgn in t.flags:
+      discard considerOverloadedOp(c, t, body, x, y)
+    else:
+      defaultOp(c, t, body, x, y)
   of tyObject, tyDistinct:
     if not considerOverloadedOp(c, t, body, x, y):
       if t.sons[0] != nil:
@@ -216,20 +250,20 @@ proc liftBodyAux(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       # have to go through some indirection; we delegate this to the codegen:
       let call = newNodeI(nkCall, c.info, 2)
       call.typ = t
-      call.sons[0] = newSymNode(createMagic("deepCopy", mDeepCopy))
+      call.sons[0] = newSymNode(createMagic(c.c.graph, "deepCopy", mDeepCopy))
       call.sons[1] = y
       body.add newAsgnStmt(x, call)
   of tyVarargs, tyOpenArray:
-    localError(c.info, errGenerated, "cannot copy openArray")
+    localError(c.c.config, c.info, "cannot copy openArray")
   of tyFromExpr, tyProxy, tyBuiltInTypeClass, tyUserTypeClass,
      tyUserTypeClassInst, tyCompositeTypeClass, tyAnd, tyOr, tyNot, tyAnything,
      tyGenericParam, tyGenericBody, tyNil, tyExpr, tyStmt,
      tyTypeDesc, tyGenericInvocation, tyForward:
-    internalError(c.info, "assignment requested for type: " & typeToString(t))
-  of tyOrdinal, tyRange,
-     tyGenericInst, tyFieldAccessor, tyStatic, tyVar, tyAlias:
+    internalError(c.c.config, c.info, "assignment requested for type: " & typeToString(t))
+  of tyOrdinal, tyRange, tyInferred,
+     tyGenericInst, tyStatic, tyVar, tyLent, tyAlias, tySink:
     liftBodyAux(c, lastSon(t), body, x, y)
-  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("liftBodyAux")
+  of tyOptAsRef: internalError(c.c.config, "liftBodyAux")
 
 proc newProcType(info: TLineInfo; owner: PSym): PType =
   result = newType(tyProc, owner)
@@ -245,43 +279,82 @@ proc addParam(procType: PType; param: PSym) =
   addSon(procType.n, newSymNode(param))
   rawAddSon(procType, param.typ)
 
-proc liftBody(c: PContext; typ: PType; info: TLineInfo): PSym =
+proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp;
+              info: TLineInfo): PSym =
   var a: TLiftCtx
   a.info = info
   a.c = c
+  a.kind = kind
   let body = newNodeI(nkStmtList, info)
-  result = newSym(skProc, getIdent":lifted=", typ.owner, info)
+  let procname = case kind
+                 of attachedAsgn: getIdent(c.cache, "=")
+                 of attachedSink: getIdent(c.cache, "=sink")
+                 of attachedDeepCopy: getIdent(c.cache, "=deepcopy")
+                 of attachedDestructor: getIdent(c.cache, "=destroy")
+
+  result = newSym(skProc, procname, typ.owner, info)
   a.fn = result
   a.asgnForType = typ
 
-  let dest = newSym(skParam, getIdent"dest", result, info)
-  let src = newSym(skParam, getIdent"src", result, info)
+  let dest = newSym(skParam, getIdent(c.cache, "dest"), result, info)
+  let src = newSym(skParam, getIdent(c.cache, "src"), result, info)
   dest.typ = makeVarType(c, typ)
   src.typ = typ
 
   result.typ = newProcType(info, typ.owner)
   result.typ.addParam dest
-  result.typ.addParam src
+  if kind != attachedDestructor:
+    result.typ.addParam src
 
   liftBodyAux(a, typ, body, newSymNode(dest).newDeref, newSymNode(src))
+  # recursion is handled explicitly, do not register the type based operation
+  # before 'liftBodyAux':
+  if c.config.selectedGC == gcDestructors and
+      typ.kind in {tySequence, tyString} and body.len == 0:
+    discard "do not cache it yet"
+  else:
+    case kind
+    of attachedAsgn: typ.assignment = result
+    of attachedSink: typ.sink = result
+    of attachedDeepCopy: typ.deepCopy = result
+    of attachedDestructor: typ.destructor = result
 
   var n = newNodeI(nkProcDef, info, bodyPos+1)
-  for i in 0 .. < n.len: n.sons[i] = emptyNode
+  for i in 0 ..< n.len: n.sons[i] = newNodeI(nkEmpty, info)
   n.sons[namePos] = newSymNode(result)
   n.sons[paramsPos] = result.typ.n
   n.sons[bodyPos] = body
   result.ast = n
+  incl result.flags, sfFromGeneric
 
-  # register late as recursion is handled differently
-  typ.assignment = result
-  #echo "Produced this ", n
 
 proc getAsgnOrLiftBody(c: PContext; typ: PType; info: TLineInfo): PSym =
-  let t = typ.skipTypes({tyGenericInst, tyVar, tyAlias})
+  let t = typ.skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink})
   result = t.assignment
   if result.isNil:
-    result = liftBody(c, t, info)
+    result = liftBody(c, t, attachedAsgn, info)
 
 proc overloadedAsgn(c: PContext; dest, src: PNode): PNode =
   let a = getAsgnOrLiftBody(c, dest.typ, dest.info)
   result = newAsgnCall(c, a, dest, src)
+
+proc liftTypeBoundOps*(c: PContext; typ: PType; info: TLineInfo) =
+  ## In the semantic pass this is called in strategic places
+  ## to ensure we lift assignment, destructors and moves properly.
+  ## The later 'destroyer' pass depends on it.
+  if not hasDestructor(typ): return
+  when false:
+    # do not produce wrong liftings while we're still instantiating generics:
+    # now disabled; breaks topttree.nim!
+    if c.typesWithOps.len > 0: return
+  let typ = typ.skipTypes({tyGenericInst, tyAlias})
+  # we generate the destructor first so that other operators can depend on it:
+  if typ.destructor == nil:
+    liftBody(c, typ, attachedDestructor, info)
+  if typ.assignment == nil:
+    liftBody(c, typ, attachedAsgn, info)
+  if typ.sink == nil:
+    liftBody(c, typ, attachedSink, info)
+
+#proc patchResolvedTypeBoundOp*(c: PContext; n: PNode): PNode =
+#  if n.kind == nkCall and
diff --git a/compiler/semcall.nim b/compiler/semcall.nim
index ffa940291..7e0ea5490 100644
--- a/compiler/semcall.nim
+++ b/compiler/semcall.nim
@@ -27,7 +27,7 @@ proc sameMethodDispatcher(a, b: PSym): bool =
       # method collide[T](a: TThing, b: TUnit[T]) is instantiated and not
       # method collide[T](a: TUnit[T], b: TThing)! This means we need to
       # *instantiate* every candidate! However, we don't keep more than 2-3
-      # candidated around so we cannot implement that for now. So in order
+      # candidates around so we cannot implement that for now. So in order
       # to avoid subtle problems, the call remains ambiguous and needs to
       # be disambiguated by the programmer; this way the right generic is
       # instantiated.
@@ -35,10 +35,11 @@ proc sameMethodDispatcher(a, b: PSym): bool =
 proc determineType(c: PContext, s: PSym)
 
 proc initCandidateSymbols(c: PContext, headSymbol: PNode,
-                       initialBinding: PNode,
-                       filter: TSymKinds,
-                       best, alt: var TCandidate,
-                       o: var TOverloadIter): seq[tuple[s: PSym, scope: int]] =
+                          initialBinding: PNode,
+                          filter: TSymKinds,
+                          best, alt: var TCandidate,
+                          o: var TOverloadIter,
+                          diagnostics: bool): seq[tuple[s: PSym, scope: int]] =
   result = @[]
   var symx = initOverloadIter(o, c, headSymbol)
   while symx != nil:
@@ -46,8 +47,10 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode,
       result.add((symx, o.lastOverloadScope))
     symx = nextOverloadIter(o, c, headSymbol)
   if result.len > 0:
-    initCandidate(c, best, result[0].s, initialBinding, result[0].scope)
-    initCandidate(c, alt, result[0].s, initialBinding, result[0].scope)
+    initCandidate(c, best, result[0].s, initialBinding,
+                  result[0].scope, diagnostics)
+    initCandidate(c, alt, result[0].s, initialBinding,
+                  result[0].scope, diagnostics)
     best.state = csNoMatch
 
 proc pickBestCandidate(c: PContext, headSymbol: PNode,
@@ -55,7 +58,9 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
                        initialBinding: PNode,
                        filter: TSymKinds,
                        best, alt: var TCandidate,
-                       errors: var CandidateErrors) =
+                       errors: var CandidateErrors,
+                       diagnosticsFlag: bool,
+                       errorsEnabled: bool) =
   var o: TOverloadIter
   var sym = initOverloadIter(o, c, headSymbol)
   var scope = o.lastOverloadScope
@@ -64,12 +69,13 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
   # This can occur in cases like 'init(a, 1, (var b = new(Type2); b))'
   let counterInitial = c.currentScope.symbols.counter
   var syms: seq[tuple[s: PSym, scope: int]]
+  var noSyms = true
   var nextSymIndex = 0
   while sym != nil:
     if sym.kind in filter:
       # Initialise 'best' and 'alt' with the first available symbol
-      initCandidate(c, best, sym, initialBinding, scope)
-      initCandidate(c, alt, sym, initialBinding, scope)
+      initCandidate(c, best, sym, initialBinding, scope, diagnosticsFlag)
+      initCandidate(c, alt, sym, initialBinding, scope, diagnosticsFlag)
       best.state = csNoMatch
       break
     else:
@@ -82,15 +88,14 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
       scope = o.lastOverloadScope
       continue
     determineType(c, sym)
-    initCandidate(c, z, sym, initialBinding, scope)
-    if c.currentScope.symbols.counter == counterInitial or syms != nil:
+    initCandidate(c, z, sym, initialBinding, scope, diagnosticsFlag)
+    if c.currentScope.symbols.counter == counterInitial or syms.len != 0:
       matches(c, n, orig, z)
-      if errors != nil:
-        errors.safeAdd((sym, int z.mutabilityProblem))
-        if z.errors != nil:
-          for err in z.errors:
-            errors.add(err)
       if z.state == csMatch:
+        #if sym.name.s == "==" and (n.info ?? "temp3"):
+        #  echo typeToString(sym.typ)
+        #  writeMatches(z)
+
         # little hack so that iterators are preferred over everything else:
         if sym.kind == skIterator: inc(z.exactMatches, 200)
         case best.state
@@ -99,11 +104,19 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
           var cmp = cmpCandidates(best, z)
           if cmp < 0: best = z   # x is better than the best so far
           elif cmp == 0: alt = z # x is as good as the best so far
+      elif errorsEnabled or z.diagnosticsEnabled:
+        errors.add(CandidateError(
+          sym: sym,
+          unmatchedVarParam: int z.mutabilityProblem,
+          firstMismatch: z.firstMismatch,
+          diagnostics: z.diagnostics))
     else:
       # Symbol table has been modified. Restart and pre-calculate all syms
       # before any further candidate init and compare. SLOW, but rare case.
-      syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o)
-    if syms == nil:
+      syms = initCandidateSymbols(c, headSymbol, initialBinding, filter,
+                                  best, alt, o, diagnosticsFlag)
+      noSyms = false
+    if noSyms:
       sym = nextOverloadIter(o, c, headSymbol)
       scope = o.lastOverloadScope
     elif nextSymIndex < syms.len:
@@ -114,17 +127,26 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode,
     else:
       break
 
-proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
-  # Gives a detailed error message; this is separated from semOverloadedCall,
-  # as semOverlodedCall is already pretty slow (and we need this information
-  # only in case of an error).
-  if c.compilesContextId > 0 and optReportConceptFailures notin gGlobalOptions:
-    # fail fast:
-    globalError(n.info, errTypeMismatch, "")
-  if errors.isNil or errors.len == 0:
-    localError(n.info, errExprXCannotBeCalled, n[0].renderTree)
-    return
-
+proc effectProblem(f, a: PType; result: var string) =
+  if f.kind == tyProc and a.kind == tyProc:
+    if tfThread in f.flags and tfThread notin a.flags:
+      result.add "\n  This expression is not GC-safe. Annotate the " &
+          "proc with {.gcsafe.} to get extended error information."
+    elif tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
+      result.add "\n  This expression can have side effects. Annotate the " &
+          "proc with {.noSideEffect.} to get extended error information."
+
+proc renderNotLValue(n: PNode): string =
+  result = $n
+  let n = if n.kind == nkHiddenDeref: n[0] else: n
+  if n.kind == nkHiddenCallConv and n.len > 1:
+    result = $n[0] & "(" & result & ")"
+  elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2:
+    result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")"
+
+proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors):
+                            (TPreferedDesc, string) =
+  var prefer = preferName
   # to avoid confusing errors like:
   #   got (SslPtr, SocketHandle)
   #   but expected one of:
@@ -132,11 +154,9 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
   # we do a pre-analysis. If all types produce the same string, we will add
   # module information.
   let proto = describeArgs(c, n, 1, preferName)
-
-  var prefer = preferName
-  for err, mut in items(errors):
+  for err in errors:
     var errProto = ""
-    let n = err.typ.n
+    let n = err.sym.typ.n
     for i in countup(1, n.len - 1):
       var p = n.sons[i]
       if p.kind == nkSym:
@@ -147,26 +167,89 @@ proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
       prefer = preferModuleInfo
       break
 
-  # now use the information stored in 'prefer' to produce a nice error message:
-  var result = msgKindToString(errTypeMismatch)
-  add(result, describeArgs(c, n, 1, prefer))
-  add(result, ')')
+  # we pretend procs are attached to the type of the first
+  # argument in order to remove plenty of candidates. This is
+  # comparable to what C# does and C# is doing fine.
+  var filterOnlyFirst = false
+  if optShowAllMismatches notin c.config.globalOptions:
+    for err in errors:
+      if err.firstMismatch > 1:
+        filterOnlyFirst = true
+        break
+
   var candidates = ""
-  for err, mut in items(errors):
-    if err.kind in routineKinds and err.ast != nil:
-      add(candidates, renderTree(err.ast,
-            {renderNoBody, renderNoComments,renderNoPragmas}))
+  var skipped = 0
+  for err in errors:
+    if filterOnlyFirst and err.firstMismatch == 1:
+      inc skipped
+      continue
+    if err.sym.kind in routineKinds and err.sym.ast != nil:
+      add(candidates, renderTree(err.sym.ast,
+            {renderNoBody, renderNoComments, renderNoPragmas}))
     else:
-      add(candidates, err.getProcHeader(prefer))
+      add(candidates, getProcHeader(c.config, err.sym, prefer))
     add(candidates, "\n")
-    if mut != 0 and mut < n.len:
-      add(candidates, "for a 'var' type a variable needs to be passed, but '" & renderTree(n[mut]) & "' is immutable\n")
+    if err.firstMismatch != 0 and n.len > 1:
+      let cond = n.len > 2
+      if cond:
+        candidates.add("  first type mismatch at position: " & $abs(err.firstMismatch))
+        if err.firstMismatch >= 0: candidates.add("\n  required type: ")
+        else: candidates.add("\n  unknown named parameter: " & $n[-err.firstMismatch][0])
+      var wanted, got: PType = nil
+      if err.firstMismatch < 0:
+        discard
+      elif err.firstMismatch < err.sym.typ.len:
+        wanted = err.sym.typ.sons[err.firstMismatch]
+        if cond: candidates.add typeToString(wanted)
+      else:
+        if cond: candidates.add "none"
+      if err.firstMismatch > 0 and err.firstMismatch < n.len:
+        if cond:
+          candidates.add "\n  but expression '"
+          candidates.add renderTree(n[err.firstMismatch])
+          candidates.add "' is of type: "
+        got = n[err.firstMismatch].typ
+        if cond: candidates.add typeToString(got)
+      if wanted != nil and got != nil:
+        effectProblem(wanted, got, candidates)
+      if cond: candidates.add "\n"
+    if err.unmatchedVarParam != 0 and err.unmatchedVarParam < n.len:
+      candidates.add("  for a 'var' type a variable needs to be passed, but '" &
+                      renderNotLValue(n[err.unmatchedVarParam]) &
+                      "' is immutable\n")
+    for diag in err.diagnostics:
+      candidates.add(diag & "\n")
+  if skipped > 0:
+    candidates.add($skipped & " other mismatching symbols have been " &
+        " suppressed; compile with --showAllMismatches:on to see them\n")
+  result = (prefer, candidates)
+
+const
+  errTypeMismatch = "type mismatch: got <"
+  errButExpected = "but expected one of: "
+  errUndeclaredField = "undeclared field: '$1'"
+  errUndeclaredRoutine = "attempting to call undeclared routine: '$1'"
+  errBadRoutine = "attempting to call routine: '$1'$2"
+  errAmbiguousCallXYZ = "ambiguous call; both $1 and $2 match for: $3"
+
+proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) =
+  # Gives a detailed error message; this is separated from semOverloadedCall,
+  # as semOverlodedCall is already pretty slow (and we need this information
+  # only in case of an error).
+  if c.config.m.errorOutputs == {}:
+    # fail fast:
+    globalError(c.config, n.info, "type mismatch")
+  if errors.len == 0:
+    localError(c.config, n.info, "expression '$1' cannot be called" % n[0].renderTree)
+    return
+
+  let (prefer, candidates) = presentFailedCandidates(c, n, errors)
+  var result = errTypeMismatch
+  add(result, describeArgs(c, n, 1, prefer))
+  add(result, '>')
   if candidates != "":
-    add(result, "\n" & msgKindToString(errButExpected) & "\n" & candidates)
-  if c.compilesContextId > 0 and optReportConceptFailures in gGlobalOptions:
-    globalError(n.info, errGenerated, result)
-  else:
-    localError(n.info, errGenerated, result)
+    add(result, "\n" & errButExpected & "\n" & candidates)
+  localError(c.config, n.info, result & "\nexpression: " & $n)
 
 proc bracketNotFoundError(c: PContext; n: PNode) =
   var errors: CandidateErrors = @[]
@@ -175,21 +258,50 @@ proc bracketNotFoundError(c: PContext; n: PNode) =
   var symx = initOverloadIter(o, c, headSymbol)
   while symx != nil:
     if symx.kind in routineKinds:
-      errors.add((symx, 0))
+      errors.add(CandidateError(sym: symx,
+                                unmatchedVarParam: 0, firstMismatch: 0,
+                                diagnostics: @[],
+                                enabled: false))
     symx = nextOverloadIter(o, c, headSymbol)
   if errors.len == 0:
-    localError(n.info, "could not resolve: " & $n)
+    localError(c.config, n.info, "could not resolve: " & $n)
   else:
     notFoundError(c, n, errors)
 
+proc getMsgDiagnostic(c: PContext, flags: TExprFlags, n, f: PNode): string =
+  if c.compilesContextId > 0:
+    # we avoid running more diagnostic when inside a `compiles(expr)`, to
+    # errors while running diagnostic (see test D20180828T234921), and
+    # also avoid slowdowns in evaluating `compiles(expr)`.
+    discard
+  else:
+    var o: TOverloadIter
+    var sym = initOverloadIter(o, c, f)
+    while sym != nil:
+      proc toHumanStr(kind: TSymKind): string =
+        result = $kind
+        assert result.startsWith "sk"
+        result = result[2..^1].toLowerAscii
+      result &= "\n  found '$1' of kind '$2'" % [getSymRepr(c.config, sym), sym.kind.toHumanStr]
+      sym = nextOverloadIter(o, c, n)
+
+  let ident = considerQuotedIdent(c, f, n).s
+  if nfDotField in n.flags and nfExplicitCall notin n.flags:
+    result = errUndeclaredField % ident & result
+  else:
+    if result.len == 0: result = errUndeclaredRoutine % ident
+    else: result = errBadRoutine % [ident, result]
+
 proc resolveOverloads(c: PContext, n, orig: PNode,
-                      filter: TSymKinds;
-                      errors: var CandidateErrors): TCandidate =
+                      filter: TSymKinds, flags: TExprFlags,
+                      errors: var CandidateErrors,
+                      errorsEnabled: bool): TCandidate =
   var initialBinding: PNode
   var alt: TCandidate
   var f = n.sons[0]
   if f.kind == nkBracketExpr:
     # fill in the bindings:
+    semOpAux(c, f)
     initialBinding = f
     f = f.sons[0]
   else:
@@ -197,7 +309,8 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
 
   template pickBest(headSymbol) =
     pickBestCandidate(c, headSymbol, n, orig, initialBinding,
-                      filter, result, alt, errors)
+                      filter, result, alt, errors, efExplain in flags,
+                      errorsEnabled)
   pickBest(f)
 
   let overloadsState = result.state
@@ -219,16 +332,15 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
       else: return
 
     if nfDotField in n.flags:
-      internalAssert f.kind == nkIdent and n.sonsLen >= 2
-      let calleeName = newStrNode(nkStrLit, f.ident.s).withInfo(n.info)
+      internalAssert c.config, f.kind == nkIdent and n.len >= 2
 
       # leave the op head symbol empty,
       # we are going to try multiple variants
-      n.sons[0..1] = [nil, n[1], calleeName]
-      orig.sons[0..1] = [nil, orig[1], calleeName]
+      n.sons[0..1] = [nil, n[1], f]
+      orig.sons[0..1] = [nil, orig[1], f]
 
       template tryOp(x) =
-        let op = newIdentNode(getIdent(x), n.info)
+        let op = newIdentNode(getIdent(c.cache, x), n.info)
         n.sons[0] = op
         orig.sons[0] = op
         pickBest(op)
@@ -239,45 +351,37 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
       if result.state in {csEmpty, csNoMatch}:
         tryOp "."
 
-    elif nfDotSetter in n.flags:
-      internalAssert f.kind == nkIdent and n.sonsLen == 3
-      let calleeName = newStrNode(nkStrLit,
-        f.ident.s[0..f.ident.s.len-2]).withInfo(n.info)
-      let callOp = newIdentNode(getIdent".=", n.info)
+    elif nfDotSetter in n.flags and f.kind == nkIdent and n.len == 3:
+      # we need to strip away the trailing '=' here:
+      let calleeName = newIdentNode(getIdent(c.cache, f.ident.s[0..f.ident.s.len-2]), n.info)
+      let callOp = newIdentNode(getIdent(c.cache, ".="), n.info)
       n.sons[0..1] = [callOp, n[1], calleeName]
       orig.sons[0..1] = [callOp, orig[1], calleeName]
       pickBest(callOp)
 
     if overloadsState == csEmpty and result.state == csEmpty:
-      if nfDotField in n.flags and nfExplicitCall notin n.flags:
-        localError(n.info, errUndeclaredField, considerQuotedIdent(f).s)
-      else:
-        localError(n.info, errUndeclaredRoutine, considerQuotedIdent(f).s)
+      if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim
+        localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f))
       return
     elif result.state != csMatch:
       if nfExprCall in n.flags:
-        localError(n.info, errExprXCannotBeCalled,
+        localError(c.config, n.info, "expression '$1' cannot be called" %
                    renderTree(n, {renderNoComments}))
       else:
         if {nfDotField, nfDotSetter} * n.flags != {}:
           # clean up the inserted ops
           n.sons.delete(2)
           n.sons[0] = f
-
-        errors = @[]
-        pickBest(f)
-        #notFoundError(c, n, errors)
-
       return
   if alt.state == csMatch and cmpCandidates(result, alt) == 0 and
       not sameMethodDispatcher(result.calleeSym, alt.calleeSym):
-    internalAssert result.state == csMatch
+    internalAssert c.config, result.state == csMatch
     #writeMatches(result)
     #writeMatches(alt)
-    if c.compilesContextId > 0:
+    if c.config.m.errorOutputs == {}:
       # quick error message for performance of 'compiles' built-in:
-      globalError(n.info, errGenerated, "ambiguous call")
-    elif gErrorCounter == 0:
+      globalError(c.config, n.info, errGenerated, "ambiguous call")
+    elif c.config.errorCounter == 0:
       # don't cascade errors
       var args = "("
       for i in countup(1, sonsLen(n) - 1):
@@ -285,12 +389,13 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
         add(args, typeToString(n.sons[i].typ))
       add(args, ")")
 
-      localError(n.info, errGenerated, msgKindToString(errAmbiguousCallXYZ) % [
-        getProcHeader(result.calleeSym), getProcHeader(alt.calleeSym),
+      localError(c.config, n.info, errAmbiguousCallXYZ % [
+        getProcHeader(c.config, result.calleeSym),
+        getProcHeader(c.config, alt.calleeSym),
         args])
 
-
 proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
+  let a = if a.kind == nkHiddenDeref: a[0] else: a
   if a.kind == nkHiddenCallConv and a.sons[0].kind == nkSym:
     let s = a.sons[0].sym
     if s.ast != nil and s.ast[genericParamsPos].kind != nkEmpty:
@@ -302,7 +407,7 @@ proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
 proc instGenericConvertersSons*(c: PContext, n: PNode, x: TCandidate) =
   assert n.kind in nkCallKinds
   if x.genericConverter:
-    for i in 1 .. <n.len:
+    for i in 1 ..< n.len:
       instGenericConvertersArg(c, n.sons[i], x)
 
 proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode =
@@ -327,16 +432,31 @@ proc inferWithMetatype(c: PContext, formal: PType,
     result.typ = generateTypeInstance(c, m.bindings, arg.info,
                                       formal.skipTypes({tyCompositeTypeClass}))
   else:
-    typeMismatch(arg.info, formal, arg.typ)
+    typeMismatch(c.config, arg.info, formal, arg.typ)
     # error correction:
     result = copyTree(arg)
     result.typ = formal
 
-proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode =
+proc updateDefaultParams(call: PNode) =
+  # In generic procs, the default parameter may be unique for each
+  # instantiation (see tlateboundgenericparams).
+  # After a call is resolved, we need to re-assign any default value
+  # that was used during sigmatch. sigmatch is responsible for marking
+  # the default params with `nfDefaultParam` and `instantiateProcType`
+  # computes correctly the default values for each instantiation.
+  let calleeParams = call[0].sym.typ.n
+  for i in 1..<call.len:
+    if nfDefaultParam in call[i].flags:
+      let def = calleeParams[i].sym.ast
+      if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam
+      call[i] = def
+
+proc semResolvedCall(c: PContext, x: TCandidate,
+                     n: PNode, flags: TExprFlags): PNode =
   assert x.state == csMatch
   var finalCallee = x.calleeSym
-  markUsed(n.sons[0].info, finalCallee, c.graph.usageSym)
-  styleCheckUse(n.sons[0].info, finalCallee)
+  markUsed(c.config, n.sons[0].info, finalCallee, c.graph.usageSym)
+  onUse(n.sons[0].info, finalCallee)
   assert finalCallee.ast != nil
   if x.hasFauxMatch:
     result = x.call
@@ -361,16 +481,17 @@ proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode =
         of skType:
           x.call.add newSymNode(s, n.info)
         else:
-          internalAssert false
+          internalAssert c.config, false
 
   result = x.call
   instGenericConvertersSons(c, result, x)
-  result.sons[0] = newSymNode(finalCallee, result.sons[0].info)
+  result[0] = newSymNode(finalCallee, result[0].info)
   result.typ = finalCallee.typ.sons[0]
+  updateDefaultParams(result)
 
 proc canDeref(n: PNode): bool {.inline.} =
   result = n.len >= 2 and (let t = n[1].typ;
-    t != nil and t.skipTypes({tyGenericInst, tyAlias}).kind in {tyPtr, tyRef})
+    t != nil and t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyPtr, tyRef})
 
 proc tryDeref(n: PNode): PNode =
   result = newNodeI(nkHiddenDeref, n.info)
@@ -378,26 +499,48 @@ proc tryDeref(n: PNode): PNode =
   result.addSon(n)
 
 proc semOverloadedCall(c: PContext, n, nOrig: PNode,
-                       filter: TSymKinds): PNode =
-  var errors: CandidateErrors
-
-  var r = resolveOverloads(c, n, nOrig, filter, errors)
-  if r.state == csMatch: result = semResolvedCall(c, n, r)
-  elif experimentalMode(c) and canDeref(n):
+                       filter: TSymKinds, flags: TExprFlags): PNode =
+  var errors: CandidateErrors = @[] # if efExplain in flags: @[] else: nil
+  var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags)
+  if r.state == csMatch:
+    # this may be triggered, when the explain pragma is used
+    if errors.len > 0:
+      let (_, candidates) = presentFailedCandidates(c, n, errors)
+      message(c.config, n.info, hintUserRaw,
+              "Non-matching candidates for " & renderTree(n) & "\n" &
+              candidates)
+    result = semResolvedCall(c, r, n, flags)
+  elif implicitDeref in c.features and canDeref(n):
     # try to deref the first argument and then try overloading resolution again:
+    #
+    # XXX: why is this here?
+    # it could be added to the long list of alternatives tried
+    # inside `resolveOverloads` or it could be moved all the way
+    # into sigmatch with hidden conversion produced there
+    #
     n.sons[1] = n.sons[1].tryDeref
-    var r = resolveOverloads(c, n, nOrig, filter, errors)
-    if r.state == csMatch: result = semResolvedCall(c, n, r)
+    var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags)
+    if r.state == csMatch: result = semResolvedCall(c, r, n, flags)
     else:
       # get rid of the deref again for a better error message:
       n.sons[1] = n.sons[1].sons[0]
-      notFoundError(c, n, errors)
+      #notFoundError(c, n, errors)
+      if efExplain notin flags:
+        # repeat the overload resolution,
+        # this time enabling all the diagnostic output (this should fail again)
+        discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
+      elif efNoUndeclared notin flags:
+        notFoundError(c, n, errors)
   else:
-    notFoundError(c, n, errors)
-  # else: result = errorNode(c, n)
+    if efExplain notin flags:
+      # repeat the overload resolution,
+      # this time enabling all the diagnostic output (this should fail again)
+      discard semOverloadedCall(c, n, nOrig, filter, flags + {efExplain})
+    elif efNoUndeclared notin flags:
+      notFoundError(c, n, errors)
 
-proc explicitGenericInstError(n: PNode): PNode =
-  localError(n.info, errCannotInstantiateX, renderTree(n))
+proc explicitGenericInstError(c: PContext; n: PNode): PNode =
+  localError(c.config, n.info, errCannotInstantiateX % renderTree(n))
   result = n
 
 proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
@@ -407,19 +550,31 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode =
 
   for i in 1..sonsLen(n)-1:
     let formal = s.ast.sons[genericParamsPos].sons[i-1].typ
-    let arg = n[i].typ
-    let tm = typeRel(m, formal, arg, true)
+    var arg = n[i].typ
+    # try transforming the argument into a static one before feeding it into
+    # typeRel
+    if formal.kind == tyStatic and arg.kind != tyStatic:
+      let evaluated = c.semTryConstExpr(c, n[i])
+      if evaluated != nil:
+        arg = newTypeS(tyStatic, c)
+        arg.sons = @[evaluated.typ]
+        arg.n = evaluated
+    let tm = typeRel(m, formal, arg)
     if tm in {isNone, isConvertible}: return nil
   var newInst = generateInstance(c, s, m.bindings, n.info)
   newInst.typ.flags.excl tfUnresolved
-  markUsed(n.info, s, c.graph.usageSym)
-  styleCheckUse(n.info, s)
+  markUsed(c.config, n.info, s, c.graph.usageSym)
+  onUse(n.info, s)
   result = newSymNode(newInst, n.info)
 
 proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
   assert n.kind == nkBracketExpr
   for i in 1..sonsLen(n)-1:
-    n.sons[i].typ = semTypeNode(c, n.sons[i], nil)
+    let e = semExpr(c, n.sons[i])
+    if e.typ == nil:
+      localError(c.config, e.info, "expression has no type")
+    else:
+      n.sons[i].typ = e.typ.skipTypes({tyTypeDesc})
   var s = s
   var a = n.sons[0]
   if a.kind == nkSym:
@@ -427,11 +582,11 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
     # number of generic type parameters:
     if safeLen(s.ast.sons[genericParamsPos]) != n.len-1:
       let expected = safeLen(s.ast.sons[genericParamsPos])
-      localError(n.info, errGenerated, "cannot instantiate: " & renderTree(n) &
-         "; got " & $(n.len-1) & " type(s) but expected " & $expected)
+      localError(c.config, n.info, errGenerated, "cannot instantiate: '" & renderTree(n) &
+         "'; got " & $(n.len-1) & " type(s) but expected " & $expected)
       return n
     result = explicitGenericSym(c, n, s)
-    if result == nil: result = explicitGenericInstError(n)
+    if result == nil: result = explicitGenericInstError(c, n)
   elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}:
     # choose the generic proc with the proper number of type parameters.
     # XXX I think this could be improved by reusing sigmatch.paramTypesMatch.
@@ -440,7 +595,7 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
     for i in countup(0, len(a)-1):
       var candidate = a.sons[i].sym
       if candidate.kind in {skProc, skMethod, skConverter,
-                            skIterator}:
+                            skFunc, skIterator}:
         # it suffices that the candidate has the proper number of generic
         # type parameters:
         if safeLen(candidate.ast.sons[genericParamsPos]) == n.len-1:
@@ -449,9 +604,10 @@ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
     # get rid of nkClosedSymChoice if not ambiguous:
     if result.len == 1 and a.kind == nkClosedSymChoice:
       result = result[0]
-    elif result.len == 0: result = explicitGenericInstError(n)
+    elif result.len == 0: result = explicitGenericInstError(c, n)
+    # candidateCount != 1: return explicitGenericInstError(c, n)
   else:
-    result = explicitGenericInstError(n)
+    result = explicitGenericInstError(c, n)
 
 proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
   # Searchs for the fn in the symbol table. If the parameter lists are suitable
@@ -461,7 +617,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
   var call = newNodeI(nkCall, fn.info)
   var hasDistinct = false
   call.add(newIdentNode(fn.name, fn.info))
-  for i in 1.. <fn.typ.n.len:
+  for i in 1..<fn.typ.n.len:
     let param = fn.typ.n.sons[i]
     let t = skipTypes(param.typ, abstractVar-{tyTypeDesc, tyDistinct})
     if t.kind == tyDistinct or param.typ.kind == tyDistinct: hasDistinct = true
@@ -473,7 +629,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
       x = t.baseOfDistinct
     call.add(newNodeIT(nkEmpty, fn.info, x))
   if hasDistinct:
-    var resolved = semOverloadedCall(c, call, call, {fn.kind})
+    var resolved = semOverloadedCall(c, call, call, {fn.kind}, {})
     if resolved != nil:
       result = resolved.sons[0].sym
       if not compareTypes(result.typ.sons[0], fn.typ.sons[0], dcEqIgnoreDistinct):
@@ -481,4 +637,3 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym =
       elif result.magic in {mArrPut, mArrGet}:
         # cannot borrow these magics for now
         result = nil
-
diff --git a/compiler/semdata.nim b/compiler/semdata.nim
index ef23e40f2..735c6f6b1 100644
--- a/compiler/semdata.nim
+++ b/compiler/semdata.nim
@@ -13,8 +13,8 @@ import
   strutils, intsets, options, lexer, ast, astalgo, trees, treetab,
   wordrecg,
   ropes, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math,
-  magicsys, nversion, nimsets, parser, times, passes, rodread, vmdef,
-  modulegraphs
+  magicsys, nversion, nimsets, parser, times, passes, vmdef,
+  modulegraphs, lineinfos
 
 type
   TOptionEntry* = object      # entries to put on a stack for pragma parsing
@@ -22,6 +22,7 @@ type
     defaultCC*: TCallingConvention
     dynlib*: PLib
     notes*: TNoteKinds
+    features*: set[Feature]
     otherPragmas*: PNode      # every pragma can be pushed
 
   POptionEntry* = ref TOptionEntry
@@ -37,32 +38,55 @@ type
                               # in standalone ``except`` and ``finally``
     next*: PProcCon           # used for stacking procedure contexts
     wasForwarded*: bool       # whether the current proc has a separate header
-    bracketExpr*: PNode       # current bracket expression (for ^ support)
+    mappingExists*: bool
     mapping*: TIdTable
 
+  TMatchedConcept* = object
+    candidateType*: PType
+    prev*: ptr TMatchedConcept
+    depth*: int
+
   TInstantiationPair* = object
     genericSym*: PSym
     inst*: PInstantiation
 
   TExprFlag* = enum
     efLValue, efWantIterator, efInTypeof,
-    efWantStmt, efAllowStmt, efDetermineType,
+    efNeedStatic,
+      # Use this in contexts where a static value is mandatory
+    efPreferStatic,
+      # Use this in contexts where a static value could bring more
+      # information, but it's not strictly mandatory. This may become
+      # the default with implicit statics in the future.
+    efPreferNilResult,
+      # Use this if you want a certain result (e.g. static value),
+      # but you don't want to trigger a hard error. For example,
+      # you may be in position to supply a better error message
+      # to the user.
+    efWantStmt, efAllowStmt, efDetermineType, efExplain,
     efAllowDestructor, efWantValue, efOperand, efNoSemCheck,
-    efNoProcvarCheck, efNoEvaluateGeneric, efInCall, efFromHlo
+    efNoEvaluateGeneric, efInCall, efFromHlo,
+    efNoUndeclared
+      # Use this if undeclared identifiers should not raise an error during
+      # overload resolution.
+
   TExprFlags* = set[TExprFlag]
 
   TTypeAttachedOp* = enum
     attachedAsgn,
+    attachedSink,
     attachedDeepCopy,
     attachedDestructor
 
   PContext* = ref TContext
   TContext* = object of TPassContext # a context represents a module
+    enforceVoidContext*: PType
     module*: PSym              # the module sym belonging to the context
     currentScope*: PScope      # current scope
     importTable*: PScope       # scope for all imported symbols
     topLevelScope*: PScope     # scope for all top-level symbols
     p*: PProcCon               # procedure context
+    matchedConcept*: ptr TMatchedConcept # the current concept being matched
     friendModules*: seq[PSym]  # friend modules; may access private data;
                                # this is used so that generic instantiations
                                # can access private object fields
@@ -70,31 +94,32 @@ type
 
     ambiguousSymbols*: IntSet  # ids of all ambiguous symbols (cannot
                                # store this info in the syms themselves!)
-    inTypeClass*: int          # > 0 if we are in a user-defined type class
     inGenericContext*: int     # > 0 if we are in a generic type
+    inStaticContext*: int      # > 0 if we are inside a static: block
     inUnrolledContext*: int    # > 0 if we are unrolling a loop
     compilesContextId*: int    # > 0 if we are in a ``compiles`` magic
     compilesContextIdGenerator*: int
     inGenericInst*: int        # > 0 if we are instantiating a generic
-    converters*: TSymSeq       # sequence of converters
-    patterns*: TSymSeq         # sequence of pattern matchers
+    converters*: seq[PSym]
+    patterns*: seq[PSym]       # sequence of pattern matchers
     optionStack*: seq[POptionEntry]
     symMapping*: TIdTable      # every gensym'ed symbol needs to be mapped
                                # to some new symbol in a generic instantiation
     libs*: seq[PLib]           # all libs used by this module
     semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas
     semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
-    semTryExpr*: proc (c: PContext, n: PNode,flags: TExprFlags = {}): PNode {.nimcall.}
+    semTryExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
     semTryConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.}
     semOperand*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.}
     semConstBoolExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # XXX bite the bullet
     semOverloadedCall*: proc (c: PContext, n, nOrig: PNode,
-                              filter: TSymKinds): PNode {.nimcall.}
+                              filter: TSymKinds, flags: TExprFlags): PNode {.nimcall.}
     semTypeNode*: proc(c: PContext, n: PNode, prev: PType): PType {.nimcall.}
     semInferredLambda*: proc(c: PContext, pt: TIdTable, n: PNode): PNode
     semGenerateInstance*: proc (c: PContext, fn: PSym, pt: TIdTable,
                                 info: TLineInfo): PSym
     includedFiles*: IntSet    # used to detect recursive include files
+    pureEnumFields*: TStrTable   # pure enum fields that can be used unambiguously
     userPragmas*: TStrTable
     evalContext*: PEvalContext
     unknownIdents*: IntSet     # ids of all unknown identifiers to prevent
@@ -112,7 +137,15 @@ type
     signatures*: TStrTable
     recursiveDep*: string
     suggestionsMade*: bool
+    features*: set[Feature]
     inTypeContext*: int
+    typesWithOps*: seq[(PType, PType)] #\
+      # We need to instantiate the type bound ops lazily after
+      # the generic type has been constructed completely. See
+      # tests/destructor/topttree.nim for an example that
+      # would otherwise fail.
+
+template config*(c: PContext): ConfigRef = c.graph.config
 
 proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
   result.genericSym = s
@@ -120,7 +153,7 @@ proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
 
 proc filename*(c: PContext): string =
   # the module's filename
-  return c.module.filename
+  return toFilename(c.config, FileIndex c.module.position)
 
 proc scopeDepth*(c: PContext): int {.inline.} =
   result = if c.currentScope != nil: c.currentScope.depthLevel
@@ -139,7 +172,7 @@ proc pushOwner*(c: PContext; owner: PSym) =
 proc popOwner*(c: PContext) =
   var length = len(c.graph.owners)
   if length > 0: setLen(c.graph.owners, length - 1)
-  else: internalError("popOwner")
+  else: internalError(c.config, "popOwner")
 
 proc lastOptionEntry*(c: PContext): POptionEntry =
   result = c.optionStack[^1]
@@ -147,12 +180,14 @@ proc lastOptionEntry*(c: PContext): POptionEntry =
 proc popProcCon*(c: PContext) {.inline.} = c.p = c.p.next
 
 proc put*(p: PProcCon; key, val: PSym) =
-  if p.mapping.data == nil: initIdTable(p.mapping)
+  if not p.mappingExists:
+    initIdTable(p.mapping)
+    p.mappingExists = true
   #echo "put into table ", key.info
   p.mapping.idTablePut(key, val)
 
 proc get*(p: PProcCon; key: PSym): PSym =
-  if p.mapping.data == nil: return nil
+  if not p.mappingExists: return nil
   result = PSym(p.mapping.idTableGet(key))
 
 proc getGenSym*(c: PContext; s: PSym): PSym =
@@ -175,33 +210,36 @@ proc considerGenSyms*(c: PContext; n: PNode) =
     for i in 0..<n.safeLen:
       considerGenSyms(c, n.sons[i])
 
-proc newOptionEntry*(): POptionEntry =
+proc newOptionEntry*(conf: ConfigRef): POptionEntry =
   new(result)
-  result.options = gOptions
+  result.options = conf.options
   result.defaultCC = ccDefault
   result.dynlib = nil
-  result.notes = gNotes
+  result.notes = conf.notes
 
-proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext =
+proc newContext*(graph: ModuleGraph; module: PSym): PContext =
   new(result)
+  result.enforceVoidContext = PType(kind: tyStmt)
   result.ambiguousSymbols = initIntSet()
   result.optionStack = @[]
   result.libs = @[]
-  result.optionStack.add(newOptionEntry())
+  result.optionStack.add(newOptionEntry(graph.config))
   result.module = module
   result.friendModules = @[module]
   result.converters = @[]
   result.patterns = @[]
   result.includedFiles = initIntSet()
+  initStrTable(result.pureEnumFields)
   initStrTable(result.userPragmas)
   result.generics = @[]
   result.unknownIdents = initIntSet()
-  result.cache = cache
+  result.cache = graph.cache
   result.graph = graph
   initStrTable(result.signatures)
+  result.typesWithOps = @[]
+  result.features = graph.config.features
 
-
-proc inclSym(sq: var TSymSeq, s: PSym) =
+proc inclSym(sq: var seq[PSym], s: PSym) =
   var L = len(sq)
   for i in countup(0, L - 1):
     if sq[i].id == s.id: return
@@ -228,22 +266,38 @@ proc newTypeS*(kind: TTypeKind, c: PContext): PType =
 
 proc makePtrType*(c: PContext, baseType: PType): PType =
   result = newTypeS(tyPtr, c)
-  addSonSkipIntLit(result, baseType.assertNotNil)
+  addSonSkipIntLit(result, baseType)
 
-proc makeVarType*(c: PContext, baseType: PType): PType =
-  if baseType.kind == tyVar:
+proc makeTypeWithModifier*(c: PContext,
+                           modifier: TTypeKind,
+                           baseType: PType): PType =
+  assert modifier in {tyVar, tyLent, tyPtr, tyRef, tyStatic, tyTypeDesc}
+
+  if modifier in {tyVar, tyLent, tyTypeDesc} and baseType.kind == modifier:
     result = baseType
   else:
-    result = newTypeS(tyVar, c)
-    addSonSkipIntLit(result, baseType.assertNotNil)
+    result = newTypeS(modifier, c)
+    addSonSkipIntLit(result, baseType)
+
+proc makeVarType*(c: PContext, baseType: PType; kind = tyVar): PType =
+  if baseType.kind == kind:
+    result = baseType
+  else:
+    result = newTypeS(kind, c)
+    addSonSkipIntLit(result, baseType)
 
 proc makeTypeDesc*(c: PContext, typ: PType): PType =
-  result = newTypeS(tyTypeDesc, c)
-  result.addSonSkipIntLit(typ.assertNotNil)
+  if typ.kind == tyTypeDesc:
+    result = typ
+  else:
+    result = newTypeS(tyTypeDesc, c)
+    result.addSonSkipIntLit(typ)
 
 proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode =
-  let typedesc = makeTypeDesc(c, typ)
-  let sym = newSym(skType, c.cache.idAnon, getCurrOwner(c), info).linkTo(typedesc)
+  let typedesc = newTypeS(tyTypeDesc, c)
+  typedesc.addSonSkipIntLit(assertNotNil(c.config, typ))
+  let sym = newSym(skType, c.cache.idAnon, getCurrOwner(c), info,
+                   c.config.options).linkTo(typedesc)
   return newSymNode(sym, info)
 
 proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
@@ -251,6 +305,10 @@ proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
   assert n != nil
   result.n = n
 
+proc newTypeWithSons*(owner: PSym, kind: TTypeKind, sons: seq[PType]): PType =
+  result = newType(kind, owner)
+  result.sons = sons
+
 proc newTypeWithSons*(c: PContext, kind: TTypeKind,
                       sons: seq[PType]): PType =
   result = newType(kind, getCurrOwner(c))
@@ -259,7 +317,8 @@ proc newTypeWithSons*(c: PContext, kind: TTypeKind,
 proc makeStaticExpr*(c: PContext, n: PNode): PNode =
   result = newNodeI(nkStaticExpr, n.info)
   result.sons = @[n]
-  result.typ = newTypeWithSons(c, tyStatic, @[n.typ])
+  result.typ = if n.typ != nil and n.typ.kind == tyStatic: n.typ
+               else: newTypeWithSons(c, tyStatic, @[n.typ])
 
 proc makeAndType*(c: PContext, t1, t2: PType): PType =
   result = newTypeS(tyAnd, c)
@@ -293,26 +352,23 @@ proc makeNotType*(c: PContext, t1: PType): PType =
   result.flags.incl(t1.flags * {tfHasStatic})
   result.flags.incl tfHasMeta
 
-proc nMinusOne*(n: PNode): PNode =
+proc nMinusOne(c: PContext; n: PNode): PNode =
   result = newNode(nkCall, n.info, @[
-    newSymNode(getSysMagic("<", mUnaryLt)),
-    n])
+    newSymNode(getSysMagic(c.graph, n.info, "pred", mPred)), n])
 
 # Remember to fix the procs below this one when you make changes!
 proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
-  let intType = getSysType(tyInt)
+  let intType = getSysType(c.graph, n.info, tyInt)
   result = newTypeS(tyRange, c)
   result.sons = @[intType]
+  if n.typ != nil and n.typ.n == nil:
+    result.flags.incl tfUnresolved
   result.n = newNode(nkRange, n.info, @[
     newIntTypeNode(nkIntLit, 0, intType),
-    makeStaticExpr(c, n.nMinusOne)])
-
-template rangeHasStaticIf*(t: PType): bool =
-  # this accepts the ranges's node
-  t.n != nil and t.n.len > 1 and t.n[1].kind == nkStaticExpr
+    makeStaticExpr(c, nMinusOne(c, n))])
 
-template getStaticTypeFromRange*(t: PType): PType =
-  t.n[1][0][1].typ
+template rangeHasUnresolvedStatic*(t: PType): bool =
+  tfUnresolved in t.flags
 
 proc errorType*(c: PContext): PType =
   ## creates a type representing an error state
@@ -328,7 +384,8 @@ proc fillTypeS*(dest: PType, kind: TTypeKind, c: PContext) =
   dest.size = - 1
 
 proc makeRangeType*(c: PContext; first, last: BiggestInt;
-                    info: TLineInfo; intType = getSysType(tyInt)): PType =
+                    info: TLineInfo; intType: PType = nil): PType =
+  let intType = if intType != nil: intType else: getSysType(c.graph, info, tyInt)
   var n = newNodeI(nkRange, info)
   addSon(n, newIntTypeNode(nkIntLit, first, intType))
   addSon(n, newIntTypeNode(nkIntLit, last, intType))
@@ -337,24 +394,21 @@ proc makeRangeType*(c: PContext; first, last: BiggestInt;
   addSonSkipIntLit(result, intType) # basetype of range
 
 proc markIndirect*(c: PContext, s: PSym) {.inline.} =
-  if s.kind in {skProc, skConverter, skMethod, skIterator}:
+  if s.kind in {skProc, skFunc, skConverter, skMethod, skIterator}:
     incl(s.flags, sfAddrTaken)
     # XXX add to 'c' for global analysis
 
-proc illFormedAst*(n: PNode) =
-  globalError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))
+proc illFormedAst*(n: PNode; conf: ConfigRef) =
+  globalError(conf, n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))
 
-proc illFormedAstLocal*(n: PNode) =
-  localError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))
+proc illFormedAstLocal*(n: PNode; conf: ConfigRef) =
+  localError(conf, n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))
 
-proc checkSonsLen*(n: PNode, length: int) =
-  if sonsLen(n) != length: illFormedAst(n)
+proc checkSonsLen*(n: PNode, length: int; conf: ConfigRef) =
+  if sonsLen(n) != length: illFormedAst(n, conf)
 
-proc checkMinSonsLen*(n: PNode, length: int) =
-  if sonsLen(n) < length: illFormedAst(n)
+proc checkMinSonsLen*(n: PNode, length: int; conf: ConfigRef) =
+  if sonsLen(n) < length: illFormedAst(n, conf)
 
 proc isTopLevel*(c: PContext): bool {.inline.} =
   result = c.currentScope.depthLevel <= 2
-
-proc experimentalMode*(c: PContext): bool {.inline.} =
-  result = gExperimentalMode or sfExperimental in c.module.flags
diff --git a/compiler/semdestruct.nim b/compiler/semdestruct.nim
deleted file mode 100644
index b09404b39..000000000
--- a/compiler/semdestruct.nim
+++ /dev/null
@@ -1,245 +0,0 @@
-#
-#
-#           The Nim Compiler
-#        (c) Copyright 2013 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-## This module implements destructors.
-
-# included from sem.nim
-
-# special marker values that indicates that we are
-# 1) AnalyzingDestructor: currently analyzing the type for destructor
-# generation (needed for recursive types)
-# 2) DestructorIsTrivial: completed the analysis before and determined
-# that the type has a trivial destructor
-var analyzingDestructor, destructorIsTrivial: PSym
-new(analyzingDestructor)
-new(destructorIsTrivial)
-
-var
-  destructorName = getIdent"destroy_"
-  destructorParam = getIdent"this_"
-  destructorPragma = newIdentNode(getIdent"destructor", unknownLineInfo())
-
-proc instantiateDestructor(c: PContext, typ: PType): PType
-
-proc doDestructorStuff(c: PContext, s: PSym, n: PNode) =
-  var t = s.typ.sons[1].skipTypes({tyVar})
-  if t.kind == tyGenericInvocation:
-    for i in 1 .. <t.sonsLen:
-      if t.sons[i].kind != tyGenericParam:
-        localError(n.info, errDestructorNotGenericEnough)
-        return
-    t = t.base
-  elif t.kind == tyCompositeTypeClass:
-    t = t.base
-    if t.kind != tyGenericBody:
-      localError(n.info, errDestructorNotGenericEnough)
-      return
-
-  t.destructor = s
-  # automatically insert calls to base classes' destructors
-  if n.sons[bodyPos].kind != nkEmpty:
-    for i in countup(0, t.sonsLen - 1):
-      # when inheriting directly from object
-      # there will be a single nil son
-      if t.sons[i] == nil: continue
-      let destructableT = instantiateDestructor(c, t.sons[i])
-      if destructableT != nil:
-        n.sons[bodyPos].addSon(newNode(nkCall, t.sym.info, @[
-            useSym(destructableT.destructor, c.graph.usageSym),
-            n.sons[paramsPos][1][0]]))
-
-proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode
-
-proc destroySym(c: PContext, field: PSym, holder: PNode): PNode =
-  let destructableT = instantiateDestructor(c, field.typ)
-  if destructableT != nil:
-    result = newNode(nkCall, field.info, @[
-      useSym(destructableT.destructor, c.graph.usageSym),
-      newNode(nkDotExpr, field.info, @[holder, useSym(field, c.graph.usageSym)])])
-
-proc destroyCase(c: PContext, n: PNode, holder: PNode): PNode =
-  var nonTrivialFields = 0
-  result = newNode(nkCaseStmt, n.info, @[])
-  # case x.kind
-  result.addSon(newNode(nkDotExpr, n.info, @[holder, n.sons[0]]))
-  for i in countup(1, n.len - 1):
-    # of A, B:
-    let ni = n[i]
-    var caseBranch = newNode(ni.kind, ni.info, ni.sons[0..ni.len-2])
-
-    let stmt = destroyFieldOrFields(c, ni.lastSon, holder)
-    if stmt == nil:
-      caseBranch.addSon(newNode(nkStmtList, ni.info, @[]))
-    else:
-      caseBranch.addSon(stmt)
-      nonTrivialFields += stmt.len
-
-    result.addSon(caseBranch)
-
-  # maybe no fields were destroyed?
-  if nonTrivialFields == 0:
-    result = nil
-
-proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode =
-  template maybeAddLine(e) =
-    let stmt = e
-    if stmt != nil:
-      if result == nil: result = newNode(nkStmtList)
-      result.addSon(stmt)
-
-  case field.kind
-  of nkRecCase:
-    maybeAddLine destroyCase(c, field, holder)
-  of nkSym:
-    maybeAddLine destroySym(c, field.sym, holder)
-  of nkRecList:
-    for son in field:
-      maybeAddLine destroyFieldOrFields(c, son, holder)
-  else:
-    internalAssert false
-
-proc generateDestructor(c: PContext, t: PType): PNode =
-  ## generate a destructor for a user-defined object or tuple type
-  ## returns nil if the destructor turns out to be trivial
-
-  # XXX: This may be true for some C-imported types such as
-  # Tposix_spawnattr
-  if t.n == nil or t.n.sons == nil: return
-  internalAssert t.n.kind == nkRecList
-  let destructedObj = newIdentNode(destructorParam, unknownLineInfo())
-  # call the destructods of all fields
-  result = destroyFieldOrFields(c, t.n, destructedObj)
-  # base classes' destructors will be automatically called by
-  # semProcAux for both auto-generated and user-defined destructors
-
-proc instantiateDestructor(c: PContext, typ: PType): PType =
-  # returns nil if a variable of type `typ` doesn't require a
-  # destructor. Otherwise, returns the type, which holds the
-  # destructor that must be used for the varialbe.
-  # The destructor is either user-defined or automatically
-  # generated by the compiler in a member-wise fashion.
-  var t = typ.skipGenericAlias
-  let typeHoldingUserDefinition = if t.kind == tyGenericInst: t.base else: t
-
-  if typeHoldingUserDefinition.destructor != nil:
-    # XXX: This is not entirely correct for recursive types, but we need
-    # it temporarily to hide the "destroy is already defined" problem
-    if typeHoldingUserDefinition.destructor notin
-        [analyzingDestructor, destructorIsTrivial]:
-      return typeHoldingUserDefinition
-    else:
-      return nil
-
-  t = t.skipTypes({tyGenericInst, tyAlias})
-  case t.kind
-  of tySequence, tyArray, tyOpenArray, tyVarargs:
-    t.destructor = analyzingDestructor
-    if instantiateDestructor(c, t.sons[0]) != nil:
-      t.destructor = getCompilerProc"nimDestroyRange"
-      return t
-    else:
-      return nil
-  of tyTuple, tyObject:
-    t.destructor = analyzingDestructor
-    let generated = generateDestructor(c, t)
-    if generated != nil:
-      internalAssert t.sym != nil
-      var i = t.sym.info
-      let fullDef = newNode(nkProcDef, i, @[
-        newIdentNode(destructorName, i),
-        emptyNode,
-        emptyNode,
-        newNode(nkFormalParams, i, @[
-          emptyNode,
-          newNode(nkIdentDefs, i, @[
-            newIdentNode(destructorParam, i),
-            symNodeFromType(c, makeVarType(c, t), t.sym.info),
-            emptyNode]),
-          ]),
-        newNode(nkPragma, i, @[destructorPragma]),
-        emptyNode,
-        generated
-        ])
-      let semantizedDef = semProc(c, fullDef)
-      t.destructor = semantizedDef[namePos].sym
-      return t
-    else:
-      t.destructor = destructorIsTrivial
-      return nil
-  else:
-    return nil
-
-proc createDestructorCall(c: PContext, s: PSym): PNode =
-  let varTyp = s.typ
-  if varTyp == nil or sfGlobal in s.flags: return
-  let destructableT = instantiateDestructor(c, varTyp)
-  if destructableT != nil:
-    let call = semStmt(c, newNode(nkCall, s.info, @[
-      useSym(destructableT.destructor, c.graph.usageSym),
-      useSym(s, c.graph.usageSym)]))
-    result = newNode(nkDefer, s.info, @[call])
-
-proc insertDestructors(c: PContext,
-                       varSection: PNode): tuple[outer, inner: PNode] =
-  # Accepts a var or let section.
-  #
-  # When a var section has variables with destructors
-  # the var section is split up and finally blocks are inserted
-  # immediately after all "destructable" vars
-  #
-  # In case there were no destrucable variables, the proc returns
-  # (nil, nil) and the enclosing stmt-list requires no modifications.
-  #
-  # Otherwise, after the try blocks are created, the rest of the enclosing
-  # stmt-list should be inserted in the most `inner` such block (corresponding
-  # to the last variable).
-  #
-  # `outer` is a statement list that should replace the original var section.
-  # It will include the new truncated var section followed by the outermost
-  # try block.
-  let totalVars = varSection.sonsLen
-  for j in countup(0, totalVars - 1):
-    let
-      varId = varSection[j][0]
-      varTyp = varId.sym.typ
-      info = varId.info
-
-    if varTyp == nil or sfGlobal in varId.sym.flags: continue
-    let destructableT = instantiateDestructor(c, varTyp)
-
-    if destructableT != nil:
-      var tryStmt = newNodeI(nkTryStmt, info)
-
-      if j < totalVars - 1:
-        var remainingVars = newNodeI(varSection.kind, info)
-        remainingVars.sons = varSection.sons[(j+1)..varSection.len-1]
-        let (outer, inner) = insertDestructors(c, remainingVars)
-        if outer != nil:
-          tryStmt.addSon(outer)
-          result.inner = inner
-        else:
-          result.inner = newNodeI(nkStmtList, info)
-          result.inner.addSon(remainingVars)
-          tryStmt.addSon(result.inner)
-      else:
-        result.inner = newNodeI(nkStmtList, info)
-        tryStmt.addSon(result.inner)
-
-      tryStmt.addSon(
-        newNode(nkFinally, info, @[
-          semStmt(c, newNode(nkCall, info, @[
-            useSym(destructableT.destructor, c.graph.usageSym),
-            useSym(varId.sym, c.graph.usageSym)]))]))
-
-      result.outer = newNodeI(nkStmtList, info)
-      varSection.sons.setLen(j+1)
-      result.outer.addSon(varSection)
-      result.outer.addSon(tryStmt)
-
-      return
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 32f74e050..ddec457a1 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -10,14 +10,29 @@
 # this module does the semantic checking for expressions
 # included from sem.nim
 
+const
+  errExprXHasNoType = "expression '$1' has no type (or is ambiguous)"
+  errXExpectsTypeOrValue = "'$1' expects a type or value"
+  errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable"
+  errXStackEscape = "address of '$1' may not escape its stack frame"
+  errExprHasNoAddress = "expression has no address; maybe use 'unsafeAddr'"
+  errCannotInterpretNodeX = "cannot evaluate '$1'"
+  errNamedExprExpected = "named expression expected"
+  errNamedExprNotAllowed = "named expression not allowed here"
+  errFieldInitTwice = "field initialized twice: '$1'"
+  errUndeclaredFieldX = "undeclared field: '$1'"
+
 proc semTemplateExpr(c: PContext, n: PNode, s: PSym,
                      flags: TExprFlags = {}): PNode =
-  markUsed(n.info, s, c.graph.usageSym)
-  styleCheckUse(n.info, s)
-  pushInfoContext(n.info)
-  result = evalTemplate(n, s, getCurrOwner(c), efFromHlo in flags)
-  if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, s, flags)
-  popInfoContext()
+  markUsed(c.config, n.info, s, c.graph.usageSym)
+  onUse(n.info, s)
+  pushInfoContext(c.config, n.info, s.detailedInfo)
+  result = evalTemplate(n, s, getCurrOwner(c), c.config, efFromHlo in flags)
+  if efNoSemCheck notin flags: result = semAfterMacroCall(c, n, result, s, flags)
+  popInfoContext(c.config)
+
+  # XXX: A more elaborate line info rewrite might be needed
+  result.info = n.info
 
 proc semFieldAccess(c: PContext, n: PNode, flags: TExprFlags = {}): PNode
 
@@ -31,12 +46,12 @@ proc semOperand(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   if result.typ != nil:
     # XXX tyGenericInst here?
     if result.typ.kind == tyProc and tfUnresolved in result.typ.flags:
-      localError(n.info, errProcHasNoConcreteType, n.renderTree)
-    if result.typ.kind == tyVar: result = newDeref(result)
+      localError(c.config, n.info, errProcHasNoConcreteType % n.renderTree)
+    if result.typ.kind in {tyVar, tyLent}: result = newDeref(result)
   elif {efWantStmt, efAllowStmt} * flags != {}:
     result.typ = newTypeS(tyVoid, c)
   else:
-    localError(n.info, errExprXHasNoType,
+    localError(c.config, n.info, errExprXHasNoType %
                renderTree(result, {renderNoComments}))
     result.typ = errorType(c)
 
@@ -46,15 +61,12 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     # do not produce another redundant error message:
     #raiseRecoverableError("")
     result = errorNode(c, n)
-  if result.typ == nil or result.typ == enforceVoidContext:
-    # we cannot check for 'void' in macros ...
-    localError(n.info, errExprXHasNoType,
-               renderTree(result, {renderNoComments}))
+  if result.typ == nil or result.typ == c.enforceVoidContext:
+    localError(c.config, n.info, errExprXHasNoType %
+                renderTree(result, {renderNoComments}))
     result.typ = errorType(c)
   else:
-    if efNoProcvarCheck notin flags: semProcvarCheck(c, result)
-    if result.typ.kind == tyVar: result = newDeref(result)
-    semDestructorCheck(c, result, flags)
+    if result.typ.kind in {tyVar, tyLent}: result = newDeref(result)
 
 proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   result = semExpr(c, n, flags)
@@ -62,20 +74,17 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     # do not produce another redundant error message:
     result = errorNode(c, n)
   if result.typ == nil:
-    localError(n.info, errExprXHasNoType,
+    localError(c.config, n.info, errExprXHasNoType %
                renderTree(result, {renderNoComments}))
     result.typ = errorType(c)
-  else:
-    semProcvarCheck(c, result)
-    semDestructorCheck(c, result, flags)
 
 proc semSymGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode =
   result = symChoice(c, n, s, scClosed)
 
-proc inlineConst(n: PNode, s: PSym): PNode {.inline.} =
+proc inlineConst(c: PContext, n: PNode, s: PSym): PNode {.inline.} =
   result = copyTree(s.ast)
   if result.isNil:
-    localError(n.info, "constant of type '" & typeToString(s.typ) & "' has no value")
+    localError(c.config, n.info, "constant of type '" & typeToString(s.typ) & "' has no value")
     result = newSymNode(s)
   else:
     result.typ = s.typ
@@ -104,8 +113,12 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus =
     if castDest.kind notin IntegralTypes+{tyRange}:
       result = convNotNeedeed
     return
+  # Save for later
   var d = skipTypes(castDest, abstractVar)
-  var s = skipTypes(src, abstractVar-{tyTypeDesc})
+  var s = src
+  if s.kind in tyUserTypeClasses and s.isResolvedUserTypeClass:
+    s = s.lastSon
+  s = skipTypes(s, abstractVar-{tyTypeDesc})
   var pointers = 0
   while (d != nil) and (d.kind in {tyPtr, tyRef}) and (d.kind == s.kind):
     d = d.lastSon
@@ -123,12 +136,12 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus =
     # we use d, s here to speed up that operation a bit:
     case cmpTypes(c, d, s)
     of isNone, isGeneric:
-      if not compareTypes(castDest, src, dcEqIgnoreDistinct):
+      if not compareTypes(castDest.skipTypes(abstractVar), src, dcEqIgnoreDistinct):
         result = convNotLegal
     else:
       discard
 
-proc isCastable(dst, src: PType): bool =
+proc isCastable(conf: ConfigRef; dst, src: PType): bool =
   ## Checks whether the source type can be cast to the destination type.
   ## Casting is very unrestrictive; casts are allowed as long as
   ## castDest.size >= src.size, and typeAllowed(dst, skParam)
@@ -136,26 +149,33 @@ proc isCastable(dst, src: PType): bool =
   #  castableTypeKinds = {tyInt, tyPtr, tyRef, tyCstring, tyString,
   #                       tySequence, tyPointer, tyNil, tyOpenArray,
   #                       tyProc, tySet, tyEnum, tyBool, tyChar}
+  let src = src.skipTypes(tyUserTypeClasses)
   if skipTypes(dst, abstractInst-{tyOpenArray}).kind == tyOpenArray:
     return false
   if skipTypes(src, abstractInst-{tyTypeDesc}).kind == tyTypeDesc:
     return false
 
   var dstSize, srcSize: BiggestInt
-  dstSize = computeSize(dst)
-  srcSize = computeSize(src)
+  dstSize = computeSize(conf, dst)
+  srcSize = computeSize(conf, src)
+  if dstSize == -3 or srcSize == -3: # szUnknownSize
+    # The Nim compiler can't detect if it's legal or not.
+    # Just assume the programmer knows what he is doing.
+    return true
   if dstSize < 0:
     result = false
   elif srcSize < 0:
     result = false
   elif typeAllowed(dst, skParam) != nil:
     result = false
+  elif dst.kind == tyProc and dst.callConv == ccClosure:
+    result = src.kind == tyProc and src.callConv == ccClosure
   else:
     result = (dstSize >= srcSize) or
         (skipTypes(dst, abstractInst).kind in IntegralTypes) or
         (skipTypes(src, abstractInst-{tyTypeDesc}).kind in IntegralTypes)
   if result and src.kind == tyNil:
-    result = dst.size <= platform.ptrSize
+    result = dst.size <= conf.target.ptrSize
 
 proc isSymChoice(n: PNode): bool {.inline.} =
   result = n.kind in nkSymChoices
@@ -172,15 +192,46 @@ proc maybeLiftType(t: var PType, c: PContext, info: TLineInfo) =
 
 proc semConv(c: PContext, n: PNode): PNode =
   if sonsLen(n) != 2:
-    localError(n.info, errConvNeedsOneArg)
+    localError(c.config, n.info, "a type conversion takes exactly one argument")
     return n
 
   result = newNodeI(nkConv, n.info)
-  var targetType = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc})
+
+  var targetType = semTypeNode(c, n.sons[0], nil)
+  if targetType.kind == tyTypeDesc:
+    internalAssert c.config, targetType.len > 0
+    if targetType.base.kind == tyNone:
+      return semTypeOf(c, n)
+    else:
+      targetType = targetType.base
+  elif targetType.kind == tyStatic:
+    var evaluated = semStaticExpr(c, n[1])
+    if evaluated.kind == nkType or evaluated.typ.kind == tyTypeDesc:
+      result = n
+      result.typ = c.makeTypeDesc semStaticType(c, evaluated, nil)
+      return
+    elif targetType.base.kind == tyNone:
+      return evaluated
+    else:
+      targetType = targetType.base
+
   maybeLiftType(targetType, c, n[0].info)
+
+  if targetType.kind in {tySink, tyLent}:
+    let baseType = semTypeNode(c, n.sons[1], nil).skipTypes({tyTypeDesc})
+    let t = newTypeS(targetType.kind, c)
+    t.rawAddSonNoPropagationOfTypeFlags baseType
+    result = newNodeI(nkType, n.info)
+    result.typ = makeTypeDesc(c, t)
+    return
+
   result.addSon copyTree(n.sons[0])
-  var op = semExprWithType(c, n.sons[1])
 
+  # special case to make MyObject(x = 3) produce a nicer error message:
+  if n[1].kind == nkExprEqExpr and
+      targetType.skipTypes(abstractPtrs).kind == tyObject:
+    localError(c.config, n.info, "object contruction uses ':', not '='")
+  var op = semExprWithType(c, n.sons[1])
   if targetType.isMetaType:
     let final = inferWithMetatype(c, targetType, op, true)
     result.addSon final
@@ -188,6 +239,8 @@ proc semConv(c: PContext, n: PNode): PNode =
     return
 
   result.typ = targetType
+  # XXX op is overwritten later on, this is likely added too early
+  # here or needs to be overwritten too then.
   addSon(result, op)
 
   if not isSymChoice(op):
@@ -197,35 +250,38 @@ proc semConv(c: PContext, n: PNode): PNode =
       # handle SomeProcType(SomeGenericProc)
       if op.kind == nkSym and op.sym.isGenericRoutine:
         result.sons[1] = fitNode(c, result.typ, result.sons[1], result.info)
-      elif op.kind == nkPar and targetType.kind == tyTuple:
+      elif op.kind in {nkPar, nkTupleConstr} and targetType.kind == tyTuple:
         op = fitNode(c, targetType, op, result.info)
     of convNotNeedeed:
-      message(n.info, hintConvFromXtoItselfNotNeeded, result.typ.typeToString)
+      message(c.config, n.info, hintConvFromXtoItselfNotNeeded, result.typ.typeToString)
     of convNotLegal:
       result = fitNode(c, result.typ, result.sons[1], result.info)
       if result == nil:
-        localError(n.info, errGenerated, msgKindToString(errIllegalConvFromXtoY)%
+        localError(c.config, n.info, "illegal conversion from '$1' to '$2'" %
           [op.typ.typeToString, result.typ.typeToString])
   else:
     for i in countup(0, sonsLen(op) - 1):
       let it = op.sons[i]
       let status = checkConvertible(c, result.typ, it.typ)
       if status in {convOK, convNotNeedeed}:
-        markUsed(n.info, it.sym, c.graph.usageSym)
-        styleCheckUse(n.info, it.sym)
+        markUsed(c.config, n.info, it.sym, c.graph.usageSym)
+        onUse(n.info, it.sym)
         markIndirect(c, it.sym)
         return it
     errorUseQualifier(c, n.info, op.sons[0].sym)
 
 proc semCast(c: PContext, n: PNode): PNode =
   ## Semantically analyze a casting ("cast[type](param)")
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   let targetType = semTypeNode(c, n.sons[0], nil)
   let castedExpr = semExprWithType(c, n.sons[1])
   if tfHasMeta in targetType.flags:
-    localError(n.sons[0].info, errCastToANonConcreteType, $targetType)
-  if not isCastable(targetType, castedExpr.typ):
-    localError(n.info, errExprCannotBeCastToX, $targetType)
+    localError(c.config, n.sons[0].info, "cannot cast to a non concrete type: '$1'" % $targetType)
+  if not isCastable(c.config, targetType, castedExpr.typ):
+    let tar = $targetType
+    let alt = typeToString(targetType, preferDesc)
+    let msg = if tar != alt: tar & "=" & alt else: tar
+    localError(c.config, n.info, "expression cannot be cast to " & msg)
   result = newNodeI(nkCast, n.info)
   result.typ = targetType
   addSon(result, copyTree(n.sons[0]))
@@ -235,127 +291,132 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode =
   const
     opToStr: array[mLow..mHigh, string] = ["low", "high"]
   if sonsLen(n) != 2:
-    localError(n.info, errXExpectsTypeOrValue, opToStr[m])
+    localError(c.config, n.info, errXExpectsTypeOrValue % opToStr[m])
   else:
     n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType})
-    var typ = skipTypes(n.sons[1].typ, abstractVarRange +
-                                       {tyTypeDesc, tyFieldAccessor})
+    var typ = skipTypes(n.sons[1].typ, abstractVarRange + {tyTypeDesc, tyUserTypeClassInst})
     case typ.kind
     of tySequence, tyString, tyCString, tyOpenArray, tyVarargs:
-      n.typ = getSysType(tyInt)
+      n.typ = getSysType(c.graph, n.info, tyInt)
     of tyArray:
       n.typ = typ.sons[0] # indextype
     of tyInt..tyInt64, tyChar, tyBool, tyEnum, tyUInt8, tyUInt16, tyUInt32:
       # do not skip the range!
-      n.typ = n.sons[1].typ.skipTypes(abstractVar + {tyFieldAccessor})
+      n.typ = n.sons[1].typ.skipTypes(abstractVar)
     of tyGenericParam:
       # prepare this for resolving in semtypinst:
       # we must use copyTree here in order to avoid creating a cycle
       # that could easily turn into an infinite recursion in semtypinst
       n.typ = makeTypeFromExpr(c, n.copyTree)
     else:
-      localError(n.info, errInvalidArgForX, opToStr[m])
-  result = n
-
-proc semSizeof(c: PContext, n: PNode): PNode =
-  if sonsLen(n) != 2:
-    localError(n.info, errXExpectsTypeOrValue, "sizeof")
-  else:
-    n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType})
-    #restoreOldStyleType(n.sons[1])
-  n.typ = getSysType(tyInt)
-  result = n
-
-proc semOf(c: PContext, n: PNode): PNode =
-  if sonsLen(n) == 3:
-    n.sons[1] = semExprWithType(c, n.sons[1])
-    n.sons[2] = semExprWithType(c, n.sons[2], {efDetermineType})
-    #restoreOldStyleType(n.sons[1])
-    #restoreOldStyleType(n.sons[2])
-    let a = skipTypes(n.sons[1].typ, abstractPtrs)
-    let b = skipTypes(n.sons[2].typ, abstractPtrs)
-    let x = skipTypes(n.sons[1].typ, abstractPtrs-{tyTypeDesc})
-    let y = skipTypes(n.sons[2].typ, abstractPtrs-{tyTypeDesc})
-
-    if x.kind == tyTypeDesc or y.kind != tyTypeDesc:
-      localError(n.info, errXExpectsObjectTypes, "of")
-    elif b.kind != tyObject or a.kind != tyObject:
-      localError(n.info, errXExpectsObjectTypes, "of")
-    else:
-      let diff = inheritanceDiff(a, b)
-      # | returns: 0 iff `a` == `b`
-      # | returns: -x iff `a` is the x'th direct superclass of `b`
-      # | returns: +x iff `a` is the x'th direct subclass of `b`
-      # | returns: `maxint` iff `a` and `b` are not compatible at all
-      if diff <= 0:
-        # optimize to true:
-        message(n.info, hintConditionAlwaysTrue, renderTree(n))
-        result = newIntNode(nkIntLit, 1)
-        result.info = n.info
-        result.typ = getSysType(tyBool)
-        return result
-      elif diff == high(int):
-        localError(n.info, errXcanNeverBeOfThisSubtype, typeToString(a))
-  else:
-    localError(n.info, errXExpectsTwoArguments, "of")
-  n.typ = getSysType(tyBool)
+      localError(c.config, n.info, "invalid argument for: " & opToStr[m])
   result = n
 
-proc isOpImpl(c: PContext, n: PNode): PNode =
-  internalAssert n.sonsLen == 3 and
-    n[1].typ != nil and n[1].typ.kind == tyTypeDesc and
+proc fixupStaticType(c: PContext, n: PNode) =
+  # This proc can be applied to evaluated expressions to assign
+  # them a static type.
+  #
+  # XXX: with implicit static, this should not be necessary,
+  # because the output type of operations such as `semConstExpr`
+  # should be a static type (as well as the type of any other
+  # expression that can be implicitly evaluated). For now, we
+  # apply this measure only in code that is enlightened to work
+  # with static types.
+  if n.typ.kind != tyStatic:
+    n.typ = newTypeWithSons(getCurrOwner(c), tyStatic, @[n.typ])
+    n.typ.n = n # XXX: cycles like the one here look dangerous.
+                # Consider using `n.copyTree`
+
+proc isOpImpl(c: PContext, n: PNode, flags: TExprFlags): PNode =
+  internalAssert c.config,
+    n.sonsLen == 3 and
+    n[1].typ != nil and
     n[2].kind in {nkStrLit..nkTripleStrLit, nkType}
 
-  let t1 = n[1].typ.skipTypes({tyTypeDesc, tyFieldAccessor})
+  var
+    res = false
+    t1 = n[1].typ
+    t2 = n[2].typ
+
+  if t1.kind == tyTypeDesc and t2.kind != tyTypeDesc:
+    t1 = t1.base
 
   if n[2].kind in {nkStrLit..nkTripleStrLit}:
     case n[2].strVal.normalize
     of "closure":
       let t = skipTypes(t1, abstractRange)
-      result = newIntNode(nkIntLit, ord(t.kind == tyProc and
-                                        t.callConv == ccClosure and
-                                        tfIterator notin t.flags))
+      res = t.kind == tyProc and
+            t.callConv == ccClosure and
+            tfIterator notin t.flags
+    of "iterator":
+      let t = skipTypes(t1, abstractRange)
+      res = t.kind == tyProc and
+            t.callConv == ccClosure and
+            tfIterator in t.flags
     else:
-      result = newIntNode(nkIntLit, 0)
+      res = false
   else:
-    var t2 = n[2].typ.skipTypes({tyTypeDesc})
     maybeLiftType(t2, c, n.info)
     var m: TCandidate
     initCandidate(c, m, t2)
-    let match = typeRel(m, t2, t1) >= isSubtype # isNone
-    result = newIntNode(nkIntLit, ord(match))
+    if efExplain in flags:
+      m.diagnostics = @[]
+      m.diagnosticsEnabled = true
+    res = typeRel(m, t2, t1) >= isSubtype # isNone
 
+  result = newIntNode(nkIntLit, ord(res))
   result.typ = n.typ
 
-proc semIs(c: PContext, n: PNode): PNode =
+proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode =
   if sonsLen(n) != 3:
-    localError(n.info, errXExpectsTwoArguments, "is")
+    localError(c.config, n.info, "'is' operator takes 2 arguments")
 
+  let boolType = getSysType(c.graph, n.info, tyBool)
   result = n
-  n.typ = getSysType(tyBool)
+  n.typ = boolType
+  var liftLhs = true
 
   n.sons[1] = semExprWithType(c, n[1], {efDetermineType, efWantIterator})
   if n[2].kind notin {nkStrLit..nkTripleStrLit}:
     let t2 = semTypeNode(c, n[2], nil)
     n.sons[2] = newNodeIT(nkType, n[2].info, t2)
+    if t2.kind == tyStatic:
+      let evaluated = tryConstExpr(c, n[1])
+      if evaluated != nil:
+        c.fixupStaticType(evaluated)
+        n[1] = evaluated
+      else:
+        result = newIntNode(nkIntLit, 0)
+        result.typ = boolType
+        return
+    elif t2.kind == tyTypeDesc and
+        (t2.base.kind == tyNone or tfExplicit in t2.flags):
+      # When the right-hand side is an explicit type, we must
+      # not allow regular values to be matched against the type:
+      liftLhs = false
+  else:
+    n.sons[2] = semExpr(c, n[2])
 
-  let lhsType = n[1].typ
+  var lhsType = n[1].typ
   if lhsType.kind != tyTypeDesc:
-    n.sons[1] = makeTypeSymNode(c, lhsType, n[1].info)
-  elif lhsType.base.kind == tyNone:
-    # this is a typedesc variable, leave for evals
-    return
+    if liftLhs:
+      n[1] = makeTypeSymNode(c, lhsType, n[1].info)
+      lhsType = n[1].typ
+  else:
+    internalAssert c.config, lhsType.base.kind != tyNone
+    if c.inGenericContext > 0 and lhsType.base.containsGenericType:
+      # BUGFIX: don't evaluate this too early: ``T is void``
+      return
 
-  # BUGFIX: don't evaluate this too early: ``T is void``
-  if not n[1].typ.base.containsGenericType: result = isOpImpl(c, n)
+  result = isOpImpl(c, n, flags)
 
 proc semOpAux(c: PContext, n: PNode) =
   const flags = {efDetermineType}
   for i in countup(1, n.sonsLen-1):
     var a = n.sons[i]
     if a.kind == nkExprEqExpr and sonsLen(a) == 2:
-      var info = a.sons[0].info
-      a.sons[0] = newIdentNode(considerQuotedIdent(a.sons[0]), info)
+      let info = a.sons[0].info
+      a.sons[0] = newIdentNode(considerQuotedIdent(c, a.sons[0], a), info)
       a.sons[1] = semExprWithType(c, a.sons[1], flags)
       a.typ = a.sons[1].typ
     else:
@@ -363,7 +424,7 @@ proc semOpAux(c: PContext, n: PNode) =
 
 proc overloadedCallOpr(c: PContext, n: PNode): PNode =
   # quick check if there is *any* () operator overloaded:
-  var par = getIdent("()")
+  var par = getIdent(c.cache, "()")
   if searchInScopes(c, par) == nil:
     result = nil
   else:
@@ -372,34 +433,34 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
     for i in countup(0, sonsLen(n) - 1): addSon(result, n.sons[i])
     result = semExpr(c, result)
 
-proc changeType(n: PNode, newType: PType, check: bool) =
+proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
   case n.kind
   of nkCurly, nkBracket:
     for i in countup(0, sonsLen(n) - 1):
-      changeType(n.sons[i], elemType(newType), check)
-  of nkPar:
-    let tup = newType.skipTypes({tyGenericInst, tyAlias})
+      changeType(c, n.sons[i], elemType(newType), check)
+  of nkPar, nkTupleConstr:
+    let tup = newType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct})
     if tup.kind != tyTuple:
       if tup.kind == tyObject: return
-      globalError(n.info, "no tuple type for constructor")
+      globalError(c.config, n.info, "no tuple type for constructor")
     elif sonsLen(n) > 0 and n.sons[0].kind == nkExprColonExpr:
       # named tuple?
       for i in countup(0, sonsLen(n) - 1):
         var m = n.sons[i].sons[0]
         if m.kind != nkSym:
-          globalError(m.info, "invalid tuple constructor")
+          globalError(c.config, m.info, "invalid tuple constructor")
           return
         if tup.n != nil:
           var f = getSymFromList(tup.n, m.sym.name)
           if f == nil:
-            globalError(m.info, "unknown identifier: " & m.sym.name.s)
+            globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s)
             return
-          changeType(n.sons[i].sons[1], f.typ, check)
+          changeType(c, n.sons[i].sons[1], f.typ, check)
         else:
-          changeType(n.sons[i].sons[1], tup.sons[i], check)
+          changeType(c, n.sons[i].sons[1], tup.sons[i], check)
     else:
       for i in countup(0, sonsLen(n) - 1):
-        changeType(n.sons[i], tup.sons[i], check)
+        changeType(c, n.sons[i], tup.sons[i], check)
         when false:
           var m = n.sons[i]
           var a = newNodeIT(nkExprColonExpr, m.info, newType.sons[i])
@@ -409,8 +470,8 @@ proc changeType(n: PNode, newType: PType, check: bool) =
   of nkCharLit..nkUInt64Lit:
     if check and n.kind != nkUInt64Lit:
       let value = n.intVal
-      if value < firstOrd(newType) or value > lastOrd(newType):
-        localError(n.info, errGenerated, "cannot convert " & $value &
+      if value < firstOrd(c.config, newType) or value > lastOrd(c.config, newType):
+        localError(c.config, n.info, "cannot convert " & $value &
                                          " to " & typeToString(newType))
   else: discard
   n.typ = newType
@@ -421,7 +482,7 @@ proc arrayConstrType(c: PContext, n: PNode): PType =
   if sonsLen(n) == 0:
     rawAddSon(typ, newTypeS(tyEmpty, c)) # needs an empty basetype!
   else:
-    var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyOrdinal, tyAlias})
+    var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
     addSonSkipIntLit(typ, t)
   typ.sons[0] = makeRangeType(c, 0, sonsLen(n) - 1, n.info)
   result = typ
@@ -435,7 +496,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
   else:
     var x = n.sons[0]
     var lastIndex: BiggestInt = 0
-    var indexType = getSysType(tyInt)
+    var indexType = getSysType(c.graph, n.info, tyInt)
     if x.kind == nkExprColonExpr and sonsLen(x) == 2:
       var idx = semConstExpr(c, x.sons[0])
       lastIndex = getOrdValue(idx)
@@ -445,14 +506,14 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
     let yy = semExprWithType(c, x)
     var typ = yy.typ
     addSon(result, yy)
-    #var typ = skipTypes(result.sons[0].typ, {tyGenericInst, tyVar, tyOrdinal})
+    #var typ = skipTypes(result.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal})
     for i in countup(1, sonsLen(n) - 1):
       x = n.sons[i]
       if x.kind == nkExprColonExpr and sonsLen(x) == 2:
         var idx = semConstExpr(c, x.sons[0])
         idx = fitNode(c, indexType, idx, x.info)
         if lastIndex+1 != getOrdValue(idx):
-          localError(x.info, errInvalidOrderInArrayConstructor)
+          localError(c.config, x.info, "invalid order in array constructor")
         x = x.sons[1]
 
       let xx = semExprWithType(c, x, flags*{efAllowDestructor})
@@ -462,12 +523,12 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
       #addSon(result, fitNode(c, typ, n.sons[i]))
       inc(lastIndex)
     addSonSkipIntLit(result.typ, typ)
-    for i in 0 .. <result.len:
+    for i in 0 ..< result.len:
       result.sons[i] = fitNode(c, typ, result.sons[i], result.sons[i].info)
   result.typ.sons[0] = makeRangeType(c, 0, sonsLen(result) - 1, n.info)
 
 proc fixAbstractType(c: PContext, n: PNode) =
-  for i in 1 .. < n.len:
+  for i in 1 ..< n.len:
     let it = n.sons[i]
     # do not get rid of nkHiddenSubConv for OpenArrays, the codegen needs it:
     if it.kind == nkHiddenSubConv and
@@ -476,22 +537,52 @@ proc fixAbstractType(c: PContext, n: PNode) =
             {tyNil, tyTuple, tySet} or it[1].isArrayConstr:
         var s = skipTypes(it.typ, abstractVar)
         if s.kind != tyExpr:
-          changeType(it.sons[1], s, check=true)
+          changeType(c, it.sons[1], s, check=true)
         n.sons[i] = it.sons[1]
 
 proc isAssignable(c: PContext, n: PNode; isUnsafeAddr=false): TAssignableResult =
   result = parampatterns.isAssignable(c.p.owner, n, isUnsafeAddr)
 
+proc isUnresolvedSym(s: PSym): bool =
+  return s.kind == skGenericParam or
+         tfInferrableStatic in s.typ.flags or
+         (s.kind == skParam and s.typ.isMetaType) or
+         (s.kind == skType and
+          s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {})
+
+proc hasUnresolvedArgs(c: PContext, n: PNode): bool =
+  # Checks whether an expression depends on generic parameters that
+  # don't have bound values yet. E.g. this could happen in situations
+  # such as:
+  #  type Slot[T] = array[T.size, byte]
+  #  proc foo[T](x: default(T))
+  #
+  # Both static parameter and type parameters can be unresolved.
+  case n.kind
+  of nkSym:
+    return isUnresolvedSym(n.sym)
+  of nkIdent, nkAccQuoted:
+    let ident = considerQuotedIdent(c, n)
+    let sym = searchInScopes(c, ident)
+    if sym != nil:
+      return isUnresolvedSym(sym)
+    else:
+      return false
+  else:
+    for i in 0..<n.safeLen:
+      if hasUnresolvedArgs(c, n.sons[i]): return true
+    return false
+
 proc newHiddenAddrTaken(c: PContext, n: PNode): PNode =
-  if n.kind == nkHiddenDeref and not (gCmd == cmdCompileToCpp or
+  if n.kind == nkHiddenDeref and not (c.config.cmd == cmdCompileToCpp or
                                       sfCompileToCpp in c.module.flags):
-    checkSonsLen(n, 1)
+    checkSonsLen(n, 1, c.config)
     result = n.sons[0]
   else:
     result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ))
     addSon(result, n)
     if isAssignable(c, n) notin {arLValue, arLocalLValue}:
-      localError(n.info, errVarForOutParamNeeded)
+      localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n))
 
 proc analyseIfAddressTaken(c: PContext, n: PNode): PNode =
   result = n
@@ -499,35 +590,36 @@ proc analyseIfAddressTaken(c: PContext, n: PNode): PNode =
   of nkSym:
     # n.sym.typ can be nil in 'check' mode ...
     if n.sym.typ != nil and
-        skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind != tyVar:
+        skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
       incl(n.sym.flags, sfAddrTaken)
       result = newHiddenAddrTaken(c, n)
   of nkDotExpr:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     if n.sons[1].kind != nkSym:
-      internalError(n.info, "analyseIfAddressTaken")
+      internalError(c.config, n.info, "analyseIfAddressTaken")
       return
-    if skipTypes(n.sons[1].sym.typ, abstractInst-{tyTypeDesc}).kind != tyVar:
+    if skipTypes(n.sons[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
       incl(n.sons[1].sym.flags, sfAddrTaken)
       result = newHiddenAddrTaken(c, n)
   of nkBracketExpr:
-    checkMinSonsLen(n, 1)
-    if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind != tyVar:
+    checkMinSonsLen(n, 1, c.config)
+    if skipTypes(n.sons[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}:
       if n.sons[0].kind == nkSym: incl(n.sons[0].sym.flags, sfAddrTaken)
       result = newHiddenAddrTaken(c, n)
   else:
     result = newHiddenAddrTaken(c, n)
 
 proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
-  checkMinSonsLen(n, 1)
+  checkMinSonsLen(n, 1, c.config)
   const
     FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl,
       mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap,
-      mAppendSeqElem, mNewSeq, mReset, mShallowCopy, mDeepCopy}
+      mAppendSeqElem, mNewSeq, mReset, mShallowCopy, mDeepCopy, mMove,
+      mWasMoved}
 
   # get the real type of the callee
   # it may be a proc var with a generic alias type, so we skip over them
-  var t = n.sons[0].typ.skipTypes({tyGenericInst, tyAlias})
+  var t = n.sons[0].typ.skipTypes({tyGenericInst, tyAlias, tySink})
 
   if n.sons[0].kind == nkSym and n.sons[0].sym.magic in FakeVarParams:
     # BUGFIX: check for L-Value still needs to be done for the arguments!
@@ -535,16 +627,25 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) =
     for i in countup(1, sonsLen(n) - 1):
       if i < sonsLen(t) and t.sons[i] != nil and
           skipTypes(t.sons[i], abstractInst-{tyTypeDesc}).kind == tyVar:
-        if isAssignable(c, n.sons[i]) notin {arLValue, arLocalLValue}:
-          if n.sons[i].kind != nkHiddenAddr:
-            localError(n.sons[i].info, errVarForOutParamNeeded)
+        let it = n[i]
+        if isAssignable(c, it) notin {arLValue, arLocalLValue}:
+          if it.kind != nkHiddenAddr:
+            localError(c.config, it.info, errVarForOutParamNeededX % $it)
+    # bug #5113: disallow newSeq(result) where result is a 'var T':
+    if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}:
+      var arg = n[1] #.skipAddr
+      if arg.kind == nkHiddenDeref: arg = arg[0]
+      if arg.kind == nkSym and arg.sym.kind == skResult and
+          arg.typ.skipTypes(abstractInst).kind in {tyVar, tyLent}:
+        localError(c.config, n.info, errXStackEscape % renderTree(n[1], {renderNoComments}))
+
     return
   for i in countup(1, sonsLen(n) - 1):
+    let n = if n.kind == nkHiddenDeref: n[0] else: n
     if n.sons[i].kind == nkHiddenCallConv:
       # we need to recurse explicitly here as converters can create nested
       # calls and then they wouldn't be analysed otherwise
       analyseIfAddressTakenInCall(c, n.sons[i])
-    semProcvarCheck(c, n.sons[i])
     if i < sonsLen(t) and
         skipTypes(t.sons[i], abstractInst-{tyTypeDesc}).kind == tyVar:
       if n.sons[i].kind != nkHiddenAddr:
@@ -565,18 +666,17 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
     var call = newNodeIT(nkCall, n.info, n.typ)
     call.add(n.sons[0])
     var allConst = true
-    for i in 1 .. < n.len:
-      var a = getConstExpr(c.module, n.sons[i])
+    for i in 1 ..< n.len:
+      var a = getConstExpr(c.module, n.sons[i], c.graph)
       if a == nil:
         allConst = false
         a = n.sons[i]
         if a.kind == nkHiddenStdConv: a = a.sons[1]
       call.add(a)
     if allConst:
-      result = semfold.getConstExpr(c.module, call)
+      result = semfold.getConstExpr(c.module, call, c.graph)
       if result.isNil: result = n
       else: return result
-    result.typ = semfold.getIntervalType(callee.magic, call)
 
   block maybeLabelAsStatic:
     # XXX: temporary work-around needed for tlateboundstatic.
@@ -584,7 +684,7 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
     # done until we have a more robust infrastructure for
     # implicit statics.
     if n.len > 1:
-      for i in 1 .. <n.len:
+      for i in 1 ..< n.len:
         # see bug #2113, it's possible that n[i].typ for errornous code:
         if n[i].typ.isNil or n[i].typ.kind != tyStatic or
             tfUnresolved notin n[i].typ.flags:
@@ -596,39 +696,40 @@ proc evalAtCompileTime(c: PContext, n: PNode): PNode =
   if {sfNoSideEffect, sfCompileTime} * callee.flags != {} and
      {sfForward, sfImportc} * callee.flags == {} and n.typ != nil:
     if sfCompileTime notin callee.flags and
-        optImplicitStatic notin gOptions: return
+        optImplicitStatic notin c.config.options: return
 
     if callee.magic notin ctfeWhitelist: return
-    if callee.kind notin {skProc, skConverter} or callee.isGenericRoutine:
+    if callee.kind notin {skProc, skFunc, skConverter} or callee.isGenericRoutine:
       return
 
     if n.typ != nil and typeAllowed(n.typ, skConst) != nil: return
 
     var call = newNodeIT(nkCall, n.info, n.typ)
     call.add(n.sons[0])
-    for i in 1 .. < n.len:
-      let a = getConstExpr(c.module, n.sons[i])
+    for i in 1 ..< n.len:
+      let a = getConstExpr(c.module, n.sons[i], c.graph)
       if a == nil: return n
       call.add(a)
     #echo "NOW evaluating at compile time: ", call.renderTree
     if sfCompileTime in callee.flags:
-      result = evalStaticExpr(c.module, c.cache, call, c.p.owner)
+      result = evalStaticExpr(c.module, c.graph, call, c.p.owner)
       if result.isNil:
-        localError(n.info, errCannotInterpretNodeX, renderTree(call))
+        localError(c.config, n.info, errCannotInterpretNodeX % renderTree(call))
       else: result = fixupTypeAfterEval(c, result, n)
     else:
-      result = evalConstExpr(c.module, c.cache, call)
+      result = evalConstExpr(c.module, c.graph, call)
       if result.isNil: result = n
       else: result = fixupTypeAfterEval(c, result, n)
     #if result != n:
     #  echo "SUCCESS evaluated at compile time: ", call.renderTree
 
 proc semStaticExpr(c: PContext, n: PNode): PNode =
-  let a = semExpr(c, n.sons[0])
-  result = evalStaticExpr(c.module, c.cache, a, c.p.owner)
+  let a = semExpr(c, n)
+  if a.findUnresolvedStatic != nil: return a
+  result = evalStaticExpr(c.module, c.graph, a, c.p.owner)
   if result.isNil:
-    localError(n.info, errCannotInterpretNodeX, renderTree(n))
-    result = emptyNode
+    localError(c.config, n.info, errCannotInterpretNodeX % renderTree(n))
+    result = c.graph.emptyNode
   else:
     result = fixupTypeAfterEval(c, result, a)
 
@@ -640,21 +741,21 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode,
     # for typeof support.
     # for ``type(countup(1,3))``, see ``tests/ttoseq``.
     result = semOverloadedCall(c, n, nOrig,
-      {skProc, skMethod, skConverter, skMacro, skTemplate, skIterator})
+      {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate, skIterator}, flags)
   else:
     result = semOverloadedCall(c, n, nOrig,
-      {skProc, skMethod, skConverter, skMacro, skTemplate})
+      {skProc, skFunc, skMethod, skConverter, skMacro, skTemplate}, flags)
 
   if result != nil:
     if result.sons[0].kind != nkSym:
-      internalError("semOverloadedCallAnalyseEffects")
+      internalError(c.config, "semOverloadedCallAnalyseEffects")
       return
     let callee = result.sons[0].sym
     case callee.kind
     of skMacro, skTemplate: discard
     else:
       if callee.kind == skIterator and callee.id == c.p.owner.id:
-        localError(n.info, errRecursiveDependencyX, callee.name.s)
+        localError(c.config, n.info, errRecursiveDependencyX % callee.name.s)
         # error correction, prevents endless for loop elimination in transf.
         # See bug #2051:
         result.sons[0] = newSymNode(errorSym(c, n))
@@ -667,7 +768,7 @@ proc resolveIndirectCall(c: PContext; n, nOrig: PNode;
   matches(c, n, nOrig, result)
   if result.state != csMatch:
     # try to deref the first argument:
-    if experimentalMode(c) and canDeref(n):
+    if implicitDeref in c.features and canDeref(n):
       n.sons[1] = n.sons[1].tryDeref
       initCandidate(c, result, t)
       matches(c, n, nOrig, result)
@@ -678,27 +779,9 @@ proc bracketedMacro(n: PNode): PSym =
     if result.kind notin {skMacro, skTemplate}:
       result = nil
 
-proc semBracketedMacro(c: PContext; outer, inner: PNode; s: PSym;
-                       flags: TExprFlags): PNode =
-  # We received untransformed bracket expression coming from macroOrTmpl[].
-  # Transform it to macro or template call, where first come normal
-  # arguments, next come generic template arguments.
-  var sons = newSeq[PNode]()
-  sons.add inner.sons[0]
-  # Normal arguments:
-  for i in 1..<outer.len:
-    sons.add outer.sons[i]
-  # Generic template arguments from bracket expression:
-  for i in 1..<inner.len:
-    sons.add inner.sons[i]
-  shallowCopy(outer.sons, sons)
-  # FIXME: Shouldn't we check sfImmediate and call semDirectOp?
-  # However passing to semDirectOp doesn't work here.
-  case s.kind
-  of skMacro: result = semMacroExpr(c, outer, outer, s, flags)
-  of skTemplate: result = semTemplateExpr(c, outer, s, flags)
-  else: assert(false)
-  return
+proc setGenericParams(c: PContext, n: PNode) =
+  for i in 1 ..< n.len:
+    n[i].typ = semTypeNode(c, n[i], nil)
 
 proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode =
   result = n
@@ -713,15 +796,19 @@ proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode =
     analyseIfAddressTakenInCall(c, result)
     if callee.magic != mNone:
       result = magicsAfterOverloadResolution(c, result, flags)
-  if c.inTypeClass == 0:
+    if result.typ != nil and
+        not (result.typ.kind == tySequence and result.typ.sons[0].kind == tyEmpty):
+      liftTypeBoundOps(c, result.typ, n.info)
+    #result = patchResolvedTypeBoundOp(c, result)
+  if c.matchedConcept == nil:
     result = evalAtCompileTime(c, result)
 
 proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
   result = nil
-  checkMinSonsLen(n, 1)
+  checkMinSonsLen(n, 1, c.config)
   var prc = n.sons[0]
   if n.sons[0].kind == nkDotExpr:
-    checkSonsLen(n.sons[0], 2)
+    checkSonsLen(n.sons[0], 2, c.config)
     let n0 = semFieldAccess(c, n.sons[0])
     if n0.kind == nkDotCall:
       # it is a static call!
@@ -735,12 +822,13 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
   else:
     n.sons[0] = semExpr(c, n.sons[0], {efInCall})
     let t = n.sons[0].typ
-    if t != nil and t.kind == tyVar:
+    if t != nil and t.kind in {tyVar, tyLent}:
       n.sons[0] = newDeref(n.sons[0])
     elif n.sons[0].kind == nkBracketExpr:
       let s = bracketedMacro(n.sons[0])
       if s != nil:
-        return semBracketedMacro(c, n, n.sons[0], s, flags)
+        setGenericParams(c, n[0])
+        return semDirectOp(c, n, flags)
 
   let nOrig = n.copyTree
   semOpAux(c, n)
@@ -751,13 +839,13 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
     # This is a proc variable, apply normal overload resolution
     let m = resolveIndirectCall(c, n, nOrig, t)
     if m.state != csMatch:
-      if c.compilesContextId > 0:
+      if c.config.m.errorOutputs == {}:
         # speed up error generation:
-        globalError(n.info, errTypeMismatch, "")
-        return emptyNode
+        globalError(c.config, n.info, "type mismatch")
+        return c.graph.emptyNode
       else:
         var hasErrorType = false
-        var msg = msgKindToString(errTypeMismatch)
+        var msg = "type mismatch: got <"
         for i in countup(1, sonsLen(n) - 1):
           if i > 1: add(msg, ", ")
           let nt = n.sons[i].typ
@@ -766,14 +854,15 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
             hasErrorType = true
             break
         if not hasErrorType:
-          add(msg, ")\n" & msgKindToString(errButExpected) & "\n" &
+          add(msg, ">\nbut expected one of: \n" &
               typeToString(n.sons[0].typ))
-          localError(n.info, errGenerated, msg)
+          localError(c.config, n.info, msg)
         return errorNode(c, n)
       result = nil
     else:
       result = m.call
       instGenericConvertersSons(c, result, m)
+
   elif t != nil and t.kind == tyTypeDesc:
     if n.len == 1: return semObjConstr(c, n, flags)
     return semConv(c, n)
@@ -811,18 +900,21 @@ proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
 proc buildEchoStmt(c: PContext, n: PNode): PNode =
   # we MUST not check 'n' for semantics again here! But for now we give up:
   result = newNodeI(nkCall, n.info)
-  var e = strTableGet(magicsys.systemModule.tab, getIdent"echo")
+  var e = strTableGet(c.graph.systemModule.tab, getIdent(c.cache, "echo"))
   if e != nil:
     add(result, newSymNode(e))
   else:
-    localError(n.info, errSystemNeeds, "echo")
+    localError(c.config, n.info, "system needs: echo")
     add(result, errorNode(c, n))
   add(result, n)
   result = semExpr(c, result)
 
 proc semExprNoType(c: PContext, n: PNode): PNode =
+  let isPush = hintExtendedContext in c.config.notes
+  if isPush: pushInfoContext(c.config, n.info)
   result = semExpr(c, n, {efWantStmt})
-  discardCheck(c, result)
+  discardCheck(c, result, {})
+  if isPush: popInfoContext(c.config)
 
 proc isTypeExpr(n: PNode): bool =
   case n.kind
@@ -846,8 +938,8 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
       result = lookupInRecordAndBuildCheck(c, n, r.sons[i], field, check)
       if result != nil: return
   of nkRecCase:
-    checkMinSonsLen(r, 2)
-    if (r.sons[0].kind != nkSym): illFormedAst(r)
+    checkMinSonsLen(r, 2, c.config)
+    if (r.sons[0].kind != nkSym): illFormedAst(r, c.config)
     result = lookupInRecordAndBuildCheck(c, n, r.sons[0], field, check)
     if result != nil: return
     let setType = createSetType(c, r.sons[0].typ)
@@ -862,11 +954,11 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
         else:
           if check == nil:
             check = newNodeI(nkCheckedFieldExpr, n.info)
-            addSon(check, ast.emptyNode) # make space for access node
+            addSon(check, c.graph.emptyNode) # make space for access node
           s = newNodeIT(nkCurly, n.info, setType)
           for j in countup(0, sonsLen(it) - 2): addSon(s, copyTree(it.sons[j]))
-          var inExpr = newNodeIT(nkCall, n.info, getSysType(tyBool))
-          addSon(inExpr, newSymNode(opContains, n.info))
+          var inExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool))
+          addSon(inExpr, newSymNode(c.graph.opContains, n.info))
           addSon(inExpr, s)
           addSon(inExpr, copyTree(r.sons[0]))
           addSon(check, inExpr)
@@ -877,66 +969,80 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent,
         if result != nil:
           if check == nil:
             check = newNodeI(nkCheckedFieldExpr, n.info)
-            addSon(check, ast.emptyNode) # make space for access node
-          var inExpr = newNodeIT(nkCall, n.info, getSysType(tyBool))
-          addSon(inExpr, newSymNode(opContains, n.info))
+            addSon(check, c.graph.emptyNode) # make space for access node
+          var inExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool))
+          addSon(inExpr, newSymNode(c.graph.opContains, n.info))
           addSon(inExpr, s)
           addSon(inExpr, copyTree(r.sons[0]))
-          var notExpr = newNodeIT(nkCall, n.info, getSysType(tyBool))
-          addSon(notExpr, newSymNode(opNot, n.info))
+          var notExpr = newNodeIT(nkCall, n.info, getSysType(c.graph, n.info, tyBool))
+          addSon(notExpr, newSymNode(c.graph.opNot, n.info))
           addSon(notExpr, inExpr)
           addSon(check, notExpr)
           return
-      else: illFormedAst(it)
+      else: illFormedAst(it, c.config)
   of nkSym:
     if r.sym.name.id == field.id: result = r.sym
-  else: illFormedAst(n)
-
-proc makeDeref(n: PNode): PNode =
-  var t = skipTypes(n.typ, {tyGenericInst, tyAlias})
-  result = n
-  if t.kind == tyVar:
-    result = newNodeIT(nkHiddenDeref, n.info, t.sons[0])
-    addSon(result, n)
-    t = skipTypes(t.sons[0], {tyGenericInst, tyAlias})
-  while t.kind in {tyPtr, tyRef}:
-    var a = result
-    let baseTyp = t.lastSon
-    result = newNodeIT(nkHiddenDeref, n.info, baseTyp)
-    addSon(result, a)
-    t = skipTypes(baseTyp, {tyGenericInst, tyAlias})
+  else: illFormedAst(n, c.config)
 
 const
   tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass}
-  tyDotOpTransparent = {tyVar, tyPtr, tyRef, tyAlias}
+  tyDotOpTransparent = {tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink}
 
 proc readTypeParameter(c: PContext, typ: PType,
                        paramName: PIdent, info: TLineInfo): PNode =
-  let ty = if typ.kind == tyGenericInst: typ.skipGenericAlias
-           else: (internalAssert(typ.kind == tyCompositeTypeClass);
-                  typ.sons[1].skipGenericAlias)
-  let tbody = ty.sons[0]
-  for s in countup(0, tbody.len-2):
-    let tParam = tbody.sons[s]
-    if tParam.sym.name.id == paramName.id:
-      let rawTyp = ty.sons[s + 1]
-      if rawTyp.kind == tyStatic:
-        return rawTyp.n
+  # Note: This function will return emptyNode when attempting to read
+  # a static type parameter that is not yet resolved (e.g. this may
+  # happen in proc signatures such as `proc(x: T): array[T.sizeParam, U]`
+  if typ.kind in {tyUserTypeClass, tyUserTypeClassInst}:
+    for statement in typ.n:
+      case statement.kind
+      of nkTypeSection:
+        for def in statement:
+          if def[0].sym.name.id == paramName.id:
+            # XXX: Instead of lifting the section type to a typedesc
+            # here, we could try doing it earlier in semTypeSection.
+            # This seems semantically correct and then we'll be able
+            # to return the section symbol directly here
+            let foundType = makeTypeDesc(c, def[2].typ)
+            return newSymNode(copySym(def[0].sym).linkTo(foundType), info)
+
+      of nkConstSection:
+        for def in statement:
+          if def[0].sym.name.id == paramName.id:
+            return def[2]
+
       else:
-        let foundTyp = makeTypeDesc(c, rawTyp)
-        return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info)
-  #echo "came here: returned nil"
+        discard
+
+  if typ.kind != tyUserTypeClass:
+    let ty = if typ.kind == tyCompositeTypeClass: typ.sons[1].skipGenericAlias
+             else: typ.skipGenericAlias
+    let tbody = ty.sons[0]
+    for s in countup(0, tbody.len-2):
+      let tParam = tbody.sons[s]
+      if tParam.sym.name.id == paramName.id:
+        let rawTyp = ty.sons[s + 1]
+        if rawTyp.kind == tyStatic:
+          if rawTyp.n != nil:
+            return rawTyp.n
+          else:
+            return c.graph.emptyNode
+        else:
+          let foundTyp = makeTypeDesc(c, rawTyp)
+          return newSymNode(copySym(tParam.sym).linkTo(foundTyp), info)
+
+  return nil
 
 proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
   let s = getGenSym(c, sym)
   case s.kind
   of skConst:
-    markUsed(n.info, s, c.graph.usageSym)
-    styleCheckUse(n.info, s)
+    markUsed(c.config, n.info, s, c.graph.usageSym)
+    onUse(n.info, s)
     case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind
     of  tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
         tyTuple, tySet, tyUInt..tyUInt64:
-      if s.magic == mNone: result = inlineConst(n, s)
+      if s.magic == mNone: result = inlineConst(c, n, s)
       else: result = newSymNode(s, n.info)
     of tyArray, tySequence:
       # Consider::
@@ -949,56 +1055,61 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
       # It is clear that ``[]`` means two totally different things. Thus, we
       # copy `x`'s AST into each context, so that the type fixup phase can
       # deal with two different ``[]``.
-      if s.ast.len == 0: result = inlineConst(n, s)
+      if s.ast.len == 0: result = inlineConst(c, n, s)
       else: result = newSymNode(s, n.info)
     else:
       result = newSymNode(s, n.info)
   of skMacro:
-    if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0:
-      markUsed(n.info, s, c.graph.usageSym)
-      styleCheckUse(n.info, s)
-      result = newSymNode(s, n.info)
+    if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
+       (n.kind notin nkCallKinds and s.requiredParams > 0):
+      markUsed(c.config, n.info, s, c.graph.usageSym)
+      onUse(n.info, s)
+      result = symChoice(c, n, s, scClosed)
     else:
       result = semMacroExpr(c, n, n, s, flags)
   of skTemplate:
-    if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0:
-      markUsed(n.info, s, c.graph.usageSym)
-      styleCheckUse(n.info, s)
-      result = newSymNode(s, n.info)
+    if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0 or
+       (n.kind notin nkCallKinds and s.requiredParams > 0) or
+       sfCustomPragma in sym.flags:
+      markUsed(c.config, n.info, s, c.graph.usageSym)
+      onUse(n.info, s)
+      result = symChoice(c, n, s, scClosed)
     else:
       result = semTemplateExpr(c, n, s, flags)
   of skParam:
-    markUsed(n.info, s, c.graph.usageSym)
-    styleCheckUse(n.info, s)
-    if s.typ.kind == tyStatic and s.typ.n != nil:
+    markUsed(c.config, n.info, s, c.graph.usageSym)
+    onUse(n.info, s)
+    if s.typ != nil and s.typ.kind == tyStatic and s.typ.n != nil:
       # XXX see the hack in sigmatch.nim ...
       return s.typ.n
     elif sfGenSym in s.flags:
+      # the owner should have been set by now by addParamOrResult
+      internalAssert c.config, s.owner != nil
       if c.p.wasForwarded:
         # gensym'ed parameters that nevertheless have been forward declared
         # need a special fixup:
         let realParam = c.p.owner.typ.n[s.position+1]
-        internalAssert realParam.kind == nkSym and realParam.sym.kind == skParam
+        internalAssert c.config, realParam.kind == nkSym and realParam.sym.kind == skParam
         return newSymNode(c.p.owner.typ.n[s.position+1].sym, n.info)
       elif c.p.owner.kind == skMacro:
         # gensym'ed macro parameters need a similar hack (see bug #1944):
         var u = searchInScopes(c, s.name)
-        internalAssert u != nil and u.kind == skParam and u.owner == s.owner
+        internalAssert c.config, u != nil and u.kind == skParam and u.owner == s.owner
         return newSymNode(u, n.info)
     result = newSymNode(s, n.info)
   of skVar, skLet, skResult, skForVar:
     if s.magic == mNimvm:
-      localError(n.info, "illegal context for 'nimvm' magic")
+      localError(c.config, n.info, "illegal context for 'nimvm' magic")
 
-    markUsed(n.info, s, c.graph.usageSym)
-    styleCheckUse(n.info, s)
+    markUsed(c.config, n.info, s, c.graph.usageSym)
+    onUse(n.info, s)
     result = newSymNode(s, n.info)
     # We cannot check for access to outer vars for example because it's still
     # not sure the symbol really ends up being used:
     # var len = 0 # but won't be called
     # genericThatUsesLen(x) # marked as taking a closure?
   of skGenericParam:
-    styleCheckUse(n.info, s)
+    onUse(n.info, s)
     if s.typ.kind == tyStatic:
       result = newSymNode(s, n.info)
       result.typ = s.typ
@@ -1008,9 +1119,9 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
       n.typ = s.typ
       return n
   of skType:
-    markUsed(n.info, s, c.graph.usageSym)
-    styleCheckUse(n.info, s)
-    if s.typ.kind == tyStatic and s.typ.n != nil:
+    markUsed(c.config, n.info, s, c.graph.usageSym)
+    onUse(n.info, s)
+    if s.typ.kind == tyStatic and s.typ.base.kind != tyNone and s.typ.n != nil:
       return s.typ.n
     result = newSymNode(s, n.info)
     result.typ = makeTypeDesc(c, s.typ)
@@ -1019,8 +1130,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
     while p != nil and p.selfSym == nil:
       p = p.next
     if p != nil and p.selfSym != nil:
-      var ty = skipTypes(p.selfSym.typ, {tyGenericInst, tyVar, tyPtr, tyRef,
-                                         tyAlias})
+      var ty = skipTypes(p.selfSym.typ, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef,
+                                         tyAlias, tySink})
       while tfBorrowDot in ty.flags: ty = ty.skipTypes({tyDistinct})
       var check: PNode = nil
       if ty.kind == tyObject:
@@ -1030,8 +1141,8 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
           if f != nil and fieldVisible(c, f):
             # is the access to a public field or in the same module or in a friend?
             doAssert f == s
-            markUsed(n.info, f, c.graph.usageSym)
-            styleCheckUse(n.info, f)
+            markUsed(c.config, n.info, f, c.graph.usageSym)
+            onUse(n.info, f)
             result = newNodeIT(nkDotExpr, n.info, f.typ)
             result.add makeDeref(newSymNode(p.selfSym))
             result.add newSymNode(f) # we now have the correct field
@@ -1043,25 +1154,25 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
           if ty.sons[0] == nil: break
           ty = skipTypes(ty.sons[0], skipPtrs)
     # old code, not sure if it's live code:
-    markUsed(n.info, s, c.graph.usageSym)
-    styleCheckUse(n.info, s)
+    markUsed(c.config, n.info, s, c.graph.usageSym)
+    onUse(n.info, s)
     result = newSymNode(s, n.info)
   else:
-    markUsed(n.info, s, c.graph.usageSym)
-    styleCheckUse(n.info, s)
+    markUsed(c.config, n.info, s, c.graph.usageSym)
+    onUse(n.info, s)
     result = newSymNode(s, n.info)
 
 proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
   ## returns nil if it's not a built-in field access
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   # tests/bind/tbindoverload.nim wants an early exit here, but seems to
   # work without now. template/tsymchoicefield doesn't like an early exit
   # here at all!
   #if isSymChoice(n.sons[1]): return
   when defined(nimsuggest):
-    if gCmd == cmdIdeTools:
+    if c.config.cmd == cmdIdeTools:
       suggestExpr(c, n)
-      if exactEquals(gTrackPos, n[1].info): suggestExprNoCheck(c, n)
+      if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n)
 
   var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule})
   if s != nil:
@@ -1069,19 +1180,58 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
       result = symChoice(c, n, s, scClosed)
       if result.kind == nkSym: result = semSym(c, n, s, flags)
     else:
-      markUsed(n.sons[1].info, s, c.graph.usageSym)
+      markUsed(c.config, n.sons[1].info, s, c.graph.usageSym)
       result = semSym(c, n, s, flags)
-    styleCheckUse(n.sons[1].info, s)
+    onUse(n.sons[1].info, s)
     return
 
   n.sons[0] = semExprWithType(c, n.sons[0], flags+{efDetermineType})
   #restoreOldStyleType(n.sons[0])
-  var i = considerQuotedIdent(n.sons[1])
+  var i = considerQuotedIdent(c, n.sons[1], n)
   var ty = n.sons[0].typ
   var f: PSym = nil
   result = nil
-  if isTypeExpr(n.sons[0]) or (ty.kind == tyTypeDesc and ty.base.kind != tyNone):
-    if ty.kind == tyTypeDesc: ty = ty.base
+
+  template tryReadingGenericParam(t: PType) =
+    case t.kind
+    of tyTypeParamsHolders:
+      result = readTypeParameter(c, t, i, n.info)
+      if result == c.graph.emptyNode:
+        result = n
+        n.typ = makeTypeFromExpr(c, n.copyTree)
+      return
+    of tyUserTypeClasses:
+      if t.isResolvedUserTypeClass:
+        return readTypeParameter(c, t, i, n.info)
+      else:
+        n.typ = makeTypeFromExpr(c, copyTree(n))
+        return n
+    of tyGenericParam, tyAnything:
+      n.typ = makeTypeFromExpr(c, copyTree(n))
+      return n
+    else:
+      discard
+
+  var argIsType = false
+
+  if ty.kind == tyTypeDesc:
+    if ty.base.kind == tyNone:
+      # This is a still unresolved typedesc parameter.
+      # If this is a regular proc, then all bets are off and we must return
+      # tyFromExpr, but when this happen in a macro this is not a built-in
+      # field access and we leave the compiler to compile a normal call:
+      if getCurrOwner(c).kind != skMacro:
+        n.typ = makeTypeFromExpr(c, n.copyTree)
+        return n
+      else:
+        return nil
+    else:
+      ty = ty.base
+      argIsType = true
+  else:
+    argIsType = isTypeExpr(n.sons[0])
+
+  if argIsType:
     ty = ty.skipTypes(tyDotOpTransparent)
     case ty.kind
     of tyEnum:
@@ -1094,27 +1244,25 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
         result = newSymNode(f)
         result.info = n.info
         result.typ = ty
-        markUsed(n.info, f, c.graph.usageSym)
-        styleCheckUse(n.info, f)
+        markUsed(c.config, n.info, f, c.graph.usageSym)
+        onUse(n.info, f)
         return
-    of tyTypeParamsHolders:
-      return readTypeParameter(c, ty, i, n.info)
     of tyObject, tyTuple:
       if ty.n != nil and ty.n.kind == nkRecList:
         let field = lookupInRecord(ty.n, i)
         if field != nil:
-          n.typ = newTypeWithSons(c, tyFieldAccessor, @[ty, field.typ])
-          n.typ.n = copyTree(n)
+          n.typ = makeTypeDesc(c, field.typ)
           return n
     else:
-      # echo "TYPE FIELD ACCESS"
-      # debug ty
+      tryReadingGenericParam(ty)
       return
     # XXX: This is probably not relevant any more
     # reset to prevent 'nil' bug: see "tests/reject/tenumitems.nim":
     ty = n.sons[0].typ
     return nil
-  ty = skipTypes(ty, {tyGenericInst, tyVar, tyPtr, tyRef, tyAlias})
+  if ty.kind in tyUserTypeClasses and ty.isResolvedUserTypeClass:
+    ty = ty.lastSon
+  ty = skipTypes(ty, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink})
   while tfBorrowDot in ty.flags: ty = ty.skipTypes({tyDistinct})
   var check: PNode = nil
   if ty.kind == tyObject:
@@ -1127,8 +1275,8 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
     if f != nil:
       if fieldVisible(c, f):
         # is the access to a public field or in the same module or in a friend?
-        markUsed(n.sons[1].info, f, c.graph.usageSym)
-        styleCheckUse(n.sons[1].info, f)
+        markUsed(c.config, n.sons[1].info, f, c.graph.usageSym)
+        onUse(n.sons[1].info, f)
         n.sons[0] = makeDeref(n.sons[0])
         n.sons[1] = newSymNode(f) # we now have the correct field
         n.typ = f.typ
@@ -1141,8 +1289,8 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
   elif ty.kind == tyTuple and ty.n != nil:
     f = getSymFromList(ty.n, i)
     if f != nil:
-      markUsed(n.sons[1].info, f, c.graph.usageSym)
-      styleCheckUse(n.sons[1].info, f)
+      markUsed(c.config, n.sons[1].info, f, c.graph.usageSym)
+      onUse(n.sons[1].info, f)
       n.sons[0] = makeDeref(n.sons[0])
       n.sons[1] = newSymNode(f)
       n.typ = f.typ
@@ -1151,8 +1299,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # we didn't find any field, let's look for a generic param
   if result == nil:
     let t = n.sons[0].typ.skipTypes(tyDotOpTransparent)
-    if t.kind in tyTypeParamsHolders:
-      result = readTypeParameter(c, t, i, n.info)
+    tryReadingGenericParam(t)
 
 proc dotTransformation(c: PContext, n: PNode): PNode =
   if isSymChoice(n.sons[1]):
@@ -1160,7 +1307,7 @@ proc dotTransformation(c: PContext, n: PNode): PNode =
     addSon(result, n.sons[1])
     addSon(result, copyTree(n[0]))
   else:
-    var i = considerQuotedIdent(n.sons[1])
+    var i = considerQuotedIdent(c, n.sons[1], n)
     result = newNodeI(nkDotCall, n.info)
     result.flags.incl nfDotField
     addSon(result, newIdentNode(i, n[1].info))
@@ -1179,10 +1326,10 @@ proc buildOverloadedSubscripts(n: PNode, ident: PIdent): PNode =
   for i in 0 .. n.len-1: result.add(n[i])
 
 proc semDeref(c: PContext, n: PNode): PNode =
-  checkSonsLen(n, 1)
+  checkSonsLen(n, 1, c.config)
   n.sons[0] = semExprWithType(c, n.sons[0])
   result = n
-  var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyAlias})
+  var t = skipTypes(n.sons[0].typ, {tyGenericInst, tyVar, tyLent, tyAlias, tySink})
   case t.kind
   of tyRef, tyPtr: n.typ = t.lastSon
   else: result = nil
@@ -1197,28 +1344,43 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
     result = newNodeIT(nkDerefExpr, x.info, x.typ)
     result.add(x[0])
     return
-  checkMinSonsLen(n, 2)
+  checkMinSonsLen(n, 2, c.config)
   # make sure we don't evaluate generic macros/templates
   n.sons[0] = semExprWithType(c, n.sons[0],
-                              {efNoProcvarCheck, efNoEvaluateGeneric})
-  let arr = skipTypes(n.sons[0].typ, {tyGenericInst,
-                                      tyVar, tyPtr, tyRef, tyAlias})
+                              {efNoEvaluateGeneric})
+  var arr = skipTypes(n.sons[0].typ, {tyGenericInst, tyUserTypeClassInst,
+                                      tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink})
+  if arr.kind == tyStatic:
+    if arr.base.kind == tyNone:
+      result = n
+      result.typ = semStaticType(c, n[1], nil)
+      return
+    elif arr.n != nil:
+      return semSubscript(c, arr.n, flags)
+    else:
+      arr = arr.base
+
   case arr.kind
-  of tyArray, tyOpenArray, tyVarargs, tySequence, tyString,
-     tyCString:
+  of tyArray, tyOpenArray, tyVarargs, tySequence, tyString, tyCString,
+    tyUncheckedArray:
     if n.len != 2: return nil
     n.sons[0] = makeDeref(n.sons[0])
-    c.p.bracketExpr = n.sons[0]
     for i in countup(1, sonsLen(n) - 1):
       n.sons[i] = semExprWithType(c, n.sons[i],
                                   flags*{efInTypeof, efDetermineType})
-    var indexType = if arr.kind == tyArray: arr.sons[0] else: getSysType(tyInt)
-    var arg = indexTypesMatch(c, indexType, n.sons[1].typ, n.sons[1])
-    if arg != nil:
-      n.sons[1] = arg
+    # Arrays index type is dictated by the range's type
+    if arr.kind == tyArray:
+      var indexType = arr.sons[0]
+      var arg = indexTypesMatch(c, indexType, n.sons[1].typ, n.sons[1])
+      if arg != nil:
+        n.sons[1] = arg
+        result = n
+        result.typ = elemType(arr)
+    # Other types have a bit more of leeway
+    elif n.sons[1].typ.skipTypes(abstractRange-{tyDistinct}).kind in
+        {tyInt..tyInt64, tyUInt..tyUInt64}:
       result = n
       result.typ = elemType(arr)
-    #GlobalError(n.info, errIndexTypesDoNotMatch)
   of tyTypeDesc:
     # The result so far is a tyTypeDesc bound
     # a tyGenericBody. The line below will substitute
@@ -1227,26 +1389,25 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
     result.typ = makeTypeDesc(c, semTypeNode(c, n, nil))
     #result = symNodeFromType(c, semTypeNode(c, n, nil), n.info)
   of tyTuple:
-    checkSonsLen(n, 2)
+    if n.len != 2: return nil
     n.sons[0] = makeDeref(n.sons[0])
-    c.p.bracketExpr = n.sons[0]
     # [] operator for tuples requires constant expression:
     n.sons[1] = semConstExpr(c, n.sons[1])
-    if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias}).kind in
+    if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias, tySink}).kind in
         {tyInt..tyInt64}:
-      var idx = getOrdValue(n.sons[1])
-      if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)]
-      else: localError(n.info, errInvalidIndexValueForTuple)
+      let idx = getOrdValue(n.sons[1])
+      if idx >= 0 and idx < len(arr): n.typ = arr.sons[int(idx)]
+      else: localError(c.config, n.info, "invalid index value for tuple subscript")
+      result = n
     else:
-      localError(n.info, errIndexTypesDoNotMatch)
-    result = n
+      result = nil
   else:
     let s = if n.sons[0].kind == nkSym: n.sons[0].sym
             elif n[0].kind in nkSymChoices: n.sons[0][0].sym
             else: nil
     if s != nil:
       case s.kind
-      of skProc, skMethod, skConverter, skIterator:
+      of skProc, skFunc, skMethod, skConverter, skIterator:
         # type parameters: partial generic specialization
         n.sons[0] = semSymGenericInstantiation(c, n.sons[0], s)
         result = explicitGenericInstantiation(c, n, s)
@@ -1267,21 +1428,17 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
       of skType:
         result = symNodeFromType(c, semTypeNode(c, n, nil), n.info)
       else:
-        c.p.bracketExpr = n.sons[0]
-    else:
-      c.p.bracketExpr = n.sons[0]
+        discard
 
 proc semArrayAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
-  let oldBracketExpr = c.p.bracketExpr
   result = semSubscript(c, n, flags)
   if result == nil:
     # overloaded [] operator:
-    result = semExpr(c, buildOverloadedSubscripts(n, getIdent"[]"))
-  c.p.bracketExpr = oldBracketExpr
+    result = semExpr(c, buildOverloadedSubscripts(n, getIdent(c.cache, "[]")))
 
 proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode =
-  var id = considerQuotedIdent(a[1])
-  var setterId = newIdentNode(getIdent(id.s & '='), n.info)
+  var id = considerQuotedIdent(c, a[1], a)
+  var setterId = newIdentNode(getIdent(c.cache, id.s & '='), n.info)
   # a[0] is already checked for semantics, that does ``builtinFieldAccess``
   # this is ugly. XXX Semantic checking should use the ``nfSem`` flag for
   # nodes?
@@ -1297,36 +1454,53 @@ proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode =
     #fixAbstractType(c, result)
     #analyseIfAddressTakenInCall(c, result)
 
-proc takeImplicitAddr(c: PContext, n: PNode): PNode =
+proc takeImplicitAddr(c: PContext, n: PNode; isLent: bool): PNode =
+  # See RFC #7373, calls returning 'var T' are assumed to
+  # return a view into the first argument (if there is one):
+  let root = exprRoot(n)
+  if root != nil and root.owner == c.p.owner:
+    if root.kind in {skLet, skVar, skTemp} and sfGlobal notin root.flags:
+      localError(c.config, n.info, "'$1' escapes its stack frame; context: '$2'; see $3/var_t_return.html" % [
+        root.name.s, renderTree(n, {renderNoComments}), explanationsBaseUrl])
+    elif root.kind == skParam and root.position != 0:
+      localError(c.config, n.info, "'$1' is not the first parameter; context: '$2'; see $3/var_t_return.html" % [
+        root.name.s, renderTree(n, {renderNoComments}), explanationsBaseUrl])
   case n.kind
   of nkHiddenAddr, nkAddr: return n
   of nkHiddenDeref, nkDerefExpr: return n.sons[0]
   of nkBracketExpr:
     if len(n) == 1: return n.sons[0]
   else: discard
-  var valid = isAssignable(c, n)
+  let valid = isAssignable(c, n)
   if valid != arLValue:
     if valid == arLocalLValue:
-      localError(n.info, errXStackEscape, renderTree(n, {renderNoComments}))
-    else:
-      localError(n.info, errExprHasNoAddress)
+      localError(c.config, n.info, errXStackEscape % renderTree(n, {renderNoComments}))
+    elif not isLent:
+      localError(c.config, n.info, errExprHasNoAddress)
   result = newNodeIT(nkHiddenAddr, n.info, makePtrType(c, n.typ))
   result.add(n)
 
 proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} =
   if le.kind == nkHiddenDeref:
     var x = le.sons[0]
-    if x.typ.kind == tyVar and x.kind == nkSym and x.sym.kind == skResult:
+    if x.typ.kind in {tyVar, tyLent} and x.kind == nkSym and x.sym.kind == skResult:
       n.sons[0] = x # 'result[]' --> 'result'
-      n.sons[1] = takeImplicitAddr(c, ri)
+      n.sons[1] = takeImplicitAddr(c, ri, x.typ.kind == tyLent)
       x.typ.flags.incl tfVarIsPtr
       #echo x.info, " setting it for this type ", typeToString(x.typ), " ", n.info
 
 template resultTypeIsInferrable(typ: PType): untyped =
   typ.isMetaType and typ.kind != tyTypeDesc
 
+
+proc goodLineInfo(arg: PNode): TLineinfo =
+  if arg.kind == nkStmtListExpr and arg.len > 0:
+    goodLineInfo(arg[^1])
+  else:
+    arg.info
+
 proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   var a = n.sons[0]
   case a.kind
   of nkDotExpr:
@@ -1346,30 +1520,27 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
   of nkBracketExpr:
     # a[i] = x
     # --> `[]=`(a, i, x)
-    let oldBracketExpr = c.p.bracketExpr
     a = semSubscript(c, a, {efLValue})
     if a == nil:
-      result = buildOverloadedSubscripts(n.sons[0], getIdent"[]=")
+      result = buildOverloadedSubscripts(n.sons[0], getIdent(c.cache, "[]="))
       add(result, n[1])
       if mode == noOverloadedSubscript:
         bracketNotFoundError(c, result)
         return n
       else:
         result = semExprNoType(c, result)
-        c.p.bracketExpr = oldBracketExpr
         return result
-    c.p.bracketExpr = oldBracketExpr
   of nkCurlyExpr:
     # a{i} = x -->  `{}=`(a, i, x)
-    result = buildOverloadedSubscripts(n.sons[0], getIdent"{}=")
+    result = buildOverloadedSubscripts(n.sons[0], getIdent(c.cache, "{}="))
     add(result, n[1])
     return semExprNoType(c, result)
-  of nkPar:
+  of nkPar, nkTupleConstr:
     if a.len >= 2:
       # unfortunately we need to rewrite ``(x, y) = foo()`` already here so
       # that overloading of the assignment operator still works. Usually we
       # prefer to do these rewritings in transf.nim:
-      return semStmt(c, lowerTupleUnpackingForAsgn(n, c.p.owner))
+      return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner), {})
     else:
       a = semExprWithType(c, a, {efLValue})
   else:
@@ -1378,11 +1549,13 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
   # a = b # both are vars, means: a[] = b[]
   # a = b # b no 'var T' means: a = addr(b)
   var le = a.typ
-  if (skipTypes(le, {tyGenericInst, tyAlias}).kind != tyVar and
+  if le == nil:
+    localError(c.config, a.info, "expression has no type")
+  elif (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind != tyVar and
         isAssignable(c, a) == arNone) or
       skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs}:
     # Direct assignment to a discriminant is allowed!
-    localError(a.info, errXCannotBeAssignedTo,
+    localError(c.config, a.info, errXCannotBeAssignedTo %
                renderTree(a, {renderNoComments}))
   else:
     let
@@ -1392,20 +1565,24 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
       rhs = semExprWithType(c, n.sons[1],
         if lhsIsResult: {efAllowDestructor} else: {})
     if lhsIsResult:
-      n.typ = enforceVoidContext
+      n.typ = c.enforceVoidContext
       if c.p.owner.kind != skMacro and resultTypeIsInferrable(lhs.sym.typ):
-        if cmpTypes(c, lhs.typ, rhs.typ) == isGeneric:
-          internalAssert c.p.resultSym != nil
-          lhs.typ = rhs.typ
-          c.p.resultSym.typ = rhs.typ
-          c.p.owner.typ.sons[0] = rhs.typ
+        var rhsTyp = rhs.typ
+        if rhsTyp.kind in tyUserTypeClasses and rhsTyp.isResolvedUserTypeClass:
+          rhsTyp = rhsTyp.lastSon
+        if cmpTypes(c, lhs.typ, rhsTyp) in {isGeneric, isEqual}:
+          internalAssert c.config, c.p.resultSym != nil
+          # Make sure the type is valid for the result variable
+          typeAllowedCheck(c.config, n.info, rhsTyp, skResult)
+          lhs.typ = rhsTyp
+          c.p.resultSym.typ = rhsTyp
+          c.p.owner.typ.sons[0] = rhsTyp
         else:
-          typeMismatch(n.info, lhs.typ, rhs.typ)
+          typeMismatch(c.config, n.info, lhs.typ, rhsTyp)
 
-    n.sons[1] = fitNode(c, le, rhs, n.info)
-    if tfHasAsgn in lhs.typ.flags and not lhsIsResult and
-        mode != noOverloadedAsgn:
-      return overloadedAsgn(c, lhs, n.sons[1])
+    n.sons[1] = fitNode(c, le, rhs, goodLineInfo(n[1]))
+    liftTypeBoundOps(c, lhs.typ, lhs.info)
+    #liftTypeBoundOps(c, n.sons[0].typ, n.sons[0].info)
 
     fixAbstractType(c, n)
     asgnToResultVar(c, n, n.sons[0], n.sons[1])
@@ -1413,8 +1590,8 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
 
 proc semReturn(c: PContext, n: PNode): PNode =
   result = n
-  checkSonsLen(n, 1)
-  if c.p.owner.kind in {skConverter, skMethod, skProc, skMacro} or (
+  checkSonsLen(n, 1, c.config)
+  if c.p.owner.kind in {skConverter, skMethod, skProc, skFunc, skMacro} or (
      c.p.owner.kind == skIterator and c.p.owner.typ.callConv == ccClosure):
     if n.sons[0].kind != nkEmpty:
       # transform ``return expr`` to ``result = expr; return``
@@ -1425,22 +1602,17 @@ proc semReturn(c: PContext, n: PNode): PNode =
         n.sons[0] = semAsgn(c, a)
         # optimize away ``result = result``:
         if n[0][1].kind == nkSym and n[0][1].sym == c.p.resultSym:
-          n.sons[0] = ast.emptyNode
+          n.sons[0] = c.graph.emptyNode
       else:
-        localError(n.info, errNoReturnTypeDeclared)
+        localError(c.config, n.info, errNoReturnTypeDeclared)
   else:
-    localError(n.info, errXNotAllowedHere, "\'return\'")
+    localError(c.config, n.info, "'return' not allowed here")
 
 proc semProcBody(c: PContext, n: PNode): PNode =
   openScope(c)
-
   result = semExpr(c, n)
   if c.p.resultSym != nil and not isEmptyType(result.typ):
-    # transform ``expr`` to ``result = expr``, but not if the expr is already
-    # ``result``:
-    if result.kind == nkSym and result.sym == c.p.resultSym:
-      discard
-    elif result.kind == nkNilLit:
+    if result.kind == nkNilLit:
       # or ImplicitlyDiscardable(result):
       # new semantic: 'result = x' triggers the void context
       result.typ = nil
@@ -1449,14 +1621,14 @@ proc semProcBody(c: PContext, n: PNode): PNode =
       #   nil
       #   # comment
       # are not expressions:
-      fixNilType(result)
+      fixNilType(c, result)
     else:
       var a = newNodeI(nkAsgn, n.info, 2)
       a.sons[0] = newSymNode(c.p.resultSym)
       a.sons[1] = result
       result = semAsgn(c, a)
   else:
-    discardCheck(c, result)
+    discardCheck(c, result, {})
 
   if c.p.owner.kind notin {skMacro, skTemplate} and
      c.p.resultSym != nil and c.p.resultSym.typ.isMetaType:
@@ -1465,36 +1637,38 @@ proc semProcBody(c: PContext, n: PNode): PNode =
       c.p.resultSym.typ = errorType(c)
       c.p.owner.typ.sons[0] = nil
     else:
-      localError(c.p.resultSym.info, errCannotInferReturnType)
+      localError(c.config, c.p.resultSym.info, errCannotInferReturnType)
 
   closeScope(c)
 
 proc semYieldVarResult(c: PContext, n: PNode, restype: PType) =
-  var t = skipTypes(restype, {tyGenericInst, tyAlias})
+  var t = skipTypes(restype, {tyGenericInst, tyAlias, tySink})
   case t.kind
-  of tyVar:
-    n.sons[0] = takeImplicitAddr(c, n.sons[0])
+  of tyVar, tyLent:
+    if t.kind == tyVar: t.flags.incl tfVarIsPtr # bugfix for #4048, #4910, #6892
+    if n.sons[0].kind in {nkHiddenStdConv, nkHiddenSubConv}:
+      n.sons[0] = n.sons[0].sons[1]
+    n.sons[0] = takeImplicitAddr(c, n.sons[0], t.kind == tyLent)
   of tyTuple:
-    for i in 0.. <t.sonsLen:
-      var e = skipTypes(t.sons[i], {tyGenericInst, tyAlias})
-      if e.kind == tyVar:
-        if n.sons[0].kind == nkPar:
-          n.sons[0].sons[i] = takeImplicitAddr(c, n.sons[0].sons[i])
+    for i in 0..<t.sonsLen:
+      var e = skipTypes(t.sons[i], {tyGenericInst, tyAlias, tySink})
+      if e.kind in {tyVar, tyLent}:
+        if e.kind == tyVar: e.flags.incl tfVarIsPtr # bugfix for #4048, #4910, #6892
+        if n.sons[0].kind in {nkPar, nkTupleConstr}:
+          n.sons[0].sons[i] = takeImplicitAddr(c, n.sons[0].sons[i], e.kind == tyLent)
         elif n.sons[0].kind in {nkHiddenStdConv, nkHiddenSubConv} and
-             n.sons[0].sons[1].kind == nkPar:
+             n.sons[0].sons[1].kind in {nkPar, nkTupleConstr}:
           var a = n.sons[0].sons[1]
-          a.sons[i] = takeImplicitAddr(c, a.sons[i])
+          a.sons[i] = takeImplicitAddr(c, a.sons[i], false)
         else:
-          localError(n.sons[0].info, errXExpected, "tuple constructor")
+          localError(c.config, n.sons[0].info, errXExpected, "tuple constructor")
   else: discard
 
 proc semYield(c: PContext, n: PNode): PNode =
   result = n
-  checkSonsLen(n, 1)
+  checkSonsLen(n, 1, c.config)
   if c.p.owner == nil or c.p.owner.kind != skIterator:
-    localError(n.info, errYieldNotAllowedHere)
-  elif c.p.inTryStmt > 0 and c.p.owner.typ.callConv != ccInline:
-    localError(n.info, errYieldNotAllowedInTryStmt)
+    localError(c.config, n.info, errYieldNotAllowedHere)
   elif n.sons[0].kind != nkEmpty:
     n.sons[0] = semExprWithType(c, n.sons[0]) # check for type compatibility:
     var iterType = c.p.owner.typ
@@ -1502,7 +1676,7 @@ proc semYield(c: PContext, n: PNode): PNode =
     if restype != nil:
       if restype.kind != tyExpr:
         n.sons[0] = fitNode(c, restype, n.sons[0], n.info)
-      if n.sons[0].typ == nil: internalError(n.info, "semYield")
+      if n.sons[0].typ == nil: internalError(c.config, n.info, "semYield")
 
       if resultTypeIsInferrable(restype):
         let inferred = n.sons[0].typ
@@ -1510,9 +1684,9 @@ proc semYield(c: PContext, n: PNode): PNode =
 
       semYieldVarResult(c, n, restype)
     else:
-      localError(n.info, errCannotReturnExpr)
+      localError(c.config, n.info, errCannotReturnExpr)
   elif c.p.owner.typ.sons[0] != nil:
-    localError(n.info, errGenerated, "yield statement must yield a value")
+    localError(c.config, n.info, errGenerated, "yield statement must yield a value")
 
 proc lookUpForDefined(c: PContext, i: PIdent, onlyCurrentScope: bool): PSym =
   if onlyCurrentScope:
@@ -1527,37 +1701,35 @@ proc lookUpForDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PSym =
   of nkDotExpr:
     result = nil
     if onlyCurrentScope: return
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     var m = lookUpForDefined(c, n.sons[0], onlyCurrentScope)
     if m != nil and m.kind == skModule:
-      let ident = considerQuotedIdent(n[1])
+      let ident = considerQuotedIdent(c, n[1], n)
       if m == c.module:
         result = strTableGet(c.topLevelScope.symbols, ident)
       else:
         result = strTableGet(m.tab, ident)
   of nkAccQuoted:
-    result = lookUpForDefined(c, considerQuotedIdent(n), onlyCurrentScope)
+    result = lookUpForDefined(c, considerQuotedIdent(c, n), onlyCurrentScope)
   of nkSym:
     result = n.sym
   of nkOpenSymChoice, nkClosedSymChoice:
     result = n.sons[0].sym
   else:
-    localError(n.info, errIdentifierExpected, renderTree(n))
+    localError(c.config, n.info, "identifier expected, but got: " & renderTree(n))
     result = nil
 
 proc semDefined(c: PContext, n: PNode, onlyCurrentScope: bool): PNode =
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   # we replace this node by a 'true' or 'false' node:
   result = newIntNode(nkIntLit, 0)
-  if not onlyCurrentScope and considerQuotedIdent(n[0]).s == "defined":
-    if n.sons[1].kind != nkIdent:
-      localError(n.info, "obsolete usage of 'defined', use 'declared' instead")
-    elif condsyms.isDefined(n.sons[1].ident):
-      result.intVal = 1
+  if not onlyCurrentScope and considerQuotedIdent(c, n[0], n).s == "defined":
+    let d = considerQuotedIdent(c, n[1], n)
+    result.intVal = ord isDefined(c.config, d.s)
   elif lookUpForDefined(c, n.sons[1], onlyCurrentScope) != nil:
     result.intVal = 1
   result.info = n.info
-  result.typ = getSysType(tyBool)
+  result.typ = getSysType(c.graph, n.info, tyBool)
 
 proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym =
   ## The argument to the proc should be nkCall(...) or similar
@@ -1569,12 +1741,12 @@ proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym =
       return errorSym(c, n[0])
 
     if expandedSym.kind notin {skMacro, skTemplate}:
-      localError(n.info, errXisNoMacroOrTemplate, expandedSym.name.s)
+      localError(c.config, n.info, "'$1' is not a macro or template" % expandedSym.name.s)
       return errorSym(c, n[0])
 
     result = expandedSym
   else:
-    localError(n.info, errXisNoMacroOrTemplate, n.renderTree)
+    localError(c.config, n.info, "'$1' is not a macro or template" % n.renderTree)
     result = errorSym(c, n)
 
 proc expectString(c: PContext, n: PNode): string =
@@ -1582,11 +1754,7 @@ proc expectString(c: PContext, n: PNode): string =
   if n.kind in nkStrKinds:
     return n.strVal
   else:
-    localError(n.info, errStringLiteralExpected)
-
-proc getMagicSym(magic: TMagic): PSym =
-  result = newSym(skProc, getIdent($magic), systemModule, gCodegenLineInfo)
-  result.magic = magic
+    localError(c.config, n.info, errStringLiteralExpected)
 
 proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym =
   result = newSym(kind, c.cache.idAnon, getCurrOwner(c), info)
@@ -1600,8 +1768,8 @@ proc semExpandToAst(c: PContext, n: PNode): PNode =
     if expandedSym.kind == skError: return n
 
     macroCall.sons[0] = newSymNode(expandedSym, macroCall.info)
-    markUsed(n.info, expandedSym, c.graph.usageSym)
-    styleCheckUse(n.info, expandedSym)
+    markUsed(c.config, n.info, expandedSym, c.graph.usageSym)
+    onUse(n.info, expandedSym)
 
   if isCallExpr(macroCall):
     for i in countup(1, macroCall.len-1):
@@ -1619,25 +1787,25 @@ proc semExpandToAst(c: PContext, n: PNode): PNode =
         inc cands
       symx = nextOverloadIter(o, c, headSymbol)
     if cands == 0:
-      localError(n.info, "expected a template that takes " & $(macroCall.len-1) & " arguments")
+      localError(c.config, n.info, "expected a template that takes " & $(macroCall.len-1) & " arguments")
     elif cands >= 2:
-      localError(n.info, "ambiguous symbol in 'getAst' context: " & $macroCall)
+      localError(c.config, n.info, "ambiguous symbol in 'getAst' context: " & $macroCall)
     else:
       let info = macroCall.sons[0].info
       macroCall.sons[0] = newSymNode(cand, info)
-      markUsed(info, cand, c.graph.usageSym)
-      styleCheckUse(info, cand)
+      markUsed(c.config, info, cand, c.graph.usageSym)
+      onUse(info, cand)
 
     # we just perform overloading resolution here:
     #n.sons[1] = semOverloadedCall(c, macroCall, macroCall, {skTemplate, skMacro})
   else:
-    localError(n.info, "getAst takes a call, but got " & n.renderTree)
+    localError(c.config, n.info, "getAst takes a call, but got " & n.renderTree)
   # Preserve the magic symbol in order to be handled in evals.nim
-  internalAssert n.sons[0].sym.magic == mExpandToAst
+  internalAssert c.config, n.sons[0].sym.magic == mExpandToAst
   #n.typ = getSysSym("NimNode").typ # expandedSym.getReturnType
-  n.typ = if getCompilerProc("NimNode") != nil: sysTypeFromName"NimNode"
-          else: sysTypeFromName"PNimrodNode"
-  result = n
+  if n.kind == nkStmtList and n.len == 1: result = n[0]
+  else: result = n
+  result.typ = sysTypeFromName(c.graph, n.info, "NimNode")
 
 proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym,
                     flags: TExprFlags = {}): PNode =
@@ -1647,108 +1815,122 @@ proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym,
   else:
     result = semDirectOp(c, n, flags)
 
-proc processQuotations(n: var PNode, op: string,
+proc processQuotations(c: PContext; n: var PNode, op: string,
                        quotes: var seq[PNode],
                        ids: var seq[PNode]) =
   template returnQuote(q) =
     quotes.add q
-    n = newIdentNode(getIdent($quotes.len), n.info)
+    n = newIdentNode(getIdent(c.cache, $quotes.len), n.info)
     ids.add n
     return
 
+
   if n.kind == nkPrefix:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     if n[0].kind == nkIdent:
       var examinedOp = n[0].ident.s
       if examinedOp == op:
         returnQuote n[1]
       elif examinedOp.startsWith(op):
-        n.sons[0] = newIdentNode(getIdent(examinedOp.substr(op.len)), n.info)
+        n.sons[0] = newIdentNode(getIdent(c.cache, examinedOp.substr(op.len)), n.info)
   elif n.kind == nkAccQuoted and op == "``":
     returnQuote n[0]
+  elif n.kind == nkIdent:
+    if n.ident.s == "result":
+      n = ids[0]
 
-  for i in 0 .. <n.safeLen:
-    processQuotations(n.sons[i], op, quotes, ids)
+  for i in 0 ..< n.safeLen:
+    processQuotations(c, n.sons[i], op, quotes, ids)
 
 proc semQuoteAst(c: PContext, n: PNode): PNode =
-  internalAssert n.len == 2 or n.len == 3
+  internalAssert c.config, n.len == 2 or n.len == 3
   # We transform the do block into a template with a param for
   # each interpolation. We'll pass this template to getAst.
   var
-    doBlk = n{-1}
+    quotedBlock = n[^1]
     op = if n.len == 3: expectString(c, n[1]) else: "``"
-    quotes = newSeq[PNode](1)
+    quotes = newSeq[PNode](2)
       # the quotes will be added to a nkCall statement
-      # leave some room for the callee symbol
-    ids = newSeq[PNode]()
+      # leave some room for the callee symbol and the result symbol
+    ids = newSeq[PNode](1)
       # this will store the generated param names
+      # leave some room for the result symbol
 
-  if doBlk.kind != nkDo:
-    localError(n.info, errXExpected, "block")
+  if quotedBlock.kind != nkStmtList:
+    localError(c.config, n.info, errXExpected, "block")
 
-  processQuotations(doBlk.sons[bodyPos], op, quotes, ids)
+  # This adds a default first field to pass the result symbol
+  ids[0] = newAnonSym(c, skParam, n.info).newSymNode
+  processQuotations(c, quotedBlock, op, quotes, ids)
+
+  var dummyTemplate = newProcNode(
+    nkTemplateDef, quotedBlock.info, body = quotedBlock,
+    params = c.graph.emptyNode,
+    name = newAnonSym(c, skTemplate, n.info).newSymNode,
+              pattern = c.graph.emptyNode, genericParams = c.graph.emptyNode,
+              pragmas = c.graph.emptyNode, exceptions = c.graph.emptyNode)
 
-  doBlk.sons[namePos] = newAnonSym(c, skTemplate, n.info).newSymNode
   if ids.len > 0:
-    doBlk.sons[paramsPos] = newNodeI(nkFormalParams, n.info)
-    doBlk[paramsPos].add getSysSym("typed").newSymNode # return type
-    ids.add getSysSym("untyped").newSymNode # params type
-    ids.add emptyNode # no default value
-    doBlk[paramsPos].add newNode(nkIdentDefs, n.info, ids)
+    dummyTemplate.sons[paramsPos] = newNodeI(nkFormalParams, n.info)
+    dummyTemplate[paramsPos].add getSysSym(c.graph, n.info, "typed").newSymNode # return type
+    ids.add getSysSym(c.graph, n.info, "untyped").newSymNode # params type
+    ids.add c.graph.emptyNode # no default value
+    dummyTemplate[paramsPos].add newNode(nkIdentDefs, n.info, ids)
 
-  var tmpl = semTemplateDef(c, doBlk)
+  var tmpl = semTemplateDef(c, dummyTemplate)
   quotes[0] = tmpl[namePos]
+  # This adds a call to newIdentNode("result") as the first argument to the template call
+  quotes[1] = newNode(nkCall, n.info, @[newIdentNode(getIdent(c.cache, "newIdentNode"), n.info), newStrNode(nkStrLit, "result")])
   result = newNode(nkCall, n.info, @[
-    getMagicSym(mExpandToAst).newSymNode,
+     createMagic(c.graph, "getAst", mExpandToAst).newSymNode,
     newNode(nkCall, n.info, quotes)])
   result = semExpandToAst(c, result)
 
 proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   # watch out, hacks ahead:
-  let oldErrorCount = msgs.gErrorCounter
-  let oldErrorMax = msgs.gErrorMax
+  let oldErrorCount = c.config.errorCounter
+  let oldErrorMax = c.config.errorMax
   let oldCompilesId = c.compilesContextId
   inc c.compilesContextIdGenerator
   c.compilesContextId = c.compilesContextIdGenerator
   # do not halt after first error:
-  msgs.gErrorMax = high(int)
+  c.config.errorMax = high(int)
 
   # open a scope for temporary symbol inclusions:
   let oldScope = c.currentScope
   openScope(c)
   let oldOwnerLen = len(c.graph.owners)
   let oldGenerics = c.generics
-  let oldErrorOutputs = errorOutputs
-  errorOutputs = {}
-  let oldContextLen = msgs.getInfoContextLen()
+  let oldErrorOutputs = c.config.m.errorOutputs
+  if efExplain notin flags: c.config.m.errorOutputs = {}
+  let oldContextLen = msgs.getInfoContextLen(c.config)
 
   let oldInGenericContext = c.inGenericContext
   let oldInUnrolledContext = c.inUnrolledContext
   let oldInGenericInst = c.inGenericInst
+  let oldInStaticContext = c.inStaticContext
   let oldProcCon = c.p
   c.generics = @[]
   var err: string
   try:
     result = semExpr(c, n, flags)
-    if msgs.gErrorCounter != oldErrorCount: result = nil
+    if c.config.errorCounter != oldErrorCount: result = nil
   except ERecoverableError:
-    if optReportConceptFailures in gGlobalOptions:
-      err = getCurrentExceptionMsg()
+    discard
   # undo symbol table changes (as far as it's possible):
   c.compilesContextId = oldCompilesId
   c.generics = oldGenerics
   c.inGenericContext = oldInGenericContext
   c.inUnrolledContext = oldInUnrolledContext
   c.inGenericInst = oldInGenericInst
+  c.inStaticContext = oldInStaticContext
   c.p = oldProcCon
-  msgs.setInfoContextLen(oldContextLen)
+  msgs.setInfoContextLen(c.config, oldContextLen)
   setLen(c.graph.owners, oldOwnerLen)
   c.currentScope = oldScope
-  errorOutputs = oldErrorOutputs
-  msgs.gErrorCounter = oldErrorCount
-  msgs.gErrorMax = oldErrorMax
-  if optReportConceptFailures in gGlobalOptions and not err.isNil:
-    localError(n.info, err)
+  c.config.m.errorOutputs = oldErrorOutputs
+  c.config.errorCounter = oldErrorCount
+  c.config.errorMax = oldErrorMax
 
 proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # we replace this node by a 'true' or 'false' node:
@@ -1756,7 +1938,7 @@ proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode =
 
   result = newIntNode(nkIntLit, ord(tryExpr(c, n[1], flags) != nil))
   result.info = n.info
-  result.typ = getSysType(tyBool)
+  result.typ = getSysType(c.graph, n.info, tyBool)
 
 proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode =
   if sonsLen(n) == 3:
@@ -1771,15 +1953,15 @@ proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode =
 
 proc createFlowVar(c: PContext; t: PType; info: TLineInfo): PType =
   result = newType(tyGenericInvocation, c.module)
-  addSonSkipIntLit(result, magicsys.getCompilerProc("FlowVar").typ)
+  addSonSkipIntLit(result, magicsys.getCompilerProc(c.graph, "FlowVar").typ)
   addSonSkipIntLit(result, t)
   result = instGenericContainer(c, info, result, allowMetaTypes = false)
 
 proc instantiateCreateFlowVarCall(c: PContext; t: PType;
                                   info: TLineInfo): PSym =
-  let sym = magicsys.getCompilerProc("nimCreateFlowVar")
+  let sym = magicsys.getCompilerProc(c.graph, "nimCreateFlowVar")
   if sym == nil:
-    localError(info, errSystemNeeds, "nimCreateFlowVar")
+    localError(c.config, info, "system needs: nimCreateFlowVar")
   var bindings: TIdTable
   initIdTable(bindings)
   bindings.idTablePut(sym.ast[genericParamsPos].sons[0].typ, t)
@@ -1795,47 +1977,60 @@ proc setMs(n: PNode, s: PSym): PNode =
   n.sons[0] = newSymNode(s)
   n.sons[0].info = n.info
 
+proc semSizeof(c: PContext, n: PNode): PNode =
+  if sonsLen(n) != 2:
+    localError(c.config, n.info, errXExpectsTypeOrValue % "sizeof")
+  else:
+    n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType})
+    #restoreOldStyleType(n.sons[1])
+  n.typ = getSysType(c.graph, n.info, tyInt)
+
+  let size = getSize(c.config, n[1].typ)
+  if size >= 0:
+    result = newIntNode(nkIntLit, size)
+    result.info = n.info
+    result.typ = n.typ
+  else:
+    result = n
+
 proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
   # this is a hotspot in the compiler!
-  # DON'T forget to update ast.SpecialSemMagics if you add a magic here!
   result = n
   case s.magic # magics that need special treatment
   of mAddr:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     result = semAddr(c, n.sons[1], s.name.s == "unsafeAddr")
   of mTypeOf:
-    checkSonsLen(n, 2)
-    result = semTypeOf(c, n.sons[1])
+    result = semTypeOf(c, n)
   #of mArrGet: result = semArrGet(c, n, flags)
   #of mArrPut: result = semArrPut(c, n, flags)
   #of mAsgn: result = semAsgnOpr(c, n)
   of mDefined: result = semDefined(c, setMs(n, s), false)
   of mDefinedInScope: result = semDefined(c, setMs(n, s), true)
   of mCompiles: result = semCompiles(c, setMs(n, s), flags)
-  of mLow: result = semLowHigh(c, setMs(n, s), mLow)
-  of mHigh: result = semLowHigh(c, setMs(n, s), mHigh)
-  of mSizeOf: result = semSizeof(c, setMs(n, s))
-  of mIs: result = semIs(c, setMs(n, s))
-  of mOf: result = semOf(c, setMs(n, s))
+  #of mLow: result = semLowHigh(c, setMs(n, s), mLow)
+  #of mHigh: result = semLowHigh(c, setMs(n, s), mHigh)
+  of mIs: result = semIs(c, setMs(n, s), flags)
+  #of mOf: result = semOf(c, setMs(n, s))
   of mShallowCopy: result = semShallowCopy(c, n, flags)
   of mExpandToAst: result = semExpandToAst(c, n, s, flags)
   of mQuoteAst: result = semQuoteAst(c, n)
   of mAstToStr:
-    checkSonsLen(n, 2)
-    result = newStrNodeT(renderTree(n[1], {renderNoComments}), n)
-    result.typ = getSysType(tyString)
+    checkSonsLen(n, 2, c.config)
+    result = newStrNodeT(renderTree(n[1], {renderNoComments}), n, c.graph)
+    result.typ = getSysType(c.graph, n.info, tyString)
   of mParallel:
-    if not experimentalMode(c):
-      localError(n.info, "use the {.experimental.} pragma to enable 'parallel'")
+    if parallel notin c.features:
+      localError(c.config, n.info, "use the {.experimental.} pragma to enable 'parallel'")
     result = setMs(n, s)
     var x = n.lastSon
     if x.kind == nkDo: x = x.sons[bodyPos]
     inc c.inParallelStmt
-    result.sons[1] = semStmt(c, x)
+    result.sons[1] = semStmt(c, x, {})
     dec c.inParallelStmt
   of mSpawn:
     result = setMs(n, s)
-    for i in 1 .. <n.len:
+    for i in 1 ..< n.len:
       result.sons[i] = semExpr(c, n.sons[i])
     let typ = result[^1].typ
     if not typ.isEmptyType:
@@ -1845,7 +2040,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
         result.typ = typ
       result.add instantiateCreateFlowVarCall(c, typ, n.info).newSymNode
     else:
-      result.add emptyNode
+      result.add c.graph.emptyNode
   of mProcCall:
     result = setMs(n, s)
     result.sons[1] = semExpr(c, n.sons[1])
@@ -1866,6 +2061,27 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
       analyseIfAddressTakenInCall(c, result)
       if callee.magic != mNone:
         result = magicsAfterOverloadResolution(c, result, flags)
+  of mRunnableExamples:
+    if c.config.cmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList:
+      when false:
+        # some of this dead code was moved to `prepareExamples`
+        if sfMainModule in c.module.flags:
+          let inp = toFullPath(c.config, c.module.info)
+          if c.runnableExamples == nil:
+            c.runnableExamples = newTree(nkStmtList,
+              newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp))))
+          let imports = newTree(nkStmtList)
+          var savedLastSon = copyTree n.lastSon
+          extractImports(savedLastSon, imports)
+          for imp in imports: c.runnableExamples.add imp
+          c.runnableExamples.add newTree(nkBlockStmt, c.graph.emptyNode, copyTree savedLastSon)
+      result = setMs(n, s)
+    else:
+      result = c.graph.emptyNode
+  of mSizeOf: result = semSizeof(c, setMs(n, s))
+  of mOmpParFor:
+    checkMinSonsLen(n, 3, c.config)
+    result = semDirectOp(c, n, flags)
   else:
     result = semDirectOp(c, n, flags)
 
@@ -1898,14 +2114,14 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
     var it = n.sons[i]
     case it.kind
     of nkElifBranch, nkElifExpr:
-      checkSonsLen(it, 2)
+      checkSonsLen(it, 2, c.config)
       if whenNimvm:
         if semCheck:
           it.sons[1] = semExpr(c, it.sons[1])
           typ = commonType(typ, it.sons[1].typ)
         result = n # when nimvm is not elimited until codegen
       else:
-        var e = semConstExpr(c, it.sons[0])
+        let e = forceBool(c, semConstExpr(c, it.sons[0]))
         if e.kind != nkIntLit:
           # can happen for cascading errors, assume false
           # InternalError(n.info, "semWhen")
@@ -1913,14 +2129,14 @@ proc semWhen(c: PContext, n: PNode, semCheck = true): PNode =
         elif e.intVal != 0 and result == nil:
           setResult(it.sons[1])
     of nkElse, nkElseExpr:
-      checkSonsLen(it, 1)
+      checkSonsLen(it, 1, c.config)
       if result == nil or whenNimvm:
         if semCheck:
           it.sons[0] = semExpr(c, it.sons[0])
           typ = commonType(typ, it.sons[0].typ)
         if result == nil:
           result = it.sons[0]
-    else: illFormedAst(n)
+    else: illFormedAst(n, c.config)
   if result == nil:
     result = newNodeI(nkEmpty, n.info)
   if whenNimvm: result.typ = typ
@@ -1939,26 +2155,26 @@ proc semSetConstr(c: PContext, n: PNode): PNode =
     var typ: PType = nil
     for i in countup(0, sonsLen(n) - 1):
       if isRange(n.sons[i]):
-        checkSonsLen(n.sons[i], 3)
+        checkSonsLen(n.sons[i], 3, c.config)
         n.sons[i].sons[1] = semExprWithType(c, n.sons[i].sons[1])
         n.sons[i].sons[2] = semExprWithType(c, n.sons[i].sons[2])
         if typ == nil:
           typ = skipTypes(n.sons[i].sons[1].typ,
-                          {tyGenericInst, tyVar, tyOrdinal, tyAlias})
+                          {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
         n.sons[i].typ = n.sons[i].sons[2].typ # range node needs type too
       elif n.sons[i].kind == nkRange:
         # already semchecked
         if typ == nil:
           typ = skipTypes(n.sons[i].sons[0].typ,
-                          {tyGenericInst, tyVar, tyOrdinal, tyAlias})
+                          {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
       else:
         n.sons[i] = semExprWithType(c, n.sons[i])
         if typ == nil:
-          typ = skipTypes(n.sons[i].typ, {tyGenericInst, tyVar, tyOrdinal, tyAlias})
-    if not isOrdinalType(typ):
-      localError(n.info, errOrdinalTypeExpected)
+          typ = skipTypes(n.sons[i].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink})
+    if not isOrdinalType(typ, allowEnumWithHoles=true):
+      localError(c.config, n.info, errOrdinalTypeExpected)
       typ = makeRangeType(c, 0, MaxSetElements-1, n.info)
-    elif lengthOrd(typ) > MaxSetElements:
+    elif lengthOrd(c.config, typ) > MaxSetElements:
       typ = makeRangeType(c, 0, MaxSetElements-1, n.info)
     addSonSkipIntLit(result.typ, typ)
     for i in countup(0, sonsLen(n) - 1):
@@ -1982,31 +2198,32 @@ proc semTableConstr(c: PContext, n: PNode): PNode =
     var x = n.sons[i]
     if x.kind == nkExprColonExpr and sonsLen(x) == 2:
       for j in countup(lastKey, i-1):
-        var pair = newNodeI(nkPar, x.info)
+        var pair = newNodeI(nkTupleConstr, x.info)
         pair.add(n.sons[j])
         pair.add(x[1])
         result.add(pair)
 
-      var pair = newNodeI(nkPar, x.info)
+      var pair = newNodeI(nkTupleConstr, x.info)
       pair.add(x[0])
       pair.add(x[1])
       result.add(pair)
 
       lastKey = i+1
 
-  if lastKey != n.len: illFormedAst(n)
+  if lastKey != n.len: illFormedAst(n, c.config)
   result = semExpr(c, result)
 
 type
   TParKind = enum
     paNone, paSingle, paTupleFields, paTuplePositions
 
-proc checkPar(n: PNode): TParKind =
+proc checkPar(c: PContext; n: PNode): TParKind =
   var length = sonsLen(n)
   if length == 0:
     result = paTuplePositions # ()
   elif length == 1:
     if n.sons[0].kind == nkExprColonExpr: result = paTupleFields
+    elif n.kind == nkTupleConstr: result = paTuplePositions
     else: result = paSingle         # (expr)
   else:
     if n.sons[0].kind == nkExprColonExpr: result = paTupleFields
@@ -2015,26 +2232,26 @@ proc checkPar(n: PNode): TParKind =
       if result == paTupleFields:
         if (n.sons[i].kind != nkExprColonExpr) or
             not (n.sons[i].sons[0].kind in {nkSym, nkIdent}):
-          localError(n.sons[i].info, errNamedExprExpected)
+          localError(c.config, n.sons[i].info, errNamedExprExpected)
           return paNone
       else:
         if n.sons[i].kind == nkExprColonExpr:
-          localError(n.sons[i].info, errNamedExprNotAllowed)
+          localError(c.config, n.sons[i].info, errNamedExprNotAllowed)
           return paNone
 
 proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
-  result = newNodeI(nkPar, n.info)
+  result = newNodeI(nkTupleConstr, n.info)
   var typ = newTypeS(tyTuple, c)
   typ.n = newNodeI(nkRecList, n.info) # nkIdentDefs
   var ids = initIntSet()
   for i in countup(0, sonsLen(n) - 1):
     if n[i].kind != nkExprColonExpr or n[i][0].kind notin {nkSym, nkIdent}:
-      illFormedAst(n.sons[i])
+      illFormedAst(n.sons[i], c.config)
     var id: PIdent
     if n.sons[i].sons[0].kind == nkIdent: id = n.sons[i].sons[0].ident
     else: id = n.sons[i].sons[0].sym.name
     if containsOrIncl(ids, id.id):
-      localError(n.sons[i].info, errFieldInitTwice, id.s)
+      localError(c.config, n.sons[i].info, errFieldInitTwice % id.s)
     n.sons[i].sons[1] = semExprWithType(c, n.sons[i].sons[1],
                                         flags*{efAllowDestructor})
     var f = newSymS(skField, n.sons[i].sons[0], c)
@@ -2048,6 +2265,7 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
 
 proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
   result = n                  # we don't modify n, but compute the type:
+  result.kind = nkTupleConstr
   var typ = newTypeS(tyTuple, c)  # leave typ.n nil!
   for i in countup(0, sonsLen(n) - 1):
     n.sons[i] = semExprWithType(c, n.sons[i], flags*{efAllowDestructor})
@@ -2062,121 +2280,73 @@ proc isTupleType(n: PNode): bool =
       return false
   return true
 
-proc checkInitialized(n: PNode, ids: IntSet, info: TLineInfo) =
-  case n.kind
-  of nkRecList:
-    for i in countup(0, sonsLen(n) - 1):
-      checkInitialized(n.sons[i], ids, info)
-  of nkRecCase:
-    if (n.sons[0].kind != nkSym): internalError(info, "checkInitialized")
-    checkInitialized(n.sons[0], ids, info)
-    when false:
-      # XXX we cannot check here, as we don't know the branch!
-      for i in countup(1, sonsLen(n) - 1):
-        case n.sons[i].kind
-        of nkOfBranch, nkElse: checkInitialized(lastSon(n.sons[i]), ids, info)
-        else: internalError(info, "checkInitialized")
-  of nkSym:
-    if {tfNotNil, tfNeedsInit} * n.sym.typ.flags != {} and
-        n.sym.name.id notin ids:
-      message(info, errGenerated, "field not initialized: " & n.sym.name.s)
-  else: internalError(info, "checkInitialized")
+include semobjconstr
 
-proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
-  var t = semTypeNode(c, n.sons[0], nil)
-  result = n
-  result.typ = t
-  result.kind = nkObjConstr
-  t = skipTypes(t, {tyGenericInst, tyAlias})
-  if t.kind == tyRef: t = skipTypes(t.sons[0], {tyGenericInst, tyAlias})
-  if t.kind != tyObject:
-    localError(n.info, errGenerated, "object constructor needs an object type")
-    return
-  var objType = t
-  var ids = initIntSet()
-  for i in 1.. <n.len:
-    let it = n.sons[i]
-    if it.kind != nkExprColonExpr:
-      localError(n.info, errNamedExprExpected)
-      break
-    let id = considerQuotedIdent(it.sons[0])
-
-    if containsOrIncl(ids, id.id):
-      localError(it.info, errFieldInitTwice, id.s)
-    var e = semExprWithType(c, it.sons[1], flags*{efAllowDestructor})
-    var
-      check: PNode = nil
-      f: PSym
-    t = objType
-    while true:
-      check = nil
-      f = lookupInRecordAndBuildCheck(c, it, t.n, id, check)
-      if f != nil: break
-      if t.sons[0] == nil: break
-      t = skipTypes(t.sons[0], skipPtrs)
-    if f != nil and fieldVisible(c, f):
-      it.sons[0] = newSymNode(f)
-      e = fitNode(c, f.typ, e, it.info)
-      # small hack here in a nkObjConstr the ``nkExprColonExpr`` node can have
-      # 3 children the last being the field check
-      if check != nil:
-        check.sons[0] = it.sons[0]
-        it.add(check)
-    else:
-      localError(it.info, errUndeclaredFieldX, id.s)
-    it.sons[1] = e
-    # XXX object field name check for 'case objects' if the kind is static?
-  if tfNeedsInit in objType.flags:
-    while true:
-      checkInitialized(objType.n, ids, n.info)
-      if objType.sons[0] == nil: break
-      objType = skipTypes(objType.sons[0], skipPtrs)
-
-proc semBlock(c: PContext, n: PNode): PNode =
+proc semBlock(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
   inc(c.p.nestedBlockCounter)
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   openScope(c) # BUGFIX: label is in the scope of block!
   if n.sons[0].kind != nkEmpty:
     var labl = newSymG(skLabel, n.sons[0], c)
     if sfGenSym notin labl.flags:
       addDecl(c, labl)
+    elif labl.owner == nil:
+      labl.owner = c.p.owner
     n.sons[0] = newSymNode(labl, n.sons[0].info)
-    suggestSym(n.sons[0].info, labl, c.graph.usageSym)
-    styleCheckDef(labl)
-  n.sons[1] = semExpr(c, n.sons[1])
+    suggestSym(c.config, n.sons[0].info, labl, c.graph.usageSym)
+    styleCheckDef(c.config, labl)
+    onDef(n[0].info, labl)
+  n.sons[1] = semExpr(c, n.sons[1], flags)
   n.typ = n.sons[1].typ
   if isEmptyType(n.typ): n.kind = nkBlockStmt
   else: n.kind = nkBlockExpr
   closeScope(c)
   dec(c.p.nestedBlockCounter)
 
+proc semExportExcept(c: PContext, n: PNode): PNode =
+  let moduleName = semExpr(c, n[0])
+  if moduleName.kind != nkSym or moduleName.sym.kind != skModule:
+    localError(c.config, n.info, "The export/except syntax expects a module name")
+    return n
+  let exceptSet = readExceptSet(c, n)
+  let exported = moduleName.sym
+  result = newNodeI(nkExportStmt, n.info)
+  strTableAdd(c.module.tab, exported)
+  var i: TTabIter
+  var s = initTabIter(i, exported.tab)
+  while s != nil:
+    if s.kind in ExportableSymKinds+{skModule} and
+       s.name.id notin exceptSet:
+      strTableAdd(c.module.tab, s)
+      result.add newSymNode(s, n.info)
+    s = nextIter(i, exported.tab)
+
 proc semExport(c: PContext, n: PNode): PNode =
-  var x = newNodeI(n.kind, n.info)
-  #let L = if n.kind == nkExportExceptStmt: L = 1 else: n.len
-  for i in 0.. <n.len:
+  result = newNodeI(nkExportStmt, n.info)
+
+  for i in 0..<n.len:
     let a = n.sons[i]
     var o: TOverloadIter
     var s = initOverloadIter(o, c, a)
     if s == nil:
-      localError(a.info, errGenerated, "cannot export: " & renderTree(a))
+      localError(c.config, a.info, errGenerated, "cannot export: " & renderTree(a))
     elif s.kind == skModule:
       # forward everything from that module:
       strTableAdd(c.module.tab, s)
-      x.add(newSymNode(s, a.info))
       var ti: TTabIter
       var it = initTabIter(ti, s.tab)
       while it != nil:
         if it.kind in ExportableSymKinds+{skModule}:
           strTableAdd(c.module.tab, it)
+          result.add newSymNode(it, a.info)
         it = nextIter(ti, s.tab)
     else:
       while s != nil:
         if s.kind in ExportableSymKinds+{skModule}:
-          x.add(newSymNode(s, a.info))
+          result.add(newSymNode(s, a.info))
           strTableAdd(c.module.tab, s)
         s = nextOverloadIter(o, c, a)
-  result = n
 
 proc shouldBeBracketExpr(n: PNode): bool =
   assert n.kind in nkCallKinds
@@ -2185,95 +2355,101 @@ proc shouldBeBracketExpr(n: PNode): bool =
     let b = a[0]
     if b.kind in nkSymChoices:
       for i in 0..<b.len:
-        if b[i].sym.magic == mArrGet:
+        if b[i].kind == nkSym and b[i].sym.magic == mArrGet:
           let be = newNodeI(nkBracketExpr, n.info)
           for i in 1..<a.len: be.add(a[i])
           n.sons[0] = be
           return true
 
-proc setGenericParams(c: PContext, n: PNode) =
-  for i in 1 .. <n.len:
-    n[i].typ = semTypeNode(c, n[i], nil)
-
 proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   result = n
-  if gCmd == cmdIdeTools: suggestExpr(c, n)
+  if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
   if nfSem in n.flags: return
   case n.kind
   of nkIdent, nkAccQuoted:
-    let checks = if efNoEvaluateGeneric in flags: {checkUndeclared}
-                 else: {checkUndeclared, checkModule, checkAmbiguity}
+    let checks = if efNoEvaluateGeneric in flags:
+        {checkUndeclared, checkPureEnumFields}
+      elif efInCall in flags:
+        {checkUndeclared, checkModule, checkPureEnumFields}
+      else:
+        {checkUndeclared, checkModule, checkAmbiguity, checkPureEnumFields}
     var s = qualifiedLookUp(c, n, checks)
-    if c.inTypeClass == 0: semCaptureSym(s, c.p.owner)
-    result = semSym(c, n, s, flags)
-    if s.kind in {skProc, skMethod, skConverter, skIterator}:
+    if c.matchedConcept == nil: semCaptureSym(s, c.p.owner)
+    if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator}:
       #performProcvarCheck(c, n, s)
       result = symChoice(c, n, s, scClosed)
       if result.kind == nkSym:
         markIndirect(c, result.sym)
         # if isGenericRoutine(result.sym):
-        #   localError(n.info, errInstantiateXExplicitly, s.name.s)
+        #   localError(c.config, n.info, errInstantiateXExplicitly, s.name.s)
+    else:
+      result = semSym(c, n, s, flags)
   of nkSym:
     # because of the changed symbol binding, this does not mean that we
     # don't have to check the symbol for semantics here again!
     result = semSym(c, n, n.sym, flags)
-  of nkEmpty, nkNone, nkCommentStmt:
+  of nkEmpty, nkNone, nkCommentStmt, nkType:
     discard
   of nkNilLit:
-    if result.typ == nil: result.typ = getSysType(tyNil)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyNil)
   of nkIntLit:
-    if result.typ == nil: setIntLitType(result)
+    if result.typ == nil: setIntLitType(c.graph, result)
   of nkInt8Lit:
-    if result.typ == nil: result.typ = getSysType(tyInt8)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt8)
   of nkInt16Lit:
-    if result.typ == nil: result.typ = getSysType(tyInt16)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt16)
   of nkInt32Lit:
-    if result.typ == nil: result.typ = getSysType(tyInt32)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt32)
   of nkInt64Lit:
-    if result.typ == nil: result.typ = getSysType(tyInt64)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyInt64)
   of nkUIntLit:
-    if result.typ == nil: result.typ = getSysType(tyUInt)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt)
   of nkUInt8Lit:
-    if result.typ == nil: result.typ = getSysType(tyUInt8)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt8)
   of nkUInt16Lit:
-    if result.typ == nil: result.typ = getSysType(tyUInt16)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt16)
   of nkUInt32Lit:
-    if result.typ == nil: result.typ = getSysType(tyUInt32)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt32)
   of nkUInt64Lit:
-    if result.typ == nil: result.typ = getSysType(tyUInt64)
-  of nkFloatLit:
-    if result.typ == nil: result.typ = getFloatLitType(result)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyUInt64)
+  #of nkFloatLit:
+  #  if result.typ == nil: result.typ = getFloatLitType(result)
   of nkFloat32Lit:
-    if result.typ == nil: result.typ = getSysType(tyFloat32)
-  of nkFloat64Lit:
-    if result.typ == nil: result.typ = getSysType(tyFloat64)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat32)
+  of nkFloat64Lit, nkFloatLit:
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat64)
   of nkFloat128Lit:
-    if result.typ == nil: result.typ = getSysType(tyFloat128)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyFloat128)
   of nkStrLit..nkTripleStrLit:
-    if result.typ == nil: result.typ = getSysType(tyString)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyString)
   of nkCharLit:
-    if result.typ == nil: result.typ = getSysType(tyChar)
+    if result.typ == nil: result.typ = getSysType(c.graph, n.info, tyChar)
   of nkDotExpr:
     result = semFieldAccess(c, n, flags)
     if result.kind == nkDotCall:
       result.kind = nkCall
       result = semExpr(c, result, flags)
   of nkBind:
-    message(n.info, warnDeprecated, "bind")
+    message(c.config, n.info, warnDeprecated, "bind")
     result = semExpr(c, n.sons[0], flags)
   of nkTypeOfExpr, nkTupleTy, nkTupleClassTy, nkRefTy..nkEnumTy, nkStaticTy:
+    if c.matchedConcept != nil and n.len == 1:
+      let modifier = n.modifierTypeKindOfNode
+      if modifier != tyNone:
+        var baseType = semExpr(c, n[0]).typ.skipTypes({tyTypeDesc})
+        result.typ = c.makeTypeDesc(c.newTypeWithSons(modifier, @[baseType]))
+        return
     var typ = semTypeNode(c, n, nil).skipTypes({tyTypeDesc})
     result.typ = makeTypeDesc(c, typ)
-    #result = symNodeFromType(c, typ, n.info)
   of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit:
     # check if it is an expression macro:
-    checkMinSonsLen(n, 1)
+    checkMinSonsLen(n, 1, c.config)
     #when defined(nimsuggest):
-    #  if gIdeCmd == ideCon and gTrackPos == n.info: suggestExprNoCheck(c, n)
+    #  if gIdeCmd == ideCon and c.config.m.trackPos == n.info: suggestExprNoCheck(c, n)
     let mode = if nfDotField in n.flags: {} else: {checkUndeclared}
     var s = qualifiedLookUp(c, n.sons[0], mode)
     if s != nil:
-      #if gCmd == cmdPretty and n.sons[0].kind == nkDotExpr:
+      #if c.config.cmd == cmdPretty and n.sons[0].kind == nkDotExpr:
       #  pretty.checkUse(n.sons[0].sons[1].info, s)
       case s.kind
       of skMacro:
@@ -2290,13 +2466,13 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
         # XXX think about this more (``set`` procs)
         if n.len == 2:
           result = semConv(c, n)
+        elif contains(c.ambiguousSymbols, s.id) and n.len == 1:
+          errorUseQualifier(c, n.info, s)
         elif n.len == 1:
           result = semObjConstr(c, n, flags)
-        elif contains(c.ambiguousSymbols, s.id):
-          errorUseQualifier(c, n.info, s)
         elif s.magic == mNone: result = semDirectOp(c, n, flags)
         else: result = semMagic(c, n, s, flags)
-      of skProc, skMethod, skConverter, skIterator:
+      of skProc, skFunc, skMethod, skConverter, skIterator:
         if s.magic == mNone: result = semDirectOp(c, n, flags)
         else: result = semMagic(c, n, s, flags)
       else:
@@ -2323,15 +2499,27 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
       else:
         result = semExpr(c, result, flags)
   of nkBracketExpr:
-    checkMinSonsLen(n, 1)
+    checkMinSonsLen(n, 1, c.config)
     result = semArrayAccess(c, n, flags)
   of nkCurlyExpr:
-    result = semExpr(c, buildOverloadedSubscripts(n, getIdent"{}"), flags)
+    result = semExpr(c, buildOverloadedSubscripts(n, getIdent(c.cache, "{}")), flags)
   of nkPragmaExpr:
-    # which pragmas are allowed for expressions? `likely`, `unlikely`
-    internalError(n.info, "semExpr() to implement") # XXX: to implement
-  of nkPar:
-    case checkPar(n)
+    var
+      expr = n[0]
+      pragma = n[1]
+      pragmaName = considerQuotedIdent(c, pragma[0])
+      flags = flags
+
+    case whichKeyword(pragmaName)
+    of wExplain:
+      flags.incl efExplain
+    else:
+      # what other pragmas are allowed for expressions? `likely`, `unlikely`
+      invalidPragma(c, n)
+
+    result = semExpr(c, n[0], flags)
+  of nkPar, nkTupleConstr:
+    case checkPar(c, n)
     of paNone: result = errorNode(c, n)
     of paTuplePositions:
       var tupexp = semTuplePositionsConstr(c, n, flags)
@@ -2346,29 +2534,28 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   of nkCurly: result = semSetConstr(c, n)
   of nkBracket: result = semArrayConstr(c, n, flags)
   of nkObjConstr: result = semObjConstr(c, n, flags)
-  of nkLambda: result = semLambda(c, n, flags)
-  of nkDo: result = semDo(c, n, flags)
+  of nkLambdaKinds: result = semLambda(c, n, flags)
   of nkDerefExpr: result = semDeref(c, n)
   of nkAddr:
     result = n
-    checkSonsLen(n, 1)
+    checkSonsLen(n, 1, c.config)
     result = semAddr(c, n.sons[0])
   of nkHiddenAddr, nkHiddenDeref:
-    checkSonsLen(n, 1)
+    checkSonsLen(n, 1, c.config)
     n.sons[0] = semExpr(c, n.sons[0], flags)
   of nkCast: result = semCast(c, n)
-  of nkIfExpr, nkIfStmt: result = semIf(c, n)
+  of nkIfExpr, nkIfStmt: result = semIf(c, n, flags)
   of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkHiddenCallConv:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     considerGenSyms(c, n)
   of nkStringToCString, nkCStringToString, nkObjDownConv, nkObjUpConv:
-    checkSonsLen(n, 1)
+    checkSonsLen(n, 1, c.config)
     considerGenSyms(c, n)
   of nkChckRangeF, nkChckRange64, nkChckRange:
-    checkSonsLen(n, 3)
+    checkSonsLen(n, 3, c.config)
     considerGenSyms(c, n)
   of nkCheckedFieldExpr:
-    checkMinSonsLen(n, 2)
+    checkMinSonsLen(n, 2, c.config)
     considerGenSyms(c, n)
   of nkTableConstr:
     result = semTableConstr(c, n)
@@ -2376,10 +2563,9 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     # handling of sym choices is context dependent
     # the node is left intact for now
     discard
-  of nkStaticExpr:
-    result = semStaticExpr(c, n)
+  of nkStaticExpr: result = semStaticExpr(c, n[0])
   of nkAsgn: result = semAsgn(c, n)
-  of nkBlockStmt, nkBlockExpr: result = semBlock(c, n)
+  of nkBlockStmt, nkBlockExpr: result = semBlock(c, n, flags)
   of nkStmtList, nkStmtListExpr: result = semStmtList(c, n, flags)
   of nkRaiseStmt: result = semRaise(c, n)
   of nkVarSection: result = semVarOrLet(c, n, skVar)
@@ -2387,11 +2573,11 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   of nkConstSection: result = semConst(c, n)
   of nkTypeSection: result = semTypeSection(c, n)
   of nkDiscardStmt: result = semDiscard(c, n)
-  of nkWhileStmt: result = semWhile(c, n)
-  of nkTryStmt: result = semTry(c, n)
+  of nkWhileStmt: result = semWhile(c, n, flags)
+  of nkTryStmt: result = semTry(c, n, flags)
   of nkBreakStmt, nkContinueStmt: result = semBreakOrContinue(c, n)
-  of nkForStmt, nkParForStmt: result = semFor(c, n)
-  of nkCaseStmt: result = semCase(c, n)
+  of nkForStmt, nkParForStmt: result = semFor(c, n, flags)
+  of nkCaseStmt: result = semCase(c, n, flags)
   of nkReturnStmt: result = semReturn(c, n)
   of nkUsingStmt: result = semUsing(c, n)
   of nkAsmStmt: result = semAsm(c, n)
@@ -2399,25 +2585,36 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   of nkPragma: pragma(c, c.p.owner, n, stmtPragmas)
   of nkIteratorDef: result = semIterator(c, n)
   of nkProcDef: result = semProc(c, n)
+  of nkFuncDef: result = semFunc(c, n)
   of nkMethodDef: result = semMethod(c, n)
   of nkConverterDef: result = semConverterDef(c, n)
   of nkMacroDef: result = semMacroDef(c, n)
   of nkTemplateDef: result = semTemplateDef(c, n)
   of nkImportStmt:
-    if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "import")
+    # this particular way allows 'import' in a 'compiles' context so that
+    # template canImport(x): bool =
+    #   compiles:
+    #     import x
+    #
+    # works:
+    if c.currentScope.depthLevel > 2 + c.compilesContextId:
+      localError(c.config, n.info, errXOnlyAtModuleScope % "import")
     result = evalImport(c, n)
   of nkImportExceptStmt:
-    if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "import")
+    if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "import")
     result = evalImportExcept(c, n)
   of nkFromStmt:
-    if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "from")
+    if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "from")
     result = evalFrom(c, n)
   of nkIncludeStmt:
-    #if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "include")
+    #if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "include")
     result = evalInclude(c, n)
-  of nkExportStmt, nkExportExceptStmt:
-    if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "export")
+  of nkExportStmt:
+    if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export")
     result = semExport(c, n)
+  of nkExportExceptStmt:
+    if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "export")
+    result = semExportExcept(c, n)
   of nkPragmaBlock:
     result = semPragmaBlock(c, n)
   of nkStaticStmt:
@@ -2425,9 +2622,14 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
   of nkDefer:
     n.sons[0] = semExpr(c, n.sons[0])
     if not n.sons[0].typ.isEmptyType and not implicitlyDiscardable(n.sons[0]):
-      localError(n.info, errGenerated, "'defer' takes a 'void' expression")
-    #localError(n.info, errGenerated, "'defer' not allowed in this context")
+      localError(c.config, n.info, "'defer' takes a 'void' expression")
+    #localError(c.config, n.info, errGenerated, "'defer' not allowed in this context")
+  of nkGotoState, nkState:
+    if n.len != 1 and n.len != 2: illFormedAst(n, c.config)
+    for i in 0 ..< n.len:
+      n.sons[i] = semExpr(c, n.sons[i])
+  of nkComesFrom: discard "ignore the comes from information for now"
   else:
-    localError(n.info, errInvalidExpressionX,
+    localError(c.config, n.info, "invalid expression: " &
                renderTree(n, {renderNoComments}))
   if result != nil: incl(result.flags, nfSem)
diff --git a/compiler/semfields.nim b/compiler/semfields.nim
index 06826ef75..07321f477 100644
--- a/compiler/semfields.nim
+++ b/compiler/semfields.nim
@@ -16,16 +16,17 @@ type
     tupleIndex: int
     field: PSym
     replaceByFieldName: bool
+    c: PContext
 
 proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode =
   case n.kind
   of nkEmpty..pred(nkIdent), succ(nkSym)..nkNilLit: result = n
   of nkIdent, nkSym:
     result = n
-    let ident = considerQuotedIdent(n)
+    let ident = considerQuotedIdent(c.c, n)
     var L = sonsLen(forLoop)
     if c.replaceByFieldName:
-      if ident.id == considerQuotedIdent(forLoop[0]).id:
+      if ident.id == considerQuotedIdent(c.c, forLoop[0]).id:
         let fieldName = if c.tupleType.isNil: c.field.name.s
                         elif c.tupleType.n.isNil: "Field" & $c.tupleIndex
                         else: c.tupleType.n.sons[c.tupleIndex].sym.name.s
@@ -33,7 +34,7 @@ proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode =
         return
     # other fields:
     for i in ord(c.replaceByFieldName)..L-3:
-      if ident.id == considerQuotedIdent(forLoop[i]).id:
+      if ident.id == considerQuotedIdent(c.c, forLoop[i]).id:
         var call = forLoop.sons[L-2]
         var tupl = call.sons[i+1-ord(c.replaceByFieldName)]
         if c.field.isNil:
@@ -47,7 +48,7 @@ proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode =
         break
   else:
     if n.kind == nkContinueStmt:
-      localError(n.info, errGenerated,
+      localError(c.c.config, n.info,
                  "'continue' not supported in a 'fields' loop")
     result = copyNode(n)
     newSons(result, sonsLen(n))
@@ -63,12 +64,13 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) =
   case typ.kind
   of nkSym:
     var fc: TFieldInstCtx  # either 'tup[i]' or 'field' is valid
+    fc.c = c.c
     fc.field = typ.sym
     fc.replaceByFieldName = c.m == mFieldPairs
     openScope(c.c)
     inc c.c.inUnrolledContext
     let body = instFieldLoopBody(fc, lastSon(forLoop), forLoop)
-    father.add(semStmt(c.c, body))
+    father.add(semStmt(c.c, body, {}))
     dec c.c.inUnrolledContext
     closeScope(c.c)
   of nkNilLit: discard
@@ -76,7 +78,7 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) =
     let L = forLoop.len
     let call = forLoop.sons[L-2]
     if call.len > 2:
-      localError(forLoop.info, errGenerated,
+      localError(c.c.config, forLoop.info,
                  "parallel 'fields' iterator does not work for 'case' objects")
       return
     # iterate over the selector:
@@ -89,7 +91,7 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) =
     access.sons[1] = newSymNode(typ.sons[0].sym, forLoop.info)
     caseStmt.add(semExprWithType(c.c, access))
     # copy the branches over, but replace the fields with the for loop body:
-    for i in 1 .. <typ.len:
+    for i in 1 ..< typ.len:
       var branch = copyTree(typ[i])
       let L = branch.len
       branch.sons[L-1] = newNodeI(nkStmtList, forLoop.info)
@@ -99,17 +101,17 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) =
   of nkRecList:
     for t in items(typ): semForObjectFields(c, t, forLoop, father)
   else:
-    illFormedAstLocal(typ)
+    illFormedAstLocal(typ, c.c.config)
 
 proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
   # so that 'break' etc. work as expected, we produce
   # a 'while true: stmt; break' loop ...
   result = newNodeI(nkWhileStmt, n.info, 2)
-  var trueSymbol = strTableGet(magicsys.systemModule.tab, getIdent"true")
+  var trueSymbol = strTableGet(c.graph.systemModule.tab, getIdent(c.cache, "true"))
   if trueSymbol == nil:
-    localError(n.info, errSystemNeeds, "true")
-    trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(c), n.info)
-    trueSymbol.typ = getSysType(tyBool)
+    localError(c.config, n.info, "system needs: 'true'")
+    trueSymbol = newSym(skUnknown, getIdent(c.cache, "true"), getCurrOwner(c), n.info)
+    trueSymbol.typ = getSysType(c.graph, n.info, tyBool)
 
   result.sons[0] = newSymNode(trueSymbol, n.info)
   var stmts = newNodeI(nkStmtList, n.info)
@@ -118,17 +120,18 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
   var length = sonsLen(n)
   var call = n.sons[length-2]
   if length-2 != sonsLen(call)-1 + ord(m==mFieldPairs):
-    localError(n.info, errWrongNumberOfVariables)
+    localError(c.config, n.info, errWrongNumberOfVariables)
     return result
 
-  var tupleTypeA = skipTypes(call.sons[1].typ, abstractVar-{tyTypeDesc})
+  const skippedTypesForFields = abstractVar - {tyTypeDesc} + tyUserTypeClasses
+  var tupleTypeA = skipTypes(call.sons[1].typ, skippedTypesForFields)
   if tupleTypeA.kind notin {tyTuple, tyObject}:
-    localError(n.info, errGenerated, "no object or tuple type")
+    localError(c.config, n.info, errGenerated, "no object or tuple type")
     return result
   for i in 1..call.len-1:
-    var tupleTypeB = skipTypes(call.sons[i].typ, abstractVar-{tyTypeDesc})
+    var tupleTypeB = skipTypes(call.sons[i].typ, skippedTypesForFields)
     if not sameType(tupleTypeA, tupleTypeB):
-      typeMismatch(call.sons[i].info, tupleTypeA, tupleTypeB)
+      typeMismatch(c.config, call.sons[i].info, tupleTypeA, tupleTypeB)
 
   inc(c.p.nestedLoopCounter)
   if tupleTypeA.kind == tyTuple:
@@ -138,10 +141,11 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
       var fc: TFieldInstCtx
       fc.tupleType = tupleTypeA
       fc.tupleIndex = i
+      fc.c = c
       fc.replaceByFieldName = m == mFieldPairs
       var body = instFieldLoopBody(fc, loopBody, n)
       inc c.inUnrolledContext
-      stmts.add(semStmt(c, body))
+      stmts.add(semStmt(c, body, {}))
       dec c.inUnrolledContext
       closeScope(c)
   else:
@@ -158,7 +162,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
   # we avoid it now if we can:
   if containsNode(stmts, {nkBreakStmt}):
     var b = newNodeI(nkBreakStmt, n.info)
-    b.add(ast.emptyNode)
+    b.add(newNodeI(nkEmpty, n.info))
     stmts.add(b)
   else:
     result = stmts
diff --git a/compiler/semfold.nim b/compiler/semfold.nim
index 58239d23e..9e7ed5cee 100644
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -11,26 +11,22 @@
 # and evaluation phase
 
 import
-  strutils, options, ast, astalgo, trees, treetab, nimsets, times,
+  strutils, options, ast, astalgo, trees, treetab, nimsets,
   nversion, platform, math, msgs, os, condsyms, idents, renderer, types,
-  commands, magicsys, saturate
+  commands, magicsys, modulegraphs, strtabs, lineinfos
 
-proc getConstExpr*(m: PSym, n: PNode): PNode
-  # evaluates the constant expression or returns nil if it is no constant
-  # expression
-proc evalOp*(m: TMagic, n, a, b, c: PNode): PNode
-proc leValueConv*(a, b: PNode): bool
-proc newIntNodeT*(intVal: BiggestInt, n: PNode): PNode
-proc newFloatNodeT(floatVal: BiggestFloat, n: PNode): PNode
-proc newStrNodeT*(strVal: string, n: PNode): PNode
-
-# implementation
+import system/helpers2
 
-proc newIntNodeT(intVal: BiggestInt, n: PNode): PNode =
+proc newIntNodeT*(intVal: BiggestInt, n: PNode; g: ModuleGraph): PNode =
   case skipTypes(n.typ, abstractVarRange).kind
   of tyInt:
     result = newIntNode(nkIntLit, intVal)
-    result.typ = getIntLitType(result)
+    # See bug #6989. 'pred' et al only produce an int literal type if the
+    # original type was 'int', not a distinct int etc.
+    if n.typ.kind == tyInt:
+      result.typ = getIntLitType(g, result)
+    else:
+      result.typ = n.typ
     # hrm, this is not correct: 1 + high(int) shouldn't produce tyInt64 ...
     #setIntLitType(result)
   of tyChar:
@@ -41,20 +37,83 @@ proc newIntNodeT(intVal: BiggestInt, n: PNode): PNode =
     result.typ = n.typ
   result.info = n.info
 
-proc newFloatNodeT(floatVal: BiggestFloat, n: PNode): PNode =
+proc newFloatNodeT*(floatVal: BiggestFloat, n: PNode; g: ModuleGraph): PNode =
   result = newFloatNode(nkFloatLit, floatVal)
-  if skipTypes(n.typ, abstractVarRange).kind == tyFloat:
-    result.typ = getFloatLitType(result)
-  else:
-    result.typ = n.typ
+  result.typ = n.typ
   result.info = n.info
 
-proc newStrNodeT(strVal: string, n: PNode): PNode =
+proc newStrNodeT*(strVal: string, n: PNode; g: ModuleGraph): PNode =
   result = newStrNode(nkStrLit, strVal)
   result.typ = n.typ
   result.info = n.info
 
-proc ordinalValToString*(a: PNode): string =
+proc getConstExpr*(m: PSym, n: PNode; g: ModuleGraph): PNode
+  # evaluates the constant expression or returns nil if it is no constant
+  # expression
+proc evalOp*(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode
+
+proc checkInRange(conf: ConfigRef; n: PNode, res: BiggestInt): bool =
+  if res in firstOrd(conf, n.typ)..lastOrd(conf, n.typ):
+    result = true
+
+proc foldAdd(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  let res = a +% b
+  if ((res xor a) >= 0'i64 or (res xor b) >= 0'i64) and
+      checkInRange(g.config, n, res):
+    result = newIntNodeT(res, n, g)
+
+proc foldSub*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  let res = a -% b
+  if ((res xor a) >= 0'i64 or (res xor not b) >= 0'i64) and
+      checkInRange(g.config, n, res):
+    result = newIntNodeT(res, n, g)
+
+proc foldUnarySub(a: BiggestInt, n: PNode, g: ModuleGraph): PNode =
+  if a != firstOrd(g.config, n.typ):
+    result = newIntNodeT(-a, n, g)
+
+proc foldAbs*(a: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  if a != firstOrd(g.config, n.typ):
+    result = newIntNodeT(abs(a), n, g)
+
+proc foldMod*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  if b != 0'i64:
+    result = newIntNodeT(a mod b, n, g)
+
+proc foldModU*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  if b != 0'i64:
+    result = newIntNodeT(a %% b, n, g)
+
+proc foldDiv*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  if b != 0'i64 and (a != firstOrd(g.config, n.typ) or b != -1'i64):
+    result = newIntNodeT(a div b, n, g)
+
+proc foldDivU*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  if b != 0'i64:
+    result = newIntNodeT(a /% b, n, g)
+
+proc foldMul*(a, b: BiggestInt, n: PNode; g: ModuleGraph): PNode =
+  let res = a *% b
+  let floatProd = toBiggestFloat(a) * toBiggestFloat(b)
+  let resAsFloat = toBiggestFloat(res)
+
+  # Fast path for normal case: small multiplicands, and no info
+  # is lost in either method.
+  if resAsFloat == floatProd and checkInRange(g.config, n, res):
+    return newIntNodeT(res, n, g)
+
+  # Somebody somewhere lost info. Close enough, or way off? Note
+  # that a != 0 and b != 0 (else resAsFloat == floatProd == 0).
+  # The difference either is or isn't significant compared to the
+  # true value (of which floatProd is a good approximation).
+
+  # abs(diff)/abs(prod) <= 1/32 iff
+  #   32 * abs(diff) <= abs(prod) -- 5 good bits is "close enough"
+  if 32.0 * abs(resAsFloat - floatProd) <= abs(floatProd) and
+      checkInRange(g.config, n, res):
+    return newIntNodeT(res, n, g)
+
+proc ordinalValToString*(a: PNode; g: ModuleGraph): string =
   # because $ has the param ordinal[T], `a` is not necessarily an enum, but an
   # ordinal
   var x = getInt(a)
@@ -66,14 +125,14 @@ proc ordinalValToString*(a: PNode): string =
   of tyEnum:
     var n = t.n
     for i in countup(0, sonsLen(n) - 1):
-      if n.sons[i].kind != nkSym: internalError(a.info, "ordinalValToString")
+      if n.sons[i].kind != nkSym: internalError(g.config, a.info, "ordinalValToString")
       var field = n.sons[i].sym
       if field.position == x:
         if field.ast == nil:
           return field.name.s
         else:
           return field.ast.strVal
-    internalError(a.info, "no symbol for ordinal value: " & $x)
+    internalError(g.config, a.info, "no symbol for ordinal value: " & $x)
   else:
     result = $x
 
@@ -92,32 +151,12 @@ proc pickIntRange(a, b: PType): PType =
 proc isIntRangeOrLit(t: PType): bool =
   result = isIntRange(t) or isIntLit(t)
 
-proc pickMinInt(n: PNode): BiggestInt =
-  if n.kind in {nkIntLit..nkUInt64Lit}:
-    result = n.intVal
-  elif isIntLit(n.typ):
-    result = n.typ.n.intVal
-  elif isIntRange(n.typ):
-    result = firstOrd(n.typ)
-  else:
-    internalError(n.info, "pickMinInt")
-
-proc pickMaxInt(n: PNode): BiggestInt =
-  if n.kind in {nkIntLit..nkUInt64Lit}:
-    result = n.intVal
-  elif isIntLit(n.typ):
-    result = n.typ.n.intVal
-  elif isIntRange(n.typ):
-    result = lastOrd(n.typ)
-  else:
-    internalError(n.info, "pickMaxInt")
-
-proc makeRange(typ: PType, first, last: BiggestInt): PType =
+proc makeRange(typ: PType, first, last: BiggestInt; g: ModuleGraph): PType =
   let minA = min(first, last)
   let maxA = max(first, last)
   let lowerNode = newIntNode(nkIntLit, minA)
   if typ.kind == tyInt and minA == maxA:
-    result = getIntLitType(lowerNode)
+    result = getIntLitType(g, lowerNode)
   elif typ.kind in {tyUint, tyUInt64}:
     # these are not ordinal types, so you get no subrange type for these:
     result = typ
@@ -129,7 +168,7 @@ proc makeRange(typ: PType, first, last: BiggestInt): PType =
     result.n = n
     addSonSkipIntLit(result, skipTypes(typ, {tyRange}))
 
-proc makeRangeF(typ: PType, first, last: BiggestFloat): PType =
+proc makeRangeF(typ: PType, first, last: BiggestFloat; g: ModuleGraph): PType =
   var n = newNode(nkRange)
   addSon(n, newFloatNode(nkFloatLit, min(first.float, last.float)))
   addSon(n, newFloatNode(nkFloatLit, max(first.float, last.float)))
@@ -137,389 +176,290 @@ proc makeRangeF(typ: PType, first, last: BiggestFloat): PType =
   result.n = n
   addSonSkipIntLit(result, skipTypes(typ, {tyRange}))
 
-proc getIntervalType*(m: TMagic, n: PNode): PType =
-  # Nim requires interval arithmetic for ``range`` types. Lots of tedious
-  # work but the feature is very nice for reducing explicit conversions.
-  const ordIntLit = {nkIntLit..nkUInt64Lit}
-  result = n.typ
-
-  template commutativeOp(opr: untyped) =
-    let a = n.sons[1]
-    let b = n.sons[2]
-    if isIntRangeOrLit(a.typ) and isIntRangeOrLit(b.typ):
-      result = makeRange(pickIntRange(a.typ, b.typ),
-                         opr(pickMinInt(a), pickMinInt(b)),
-                         opr(pickMaxInt(a), pickMaxInt(b)))
-
-  template binaryOp(opr: untyped) =
-    let a = n.sons[1]
-    let b = n.sons[2]
-    if isIntRange(a.typ) and b.kind in {nkIntLit..nkUInt64Lit}:
-      result = makeRange(a.typ,
-                         opr(pickMinInt(a), pickMinInt(b)),
-                         opr(pickMaxInt(a), pickMaxInt(b)))
+proc fitLiteral(c: ConfigRef, n: PNode): PNode =
+  # Trim the literal value in order to make it fit in the destination type
+  if n == nil:
+    # `n` may be nil if the overflow check kicks in
+    return
 
-  case m
-  of mUnaryMinusI, mUnaryMinusI64:
-    let a = n.sons[1].typ
-    if isIntRange(a):
-      # (1..3) * (-1) == (-3.. -1)
-      result = makeRange(a, 0|-|lastOrd(a), 0|-|firstOrd(a))
-  of mUnaryMinusF64:
-    let a = n.sons[1].typ
-    if isFloatRange(a):
-      result = makeRangeF(a, -getFloat(a.n.sons[1]),
-                             -getFloat(a.n.sons[0]))
-  of mAbsF64:
-    let a = n.sons[1].typ
-    if isFloatRange(a):
-      # abs(-5.. 1) == (1..5)
-      if a.n[0].floatVal <= 0.0:
-        result = makeRangeF(a, 0.0, abs(getFloat(a.n.sons[0])))
-      else:
-        result = makeRangeF(a, abs(getFloat(a.n.sons[1])),
-                               abs(getFloat(a.n.sons[0])))
-  of mAbsI:
-    let a = n.sons[1].typ
-    if isIntRange(a):
-      if a.n[0].intVal <= 0:
-        result = makeRange(a, 0, `|abs|`(getInt(a.n.sons[0])))
-      else:
-        result = makeRange(a, `|abs|`(getInt(a.n.sons[1])),
-                              `|abs|`(getInt(a.n.sons[0])))
-  of mSucc:
-    let a = n.sons[1].typ
-    let b = n.sons[2].typ
-    if isIntRange(a) and isIntLit(b):
-      # (-5.. 1) + 6 == (-5 + 6)..(-1 + 6)
-      result = makeRange(a, pickMinInt(n.sons[1]) |+| pickMinInt(n.sons[2]),
-                            pickMaxInt(n.sons[1]) |+| pickMaxInt(n.sons[2]))
-  of mPred:
-    let a = n.sons[1].typ
-    let b = n.sons[2].typ
-    if isIntRange(a) and isIntLit(b):
-      result = makeRange(a, pickMinInt(n.sons[1]) |-| pickMinInt(n.sons[2]),
-                            pickMaxInt(n.sons[1]) |-| pickMaxInt(n.sons[2]))
-  of mAddI, mAddU:
-    commutativeOp(`|+|`)
-  of mMulI, mMulU:
-    commutativeOp(`|*|`)
-  of mSubI, mSubU:
-    binaryOp(`|-|`)
-  of mBitandI:
-    # since uint64 is still not even valid for 'range' (since it's no ordinal
-    # yet), we exclude it from the list (see bug #1638) for now:
-    var a = n.sons[1]
-    var b = n.sons[2]
-    # symmetrical:
-    if b.kind notin ordIntLit: swap(a, b)
-    if b.kind in ordIntLit:
-      let x = b.intVal|+|1
-      if (x and -x) == x and x >= 0:
-        result = makeRange(n.typ, 0, b.intVal)
-  of mModU:
-    let a = n.sons[1]
-    let b = n.sons[2]
-    if b.kind in ordIntLit:
-      if b.intVal >= 0:
-        result = makeRange(n.typ, 0, b.intVal-1)
-      else:
-        result = makeRange(n.typ, b.intVal+1, 0)
-  of mModI:
-    # so ... if you ever wondered about modulo's signedness; this defines it:
-    let a = n.sons[1]
-    let b = n.sons[2]
-    if b.kind in {nkIntLit..nkUInt64Lit}:
-      if b.intVal >= 0:
-        result = makeRange(n.typ, -(b.intVal-1), b.intVal-1)
-      else:
-        result = makeRange(n.typ, b.intVal+1, -(b.intVal+1))
-  of mDivI, mDivU:
-    binaryOp(`|div|`)
-  of mMinI:
-    commutativeOp(min)
-  of mMaxI:
-    commutativeOp(max)
-  else: discard
+  doAssert n.kind in {nkIntLit, nkCharLit}
 
-discard """
-  mShlI,
-  mShrI, mAddF64, mSubF64, mMulF64, mDivF64, mMaxF64, mMinF64
-"""
-
-proc evalIs(n, a: PNode): PNode =
-  # XXX: This should use the standard isOpImpl
-  internalAssert a.kind == nkSym and a.sym.kind == skType
-  internalAssert n.sonsLen == 3 and
-    n[2].kind in {nkStrLit..nkTripleStrLit, nkType}
-
-  let t1 = a.sym.typ
-
-  if n[2].kind in {nkStrLit..nkTripleStrLit}:
-    case n[2].strVal.normalize
-    of "closure":
-      let t = skipTypes(t1, abstractRange)
-      result = newIntNode(nkIntLit, ord(t.kind == tyProc and
-                                        t.callConv == ccClosure and
-                                        tfIterator notin t.flags))
-    of "iterator":
-      let t = skipTypes(t1, abstractRange)
-      result = newIntNode(nkIntLit, ord(t.kind == tyProc and
-                                        t.callConv == ccClosure and
-                                        tfIterator in t.flags))
-    else: discard
-  else:
-    # XXX semexprs.isOpImpl is slightly different and requires a context. yay.
-    let t2 = n[2].typ
-    var match = sameType(t1, t2)
-    result = newIntNode(nkIntLit, ord(match))
-  result.typ = n.typ
+  result = n
+
+  let typ = n.typ.skipTypes(abstractRange)
+  if typ.kind in tyUInt..tyUint32:
+    result.intVal = result.intVal and lastOrd(c, typ, fixedUnsigned=true)
 
-proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
+proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode =
+  template doAndFit(op: untyped): untyped =
+    # Implements wrap-around behaviour for unsigned types
+    fitLiteral(g.config, op)
   # b and c may be nil
   result = nil
   case m
-  of mOrd: result = newIntNodeT(getOrdValue(a), n)
-  of mChr: result = newIntNodeT(getInt(a), n)
-  of mUnaryMinusI, mUnaryMinusI64: result = newIntNodeT(- getInt(a), n)
-  of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n)
-  of mNot: result = newIntNodeT(1 - getInt(a), n)
-  of mCard: result = newIntNodeT(nimsets.cardSet(a), n)
-  of mBitnotI: result = newIntNodeT(not getInt(a), n)
-  of mLengthArray: result = newIntNodeT(lengthOrd(a.typ), n)
+  of mOrd: result = newIntNodeT(getOrdValue(a), n, g)
+  of mChr: result = newIntNodeT(getInt(a), n, g)
+  of mUnaryMinusI, mUnaryMinusI64: result = foldUnarySub(getInt(a), n, g)
+  of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n, g)
+  of mNot: result = newIntNodeT(1 - getInt(a), n, g)
+  of mCard: result = newIntNodeT(nimsets.cardSet(g.config, a), n, g)
+  of mBitnotI: result = doAndFit(newIntNodeT(not getInt(a), n, g))
+  of mLengthArray: result = newIntNodeT(lengthOrd(g.config, a.typ), n, g)
   of mLengthSeq, mLengthOpenArray, mXLenSeq, mLengthStr, mXLenStr:
     if a.kind == nkNilLit:
-      result = newIntNodeT(0, n)
+      result = newIntNodeT(0, n, g)
     elif a.kind in {nkStrLit..nkTripleStrLit}:
-      result = newIntNodeT(len a.strVal, n)
+      result = newIntNodeT(len a.strVal, n, g)
     else:
-      result = newIntNodeT(sonsLen(a), n) # BUGFIX
+      result = newIntNodeT(sonsLen(a), n, g)
   of mUnaryPlusI, mUnaryPlusF64: result = a # throw `+` away
   of mToFloat, mToBiggestFloat:
-    result = newFloatNodeT(toFloat(int(getInt(a))), n)
-  of mToInt, mToBiggestInt: result = newIntNodeT(system.toInt(getFloat(a)), n)
-  of mAbsF64: result = newFloatNodeT(abs(getFloat(a)), n)
-  of mAbsI:
-    if getInt(a) >= 0: result = a
-    else: result = newIntNodeT(- getInt(a), n)
+    result = newFloatNodeT(toFloat(int(getInt(a))), n, g)
+  # XXX: Hides overflow/underflow
+  of mToInt, mToBiggestInt: result = newIntNodeT(system.toInt(getFloat(a)), n, g)
+  of mAbsF64: result = newFloatNodeT(abs(getFloat(a)), n, g)
+  of mAbsI: result = foldAbs(getInt(a), n, g)
   of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64:
     # byte(-128) = 1...1..1000_0000'64 --> 0...0..1000_0000'64
-    result = newIntNodeT(getInt(a) and (`shl`(1, getSize(a.typ) * 8) - 1), n)
-  of mToU8: result = newIntNodeT(getInt(a) and 0x000000FF, n)
-  of mToU16: result = newIntNodeT(getInt(a) and 0x0000FFFF, n)
-  of mToU32: result = newIntNodeT(getInt(a) and 0x00000000FFFFFFFF'i64, n)
-  of mUnaryLt: result = newIntNodeT(getOrdValue(a) |-| 1, n)
-  of mSucc: result = newIntNodeT(getOrdValue(a) |+| getInt(b), n)
-  of mPred: result = newIntNodeT(getOrdValue(a) |-| getInt(b), n)
-  of mAddI: result = newIntNodeT(getInt(a) |+| getInt(b), n)
-  of mSubI: result = newIntNodeT(getInt(a) |-| getInt(b), n)
-  of mMulI: result = newIntNodeT(getInt(a) |*| getInt(b), n)
+    result = newIntNodeT(getInt(a) and (`shl`(1, getSize(g.config, a.typ) * 8) - 1), n, g)
+  of mToU8: result = newIntNodeT(getInt(a) and 0x000000FF, n, g)
+  of mToU16: result = newIntNodeT(getInt(a) and 0x0000FFFF, n, g)
+  of mToU32: result = newIntNodeT(getInt(a) and 0x00000000FFFFFFFF'i64, n, g)
+  of mUnaryLt: result = doAndFit(foldSub(getOrdValue(a), 1, n, g))
+  of mSucc: result = doAndFit(foldAdd(getOrdValue(a), getInt(b), n, g))
+  of mPred: result = doAndFit(foldSub(getOrdValue(a), getInt(b), n, g))
+  of mAddI: result = foldAdd(getInt(a), getInt(b), n, g)
+  of mSubI: result = foldSub(getInt(a), getInt(b), n, g)
+  of mMulI: result = foldMul(getInt(a), getInt(b), n, g)
   of mMinI:
-    if getInt(a) > getInt(b): result = newIntNodeT(getInt(b), n)
-    else: result = newIntNodeT(getInt(a), n)
+    if getInt(a) > getInt(b): result = newIntNodeT(getInt(b), n, g)
+    else: result = newIntNodeT(getInt(a), n, g)
   of mMaxI:
-    if getInt(a) > getInt(b): result = newIntNodeT(getInt(a), n)
-    else: result = newIntNodeT(getInt(b), n)
+    if getInt(a) > getInt(b): result = newIntNodeT(getInt(a), n, g)
+    else: result = newIntNodeT(getInt(b), n, g)
   of mShlI:
     case skipTypes(n.typ, abstractRange).kind
-    of tyInt8: result = newIntNodeT(int8(getInt(a)) shl int8(getInt(b)), n)
-    of tyInt16: result = newIntNodeT(int16(getInt(a)) shl int16(getInt(b)), n)
-    of tyInt32: result = newIntNodeT(int32(getInt(a)) shl int32(getInt(b)), n)
-    of tyInt64, tyInt, tyUInt..tyUInt64:
-      result = newIntNodeT(`shl`(getInt(a), getInt(b)), n)
-    else: internalError(n.info, "constant folding for shl")
+    of tyInt8: result = newIntNodeT(int8(getInt(a)) shl int8(getInt(b)), n, g)
+    of tyInt16: result = newIntNodeT(int16(getInt(a)) shl int16(getInt(b)), n, g)
+    of tyInt32: result = newIntNodeT(int32(getInt(a)) shl int32(getInt(b)), n, g)
+    of tyInt64, tyInt:
+      result = newIntNodeT(`shl`(getInt(a), getInt(b)), n, g)
+    of tyUInt..tyUInt64:
+      result = doAndFit(newIntNodeT(`shl`(getInt(a), getInt(b)), n, g))
+    else: internalError(g.config, n.info, "constant folding for shl")
   of mShrI:
     case skipTypes(n.typ, abstractRange).kind
-    of tyInt8: result = newIntNodeT(int8(getInt(a)) shr int8(getInt(b)), n)
-    of tyInt16: result = newIntNodeT(int16(getInt(a)) shr int16(getInt(b)), n)
-    of tyInt32: result = newIntNodeT(int32(getInt(a)) shr int32(getInt(b)), n)
+    of tyInt8: result = newIntNodeT(int8(getInt(a)) shr int8(getInt(b)), n, g)
+    of tyInt16: result = newIntNodeT(int16(getInt(a)) shr int16(getInt(b)), n, g)
+    of tyInt32: result = newIntNodeT(int32(getInt(a)) shr int32(getInt(b)), n, g)
     of tyInt64, tyInt, tyUInt..tyUInt64:
-      result = newIntNodeT(`shr`(getInt(a), getInt(b)), n)
-    else: internalError(n.info, "constant folding for shr")
-  of mDivI:
-    let y = getInt(b)
-    if y != 0:
-      result = newIntNodeT(`|div|`(getInt(a), y), n)
-  of mModI:
-    let y = getInt(b)
-    if y != 0:
-      result = newIntNodeT(`|mod|`(getInt(a), y), n)
-  of mAddF64: result = newFloatNodeT(getFloat(a) + getFloat(b), n)
-  of mSubF64: result = newFloatNodeT(getFloat(a) - getFloat(b), n)
-  of mMulF64: result = newFloatNodeT(getFloat(a) * getFloat(b), n)
+      result = newIntNodeT(`shr`(getInt(a), getInt(b)), n, g)
+    else: internalError(g.config, n.info, "constant folding for shr")
+  of mAshrI:
+    case skipTypes(n.typ, abstractRange).kind
+    of tyInt8: result = newIntNodeT(ashr(int8(getInt(a)), int8(getInt(b))), n, g)
+    of tyInt16: result = newIntNodeT(ashr(int16(getInt(a)), int16(getInt(b))), n, g)
+    of tyInt32: result = newIntNodeT(ashr(int32(getInt(a)), int32(getInt(b))), n, g)
+    of tyInt64, tyInt:
+      result = newIntNodeT(ashr(getInt(a), getInt(b)), n, g)
+    else: internalError(g.config, n.info, "constant folding for ashr")
+  of mDivI: result = foldDiv(getInt(a), getInt(b), n, g)
+  of mModI: result = foldMod(getInt(a), getInt(b), n, g)
+  of mAddF64: result = newFloatNodeT(getFloat(a) + getFloat(b), n, g)
+  of mSubF64: result = newFloatNodeT(getFloat(a) - getFloat(b), n, g)
+  of mMulF64: result = newFloatNodeT(getFloat(a) * getFloat(b), n, g)
   of mDivF64:
-    if getFloat(b) == 0.0:
-      if getFloat(a) == 0.0: result = newFloatNodeT(NaN, n)
-      else: result = newFloatNodeT(Inf, n)
-    else:
-      result = newFloatNodeT(getFloat(a) / getFloat(b), n)
+    result = newFloatNodeT(getFloat(a) / getFloat(b), n, g)
   of mMaxF64:
-    if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(a), n)
-    else: result = newFloatNodeT(getFloat(b), n)
+    if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(a), n, g)
+    else: result = newFloatNodeT(getFloat(b), n, g)
   of mMinF64:
-    if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(b), n)
-    else: result = newFloatNodeT(getFloat(a), n)
-  of mIsNil: result = newIntNodeT(ord(a.kind == nkNilLit), n)
+    if getFloat(a) > getFloat(b): result = newFloatNodeT(getFloat(b), n, g)
+    else: result = newFloatNodeT(getFloat(a), n, g)
+  of mIsNil: result = newIntNodeT(ord(a.kind == nkNilLit), n, g)
   of mLtI, mLtB, mLtEnum, mLtCh:
-    result = newIntNodeT(ord(getOrdValue(a) < getOrdValue(b)), n)
+    result = newIntNodeT(ord(getOrdValue(a) < getOrdValue(b)), n, g)
   of mLeI, mLeB, mLeEnum, mLeCh:
-    result = newIntNodeT(ord(getOrdValue(a) <= getOrdValue(b)), n)
+    result = newIntNodeT(ord(getOrdValue(a) <= getOrdValue(b)), n, g)
   of mEqI, mEqB, mEqEnum, mEqCh:
-    result = newIntNodeT(ord(getOrdValue(a) == getOrdValue(b)), n)
-  of mLtF64: result = newIntNodeT(ord(getFloat(a) < getFloat(b)), n)
-  of mLeF64: result = newIntNodeT(ord(getFloat(a) <= getFloat(b)), n)
-  of mEqF64: result = newIntNodeT(ord(getFloat(a) == getFloat(b)), n)
-  of mLtStr: result = newIntNodeT(ord(getStr(a) < getStr(b)), n)
-  of mLeStr: result = newIntNodeT(ord(getStr(a) <= getStr(b)), n)
-  of mEqStr: result = newIntNodeT(ord(getStr(a) == getStr(b)), n)
+    result = newIntNodeT(ord(getOrdValue(a) == getOrdValue(b)), n, g)
+  of mLtF64: result = newIntNodeT(ord(getFloat(a) < getFloat(b)), n, g)
+  of mLeF64: result = newIntNodeT(ord(getFloat(a) <= getFloat(b)), n, g)
+  of mEqF64: result = newIntNodeT(ord(getFloat(a) == getFloat(b)), n, g)
+  of mLtStr: result = newIntNodeT(ord(getStr(a) < getStr(b)), n, g)
+  of mLeStr: result = newIntNodeT(ord(getStr(a) <= getStr(b)), n, g)
+  of mEqStr: result = newIntNodeT(ord(getStr(a) == getStr(b)), n, g)
   of mLtU, mLtU64:
-    result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n)
+    result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n, g)
   of mLeU, mLeU64:
-    result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n)
-  of mBitandI, mAnd: result = newIntNodeT(a.getInt and b.getInt, n)
-  of mBitorI, mOr: result = newIntNodeT(getInt(a) or getInt(b), n)
-  of mBitxorI, mXor: result = newIntNodeT(a.getInt xor b.getInt, n)
-  of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n)
-  of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n)
-  of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n)
-  of mModU:
-    let y = getInt(b)
-    if y != 0:
-      result = newIntNodeT(`%%`(getInt(a), y), n)
-  of mDivU:
-    let y = getInt(b)
-    if y != 0:
-      result = newIntNodeT(`/%`(getInt(a), y), n)
-  of mLeSet: result = newIntNodeT(ord(containsSets(a, b)), n)
-  of mEqSet: result = newIntNodeT(ord(equalSets(a, b)), n)
+    result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n, g)
+  of mBitandI, mAnd: result = doAndFit(newIntNodeT(a.getInt and b.getInt, n, g))
+  of mBitorI, mOr: result = doAndFit(newIntNodeT(getInt(a) or getInt(b), n, g))
+  of mBitxorI, mXor: result = doAndFit(newIntNodeT(a.getInt xor b.getInt, n, g))
+  of mAddU: result = doAndFit(newIntNodeT(`+%`(getInt(a), getInt(b)), n, g))
+  of mSubU: result = doAndFit(newIntNodeT(`-%`(getInt(a), getInt(b)), n, g))
+  of mMulU: result = doAndFit(newIntNodeT(`*%`(getInt(a), getInt(b)), n, g))
+  of mModU: result = doAndFit(foldModU(getInt(a), getInt(b), n, g))
+  of mDivU: result = doAndFit(foldDivU(getInt(a), getInt(b), n, g))
+  of mLeSet: result = newIntNodeT(ord(containsSets(g.config, a, b)), n, g)
+  of mEqSet: result = newIntNodeT(ord(equalSets(g.config, a, b)), n, g)
   of mLtSet:
-    result = newIntNodeT(ord(containsSets(a, b) and not equalSets(a, b)), n)
+    result = newIntNodeT(ord(containsSets(g.config, a, b) and not equalSets(g.config, a, b)), n, g)
   of mMulSet:
-    result = nimsets.intersectSets(a, b)
+    result = nimsets.intersectSets(g.config, a, b)
     result.info = n.info
   of mPlusSet:
-    result = nimsets.unionSets(a, b)
+    result = nimsets.unionSets(g.config, a, b)
     result.info = n.info
   of mMinusSet:
-    result = nimsets.diffSets(a, b)
+    result = nimsets.diffSets(g.config, a, b)
     result.info = n.info
   of mSymDiffSet:
-    result = nimsets.symdiffSets(a, b)
+    result = nimsets.symdiffSets(g.config, a, b)
     result.info = n.info
-  of mConStrStr: result = newStrNodeT(getStrOrChar(a) & getStrOrChar(b), n)
-  of mInSet: result = newIntNodeT(ord(inSet(a, b)), n)
+  of mConStrStr: result = newStrNodeT(getStrOrChar(a) & getStrOrChar(b), n, g)
+  of mInSet: result = newIntNodeT(ord(inSet(a, b)), n, g)
   of mRepr:
     # BUGFIX: we cannot eval mRepr here for reasons that I forgot.
     discard
-  of mIntToStr, mInt64ToStr: result = newStrNodeT($(getOrdValue(a)), n)
+  of mIntToStr, mInt64ToStr: result = newStrNodeT($(getOrdValue(a)), n, g)
   of mBoolToStr:
-    if getOrdValue(a) == 0: result = newStrNodeT("false", n)
-    else: result = newStrNodeT("true", n)
-  of mCopyStr: result = newStrNodeT(substr(getStr(a), int(getOrdValue(b))), n)
+    if getOrdValue(a) == 0: result = newStrNodeT("false", n, g)
+    else: result = newStrNodeT("true", n, g)
+  of mCopyStr: result = newStrNodeT(substr(getStr(a), int(getOrdValue(b))), n, g)
   of mCopyStrLast:
     result = newStrNodeT(substr(getStr(a), int(getOrdValue(b)),
-                                           int(getOrdValue(c))), n)
-  of mFloatToStr: result = newStrNodeT($getFloat(a), n)
+                                           int(getOrdValue(c))), n, g)
+  of mFloatToStr: result = newStrNodeT($getFloat(a), n, g)
   of mCStrToStr, mCharToStr:
     if a.kind == nkBracket:
       var s = ""
       for b in a.sons:
         s.add b.getStrOrChar
-      result = newStrNodeT(s, n)
+      result = newStrNodeT(s, n, g)
     else:
-      result = newStrNodeT(getStrOrChar(a), n)
-  of mStrToStr: result = a
-  of mEnumToStr: result = newStrNodeT(ordinalValToString(a), n)
+      result = newStrNodeT(getStrOrChar(a), n, g)
+  of mStrToStr: result = newStrNodeT(getStrOrChar(a), n, g)
+  of mEnumToStr: result = newStrNodeT(ordinalValToString(a, g), n, g)
   of mArrToSeq:
     result = copyTree(a)
     result.typ = n.typ
   of mCompileOption:
-    result = newIntNodeT(ord(commands.testCompileOption(a.getStr, n.info)), n)
+    result = newIntNodeT(ord(commands.testCompileOption(g.config, a.getStr, n.info)), n, g)
   of mCompileOptionArg:
     result = newIntNodeT(ord(
-      testCompileOptionArg(getStr(a), getStr(b), n.info)), n)
+      testCompileOptionArg(g.config, getStr(a), getStr(b), n.info)), n, g)
   of mEqProc:
     result = newIntNodeT(ord(
-        exprStructuralEquivalent(a, b, strictSymEquality=true)), n)
+        exprStructuralEquivalent(a, b, strictSymEquality=true)), n, g)
   else: discard
 
-proc getConstIfExpr(c: PSym, n: PNode): PNode =
+proc getConstIfExpr(c: PSym, n: PNode; g: ModuleGraph): PNode =
   result = nil
   for i in countup(0, sonsLen(n) - 1):
     var it = n.sons[i]
     if it.len == 2:
-      var e = getConstExpr(c, it.sons[0])
+      var e = getConstExpr(c, it.sons[0], g)
       if e == nil: return nil
       if getOrdValue(e) != 0:
         if result == nil:
-          result = getConstExpr(c, it.sons[1])
+          result = getConstExpr(c, it.sons[1], g)
           if result == nil: return
     elif it.len == 1:
-      if result == nil: result = getConstExpr(c, it.sons[0])
-    else: internalError(it.info, "getConstIfExpr()")
+      if result == nil: result = getConstExpr(c, it.sons[0], g)
+    else: internalError(g.config, it.info, "getConstIfExpr()")
 
-proc leValueConv(a, b: PNode): bool =
+proc leValueConv*(a, b: PNode): bool =
   result = false
   case a.kind
   of nkCharLit..nkUInt64Lit:
     case b.kind
     of nkCharLit..nkUInt64Lit: result = a.intVal <= b.intVal
     of nkFloatLit..nkFloat128Lit: result = a.intVal <= round(b.floatVal).int
-    else: internalError(a.info, "leValueConv")
+    else: result = false #internalError(a.info, "leValueConv")
   of nkFloatLit..nkFloat128Lit:
     case b.kind
     of nkFloatLit..nkFloat128Lit: result = a.floatVal <= b.floatVal
     of nkCharLit..nkUInt64Lit: result = a.floatVal <= toFloat(int(b.intVal))
-    else: internalError(a.info, "leValueConv")
-  else: internalError(a.info, "leValueConv")
+    else: result = false # internalError(a.info, "leValueConv")
+  else: result = false # internalError(a.info, "leValueConv")
 
-proc magicCall(m: PSym, n: PNode): PNode =
+proc magicCall(m: PSym, n: PNode; g: ModuleGraph): PNode =
   if sonsLen(n) <= 1: return
 
   var s = n.sons[0].sym
-  var a = getConstExpr(m, n.sons[1])
+  var a = getConstExpr(m, n.sons[1], g)
   var b, c: PNode
   if a == nil: return
   if sonsLen(n) > 2:
-    b = getConstExpr(m, n.sons[2])
+    b = getConstExpr(m, n.sons[2], g)
     if b == nil: return
     if sonsLen(n) > 3:
-      c = getConstExpr(m, n.sons[3])
+      c = getConstExpr(m, n.sons[3], g)
       if c == nil: return
-  result = evalOp(s.magic, n, a, b, c)
-
-proc getAppType(n: PNode): PNode =
-  if gGlobalOptions.contains(optGenDynLib):
-    result = newStrNodeT("lib", n)
-  elif gGlobalOptions.contains(optGenStaticLib):
-    result = newStrNodeT("staticlib", n)
-  elif gGlobalOptions.contains(optGenGuiApp):
-    result = newStrNodeT("gui", n)
+  result = evalOp(s.magic, n, a, b, c, g)
+
+proc getAppType(n: PNode; g: ModuleGraph): PNode =
+  if g.config.globalOptions.contains(optGenDynLib):
+    result = newStrNodeT("lib", n, g)
+  elif g.config.globalOptions.contains(optGenStaticLib):
+    result = newStrNodeT("staticlib", n, g)
+  elif g.config.globalOptions.contains(optGenGuiApp):
+    result = newStrNodeT("gui", n, g)
   else:
-    result = newStrNodeT("console", n)
+    result = newStrNodeT("console", n, g)
 
-proc rangeCheck(n: PNode, value: BiggestInt) =
-  if value < firstOrd(n.typ) or value > lastOrd(n.typ):
-    localError(n.info, errGenerated, "cannot convert " & $value &
-                                     " to " & typeToString(n.typ))
+proc rangeCheck(n: PNode, value: BiggestInt; g: ModuleGraph) =
+  var err = false
+  if n.typ.skipTypes({tyRange}).kind in {tyUInt..tyUInt64}:
+    err = value <% firstOrd(g.config, n.typ) or value >% lastOrd(g.config, n.typ, fixedUnsigned=true)
+  else:
+    err = value < firstOrd(g.config, n.typ) or value > lastOrd(g.config, n.typ)
+  if err:
+    localError(g.config, n.info, "cannot convert " & $value &
+                                    " to " & typeToString(n.typ))
+
+proc foldConv(n, a: PNode; g: ModuleGraph; check = false): PNode =
+  let dstTyp = skipTypes(n.typ, abstractRange)
+  let srcTyp = skipTypes(a.typ, abstractRange)
 
-proc foldConv*(n, a: PNode; check = false): PNode =
   # XXX range checks?
-  case skipTypes(n.typ, abstractRange).kind
-  of tyInt..tyInt64, tyUInt..tyUInt64:
-    case skipTypes(a.typ, abstractRange).kind
+  case dstTyp.kind
+  of tyInt..tyInt64, tyUint..tyUInt64:
+    case srcTyp.kind
     of tyFloat..tyFloat64:
-      result = newIntNodeT(int(getFloat(a)), n)
-    of tyChar: result = newIntNodeT(getOrdValue(a), n)
+      result = newIntNodeT(int(getFloat(a)), n, g)
+    of tyChar:
+      result = newIntNodeT(getOrdValue(a), n, g)
+    of tyUInt..tyUInt64, tyInt..tyInt64:
+      let toSigned = dstTyp.kind in tyInt..tyInt64
+      var val = a.getOrdValue
+
+      if dstTyp.kind in {tyInt, tyInt64, tyUint, tyUInt64}:
+        # No narrowing needed
+        discard
+      elif dstTyp.kind in {tyInt..tyInt64}:
+        # Signed type: Overflow check (if requested) and conversion
+        if check: rangeCheck(n, val, g)
+        let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1)
+        let valSign = val < 0
+        val = abs(val) and mask
+        if valSign: val = -val
+      else:
+        # Unsigned type: Conversion
+        let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1)
+        val = val and mask
+
+      result = newIntNodeT(val, n, g)
     else:
       result = a
       result.typ = n.typ
     if check and result.kind in {nkCharLit..nkUInt64Lit}:
-      rangeCheck(n, result.intVal)
+      rangeCheck(n, result.intVal, g)
   of tyFloat..tyFloat64:
-    case skipTypes(a.typ, abstractRange).kind
+    case srcTyp.kind
     of tyInt..tyInt64, tyEnum, tyBool, tyChar:
-      result = newFloatNodeT(toBiggestFloat(getOrdValue(a)), n)
+      result = newFloatNodeT(toBiggestFloat(getOrdValue(a)), n, g)
     else:
       result = a
       result.typ = n.typ
@@ -529,47 +469,47 @@ proc foldConv*(n, a: PNode; check = false): PNode =
     result = a
     result.typ = n.typ
 
-proc getArrayConstr(m: PSym, n: PNode): PNode =
+proc getArrayConstr(m: PSym, n: PNode; g: ModuleGraph): PNode =
   if n.kind == nkBracket:
     result = n
   else:
-    result = getConstExpr(m, n)
+    result = getConstExpr(m, n, g)
     if result == nil: result = n
 
-proc foldArrayAccess(m: PSym, n: PNode): PNode =
-  var x = getConstExpr(m, n.sons[0])
-  if x == nil or x.typ.skipTypes({tyGenericInst, tyAlias}).kind == tyTypeDesc:
+proc foldArrayAccess(m: PSym, n: PNode; g: ModuleGraph): PNode =
+  var x = getConstExpr(m, n.sons[0], g)
+  if x == nil or x.typ.skipTypes({tyGenericInst, tyAlias, tySink}).kind == tyTypeDesc:
     return
 
-  var y = getConstExpr(m, n.sons[1])
+  var y = getConstExpr(m, n.sons[1], g)
   if y == nil: return
 
   var idx = getOrdValue(y)
   case x.kind
-  of nkPar:
+  of nkPar, nkTupleConstr:
     if idx >= 0 and idx < sonsLen(x):
       result = x.sons[int(idx)]
       if result.kind == nkExprColonExpr: result = result.sons[1]
     else:
-      localError(n.info, errIndexOutOfBounds)
+      localError(g.config, n.info, formatErrorIndexBound(idx, sonsLen(x)+1) & $n)
   of nkBracket:
-    idx = idx - x.typ.firstOrd
+    idx = idx - firstOrd(g.config, x.typ)
     if idx >= 0 and idx < x.len: result = x.sons[int(idx)]
-    else: localError(n.info, errIndexOutOfBounds)
+    else: localError(g.config, n.info, formatErrorIndexBound(idx, x.len+1) & $n)
   of nkStrLit..nkTripleStrLit:
     result = newNodeIT(nkCharLit, x.info, n.typ)
     if idx >= 0 and idx < len(x.strVal):
       result.intVal = ord(x.strVal[int(idx)])
-    elif idx == len(x.strVal):
+    elif idx == len(x.strVal) and optLaxStrings in g.config.options:
       discard
     else:
-      localError(n.info, errIndexOutOfBounds)
+      localError(g.config, n.info, formatErrorIndexBound(idx, len(x.strVal)-1) & $n)
   else: discard
 
-proc foldFieldAccess(m: PSym, n: PNode): PNode =
+proc foldFieldAccess(m: PSym, n: PNode; g: ModuleGraph): PNode =
   # a real field access; proc calls have already been transformed
-  var x = getConstExpr(m, n.sons[0])
-  if x == nil or x.kind notin {nkObjConstr, nkPar}: return
+  var x = getConstExpr(m, n.sons[0], g)
+  if x == nil or x.kind notin {nkObjConstr, nkPar, nkTupleConstr}: return
 
   var field = n.sons[1].sym
   for i in countup(ord(x.kind == nkObjConstr), sonsLen(x) - 1):
@@ -582,51 +522,62 @@ proc foldFieldAccess(m: PSym, n: PNode): PNode =
     if it.sons[0].sym.name.id == field.name.id:
       result = x.sons[i].sons[1]
       return
-  localError(n.info, errFieldXNotFound, field.name.s)
+  localError(g.config, n.info, "field not found: " & field.name.s)
 
-proc foldConStrStr(m: PSym, n: PNode): PNode =
+proc foldConStrStr(m: PSym, n: PNode; g: ModuleGraph): PNode =
   result = newNodeIT(nkStrLit, n.info, n.typ)
   result.strVal = ""
   for i in countup(1, sonsLen(n) - 1):
-    let a = getConstExpr(m, n.sons[i])
+    let a = getConstExpr(m, n.sons[i], g)
     if a == nil: return nil
     result.strVal.add(getStrOrChar(a))
 
 proc newSymNodeTypeDesc*(s: PSym; info: TLineInfo): PNode =
   result = newSymNode(s, info)
-  result.typ = newType(tyTypeDesc, s.owner)
-  result.typ.addSonSkipIntLit(s.typ)
+  if s.typ.kind != tyTypeDesc:
+    result.typ = newType(tyTypeDesc, s.owner)
+    result.typ.addSonSkipIntLit(s.typ)
+  else:
+    result.typ = s.typ
 
-proc getConstExpr(m: PSym, n: PNode): PNode =
+proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode =
   result = nil
   case n.kind
   of nkSym:
     var s = n.sym
     case s.kind
     of skEnumField:
-      result = newIntNodeT(s.position, n)
+      result = newIntNodeT(s.position, n, g)
     of skConst:
       case s.magic
-      of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n)
-      of mCompileDate: result = newStrNodeT(times.getDateStr(), n)
-      of mCompileTime: result = newStrNodeT(times.getClockStr(), n)
-      of mCpuEndian: result = newIntNodeT(ord(CPU[targetCPU].endian), n)
-      of mHostOS: result = newStrNodeT(toLowerAscii(platform.OS[targetOS].name), n)
-      of mHostCPU: result = newStrNodeT(platform.CPU[targetCPU].name.toLowerAscii, n)
-      of mAppType: result = getAppType(n)
-      of mNaN: result = newFloatNodeT(NaN, n)
-      of mInf: result = newFloatNodeT(Inf, n)
-      of mNegInf: result = newFloatNodeT(NegInf, n)
+      of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n, g)
+      of mCompileDate: result = newStrNodeT(getDateStr(), n, g)
+      of mCompileTime: result = newStrNodeT(getClockStr(), n, g)
+      of mCpuEndian: result = newIntNodeT(ord(CPU[g.config.target.targetCPU].endian), n, g)
+      of mHostOS: result = newStrNodeT(toLowerAscii(platform.OS[g.config.target.targetOS].name), n, g)
+      of mHostCPU: result = newStrNodeT(platform.CPU[g.config.target.targetCPU].name.toLowerAscii, n, g)
+      of mBuildOS: result = newStrNodeT(toLowerAscii(platform.OS[g.config.target.hostOS].name), n, g)
+      of mBuildCPU: result = newStrNodeT(platform.CPU[g.config.target.hostCPU].name.toLowerAscii, n, g)
+      of mAppType: result = getAppType(n, g)
+      of mNaN: result = newFloatNodeT(NaN, n, g)
+      of mInf: result = newFloatNodeT(Inf, n, g)
+      of mNegInf: result = newFloatNodeT(NegInf, n, g)
       of mIntDefine:
-        if isDefined(s.name):
-          result = newIntNodeT(lookupSymbol(s.name).parseInt, n)
+        if isDefined(g.config, s.name.s):
+          try:
+            result = newIntNodeT(g.config.symbols[s.name.s].parseInt, n, g)
+          except ValueError:
+            localError(g.config, n.info, "expression is not an integer literal")
       of mStrDefine:
-        if isDefined(s.name):
-          result = newStrNodeT(lookupSymbol(s.name), n)
+        if isDefined(g.config, s.name.s):
+          result = newStrNodeT(g.config.symbols[s.name.s], n, g)
       else:
         result = copyTree(s.ast)
-    of {skProc, skMethod}:
+    of skProc, skFunc, skMethod:
       result = n
+    of skParam:
+      if s.typ != nil and s.typ.kind == tyTypeDesc:
+        result = newSymNodeTypeDesc(s, n.info)
     of skType:
       # XXX gensym'ed symbols can come here and cannot be resolved. This is
       # dirty, but correct.
@@ -634,90 +585,87 @@ proc getConstExpr(m: PSym, n: PNode): PNode =
         result = newSymNodeTypeDesc(s, n.info)
     of skGenericParam:
       if s.typ.kind == tyStatic:
-        if s.typ.n != nil:
+        if s.typ.n != nil and tfUnresolved notin s.typ.flags:
           result = s.typ.n
-          result.typ = s.typ.sons[0]
+          result.typ = s.typ.base
+      elif s.typ.isIntLit:
+        result = s.typ.n
       else:
         result = newSymNodeTypeDesc(s, n.info)
     else: discard
   of nkCharLit..nkNilLit:
     result = copyNode(n)
   of nkIfExpr:
-    result = getConstIfExpr(m, n)
+    result = getConstIfExpr(m, n, g)
   of nkCallKinds:
     if n.sons[0].kind != nkSym: return
     var s = n.sons[0].sym
-    if s.kind != skProc: return
+    if s.kind != skProc and s.kind != skFunc: return
     try:
       case s.magic
       of mNone:
         # If it has no sideEffect, it should be evaluated. But not here.
         return
-      of mSizeOf:
-        var a = n.sons[1]
-        if computeSize(a.typ) < 0:
-          localError(a.info, errCannotEvalXBecauseIncompletelyDefined,
-                     "sizeof")
-          result = nil
-        elif skipTypes(a.typ, typedescInst+{tyRange}).kind in
-             IntegralTypes+NilableTypes+{tySet}:
-          #{tyArray,tyObject,tyTuple}:
-          result = newIntNodeT(getSize(a.typ), n)
-        else:
-          result = nil
-          # XXX: size computation for complex types is still wrong
       of mLow:
-        result = newIntNodeT(firstOrd(n.sons[1].typ), n)
+        result = newIntNodeT(firstOrd(g.config, n.sons[1].typ), n, g)
       of mHigh:
-        if skipTypes(n.sons[1].typ, abstractVar).kind notin
+        if skipTypes(n.sons[1].typ, abstractVar+{tyUserTypeClassInst}).kind notin
             {tySequence, tyString, tyCString, tyOpenArray, tyVarargs}:
-          result = newIntNodeT(lastOrd(skipTypes(n[1].typ, abstractVar)), n)
+          result = newIntNodeT(lastOrd(g.config, skipTypes(n[1].typ, abstractVar)), n, g)
         else:
-          var a = getArrayConstr(m, n.sons[1])
+          var a = getArrayConstr(m, n.sons[1], g)
           if a.kind == nkBracket:
             # we can optimize it away:
-            result = newIntNodeT(sonsLen(a)-1, n)
+            result = newIntNodeT(sonsLen(a)-1, n, g)
       of mLengthOpenArray:
-        var a = getArrayConstr(m, n.sons[1])
+        var a = getArrayConstr(m, n.sons[1], g)
         if a.kind == nkBracket:
           # we can optimize it away! This fixes the bug ``len(134)``.
-          result = newIntNodeT(sonsLen(a), n)
+          result = newIntNodeT(sonsLen(a), n, g)
         else:
-          result = magicCall(m, n)
+          result = magicCall(m, n, g)
       of mLengthArray:
         # It doesn't matter if the argument is const or not for mLengthArray.
         # This fixes bug #544.
-        result = newIntNodeT(lengthOrd(n.sons[1].typ), n)
+        result = newIntNodeT(lengthOrd(g.config, n.sons[1].typ), n, g)
+      of mSizeOf:
+        let size = getSize(g.config, n[1].typ)
+        if size >= 0:
+          result = newIntNode(nkIntLit, size)
+          result.info = n.info
+          result.typ = getSysType(g, n.info, tyInt)
+        else:
+          result = nil
       of mAstToStr:
-        result = newStrNodeT(renderTree(n[1], {renderNoComments}), n)
+        result = newStrNodeT(renderTree(n[1], {renderNoComments}), n, g)
       of mConStrStr:
-        result = foldConStrStr(m, n)
+        result = foldConStrStr(m, n, g)
       of mIs:
-        let a = getConstExpr(m, n[1])
-        if a != nil and a.kind == nkSym and a.sym.kind == skType:
-          result = evalIs(n, a)
+        # The only kind of mIs node that comes here is one depending on some
+        # generic parameter and that's (hopefully) handled at instantiation time
+        discard
       else:
-        result = magicCall(m, n)
+        result = magicCall(m, n, g)
     except OverflowError:
-      localError(n.info, errOverOrUnderflow)
+      localError(g.config, n.info, "over- or underflow")
     except DivByZeroError:
-      localError(n.info, errConstantDivisionByZero)
+      localError(g.config, n.info, "division by zero")
   of nkAddr:
-    var a = getConstExpr(m, n.sons[0])
+    var a = getConstExpr(m, n.sons[0], g)
     if a != nil:
       result = n
       n.sons[0] = a
   of nkBracket:
     result = copyTree(n)
     for i in countup(0, sonsLen(n) - 1):
-      var a = getConstExpr(m, n.sons[i])
+      var a = getConstExpr(m, n.sons[i], g)
       if a == nil: return nil
       result.sons[i] = a
     incl(result.flags, nfAllConst)
   of nkRange:
-    var a = getConstExpr(m, n.sons[0])
+    var a = getConstExpr(m, n.sons[0], g)
     if a == nil: return
-    var b = getConstExpr(m, n.sons[1])
+    var b = getConstExpr(m, n.sons[1], g)
     if b == nil: return
     result = copyNode(n)
     addSon(result, a)
@@ -725,58 +673,62 @@ proc getConstExpr(m: PSym, n: PNode): PNode =
   of nkCurly:
     result = copyTree(n)
     for i in countup(0, sonsLen(n) - 1):
-      var a = getConstExpr(m, n.sons[i])
+      var a = getConstExpr(m, n.sons[i], g)
       if a == nil: return nil
       result.sons[i] = a
     incl(result.flags, nfAllConst)
-  of nkObjConstr:
-    result = copyTree(n)
-    for i in countup(1, sonsLen(n) - 1):
-      var a = getConstExpr(m, n.sons[i].sons[1])
-      if a == nil: return nil
-      result.sons[i].sons[1] = a
-    incl(result.flags, nfAllConst)
-  of nkPar:
+  #of nkObjConstr:
+  #  result = copyTree(n)
+  #  for i in countup(1, sonsLen(n) - 1):
+  #    var a = getConstExpr(m, n.sons[i].sons[1])
+  #    if a == nil: return nil
+  #    result.sons[i].sons[1] = a
+  #  incl(result.flags, nfAllConst)
+  of nkPar, nkTupleConstr:
     # tuple constructor
     result = copyTree(n)
     if (sonsLen(n) > 0) and (n.sons[0].kind == nkExprColonExpr):
       for i in countup(0, sonsLen(n) - 1):
-        var a = getConstExpr(m, n.sons[i].sons[1])
+        var a = getConstExpr(m, n.sons[i].sons[1], g)
         if a == nil: return nil
         result.sons[i].sons[1] = a
     else:
       for i in countup(0, sonsLen(n) - 1):
-        var a = getConstExpr(m, n.sons[i])
+        var a = getConstExpr(m, n.sons[i], g)
         if a == nil: return nil
         result.sons[i] = a
     incl(result.flags, nfAllConst)
   of nkChckRangeF, nkChckRange64, nkChckRange:
-    var a = getConstExpr(m, n.sons[0])
+    var a = getConstExpr(m, n.sons[0], g)
     if a == nil: return
     if leValueConv(n.sons[1], a) and leValueConv(a, n.sons[2]):
       result = a              # a <= x and x <= b
       result.typ = n.typ
     else:
-      localError(n.info, errGenerated, `%`(
-          msgKindToString(errIllegalConvFromXtoY),
-          [typeToString(n.sons[0].typ), typeToString(n.typ)]))
+      localError(g.config, n.info,
+        "conversion from $1 to $2 is invalid" %
+          [typeToString(n.sons[0].typ), typeToString(n.typ)])
   of nkStringToCString, nkCStringToString:
-    var a = getConstExpr(m, n.sons[0])
+    var a = getConstExpr(m, n.sons[0], g)
     if a == nil: return
     result = a
     result.typ = n.typ
   of nkHiddenStdConv, nkHiddenSubConv, nkConv:
-    var a = getConstExpr(m, n.sons[1])
+    var a = getConstExpr(m, n.sons[1], g)
     if a == nil: return
-    result = foldConv(n, a, check=n.kind == nkHiddenStdConv)
+    # XXX: we should enable `check` for other conversion types too
+    result = foldConv(n, a, g, check=n.kind == nkHiddenSubConv)
   of nkCast:
-    var a = getConstExpr(m, n.sons[1])
+    var a = getConstExpr(m, n.sons[1], g)
     if a == nil: return
     if n.typ != nil and n.typ.kind in NilableTypes:
       # we allow compile-time 'cast' for pointer types:
       result = a
       result.typ = n.typ
-  of nkBracketExpr: result = foldArrayAccess(m, n)
-  of nkDotExpr: result = foldFieldAccess(m, n)
+  of nkBracketExpr: result = foldArrayAccess(m, n, g)
+  of nkDotExpr: result = foldFieldAccess(m, n, g)
+  of nkStmtListExpr:
+    if n.len == 2 and n[0].kind == nkComesFrom:
+      result = getConstExpr(m, n[1], g)
   else:
     discard
diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim
index 47a094f9d..5972f3b55 100644
--- a/compiler/semgnrc.nim
+++ b/compiler/semgnrc.nim
@@ -17,13 +17,13 @@
 
 # included from sem.nim
 
-proc getIdentNode(n: PNode): PNode =
+proc getIdentNode(c: PContext; n: PNode): PNode =
   case n.kind
-  of nkPostfix: result = getIdentNode(n.sons[1])
-  of nkPragmaExpr: result = getIdentNode(n.sons[0])
+  of nkPostfix: result = getIdentNode(c, n.sons[1])
+  of nkPragmaExpr: result = getIdentNode(c, n.sons[0])
   of nkIdent, nkAccQuoted, nkSym: result = n
   else:
-    illFormedAst(n)
+    illFormedAst(n, c.config)
     result = n
 
 type
@@ -32,9 +32,12 @@ type
     cursorInBody: bool # only for nimsuggest
     bracketExpr: PNode
 
-type
   TSemGenericFlag = enum
-    withinBind, withinTypeDesc, withinMixin
+    withinBind,
+    withinTypeDesc,
+    withinMixin,
+    withinConcept
+
   TSemGenericFlags = set[TSemGenericFlag]
 
 proc semGenericStmt(c: PContext, n: PNode,
@@ -51,28 +54,36 @@ template macroToExpand(s): untyped =
   s.kind in {skMacro, skTemplate} and (s.typ.len == 1 or sfAllUntyped in s.flags)
 
 template macroToExpandSym(s): untyped =
-  s.kind in {skMacro, skTemplate} and (s.typ.len == 1)
+  sfCustomPragma notin s.flags and s.kind in {skMacro, skTemplate} and
+    (s.typ.len == 1) and not fromDotExpr
+
+template isMixedIn(sym): bool =
+  let s = sym
+  s.name.id in ctx.toMixin or (withinConcept in flags and
+                               s.magic == mNone and
+                               s.kind in OverloadableSyms)
 
 proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
-                          ctx: var GenericCtx): PNode =
-  semIdeForTemplateOrGenericCheck(n, ctx.cursorInBody)
+                          ctx: var GenericCtx; flags: TSemGenericFlags,
+                          fromDotExpr=false): PNode =
+  semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody)
   incl(s.flags, sfUsed)
   case s.kind
   of skUnknown:
     # Introduced in this pass! Leave it as an identifier.
     result = n
-  of skProc, skMethod, skIterator, skConverter, skModule:
+  of skProc, skFunc, skMethod, skIterator, skConverter, skModule:
     result = symChoice(c, n, s, scOpen)
   of skTemplate:
     if macroToExpandSym(s):
-      styleCheckUse(n.info, s)
+      onUse(n.info, s)
       result = semTemplateExpr(c, n, s, {efNoSemCheck})
       result = semGenericStmt(c, result, {}, ctx)
     else:
       result = symChoice(c, n, s, scOpen)
   of skMacro:
     if macroToExpandSym(s):
-      styleCheckUse(n.info, s)
+      onUse(n.info, s)
       result = semMacroExpr(c, n, n, s, {efNoSemCheck})
       result = semGenericStmt(c, result, {}, ctx)
     else:
@@ -85,36 +96,40 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
         result = n
     else:
       result = newSymNodeTypeDesc(s, n.info)
-    styleCheckUse(n.info, s)
+    onUse(n.info, s)
   of skParam:
     result = n
-    styleCheckUse(n.info, s)
+    onUse(n.info, s)
   of skType:
     if (s.typ != nil) and
        (s.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} == {}):
       result = newSymNodeTypeDesc(s, n.info)
     else:
       result = n
-    styleCheckUse(n.info, s)
+    onUse(n.info, s)
   else:
     result = newSymNode(s, n.info)
-    styleCheckUse(n.info, s)
+    onUse(n.info, s)
 
 proc lookup(c: PContext, n: PNode, flags: TSemGenericFlags,
             ctx: var GenericCtx): PNode =
   result = n
-  let ident = considerQuotedIdent(n)
-  var s = searchInScopes(c, ident).skipAlias(n)
+  let ident = considerQuotedIdent(c, n)
+  var s = searchInScopes(c, ident).skipAlias(n, c.config)
+  if s == nil:
+    s = strTableGet(c.pureEnumFields, ident)
+    if s != nil and contains(c.ambiguousSymbols, s.id):
+      s = nil
   if s == nil:
     if ident.id notin ctx.toMixin and withinMixin notin flags:
       errorUndeclaredIdentifier(c, n.info, ident.s)
   else:
     if withinBind in flags:
       result = symChoice(c, n, s, scClosed)
-    elif s.name.id in ctx.toMixin:
+    elif s.isMixedIn:
       result = symChoice(c, n, s, scForceOpen)
     else:
-      result = semGenericStmtSymbol(c, n, s, ctx)
+      result = semGenericStmtSymbol(c, n, s, ctx, flags)
   # else: leave as nkIdent
 
 proc newDot(n, b: PNode): PNode =
@@ -125,27 +140,27 @@ proc newDot(n, b: PNode): PNode =
 proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags,
                  ctx: var GenericCtx; isMacro: var bool): PNode =
   assert n.kind == nkDotExpr
-  semIdeForTemplateOrGenericCheck(n, ctx.cursorInBody)
+  semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody)
 
   let luf = if withinMixin notin flags: {checkUndeclared, checkModule} else: {checkModule}
 
   var s = qualifiedLookUp(c, n, luf)
   if s != nil:
-    result = semGenericStmtSymbol(c, n, s, ctx)
+    result = semGenericStmtSymbol(c, n, s, ctx, flags)
   else:
     n.sons[0] = semGenericStmt(c, n.sons[0], flags, ctx)
     result = n
     let n = n[1]
-    let ident = considerQuotedIdent(n)
-    var s = searchInScopes(c, ident).skipAlias(n)
+    let ident = considerQuotedIdent(c, n)
+    var s = searchInScopes(c, ident).skipAlias(n, c.config)
     if s != nil and s.kind in routineKinds:
       isMacro = s.kind in {skTemplate, skMacro}
       if withinBind in flags:
         result = newDot(result, symChoice(c, n, s, scClosed))
-      elif s.name.id in ctx.toMixin:
+      elif s.isMixedIn:
         result = newDot(result, symChoice(c, n, s, scForceOpen))
       else:
-        let syms = semGenericStmtSymbol(c, n, s, ctx)
+        let syms = semGenericStmtSymbol(c, n, s, ctx, flags, fromDotExpr=true)
         if syms.kind == nkSym:
           let choice = symChoice(c, n, s, scForceOpen)
           choice.kind = nkClosedSymChoice
@@ -154,9 +169,10 @@ proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags,
           result = newDot(result, syms)
 
 proc addTempDecl(c: PContext; n: PNode; kind: TSymKind) =
-  let s = newSymS(skUnknown, getIdentNode(n), c)
+  let s = newSymS(skUnknown, getIdentNode(c, n), c)
   addPrelimDecl(c, s)
-  styleCheckDef(n.info, s, kind)
+  styleCheckDef(c.config, n.info, s, kind)
+  onDef(n.info, s)
 
 proc semGenericStmt(c: PContext, n: PNode,
                     flags: TSemGenericFlags, ctx: var GenericCtx): PNode =
@@ -165,8 +181,8 @@ proc semGenericStmt(c: PContext, n: PNode,
   when defined(nimsuggest):
     if withinTypeDesc in flags: inc c.inTypeContext
 
-  #if gCmd == cmdIdeTools: suggestStmt(c, n)
-  semIdeForTemplateOrGenericCheck(n, ctx.cursorInBody)
+  #if conf.cmd == cmdIdeTools: suggestStmt(c, n)
+  semIdeForTemplateOrGenericCheck(c.config, n, ctx.cursorInBody)
 
   case n.kind
   of nkIdent, nkAccQuoted:
@@ -182,7 +198,7 @@ proc semGenericStmt(c: PContext, n: PNode,
     let a = n.sym
     let b = getGenSym(c, a)
     if b != a: n.sym = b
-  of nkEmpty, succ(nkSym)..nkNilLit:
+  of nkEmpty, succ(nkSym)..nkNilLit, nkComesFrom:
     # see tests/compile/tgensymgeneric.nim:
     # We need to open the gensym'ed symbol again so that the instantiation
     # creates a fresh copy; but this is wrong the very first reason for gensym
@@ -197,25 +213,25 @@ proc semGenericStmt(c: PContext, n: PNode,
     result = semMixinStmt(c, n, ctx.toMixin)
   of nkCall, nkHiddenCallConv, nkInfix, nkPrefix, nkCommand, nkCallStrLit:
     # check if it is an expression macro:
-    checkMinSonsLen(n, 1)
+    checkMinSonsLen(n, 1, c.config)
     let fn = n.sons[0]
     var s = qualifiedLookUp(c, fn, {})
-    if s == nil and withinMixin notin flags and
+    if s == nil and
+        {withinMixin, withinConcept}*flags == {} and
         fn.kind in {nkIdent, nkAccQuoted} and
-        considerQuotedIdent(fn).id notin ctx.toMixin:
+        considerQuotedIdent(c, fn).id notin ctx.toMixin:
       errorUndeclaredIdentifier(c, n.info, fn.renderTree)
 
-    var first = 0
+    var first = int ord(withinConcept in flags)
     var mixinContext = false
     if s != nil:
       incl(s.flags, sfUsed)
       mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles}
-      let sc = symChoice(c, fn, s,
-            if s.name.id in ctx.toMixin: scForceOpen else: scOpen)
+      let sc = symChoice(c, fn, s, if s.isMixedIn: scForceOpen else: scOpen)
       case s.kind
       of skMacro:
         if macroToExpand(s) and sc.safeLen <= 1:
-          styleCheckUse(fn.info, s)
+          onUse(fn.info, s)
           result = semMacroExpr(c, n, n, s, {efNoSemCheck})
           result = semGenericStmt(c, result, flags, ctx)
         else:
@@ -224,7 +240,7 @@ proc semGenericStmt(c: PContext, n: PNode,
         mixinContext = true
       of skTemplate:
         if macroToExpand(s) and sc.safeLen <= 1:
-          styleCheckUse(fn.info, s)
+          onUse(fn.info, s)
           result = semTemplateExpr(c, n, s, {efNoSemCheck})
           result = semGenericStmt(c, result, flags, ctx)
         else:
@@ -238,26 +254,26 @@ proc semGenericStmt(c: PContext, n: PNode,
       of skUnknown, skParam:
         # Leave it as an identifier.
         discard
-      of skProc, skMethod, skIterator, skConverter, skModule:
+      of skProc, skFunc, skMethod, skIterator, skConverter, skModule:
         result.sons[0] = sc
-        # do not check of 's.magic==mRoof' here because it might be some
-        # other '^' but after overload resolution the proper one:
-        if ctx.bracketExpr != nil and n.len == 2 and s.name.s == "^":
-          result.add ctx.bracketExpr
         first = 1
+        # We're not interested in the example code during this pass so let's
+        # skip it
+        if s.magic == mRunnableExamples:
+          inc first
       of skGenericParam:
         result.sons[0] = newSymNodeTypeDesc(s, fn.info)
-        styleCheckUse(fn.info, s)
+        onUse(fn.info, s)
         first = 1
       of skType:
         # bad hack for generics:
         if (s.typ != nil) and (s.typ.kind != tyGenericParam):
           result.sons[0] = newSymNodeTypeDesc(s, fn.info)
-          styleCheckUse(fn.info, s)
+          onUse(fn.info, s)
           first = 1
       else:
         result.sons[0] = newSymNode(s, fn.info)
-        styleCheckUse(fn.info, s)
+        onUse(fn.info, s)
         first = 1
     elif fn.kind == nkDotExpr:
       result.sons[0] = fuzzyLookup(c, fn, flags, ctx, mixinContext)
@@ -270,17 +286,17 @@ proc semGenericStmt(c: PContext, n: PNode,
       result.sons[i] = semGenericStmt(c, result.sons[i], flags, ctx)
   of nkCurlyExpr:
     result = newNodeI(nkCall, n.info)
-    result.add newIdentNode(getIdent("{}"), n.info)
+    result.add newIdentNode(getIdent(c.cache, "{}"), n.info)
     for i in 0 ..< n.len: result.add(n[i])
     result = semGenericStmt(c, result, flags, ctx)
   of nkBracketExpr:
     result = newNodeI(nkCall, n.info)
-    result.add newIdentNode(getIdent("[]"), n.info)
+    result.add newIdentNode(getIdent(c.cache, "[]"), n.info)
     for i in 0 ..< n.len: result.add(n[i])
     withBracketExpr ctx, n.sons[0]:
       result = semGenericStmt(c, result, flags, ctx)
   of nkAsgn, nkFastAsgn:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     let a = n.sons[0]
     let b = n.sons[1]
 
@@ -288,13 +304,13 @@ proc semGenericStmt(c: PContext, n: PNode,
     case k
     of nkCurlyExpr:
       result = newNodeI(nkCall, n.info)
-      result.add newIdentNode(getIdent("{}="), n.info)
+      result.add newIdentNode(getIdent(c.cache, "{}="), n.info)
       for i in 0 ..< a.len: result.add(a[i])
       result.add(b)
       result = semGenericStmt(c, result, flags, ctx)
     of nkBracketExpr:
       result = newNodeI(nkCall, n.info)
-      result.add newIdentNode(getIdent("[]="), n.info)
+      result.add newIdentNode(getIdent(c.cache, "[]="), n.info)
       for i in 0 ..< a.len: result.add(a[i])
       result.add(b)
       withBracketExpr ctx, a.sons[0]:
@@ -307,7 +323,14 @@ proc semGenericStmt(c: PContext, n: PNode,
       n.sons[i] = semGenericStmtScope(c, n.sons[i], flags, ctx)
   of nkWhenStmt:
     for i in countup(0, sonsLen(n)-1):
-      n.sons[i] = semGenericStmt(c, n.sons[i], flags+{withinMixin}, ctx)
+      # bug #8603: conditions of 'when' statements are not
+      # in a 'mixin' context:
+      let it = n[i]
+      if it.kind in {nkElifExpr, nkElifBranch}:
+        n.sons[i].sons[0] = semGenericStmt(c, it[0], flags, ctx)
+        n.sons[i].sons[1] = semGenericStmt(c, it[1], flags+{withinMixin}, ctx)
+      else:
+        n.sons[i] = semGenericStmt(c, it, flags+{withinMixin}, ctx)
   of nkWhileStmt:
     openScope(c)
     for i in countup(0, sonsLen(n)-1):
@@ -318,7 +341,7 @@ proc semGenericStmt(c: PContext, n: PNode,
     n.sons[0] = semGenericStmt(c, n.sons[0], flags, ctx)
     for i in countup(1, sonsLen(n)-1):
       var a = n.sons[i]
-      checkMinSonsLen(a, 1)
+      checkMinSonsLen(a, 1, c.config)
       var L = sonsLen(a)
       for j in countup(0, L-2):
         a.sons[j] = semGenericStmt(c, a.sons[j], flags, ctx)
@@ -330,26 +353,28 @@ proc semGenericStmt(c: PContext, n: PNode,
     n.sons[L - 2] = semGenericStmt(c, n.sons[L-2], flags, ctx)
     for i in countup(0, L - 3):
       addTempDecl(c, n.sons[i], skForVar)
+    openScope(c)
     n.sons[L - 1] = semGenericStmt(c, n.sons[L-1], flags, ctx)
     closeScope(c)
+    closeScope(c)
   of nkBlockStmt, nkBlockExpr, nkBlockType:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     openScope(c)
     if n.sons[0].kind != nkEmpty:
       addTempDecl(c, n.sons[0], skLabel)
     n.sons[1] = semGenericStmt(c, n.sons[1], flags, ctx)
     closeScope(c)
   of nkTryStmt:
-    checkMinSonsLen(n, 2)
+    checkMinSonsLen(n, 2, c.config)
     n.sons[0] = semGenericStmtScope(c, n.sons[0], flags, ctx)
     for i in countup(1, sonsLen(n)-1):
       var a = n.sons[i]
-      checkMinSonsLen(a, 1)
+      checkMinSonsLen(a, 1, c.config)
       var L = sonsLen(a)
       openScope(c)
       for j in countup(0, L-2):
         if a.sons[j].isInfixAs():
-          addTempDecl(c, getIdentNode(a.sons[j][2]), skLet)
+          addTempDecl(c, getIdentNode(c, a.sons[j][2]), skLet)
           a.sons[j].sons[1] = semGenericStmt(c, a.sons[j][1], flags+{withinTypeDesc}, ctx)
         else:
           a.sons[j] = semGenericStmt(c, a.sons[j], flags+{withinTypeDesc}, ctx)
@@ -360,44 +385,44 @@ proc semGenericStmt(c: PContext, n: PNode,
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
       if a.kind == nkCommentStmt: continue
-      if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a)
-      checkMinSonsLen(a, 3)
+      if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a, c.config)
+      checkMinSonsLen(a, 3, c.config)
       var L = sonsLen(a)
       a.sons[L-2] = semGenericStmt(c, a.sons[L-2], flags+{withinTypeDesc}, ctx)
       a.sons[L-1] = semGenericStmt(c, a.sons[L-1], flags, ctx)
       for j in countup(0, L-3):
-        addTempDecl(c, getIdentNode(a.sons[j]), skVar)
+        addTempDecl(c, getIdentNode(c, a.sons[j]), skVar)
   of nkGenericParams:
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
-      if (a.kind != nkIdentDefs): illFormedAst(a)
-      checkMinSonsLen(a, 3)
+      if (a.kind != nkIdentDefs): illFormedAst(a, c.config)
+      checkMinSonsLen(a, 3, c.config)
       var L = sonsLen(a)
       a.sons[L-2] = semGenericStmt(c, a.sons[L-2], flags+{withinTypeDesc}, ctx)
       # do not perform symbol lookup for default expressions
       for j in countup(0, L-3):
-        addTempDecl(c, getIdentNode(a.sons[j]), skType)
+        addTempDecl(c, getIdentNode(c, a.sons[j]), skType)
   of nkConstSection:
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
       if a.kind == nkCommentStmt: continue
-      if (a.kind != nkConstDef): illFormedAst(a)
-      checkSonsLen(a, 3)
-      addTempDecl(c, getIdentNode(a.sons[0]), skConst)
+      if (a.kind != nkConstDef): illFormedAst(a, c.config)
+      checkSonsLen(a, 3, c.config)
+      addTempDecl(c, getIdentNode(c, a.sons[0]), skConst)
       a.sons[1] = semGenericStmt(c, a.sons[1], flags+{withinTypeDesc}, ctx)
       a.sons[2] = semGenericStmt(c, a.sons[2], flags, ctx)
   of nkTypeSection:
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
       if a.kind == nkCommentStmt: continue
-      if (a.kind != nkTypeDef): illFormedAst(a)
-      checkSonsLen(a, 3)
-      addTempDecl(c, getIdentNode(a.sons[0]), skType)
+      if (a.kind != nkTypeDef): illFormedAst(a, c.config)
+      checkSonsLen(a, 3, c.config)
+      addTempDecl(c, getIdentNode(c, a.sons[0]), skType)
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
       if a.kind == nkCommentStmt: continue
-      if (a.kind != nkTypeDef): illFormedAst(a)
-      checkSonsLen(a, 3)
+      if (a.kind != nkTypeDef): illFormedAst(a, c.config)
+      checkSonsLen(a, 3, c.config)
       if a.sons[1].kind != nkEmpty:
         openScope(c)
         a.sons[1] = semGenericStmt(c, a.sons[1], flags, ctx)
@@ -414,34 +439,34 @@ proc semGenericStmt(c: PContext, n: PNode,
         case n.sons[i].kind
         of nkEnumFieldDef: a = n.sons[i].sons[0]
         of nkIdent: a = n.sons[i]
-        else: illFormedAst(n)
-        addDecl(c, newSymS(skUnknown, getIdentNode(a), c))
+        else: illFormedAst(n, c.config)
+        addDecl(c, newSymS(skUnknown, getIdentNode(c, a), c))
   of nkObjectTy, nkTupleTy, nkTupleClassTy:
     discard
   of nkFormalParams:
-    checkMinSonsLen(n, 1)
+    checkMinSonsLen(n, 1, c.config)
     if n.sons[0].kind != nkEmpty:
       n.sons[0] = semGenericStmt(c, n.sons[0], flags+{withinTypeDesc}, ctx)
     for i in countup(1, sonsLen(n) - 1):
       var a = n.sons[i]
-      if (a.kind != nkIdentDefs): illFormedAst(a)
-      checkMinSonsLen(a, 3)
+      if (a.kind != nkIdentDefs): illFormedAst(a, c.config)
+      checkMinSonsLen(a, 3, c.config)
       var L = sonsLen(a)
       a.sons[L-2] = semGenericStmt(c, a.sons[L-2], flags+{withinTypeDesc}, ctx)
       a.sons[L-1] = semGenericStmt(c, a.sons[L-1], flags, ctx)
       for j in countup(0, L-3):
-        addTempDecl(c, getIdentNode(a.sons[j]), skParam)
+        addTempDecl(c, getIdentNode(c, a.sons[j]), skParam)
   of nkProcDef, nkMethodDef, nkConverterDef, nkMacroDef, nkTemplateDef,
-     nkIteratorDef, nkLambdaKinds:
-    checkSonsLen(n, bodyPos + 1)
+     nkFuncDef, nkIteratorDef, nkLambdaKinds:
+    checkSonsLen(n, bodyPos + 1, c.config)
     if n.sons[namePos].kind != nkEmpty:
-      addTempDecl(c, getIdentNode(n.sons[0]), skProc)
+      addTempDecl(c, getIdentNode(c, n.sons[0]), skProc)
     openScope(c)
     n.sons[genericParamsPos] = semGenericStmt(c, n.sons[genericParamsPos],
                                               flags, ctx)
     if n.sons[paramsPos].kind != nkEmpty:
       if n.sons[paramsPos].sons[0].kind != nkEmpty:
-        addPrelimDecl(c, newSym(skUnknown, getIdent("result"), nil, n.info))
+        addPrelimDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info))
       n.sons[paramsPos] = semGenericStmt(c, n.sons[paramsPos], flags, ctx)
     n.sons[pragmasPos] = semGenericStmt(c, n.sons[pragmasPos], flags, ctx)
     var body: PNode
@@ -456,7 +481,7 @@ proc semGenericStmt(c: PContext, n: PNode,
     closeScope(c)
   of nkPragma, nkPragmaExpr: discard
   of nkExprColonExpr, nkExprEqExpr:
-    checkMinSonsLen(n, 2)
+    checkMinSonsLen(n, 2, c.config)
     result.sons[1] = semGenericStmt(c, n.sons[1], flags, ctx)
   else:
     for i in countup(0, sonsLen(n) - 1):
@@ -465,9 +490,15 @@ proc semGenericStmt(c: PContext, n: PNode,
   when defined(nimsuggest):
     if withinTypeDesc in flags: dec c.inTypeContext
 
-
 proc semGenericStmt(c: PContext, n: PNode): PNode =
   var ctx: GenericCtx
   ctx.toMixin = initIntset()
   result = semGenericStmt(c, n, {}, ctx)
   semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody)
+
+proc semConceptBody(c: PContext, n: PNode): PNode =
+  var ctx: GenericCtx
+  ctx.toMixin = initIntset()
+  result = semGenericStmt(c, n, {withinConcept}, ctx)
+  semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody)
+
diff --git a/compiler/seminst.nim b/compiler/seminst.nim
index 78dd7efe5..09991048e 100644
--- a/compiler/seminst.nim
+++ b/compiler/seminst.nim
@@ -36,7 +36,8 @@ proc rawPushProcCon(c: PContext, owner: PSym) =
   c.p = x
 
 proc rawHandleSelf(c: PContext; owner: PSym) =
-  if c.selfName != nil and owner.kind in {skProc, skMethod, skConverter, skIterator, skMacro} and owner.typ != nil:
+  const callableSymbols = {skProc, skFunc, skMethod, skConverter, skIterator, skMacro}
+  if c.selfName != nil and owner.kind in callableSymbols and owner.typ != nil:
     let params = owner.typ.n
     if params.len > 1:
       let arg = params[1].sym
@@ -53,10 +54,13 @@ proc pushProcCon*(c: PContext; owner: PSym) =
   rawPushProcCon(c, owner)
   rawHandleSelf(c, owner)
 
+const
+  errCannotInstantiateX = "cannot instantiate: '$1'"
+
 iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym =
-  internalAssert n.kind == nkGenericParams
+  internalAssert c.config, n.kind == nkGenericParams
   for i, a in n.pairs:
-    internalAssert a.kind == nkSym
+    internalAssert c.config, a.kind == nkSym
     var q = a.sym
     if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses:
       continue
@@ -70,10 +74,10 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym
         # later by semAsgn in return type inference scenario
         t = q.typ
       else:
-        localError(a.info, errCannotInstantiateX, s.name.s)
+        localError(c.config, a.info, errCannotInstantiateX % s.name.s)
         t = errorType(c)
     elif t.kind == tyGenericParam:
-      localError(a.info, errCannotInstantiateX, q.name.s)
+      localError(c.config, a.info, errCannotInstantiateX % q.name.s)
       t = errorType(c)
     elif t.kind == tyGenericInvocation:
       #t = instGenericContainer(c, a, t)
@@ -87,15 +91,15 @@ proc sameInstantiation(a, b: TInstantiation): bool =
   if a.concreteTypes.len == b.concreteTypes.len:
     for i in 0..a.concreteTypes.high:
       if not compareTypes(a.concreteTypes[i], b.concreteTypes[i],
-                          flags = {ExactTypeDescValues}): return
+                          flags = {ExactTypeDescValues,
+                                   ExactGcSafety}): return
     result = true
 
 proc genericCacheGet(genericSym: PSym, entry: TInstantiation;
                      id: CompilesId): PSym =
-  if genericSym.procInstCache != nil:
-    for inst in genericSym.procInstCache:
-      if inst.compilesId == id and sameInstantiation(entry, inst[]):
-        return inst.sym
+  for inst in genericSym.procInstCache:
+    if inst.compilesId == id and sameInstantiation(entry, inst[]):
+      return inst.sym
 
 when false:
   proc `$`(x: PSym): string =
@@ -112,34 +116,38 @@ proc freshGenSyms(n: PNode, owner, orig: PSym, symMap: var TIdTable) =
     var x = PSym(idTableGet(symMap, s))
     if x != nil:
       n.sym = x
-    elif s.owner.kind == skPackage:
+    elif s.owner == nil or s.owner.kind == skPackage:
       #echo "copied this ", s.name.s
-      x = copySym(s, false)
+      x = copySym(s)
       x.owner = owner
       idTablePut(symMap, s, x)
       n.sym = x
   else:
-    for i in 0 .. <safeLen(n): freshGenSyms(n.sons[i], owner, orig, symMap)
+    for i in 0 ..< safeLen(n): freshGenSyms(n.sons[i], owner, orig, symMap)
 
 proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind)
 
 proc instantiateBody(c: PContext, n, params: PNode, result, orig: PSym) =
   if n.sons[bodyPos].kind != nkEmpty:
+    let procParams = result.typ.n
+    for i in 1 ..< procParams.len:
+      addDecl(c, procParams[i].sym)
+    maybeAddResult(c, result, result.ast)
+
     inc c.inGenericInst
     # add it here, so that recursive generic procs are possible:
     var b = n.sons[bodyPos]
     var symMap: TIdTable
     initIdTable symMap
     if params != nil:
-      for i in 1 .. <params.len:
+      for i in 1 ..< params.len:
         let param = params[i].sym
         if sfGenSym in param.flags:
           idTablePut(symMap, params[i].sym, result.typ.n[param.position+1].sym)
     freshGenSyms(b, result, orig, symMap)
     b = semProcBody(c, b)
-    b = hloBody(c, b)
-    n.sons[bodyPos] = transformBody(c.module, b, result)
-    #echo "code instantiated ", result.name.s
+    result.ast[bodyPos] = hloBody(c, b)
+    trackProc(c.graph, result, result.ast[bodyPos])
     excl(result.flags, sfForward)
     dec c.inGenericInst
 
@@ -147,13 +155,17 @@ proc fixupInstantiatedSymbols(c: PContext, s: PSym) =
   for i in countup(0, c.generics.len - 1):
     if c.generics[i].genericSym.id == s.id:
       var oldPrc = c.generics[i].inst.sym
-      pushInfoContext(oldPrc.info)
+      pushProcCon(c, oldPrc)
+      pushOwner(c, oldPrc)
+      pushInfoContext(c.config, oldPrc.info)
       openScope(c)
       var n = oldPrc.ast
       n.sons[bodyPos] = copyTree(s.getBody)
-      instantiateBody(c, n, nil, oldPrc, s)
+      instantiateBody(c, n, oldPrc.typ.n, oldPrc, s)
       closeScope(c)
-      popInfoContext()
+      popInfoContext(c.config)
+      popOwner(c)
+      popProcCon(c)
 
 proc sideEffectsCheck(c: PContext, s: PSym) =
   when false:
@@ -163,17 +175,59 @@ proc sideEffectsCheck(c: PContext, s: PSym) =
 
 proc instGenericContainer(c: PContext, info: TLineInfo, header: PType,
                           allowMetaTypes = false): PType =
-  var cl: TReplTypeVars
+  internalAssert c.config, header.kind == tyGenericInvocation
+
+  var
+    typeMap: LayeredIdTable
+    cl: TReplTypeVars
+
   initIdTable(cl.symMap)
-  initIdTable(cl.typeMap)
   initIdTable(cl.localCache)
+  initIdTable(typeMap.topLayer)
+  cl.typeMap = addr(typeMap)
   cl.info = info
   cl.c = c
   cl.allowMetaTypes = allowMetaTypes
+
+  # We must add all generic params in scope, because the generic body
+  # may include tyFromExpr nodes depending on these generic params.
+  # XXX: This looks quite similar to the code in matchUserTypeClass,
+  # perhaps the code can be extracted in a shared function.
+  openScope(c)
+  let genericTyp = header.base
+  for i in 0 .. (genericTyp.len - 2):
+    let genParam = genericTyp[i]
+    var param: PSym
+
+    template paramSym(kind): untyped =
+      newSym(kind, genParam.sym.name, genericTyp.sym, genParam.sym.info)
+
+    if genParam.kind == tyStatic:
+      param = paramSym skConst
+      param.ast = header[i+1].n
+      param.typ = header[i+1]
+    else:
+      param = paramSym skType
+      param.typ = makeTypeDesc(c, header[i+1])
+
+    # this scope was not created by the user,
+    # unused params shoudn't be reported.
+    param.flags.incl sfUsed
+    addDecl(c, param)
+
   result = replaceTypeVarsT(cl, header)
+  closeScope(c)
+
+proc referencesAnotherParam(n: PNode, p: PSym): bool =
+  if n.kind == nkSym:
+    return n.sym.kind == skParam and n.sym.owner == p
+  else:
+    for i in 0..<n.safeLen:
+      if referencesAnotherParam(n[i], p): return true
+    return false
 
 proc instantiateProcType(c: PContext, pt: TIdTable,
-                          prc: PSym, info: TLineInfo) =
+                         prc: PSym, info: TLineInfo) =
   # XXX: Instantiates a generic proc signature, while at the same
   # time adding the instantiated proc params into the current scope.
   # This is necessary, because the instantiation process may refer to
@@ -188,49 +242,80 @@ proc instantiateProcType(c: PContext, pt: TIdTable,
   # at this point semtypinst have to become part of sem, because it
   # will need to use openScope, addDecl, etc.
   #addDecl(c, prc)
-
-  pushInfoContext(info)
-  var cl = initTypeVars(c, pt, info, nil)
+  pushInfoContext(c.config, info)
+  var typeMap = initLayeredTypeMap(pt)
+  var cl = initTypeVars(c, addr(typeMap), info, nil)
   var result = instCopyType(cl, prc.typ)
   let originalParams = result.n
   result.n = originalParams.shallowCopy
-
-  for i in 1 .. <result.len:
+  for i in 1 ..< result.len:
     # twrong_field_caching requires these 'resetIdTable' calls:
     if i > 1:
       resetIdTable(cl.symMap)
       resetIdTable(cl.localCache)
-    result.sons[i] = replaceTypeVarsT(cl, result.sons[i])
-    propagateToOwner(result, result.sons[i])
-    internalAssert originalParams[i].kind == nkSym
-    when true:
-      let oldParam = originalParams[i].sym
-      let param = copySym(oldParam)
-      param.owner = prc
-      param.typ = result.sons[i]
-      if oldParam.ast != nil:
-        param.ast = fitNode(c, param.typ, oldParam.ast, oldParam.ast.info)
-
-      # don't be lazy here and call replaceTypeVarsN(cl, originalParams[i])!
-      result.n.sons[i] = newSymNode(param)
-      addDecl(c, param)
-    else:
-      let param = replaceTypeVarsN(cl, originalParams[i])
-      result.n.sons[i] = param
-      param.sym.owner = prc
-      addDecl(c, result.n.sons[i].sym)
+
+    # take a note of the original type. If't a free type or static parameter
+    # we'll need to keep it unbound for the `fitNode` operation below...
+    var typeToFit = result[i]
+
+    let needsStaticSkipping = result[i].kind == tyFromExpr
+    result[i] = replaceTypeVarsT(cl, result[i])
+    if needsStaticSkipping:
+      result[i] = result[i].skipTypes({tyStatic})
+
+    # ...otherwise, we use the instantiated type in `fitNode`
+    if (typeToFit.kind != tyTypeDesc or typeToFit.base.kind != tyNone) and
+       (typeToFit.kind != tyStatic):
+      typeToFit = result[i]
+
+    internalAssert c.config, originalParams[i].kind == nkSym
+    let oldParam = originalParams[i].sym
+    let param = copySym(oldParam)
+    param.owner = prc
+    param.typ = result[i]
+
+    # The default value is instantiated and fitted against the final
+    # concrete param type. We avoid calling `replaceTypeVarsN` on the
+    # call head symbol, because this leads to infinite recursion.
+    if oldParam.ast != nil:
+      var def = oldParam.ast.copyTree
+      if def.kind == nkCall:
+        for i in 1 ..< def.len:
+          def[i] = replaceTypeVarsN(cl, def[i])
+
+      def = semExprWithType(c, def)
+      if def.referencesAnotherParam(getCurrOwner(c)):
+        def.flags.incl nfDefaultRefsParam
+
+      var converted = indexTypesMatch(c, typeToFit, def.typ, def)
+      if converted == nil:
+        # The default value doesn't match the final instantiated type.
+        # As an example of this, see:
+        # https://github.com/nim-lang/Nim/issues/1201
+        # We are replacing the default value with an error node in case
+        # the user calls an explicit instantiation of the proc (this is
+        # the only way the default value might be inserted).
+        param.ast = errorNode(c, def)
+      else:
+        param.ast = fitNodePostMatch(c, typeToFit, converted)
+      param.typ = result[i]
+
+    result.n[i] = newSymNode(param)
+    propagateToOwner(result, result[i])
+    addDecl(c, param)
 
   resetIdTable(cl.symMap)
   resetIdTable(cl.localCache)
   result.sons[0] = replaceTypeVarsT(cl, result.sons[0])
   result.n.sons[0] = originalParams[0].copyTree
+  if result.sons[0] != nil:
+    propagateToOwner(result, result.sons[0])
 
   eraseVoidParams(result)
   skipIntLiteralParams(result)
 
   prc.typ = result
-  maybeAddResult(c, prc, prc.ast)
-  popInfoContext()
+  popInfoContext(c.config)
 
 proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
                       info: TLineInfo): PSym =
@@ -238,20 +323,21 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
   ## The `pt` parameter is a type-unsafe mapping table used to link generic
   ## parameters to their concrete types within the generic instance.
   # no need to instantiate generic templates/macros:
-  internalAssert fn.kind notin {skMacro, skTemplate}
+  internalAssert c.config, fn.kind notin {skMacro, skTemplate}
   # generates an instantiated proc
-  if c.instCounter > 1000: internalError(fn.ast.info, "nesting too deep")
+  if c.instCounter > 50:
+    globalError(c.config, info, "generic instantiation too nested")
   inc(c.instCounter)
   # careful! we copy the whole AST including the possibly nil body!
   var n = copyTree(fn.ast)
   # NOTE: for access of private fields within generics from a different module
   # we set the friend module:
   c.friendModules.add(getModule(fn))
-  let oldInTypeClass = c.inTypeClass
-  c.inTypeClass = 0
+  let oldMatchedConcept = c.matchedConcept
+  c.matchedConcept = nil
   let oldScope = c.currentScope
   while not isTopLevel(c): c.currentScope = c.currentScope.parent
-  result = copySym(fn, false)
+  result = copySym(fn)
   incl(result.flags, sfFromGeneric)
   result.owner = fn
   result.ast = n
@@ -259,9 +345,9 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
 
   openScope(c)
   let gp = n.sons[genericParamsPos]
-  internalAssert gp.kind != nkEmpty
+  internalAssert c.config, gp.kind != nkEmpty
   n.sons[namePos] = newSymNode(result)
-  pushInfoContext(info)
+  pushInfoContext(c.config, info, fn.detailedInfo)
   var entry = TInstantiation.new
   entry.sym = result
   # we need to compare both the generic types and the concrete types:
@@ -280,7 +366,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
     inc i
   if tfTriggersCompileTime in result.typ.flags:
     incl(result.flags, sfCompileTime)
-  n.sons[genericParamsPos] = ast.emptyNode
+  n.sons[genericParamsPos] = c.graph.emptyNode
   var oldPrc = genericCacheGet(fn, entry[], c.compilesContextId)
   if oldPrc == nil:
     # we MUST not add potentially wrong instantiations to the caching mechanism.
@@ -290,23 +376,26 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
     #if c.compilesContextId == 0:
     rawHandleSelf(c, result)
     entry.compilesId = c.compilesContextId
-    fn.procInstCache.safeAdd(entry)
+    fn.procInstCache.add(entry)
     c.generics.add(makeInstPair(fn, entry))
     if n.sons[pragmasPos].kind != nkEmpty:
       pragma(c, result, n.sons[pragmasPos], allRoutinePragmas)
     if isNil(n.sons[bodyPos]):
       n.sons[bodyPos] = copyTree(fn.getBody)
-    instantiateBody(c, n, fn.typ.n, result, fn)
+    if c.inGenericContext == 0:
+      instantiateBody(c, n, fn.typ.n, result, fn)
     sideEffectsCheck(c, result)
-    paramsTypeCheck(c, result.typ)
+    if result.magic != mSlice:
+      # 'toOpenArray' is special and it is allowed to return 'openArray':
+      paramsTypeCheck(c, result.typ)
   else:
     result = oldPrc
   popProcCon(c)
-  popInfoContext()
+  popInfoContext(c.config)
   closeScope(c)           # close scope for parameters
   popOwner(c)
   c.currentScope = oldScope
   discard c.friendModules.pop()
   dec(c.instCounter)
-  c.inTypeClass = oldInTypeClass
+  c.matchedConcept = oldMatchedConcept
   if result.kind == skMethod: finishMethod(c, result)
diff --git a/compiler/semmacrosanity.nim b/compiler/semmacrosanity.nim
index a6024a42f..3056f5d72 100644
--- a/compiler/semmacrosanity.nim
+++ b/compiler/semmacrosanity.nim
@@ -10,7 +10,7 @@
 ## Implements type sanity checking for ASTs resulting from macros. Lots of
 ## room for improvement here.
 
-import ast, astalgo, msgs, types
+import ast, astalgo, msgs, types, options
 
 proc ithField(n: PNode, field: var int): PSym =
   result = nil
@@ -20,7 +20,7 @@ proc ithField(n: PNode, field: var int): PSym =
       result = ithField(n.sons[i], field)
       if result != nil: return
   of nkRecCase:
-    if n.sons[0].kind != nkSym: internalError(n.info, "ithField")
+    if n.sons[0].kind != nkSym: return
     result = ithField(n.sons[0], field)
     if result != nil: return
     for i in countup(1, sonsLen(n) - 1):
@@ -28,13 +28,13 @@ proc ithField(n: PNode, field: var int): PSym =
       of nkOfBranch, nkElse:
         result = ithField(lastSon(n.sons[i]), field)
         if result != nil: return
-      else: internalError(n.info, "ithField(record case branch)")
+      else: discard
   of nkSym:
     if field == 0: result = n.sym
     else: dec(field)
   else: discard
 
-proc annotateType*(n: PNode, t: PType) =
+proc annotateType*(n: PNode, t: PType; conf: ConfigRef) =
   let x = t.skipTypes(abstractInst+{tyRange})
   # Note: x can be unequal to t and we need to be careful to use 't'
   # to not to skip tyGenericInst
@@ -42,54 +42,54 @@ proc annotateType*(n: PNode, t: PType) =
   of nkObjConstr:
     let x = t.skipTypes(abstractPtrs)
     n.typ = t
-    for i in 1 .. <n.len:
+    for i in 1 ..< n.len:
       var j = i-1
       let field = x.n.ithField(j)
       if field.isNil:
-        globalError n.info, "invalid field at index " & $i
+        globalError conf, n.info, "invalid field at index " & $i
       else:
-        internalAssert(n.sons[i].kind == nkExprColonExpr)
-        annotateType(n.sons[i].sons[1], field.typ)
-  of nkPar:
+        internalAssert(conf, n.sons[i].kind == nkExprColonExpr)
+        annotateType(n.sons[i].sons[1], field.typ, conf)
+  of nkPar, nkTupleConstr:
     if x.kind == tyTuple:
       n.typ = t
-      for i in 0 .. <n.len:
-        if i >= x.len: globalError n.info, "invalid field at index " & $i
-        else: annotateType(n.sons[i], x.sons[i])
+      for i in 0 ..< n.len:
+        if i >= x.len: globalError conf, n.info, "invalid field at index " & $i
+        else: annotateType(n.sons[i], x.sons[i], conf)
     elif x.kind == tyProc and x.callConv == ccClosure:
       n.typ = t
     else:
-      globalError(n.info, "() must have a tuple type")
+      globalError(conf, n.info, "() must have a tuple type")
   of nkBracket:
     if x.kind in {tyArray, tySequence, tyOpenArray}:
       n.typ = t
-      for m in n: annotateType(m, x.elemType)
+      for m in n: annotateType(m, x.elemType, conf)
     else:
-      globalError(n.info, "[] must have some form of array type")
+      globalError(conf, n.info, "[] must have some form of array type")
   of nkCurly:
     if x.kind in {tySet}:
       n.typ = t
-      for m in n: annotateType(m, x.elemType)
+      for m in n: annotateType(m, x.elemType, conf)
     else:
-      globalError(n.info, "{} must have the set type")
+      globalError(conf, n.info, "{} must have the set type")
   of nkFloatLit..nkFloat128Lit:
     if x.kind in {tyFloat..tyFloat128}:
       n.typ = t
     else:
-      globalError(n.info, "float literal must have some float type")
+      globalError(conf, n.info, "float literal must have some float type")
   of nkCharLit..nkUInt64Lit:
     if x.kind in {tyInt..tyUInt64, tyBool, tyChar, tyEnum}:
       n.typ = t
     else:
-      globalError(n.info, "integer literal must have some int type")
+      globalError(conf, n.info, "integer literal must have some int type")
   of nkStrLit..nkTripleStrLit:
     if x.kind in {tyString, tyCString}:
       n.typ = t
     else:
-      globalError(n.info, "string literal must be of some string type")
+      globalError(conf, n.info, "string literal must be of some string type")
   of nkNilLit:
-    if x.kind in NilableTypes:
+    if x.kind in NilableTypes+{tyString, tySequence}:
       n.typ = t
     else:
-      globalError(n.info, "nil literal must be of some pointer type")
+      globalError(conf, n.info, "nil literal must be of some pointer type")
   else: discard
diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim
index 5eed1e702..7e61854b8 100644
--- a/compiler/semmagic.nim
+++ b/compiler/semmagic.nim
@@ -16,15 +16,22 @@ proc semAddr(c: PContext; n: PNode; isUnsafeAddr=false): PNode =
   if x.kind == nkSym:
     x.sym.flags.incl(sfAddrTaken)
   if isAssignable(c, x, isUnsafeAddr) notin {arLValue, arLocalLValue}:
-    localError(n.info, errExprHasNoAddress)
+    localError(c.config, n.info, errExprHasNoAddress)
   result.add x
   result.typ = makePtrType(c, x.typ)
 
 proc semTypeOf(c: PContext; n: PNode): PNode =
+  var m = BiggestInt 1 # typeOfIter
+  if n.len == 3:
+    let mode = semConstExpr(c, n[2])
+    if mode.kind != nkIntLit:
+      localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
+    else:
+      m = mode.intVal
   result = newNodeI(nkTypeOfExpr, n.info)
-  let typExpr = semExprWithType(c, n, {efInTypeof})
+  let typExpr = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
   result.add typExpr
-  result.typ = makeTypeDesc(c, typExpr.typ.skipTypes({tyTypeDesc}))
+  result.typ = makeTypeDesc(c, typExpr.typ)
 
 type
   SemAsgnMode = enum asgnNormal, noOverloadedSubscript, noOverloadedAsgn
@@ -38,14 +45,12 @@ proc skipAddr(n: PNode): PNode {.inline.} =
 proc semArrGet(c: PContext; n: PNode; flags: TExprFlags): PNode =
   result = newNodeI(nkBracketExpr, n.info)
   for i in 1..<n.len: result.add(n[i])
-  let oldBracketExpr = c.p.bracketExpr
   result = semSubscript(c, result, flags)
-  c.p.bracketExpr = oldBracketExpr
   if result.isNil:
     let x = copyTree(n)
-    x.sons[0] = newIdentNode(getIdent"[]", n.info)
+    x.sons[0] = newIdentNode(getIdent(c.cache, "[]"), n.info)
     bracketNotFoundError(c, x)
-    #localError(n.info, "could not resolve: " & $n)
+    #localError(c.config, n.info, "could not resolve: " & $n)
     result = n
 
 proc semArrPut(c: PContext; n: PNode; flags: TExprFlags): PNode =
@@ -66,48 +71,106 @@ proc semAsgnOpr(c: PContext; n: PNode): PNode =
 
 proc semIsPartOf(c: PContext, n: PNode, flags: TExprFlags): PNode =
   var r = isPartOf(n[1], n[2])
-  result = newIntNodeT(ord(r), n)
+  result = newIntNodeT(ord(r), n, c.graph)
 
 proc expectIntLit(c: PContext, n: PNode): int =
   let x = c.semConstExpr(c, n)
   case x.kind
   of nkIntLit..nkInt64Lit: result = int(x.intVal)
-  else: localError(n.info, errIntLiteralExpected)
+  else: localError(c.config, n.info, errIntLiteralExpected)
 
 proc semInstantiationInfo(c: PContext, n: PNode): PNode =
-  result = newNodeIT(nkPar, n.info, n.typ)
+  result = newNodeIT(nkTupleConstr, n.info, n.typ)
   let idx = expectIntLit(c, n.sons[1])
   let useFullPaths = expectIntLit(c, n.sons[2])
-  let info = getInfoContext(idx)
-  var filename = newNodeIT(nkStrLit, n.info, getSysType(tyString))
-  filename.strVal = if useFullPaths != 0: info.toFullPath else: info.toFilename
-  var line = newNodeIT(nkIntLit, n.info, getSysType(tyInt))
+  let info = getInfoContext(c.config, idx)
+  var filename = newNodeIT(nkStrLit, n.info, getSysType(c.graph, n.info, tyString))
+  filename.strVal = if useFullPaths != 0: toFullPath(c.config, info) else: toFilename(c.config, info)
+  var line = newNodeIT(nkIntLit, n.info, getSysType(c.graph, n.info, tyInt))
   line.intVal = toLinenumber(info)
+  var column = newNodeIT(nkIntLit, n.info, getSysType(c.graph, n.info, tyInt))
+  column.intVal = toColumn(info)
   result.add(filename)
   result.add(line)
+  result.add(column)
+
+proc toNode(t: PType, i: TLineInfo): PNode =
+  result = newNodeIT(nkType, i, t)
+
+const
+  # these are types that use the bracket syntax for instantiation
+  # they can be subjected to the type traits `genericHead` and
+  # `Uninstantiated`
+  tyUserDefinedGenerics* = {tyGenericInst, tyGenericInvocation,
+                            tyUserTypeClassInst}
+
+  tyMagicGenerics* = {tySet, tySequence, tyArray, tyOpenArray}
+
+  tyGenericLike* = tyUserDefinedGenerics +
+                   tyMagicGenerics +
+                   {tyCompositeTypeClass}
+
+proc uninstantiate(t: PType): PType =
+  result = case t.kind
+    of tyMagicGenerics: t
+    of tyUserDefinedGenerics: t.base
+    of tyCompositeTypeClass: uninstantiate t.sons[1]
+    else: t
 
-proc evalTypeTrait(trait: PNode, operand: PType, context: PSym): PNode =
-  let typ = operand.skipTypes({tyTypeDesc})
-  case trait.sym.name.s.normalize
+proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym): PNode =
+  const skippedTypes = {tyTypeDesc, tyAlias, tySink}
+  let trait = traitCall[0]
+  internalAssert c.config, trait.kind == nkSym
+  var operand = operand.skipTypes(skippedTypes)
+
+  template operand2: PType =
+    traitCall.sons[2].typ.skipTypes({tyTypeDesc})
+
+  template typeWithSonsResult(kind, sons): PNode =
+    newTypeWithSons(context, kind, sons).toNode(traitCall.info)
+
+  case trait.sym.name.s
+  of "or", "|":
+    return typeWithSonsResult(tyOr, @[operand, operand2])
+  of "and":
+    return typeWithSonsResult(tyAnd, @[operand, operand2])
+  of "not":
+    return typeWithSonsResult(tyNot, @[operand])
   of "name":
-    result = newStrNode(nkStrLit, typ.typeToString(preferName))
+    result = newStrNode(nkStrLit, operand.typeToString(preferTypeName))
     result.typ = newType(tyString, context)
-    result.info = trait.info
+    result.info = traitCall.info
   of "arity":
-    result = newIntNode(nkIntLit, typ.len - ord(typ.kind==tyProc))
+    result = newIntNode(nkIntLit, operand.len - ord(operand.kind==tyProc))
     result.typ = newType(tyInt, context)
-    result.info = trait.info
+    result.info = traitCall.info
+  of "genericHead":
+    var res = uninstantiate(operand)
+    if res == operand and res.kind notin tyMagicGenerics:
+      localError(c.config, traitCall.info,
+        "genericHead expects a generic type. The given type was " &
+        typeToString(operand))
+      return newType(tyError, context).toNode(traitCall.info)
+    result = res.base.toNode(traitCall.info)
+  of "stripGenericParams":
+    result = uninstantiate(operand).toNode(traitCall.info)
+  of "supportsCopyMem":
+    let t = operand.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred})
+    let complexObj = containsGarbageCollectedRef(t) or
+                     hasDestructor(t)
+    result = newIntNodeT(ord(not complexObj), traitCall, c.graph)
   else:
-    internalAssert false
+    localError(c.config, traitCall.info, "unknown trait")
+    result = newNodeI(nkEmpty, traitCall.info)
 
 proc semTypeTraits(c: PContext, n: PNode): PNode =
-  checkMinSonsLen(n, 2)
+  checkMinSonsLen(n, 2, c.config)
   let t = n.sons[1].typ
-  internalAssert t != nil and t.kind == tyTypeDesc
+  internalAssert c.config, t != nil and t.kind == tyTypeDesc
   if t.sonsLen > 0:
     # This is either a type known to sem or a typedesc
     # param to a regular proc (again, known at instantiation)
-    result = evalTypeTrait(n[0], t, getCurrOwner(c))
+    result = evalTypeTrait(c, n, t, getCurrOwner(c))
   else:
     # a typedesc variable, pass unmodified to evals
     result = n
@@ -115,10 +178,12 @@ proc semTypeTraits(c: PContext, n: PNode): PNode =
 proc semOrd(c: PContext, n: PNode): PNode =
   result = n
   let parType = n.sons[1].typ
-  if isOrdinalType(parType) or parType.kind == tySet:
-    result.typ = makeRangeType(c, firstOrd(parType), lastOrd(parType), n.info)
+  if isOrdinalType(parType, allowEnumWithHoles=true):
+    discard
+  elif parType.kind == tySet:
+    result.typ = makeRangeType(c, firstOrd(c.config, parType), lastOrd(c.config, parType), n.info)
   else:
-    localError(n.info, errOrdinalTypeExpected)
+    localError(c.config, n.info, errOrdinalTypeExpected)
     result.typ = errorType(c)
 
 proc semBindSym(c: PContext, n: PNode): PNode =
@@ -127,91 +192,220 @@ proc semBindSym(c: PContext, n: PNode): PNode =
 
   let sl = semConstExpr(c, n.sons[1])
   if sl.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}:
-    localError(n.sons[1].info, errStringLiteralExpected)
+    localError(c.config, n.sons[1].info, errStringLiteralExpected)
     return errorNode(c, n)
 
   let isMixin = semConstExpr(c, n.sons[2])
   if isMixin.kind != nkIntLit or isMixin.intVal < 0 or
       isMixin.intVal > high(TSymChoiceRule).int:
-    localError(n.sons[2].info, errConstExprExpected)
+    localError(c.config, n.sons[2].info, errConstExprExpected)
     return errorNode(c, n)
 
-  let id = newIdentNode(getIdent(sl.strVal), n.info)
+  let id = newIdentNode(getIdent(c.cache, sl.strVal), n.info)
   let s = qualifiedLookUp(c, id, {checkUndeclared})
   if s != nil:
     # we need to mark all symbols:
     var sc = symChoice(c, id, s, TSymChoiceRule(isMixin.intVal))
+    if not (c.inStaticContext > 0 or getCurrOwner(c).isCompileTimeProc):
+      # inside regular code, bindSym resolves to the sym-choice
+      # nodes (see tinspectsymbol)
+      return sc
     result.add(sc)
   else:
     errorUndeclaredIdentifier(c, n.sons[1].info, sl.strVal)
 
+proc opBindSym(c: PContext, scope: PScope, n: PNode, isMixin: int, info: PNode): PNode =
+  if n.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit, nkIdent}:
+    localError(c.config, info.info, errStringOrIdentNodeExpected)
+    return errorNode(c, n)
+
+  if isMixin < 0 or isMixin > high(TSymChoiceRule).int:
+    localError(c.config, info.info, errConstExprExpected)
+    return errorNode(c, n)
+
+  let id = if n.kind == nkIdent: n
+    else: newIdentNode(getIdent(c.cache, n.strVal), info.info)
+
+  let tmpScope = c.currentScope
+  c.currentScope = scope
+  let s = qualifiedLookUp(c, id, {checkUndeclared})
+  if s != nil:
+    # we need to mark all symbols:
+    result = symChoice(c, id, s, TSymChoiceRule(isMixin))
+  else:
+    errorUndeclaredIdentifier(c, info.info, if n.kind == nkIdent: n.ident.s
+      else: n.strVal)
+  c.currentScope = tmpScope
+
+proc semDynamicBindSym(c: PContext, n: PNode): PNode =
+  # inside regular code, bindSym resolves to the sym-choice
+  # nodes (see tinspectsymbol)
+  if not (c.inStaticContext > 0 or getCurrOwner(c).isCompileTimeProc):
+    return semBindSym(c, n)
+
+  if c.graph.vm.isNil:
+    setupGlobalCtx(c.module, c.graph)
+
+  let
+    vm = PCtx c.graph.vm
+    # cache the current scope to
+    # prevent it lost into oblivion
+    scope = c.currentScope
+
+  # cannot use this
+  # vm.config.features.incl dynamicBindSym
+
+  proc bindSymWrapper(a: VmArgs) =
+    # capture PContext and currentScope
+    # param description:
+    #   0. ident, a string literal / computed string / or ident node
+    #   1. bindSym rule
+    #   2. info node
+    a.setResult opBindSym(c, scope, a.getNode(0), a.getInt(1).int, a.getNode(2))
+
+  let
+    # altough we use VM callback here, it is not
+    # executed like 'normal' VM callback
+    idx = vm.registerCallback("bindSymImpl", bindSymWrapper)
+    # dummy node to carry idx information to VM
+    idxNode = newIntTypeNode(nkIntLit, idx, c.graph.getSysType(TLineInfo(), tyInt))
+
+  result = copyNode(n)
+  for x in n: result.add x
+  result.add n # info node
+  result.add idxNode
+
 proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode
 
-proc isStrangeArray(t: PType): bool =
-  let t = t.skipTypes(abstractInst)
-  result = t.kind == tyArray and t.firstOrd != 0
+proc semOf(c: PContext, n: PNode): PNode =
+  if sonsLen(n) == 3:
+    n.sons[1] = semExprWithType(c, n.sons[1])
+    n.sons[2] = semExprWithType(c, n.sons[2], {efDetermineType})
+    #restoreOldStyleType(n.sons[1])
+    #restoreOldStyleType(n.sons[2])
+    let a = skipTypes(n.sons[1].typ, abstractPtrs)
+    let b = skipTypes(n.sons[2].typ, abstractPtrs)
+    let x = skipTypes(n.sons[1].typ, abstractPtrs-{tyTypeDesc})
+    let y = skipTypes(n.sons[2].typ, abstractPtrs-{tyTypeDesc})
+
+    if x.kind == tyTypeDesc or y.kind != tyTypeDesc:
+      localError(c.config, n.info, "'of' takes object types")
+    elif b.kind != tyObject or a.kind != tyObject:
+      localError(c.config, n.info, "'of' takes object types")
+    else:
+      let diff = inheritanceDiff(a, b)
+      # | returns: 0 iff `a` == `b`
+      # | returns: -x iff `a` is the x'th direct superclass of `b`
+      # | returns: +x iff `a` is the x'th direct subclass of `b`
+      # | returns: `maxint` iff `a` and `b` are not compatible at all
+      if diff <= 0:
+        # optimize to true:
+        message(c.config, n.info, hintConditionAlwaysTrue, renderTree(n))
+        result = newIntNode(nkIntLit, 1)
+        result.info = n.info
+        result.typ = getSysType(c.graph, n.info, tyBool)
+        return result
+      elif diff == high(int):
+        if commonSuperclass(a, b) == nil:
+          localError(c.config, n.info, "'$1' cannot be of this subtype" % typeToString(a))
+        else:
+          message(c.config, n.info, hintConditionAlwaysFalse, renderTree(n))
+          result = newIntNode(nkIntLit, 0)
+          result.info = n.info
+          result.typ = getSysType(c.graph, n.info, tyBool)
+  else:
+    localError(c.config, n.info, "'of' takes 2 arguments")
+  n.typ = getSysType(c.graph, n.info, tyBool)
+  result = n
 
 proc magicsAfterOverloadResolution(c: PContext, n: PNode,
                                    flags: TExprFlags): PNode =
+  ## This is the preferred code point to implement magics.
+  ## ``c`` the current module, a symbol table to a very good approximation
+  ## ``n`` the ast like it would be passed to a real macro
+  ## ``flags`` Some flags for more contextual information on how the
+  ## "macro" is calld.
+
   case n[0].sym.magic
   of mAddr:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     result = semAddr(c, n.sons[1], n[0].sym.name.s == "unsafeAddr")
   of mTypeOf:
-    checkSonsLen(n, 2)
-    result = semTypeOf(c, n.sons[1])
-  of mArrGet: result = semArrGet(c, n, flags)
-  of mArrPut: result = semArrPut(c, n, flags)
-  of mAsgn: result = semAsgnOpr(c, n)
+    result = semTypeOf(c, n)
+  of mSizeOf:
+    # TODO there is no proper way to find out if a type cannot be queried for the size.
+    let size = getSize(c.config, n[1].typ)
+    # We just assume here that the type might come from the c backend
+    if size == szUnknownSize:
+      # Forward to the c code generation to emit a `sizeof` in the C code.
+      result = n
+    elif size >= 0:
+      result = newIntNode(nkIntLit, size)
+      result.info = n.info
+      result.typ = n.typ
+    else:
+      localError(c.config, n.info, "cannot evaluate 'sizeof' because its type is not defined completely, type: " & n[1].typ.typeToString)
+      result = n
+  of mAlignOf:
+    result = newIntNode(nkIntLit, getAlign(c.config, n[1].typ))
+    result.info = n.info
+    result.typ = n.typ
+  of mOffsetOf:
+    var dotExpr: PNode
+
+    block findDotExpr:
+      if n[1].kind == nkDotExpr:
+        dotExpr = n[1]
+      elif n[1].kind == nkCheckedFieldExpr:
+        dotExpr = n[1][0]
+      else:
+        illFormedAst(n, c.config)
+
+    assert dotExpr != nil
+
+    let value = dotExpr[0]
+    let member = dotExpr[1]
+
+    discard computeSize(c.config, value.typ)
+
+    result = newIntNode(nkIntLit, member.sym.offset)
+    result.info = n.info
+    result.typ = n.typ
+  of mArrGet:
+    result = semArrGet(c, n, flags)
+  of mArrPut:
+    result = semArrPut(c, n, flags)
+  of mAsgn:
+    if n[0].sym.name.s == "=":
+      result = semAsgnOpr(c, n)
+    else:
+      result = semShallowCopy(c, n, flags)
   of mIsPartOf: result = semIsPartOf(c, n, flags)
   of mTypeTrait: result = semTypeTraits(c, n)
   of mAstToStr:
-    result = newStrNodeT(renderTree(n[1], {renderNoComments}), n)
-    result.typ = getSysType(tyString)
+    result = newStrNodeT(renderTree(n[1], {renderNoComments}), n, c.graph)
+    result.typ = getSysType(c.graph, n.info, tyString)
   of mInstantiationInfo: result = semInstantiationInfo(c, n)
   of mOrd: result = semOrd(c, n)
+  of mOf: result = semOf(c, n)
   of mHigh, mLow: result = semLowHigh(c, n, n[0].sym.magic)
   of mShallowCopy: result = semShallowCopy(c, n, flags)
-  of mNBindSym: result = semBindSym(c, n)
+  of mNBindSym:
+    if dynamicBindSym notin c.features:
+      result = semBindSym(c, n)
+    else:
+      result = semDynamicBindSym(c, n)
   of mProcCall:
     result = n
     result.typ = n[1].typ
   of mDotDot:
     result = n
   of mRoof:
-    let bracketExpr = if n.len == 3: n.sons[2] else: c.p.bracketExpr
-    if bracketExpr.isNil:
-      localError(n.info, "no surrounding array access context for '^'")
-      result = n.sons[1]
-    elif bracketExpr.checkForSideEffects != seNoSideEffect:
-      localError(n.info, "invalid context for '^' as '$#' has side effects" %
-        renderTree(bracketExpr))
-      result = n.sons[1]
-    elif bracketExpr.typ.isStrangeArray:
-      localError(n.info, "invalid context for '^' as len!=high+1 for '$#'" %
-        renderTree(bracketExpr))
-      result = n.sons[1]
-    else:
-      # ^x  is rewritten to: len(a)-x
-      let lenExpr = newNodeI(nkCall, n.info)
-      lenExpr.add newIdentNode(getIdent"len", n.info)
-      lenExpr.add bracketExpr
-      let lenExprB = semExprWithType(c, lenExpr)
-      if lenExprB.typ.isNil or not isOrdinalType(lenExprB.typ):
-        localError(n.info, "'$#' has to be of an ordinal type for '^'" %
-          renderTree(lenExpr))
-        result = n.sons[1]
-      else:
-        result = newNodeIT(nkCall, n.info, getSysType(tyInt))
-        let subi = getSysMagic("-", mSubI)
-        #echo "got ", typeToString(subi.typ)
-        result.add newSymNode(subi, n.info)
-        result.add lenExprB
-        result.add n.sons[1]
+    localError(c.config, n.info, "builtin roof operator is not supported anymore")
   of mPlugin:
-    let plugin = getPlugin(n[0].sym)
+    let plugin = getPlugin(c.cache, n[0].sym)
     if plugin.isNil:
-      localError(n.info, "cannot find plugin " & n[0].sym.name.s)
+      localError(c.config, n.info, "cannot find plugin " & n[0].sym.name.s)
       result = n
     else:
       result = plugin(c, n)
diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim
new file mode 100644
index 000000000..90ab2c57a
--- /dev/null
+++ b/compiler/semobjconstr.nim
@@ -0,0 +1,308 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2015 Nim Contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements Nim's object construction rules.
+
+# included from sem.nim
+
+type
+  InitStatus = enum
+    initUnknown
+    initFull     # All  of the fields have been initialized
+    initPartial  # Some of the fields have been initialized
+    initNone     # None of the fields have been initialized
+    initConflict # Fields from different branches have been initialized
+
+proc mergeInitStatus(existing: var InitStatus, newStatus: InitStatus) =
+  case newStatus
+  of initConflict:
+    existing = newStatus
+  of initPartial:
+    if existing in {initUnknown, initFull, initNone}:
+      existing = initPartial
+  of initNone:
+    if existing == initUnknown:
+      existing = initNone
+    elif existing == initFull:
+      existing = initPartial
+  of initFull:
+    if existing == initUnknown:
+      existing = initFull
+    elif existing == initNone:
+      existing = initPartial
+  of initUnknown:
+    discard
+
+proc invalidObjConstr(c: PContext, n: PNode) =
+  if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s[0] == ':':
+    localError(c.config, n.info, "incorrect object construction syntax; use a space after the colon")
+  else:
+    localError(c.config, n.info, "incorrect object construction syntax")
+
+proc locateFieldInInitExpr(c: PContext, field: PSym, initExpr: PNode): PNode =
+  # Returns the assignment nkExprColonExpr node or nil
+  let fieldId = field.name.id
+  for i in 1 ..< initExpr.len:
+    let assignment = initExpr[i]
+    if assignment.kind != nkExprColonExpr:
+      invalidObjConstr(c, assignment)
+      continue
+
+    if fieldId == considerQuotedIdent(c, assignment[0]).id:
+      return assignment
+
+proc semConstrField(c: PContext, flags: TExprFlags,
+                    field: PSym, initExpr: PNode): PNode =
+  let assignment = locateFieldInInitExpr(c, field, initExpr)
+  if assignment != nil:
+    if nfSem in assignment.flags: return assignment[1]
+    if not fieldVisible(c, field):
+      localError(c.config, initExpr.info,
+        "the field '$1' is not accessible." % [field.name.s])
+      return
+
+    var initValue = semExprFlagDispatched(c, assignment[1], flags)
+    if initValue != nil:
+      initValue = fitNode(c, field.typ, initValue, assignment.info)
+    assignment.sons[0] = newSymNode(field)
+    assignment.sons[1] = initValue
+    assignment.flags.incl nfSem
+    return initValue
+
+proc caseBranchMatchesExpr(branch, matched: PNode): bool =
+  for i in 0 .. branch.len-2:
+    if branch[i].kind == nkRange:
+      if overlap(branch[i], matched): return true
+    elif exprStructuralEquivalent(branch[i], matched):
+      return true
+
+  return false
+
+proc pickCaseBranch(caseExpr, matched: PNode): PNode =
+  # XXX: Perhaps this proc already exists somewhere
+  let endsWithElse = caseExpr[^1].kind == nkElse
+  for i in 1 .. caseExpr.len - 1 - int(endsWithElse):
+    if caseExpr[i].caseBranchMatchesExpr(matched):
+      return caseExpr[i]
+
+  if endsWithElse:
+    return caseExpr[^1]
+
+iterator directFieldsInRecList(recList: PNode): PNode =
+  # XXX: We can remove this case by making all nkOfBranch nodes
+  # regular. Currently, they try to avoid using nkRecList if they
+  # include only a single field
+  if recList.kind == nkSym:
+    yield recList
+  else:
+    doAssert recList.kind == nkRecList
+    for field in recList:
+      if field.kind != nkSym: continue
+      yield field
+
+template quoteStr(s: string): string = "'" & s & "'"
+
+proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): string =
+  result = ""
+  for field in directFieldsInRecList(fieldsRecList):
+    let assignment = locateFieldInInitExpr(c, field.sym, initExpr)
+    if assignment != nil:
+      if result.len != 0: result.add ", "
+      result.add field.sym.name.s.quoteStr
+
+proc missingMandatoryFields(c: PContext, fieldsRecList, initExpr: PNode): string =
+  for r in directFieldsInRecList(fieldsRecList):
+    if {tfNotNil, tfNeedsInit} * r.sym.typ.flags != {}:
+      let assignment = locateFieldInInitExpr(c, r.sym, initExpr)
+      if assignment == nil:
+        if result.len == 0:
+          result = r.sym.name.s
+        else:
+          result.add ", "
+          result.add r.sym.name.s
+
+proc checkForMissingFields(c: PContext, recList, initExpr: PNode) =
+  let missing = missingMandatoryFields(c, recList, initExpr)
+  if missing.len > 0:
+    localError(c.config, initExpr.info, "fields not initialized: $1.", [missing])
+
+proc semConstructFields(c: PContext, recNode: PNode,
+                        initExpr: PNode, flags: TExprFlags): InitStatus =
+  result = initUnknown
+
+  case recNode.kind
+  of nkRecList:
+    for field in recNode:
+      let status = semConstructFields(c, field, initExpr, flags)
+      mergeInitStatus(result, status)
+
+  of nkRecCase:
+    template fieldsPresentInBranch(branchIdx: int): string =
+      let branch = recNode[branchIdx]
+      let fields = branch[branch.len - 1]
+      fieldsPresentInInitExpr(c, fields, initExpr)
+
+    template checkMissingFields(branchNode: PNode) =
+      let fields = branchNode[branchNode.len - 1]
+      checkForMissingFields(c, fields, initExpr)
+
+    let discriminator = recNode.sons[0]
+    internalAssert c.config, discriminator.kind == nkSym
+    var selectedBranch = -1
+
+    for i in 1 ..< recNode.len:
+      let innerRecords = recNode[i][^1]
+      let status = semConstructFields(c, innerRecords, initExpr, flags)
+      if status notin {initNone, initUnknown}:
+        mergeInitStatus(result, status)
+        if selectedBranch != -1:
+          let prevFields = fieldsPresentInBranch(selectedBranch)
+          let currentFields = fieldsPresentInBranch(i)
+          localError(c.config, initExpr.info,
+            ("The fields '$1' and '$2' cannot be initialized together, " &
+            "because they are from conflicting branches in the case object.") %
+            [prevFields, currentFields])
+          result = initConflict
+        else:
+          selectedBranch = i
+
+    if selectedBranch != -1:
+      let branchNode = recNode[selectedBranch]
+      let flags = flags*{efAllowDestructor} + {efNeedStatic, efPreferNilResult}
+      let discriminatorVal = semConstrField(c, flags,
+                                            discriminator.sym, initExpr)
+      if discriminatorVal == nil:
+        let fields = fieldsPresentInBranch(selectedBranch)
+        localError(c.config, initExpr.info,
+          ("you must provide a compile-time value for the discriminator '$1' " &
+          "in order to prove that it's safe to initialize $2.") %
+          [discriminator.sym.name.s, fields])
+        mergeInitStatus(result, initNone)
+      else:
+        let discriminatorVal = discriminatorVal.skipHidden
+
+        template wrongBranchError(i) =
+          let fields = fieldsPresentInBranch(i)
+          localError(c.config, initExpr.info,
+            "a case selecting discriminator '$1' with value '$2' " &
+            "appears in the object construction, but the field(s) $3 " &
+            "are in conflict with this value.",
+            [discriminator.sym.name.s, discriminatorVal.renderTree, fields])
+
+        if branchNode.kind != nkElse:
+          if not branchNode.caseBranchMatchesExpr(discriminatorVal):
+            wrongBranchError(selectedBranch)
+        else:
+          # With an else clause, check that all other branches don't match:
+          for i in 1 .. (recNode.len - 2):
+            if recNode[i].caseBranchMatchesExpr(discriminatorVal):
+              wrongBranchError(i)
+              break
+
+      # When a branch is selected with a partial match, some of the fields
+      # that were not initialized may be mandatory. We must check for this:
+      if result == initPartial:
+        checkMissingFields branchNode
+
+    else:
+      result = initNone
+      let discriminatorVal = semConstrField(c, flags + {efPreferStatic},
+                                            discriminator.sym, initExpr)
+      if discriminatorVal == nil:
+        # None of the branches were explicitly selected by the user and no
+        # value was given to the discrimator. We can assume that it will be
+        # initialized to zero and this will select a particular branch as
+        # a result:
+        let matchedBranch = recNode.pickCaseBranch newIntLit(c.graph, initExpr.info, 0)
+        checkMissingFields matchedBranch
+      else:
+        result = initPartial
+        if discriminatorVal.kind == nkIntLit:
+          # When the discriminator is a compile-time value, we also know
+          # which brach will be selected:
+          let matchedBranch = recNode.pickCaseBranch discriminatorVal
+          if matchedBranch != nil: checkMissingFields matchedBranch
+        else:
+          # All bets are off. If any of the branches has a mandatory
+          # fields we must produce an error:
+          for i in 1 ..< recNode.len: checkMissingFields recNode[i]
+
+  of nkSym:
+    let field = recNode.sym
+    let e = semConstrField(c, flags, field, initExpr)
+    result = if e != nil: initFull else: initNone
+
+  else:
+    internalAssert c.config, false
+
+proc semConstructType(c: PContext, initExpr: PNode,
+                      t: PType, flags: TExprFlags): InitStatus =
+  var t = t
+  result = initUnknown
+  while true:
+    let status = semConstructFields(c, t.n, initExpr, flags)
+    mergeInitStatus(result, status)
+    if status in {initPartial, initNone, initUnknown}:
+      checkForMissingFields c, t.n, initExpr
+    let base = t.sons[0]
+    if base == nil: break
+    t = skipTypes(base, skipPtrs)
+
+proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
+  var t = semTypeNode(c, n.sons[0], nil)
+  result = newNodeIT(nkObjConstr, n.info, t)
+  for child in n: result.add child
+
+  if t == nil:
+    localError(c.config, n.info, errGenerated, "object constructor needs an object type")
+    return
+
+  t = skipTypes(t, {tyGenericInst, tyAlias, tySink})
+  if t.kind == tyRef: t = skipTypes(t.sons[0], {tyGenericInst, tyAlias, tySink})
+  if t.kind != tyObject:
+    localError(c.config, n.info, errGenerated, "object constructor needs an object type")
+    return
+
+  # Check if the object is fully initialized by recursively testing each
+  # field (if this is a case object, initialized fields in two different
+  # branches will be reported as an error):
+  let initResult = semConstructType(c, result, t, flags)
+
+  # It's possible that the object was not fully initialized while
+  # specifying a .requiresInit. pragma.
+  # XXX: Turn this into an error in the next release
+  if tfNeedsInit in t.flags and initResult != initFull:
+    # XXX: Disable this warning for now, because tfNeedsInit is propagated
+    # too aggressively from fields to object types (and this is not correct
+    # in case objects)
+    when false: message(n.info, warnUser,
+      "object type uses the 'requiresInit' pragma, but not all fields " &
+      "have been initialized. future versions of Nim will treat this as " &
+      "an error")
+
+  # Since we were traversing the object fields, it's possible that
+  # not all of the fields specified in the constructor was visited.
+  # We'll check for such fields here:
+  for i in 1..<result.len:
+    let field = result[i]
+    if nfSem notin field.flags:
+      if field.kind != nkExprColonExpr:
+        invalidObjConstr(c, field)
+        continue
+      let id = considerQuotedIdent(c, field[0])
+      # This node was not processed. There are two possible reasons:
+      # 1) It was shadowed by a field with the same name on the left
+      for j in 1 ..< i:
+        let prevId = considerQuotedIdent(c, result[j][0])
+        if prevId.id == id.id:
+          localError(c.config, field.info, errFieldInitTwice % id.s)
+          return
+      # 2) No such field exists in the constructed type
+      localError(c.config, field.info, errUndeclaredFieldX % id.s)
+      return
diff --git a/compiler/semparallel.nim b/compiler/semparallel.nim
index 90c1a315a..137c5d336 100644
--- a/compiler/semparallel.nim
+++ b/compiler/semparallel.nim
@@ -23,7 +23,8 @@
 
 import
   ast, astalgo, idents, lowerings, magicsys, guards, sempass2, msgs,
-  renderer, types
+  renderer, types, modulegraphs, options
+
 from trees import getMagic
 from strutils import `%`
 
@@ -73,15 +74,18 @@ type
                         # the 'parallel' section
     currentSpawnId: int
     inLoop: int
+    graph: ModuleGraph
 
-proc initAnalysisCtx(): AnalysisCtx =
+proc initAnalysisCtx(g: ModuleGraph): AnalysisCtx =
   result.locals = @[]
   result.slices = @[]
   result.args = @[]
-  result.guards = @[]
+  result.guards.s = @[]
+  result.guards.o = initOperators(g)
+  result.graph = g
 
 proc lookupSlot(c: AnalysisCtx; s: PSym): int =
-  for i in 0.. <c.locals.len:
+  for i in 0..<c.locals.len:
     if c.locals[i].v == s or c.locals[i].alias == s: return i
   return -1
 
@@ -94,7 +98,7 @@ proc getSlot(c: var AnalysisCtx; v: PSym): ptr MonotonicVar =
   return addr(c.locals[L])
 
 proc gatherArgs(c: var AnalysisCtx; n: PNode) =
-  for i in 0.. <n.safeLen:
+  for i in 0..<n.safeLen:
     let root = getRoot n[i]
     if root != nil:
       block addRoot:
@@ -117,23 +121,23 @@ proc checkLocal(c: AnalysisCtx; n: PNode) =
   if isLocal(n):
     let s = c.lookupSlot(n.sym)
     if s >= 0 and c.locals[s].stride != nil:
-      localError(n.info, "invalid usage of counter after increment")
+      localError(c.graph.config, n.info, "invalid usage of counter after increment")
   else:
-    for i in 0 .. <n.safeLen: checkLocal(c, n.sons[i])
+    for i in 0 ..< n.safeLen: checkLocal(c, n.sons[i])
 
 template `?`(x): untyped = x.renderTree
 
 proc checkLe(c: AnalysisCtx; a, b: PNode) =
   case proveLe(c.guards, a, b)
   of impUnknown:
-    localError(a.info, "cannot prove: " & ?a & " <= " & ?b & " (bounds check)")
+    localError(c.graph.config, a.info, "cannot prove: " & ?a & " <= " & ?b & " (bounds check)")
   of impYes: discard
   of impNo:
-    localError(a.info, "can prove: " & ?a & " > " & ?b & " (bounds check)")
+    localError(c.graph.config, a.info, "can prove: " & ?a & " > " & ?b & " (bounds check)")
 
 proc checkBounds(c: AnalysisCtx; arr, idx: PNode) =
-  checkLe(c, arr.lowBound, idx)
-  checkLe(c, idx, arr.highBound)
+  checkLe(c, lowBound(c.graph.config, arr), idx)
+  checkLe(c, idx, highBound(c.graph.config, arr, c.guards.o))
 
 proc addLowerBoundAsFacts(c: var AnalysisCtx) =
   for v in c.locals:
@@ -142,34 +146,34 @@ proc addLowerBoundAsFacts(c: var AnalysisCtx) =
 
 proc addSlice(c: var AnalysisCtx; n: PNode; x, le, ri: PNode) =
   checkLocal(c, n)
-  let le = le.canon
-  let ri = ri.canon
+  let le = le.canon(c.guards.o)
+  let ri = ri.canon(c.guards.o)
   # perform static bounds checking here; and not later!
-  let oldState = c.guards.len
+  let oldState = c.guards.s.len
   addLowerBoundAsFacts(c)
   c.checkBounds(x, le)
   c.checkBounds(x, ri)
-  c.guards.setLen(oldState)
+  c.guards.s.setLen(oldState)
   c.slices.add((x, le, ri, c.currentSpawnId, c.inLoop > 0))
 
-proc overlap(m: TModel; x,y,c,d: PNode) =
+proc overlap(m: TModel; conf: ConfigRef; x,y,c,d: PNode) =
   #  X..Y and C..D overlap iff (X <= D and C <= Y)
   case proveLe(m, c, y)
   of impUnknown:
     case proveLe(m, x, d)
     of impNo: discard
     of impUnknown, impYes:
-      localError(x.info,
+      localError(conf, x.info,
         "cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" %
             [?c, ?y, ?x, ?y, ?c, ?d])
   of impYes:
     case proveLe(m, x, d)
     of impUnknown:
-      localError(x.info,
+      localError(conf, x.info,
         "cannot prove: $# > $#; required for ($#)..($#) disjoint from ($#)..($#)" %
           [?x, ?d, ?x, ?y, ?c, ?d])
     of impYes:
-      localError(x.info, "($#)..($#) not disjoint from ($#)..($#)" %
+      localError(conf, x.info, "($#)..($#) not disjoint from ($#)..($#)" %
                 [?c, ?y, ?x, ?y, ?c, ?d])
     of impNo: discard
   of impNo: discard
@@ -180,19 +184,19 @@ proc stride(c: AnalysisCtx; n: PNode): BiggestInt =
     if s >= 0 and c.locals[s].stride != nil:
       result = c.locals[s].stride.intVal
   else:
-    for i in 0 .. <n.safeLen: result += stride(c, n.sons[i])
+    for i in 0 ..< n.safeLen: result += stride(c, n.sons[i])
 
 proc subStride(c: AnalysisCtx; n: PNode): PNode =
   # substitute with stride:
   if isLocal(n):
     let s = c.lookupSlot(n.sym)
     if s >= 0 and c.locals[s].stride != nil:
-      result = n +@ c.locals[s].stride.intVal
+      result = buildAdd(n, c.locals[s].stride.intVal, c.guards.o)
     else:
       result = n
   elif n.safeLen > 0:
     result = shallowCopy(n)
-    for i in 0 .. <n.len: result.sons[i] = subStride(c, n.sons[i])
+    for i in 0 ..< n.len: result.sons[i] = subStride(c, n.sons[i])
   else:
     result = n
 
@@ -203,7 +207,7 @@ proc checkSlicesAreDisjoint(c: var AnalysisCtx) =
   addLowerBoundAsFacts(c)
   # Every slice used in a loop needs to be disjoint with itself:
   for x,a,b,id,inLoop in items(c.slices):
-    if inLoop: overlap(c.guards, a,b, c.subStride(a), c.subStride(b))
+    if inLoop: overlap(c.guards, c.graph.config, a,b, c.subStride(a), c.subStride(b))
   # Another tricky example is:
   #   while true:
   #     spawn f(a[i])
@@ -231,27 +235,27 @@ proc checkSlicesAreDisjoint(c: var AnalysisCtx) =
           # XXX strictly speaking, 'or' is not correct here and it needs to
           # be 'and'. However this prevents too many obviously correct programs
           # like f(a[0..x]); for i in x+1 .. a.high: f(a[i])
-          overlap(c.guards, x.a, x.b, y.a, y.b)
+          overlap(c.guards, c.graph.config, x.a, x.b, y.a, y.b)
         elif (let k = simpleSlice(x.a, x.b); let m = simpleSlice(y.a, y.b);
               k >= 0 and m >= 0):
           # ah I cannot resist the temptation and add another sweet heuristic:
           # if both slices have the form (i+k)..(i+k)  and (i+m)..(i+m) we
           # check they are disjoint and k < stride and m < stride:
-          overlap(c.guards, x.a, x.b, y.a, y.b)
+          overlap(c.guards, c.graph.config, x.a, x.b, y.a, y.b)
           let stride = min(c.stride(x.a), c.stride(y.a))
           if k < stride and m < stride:
             discard
           else:
-            localError(x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" %
+            localError(c.graph.config, x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" %
               [?x.a, ?x.b, ?y.a, ?y.b])
         else:
-          localError(x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" %
+          localError(c.graph.config, x.x.info, "cannot prove ($#)..($#) disjoint from ($#)..($#)" %
             [?x.a, ?x.b, ?y.a, ?y.b])
 
 proc analyse(c: var AnalysisCtx; n: PNode)
 
 proc analyseSons(c: var AnalysisCtx; n: PNode) =
-  for i in 0 .. <safeLen(n): analyse(c, n[i])
+  for i in 0 ..< safeLen(n): analyse(c, n[i])
 
 proc min(a, b: PNode): PNode =
   if a.isNil: result = b
@@ -292,31 +296,31 @@ proc analyseCall(c: var AnalysisCtx; n: PNode; op: PSym) =
 
 proc analyseCase(c: var AnalysisCtx; n: PNode) =
   analyse(c, n.sons[0])
-  let oldFacts = c.guards.len
-  for i in 1.. <n.len:
+  let oldFacts = c.guards.s.len
+  for i in 1..<n.len:
     let branch = n.sons[i]
-    setLen(c.guards, oldFacts)
+    setLen(c.guards.s, oldFacts)
     addCaseBranchFacts(c.guards, n, i)
-    for i in 0 .. <branch.len:
+    for i in 0 ..< branch.len:
       analyse(c, branch.sons[i])
-  setLen(c.guards, oldFacts)
+  setLen(c.guards.s, oldFacts)
 
 proc analyseIf(c: var AnalysisCtx; n: PNode) =
   analyse(c, n.sons[0].sons[0])
-  let oldFacts = c.guards.len
-  addFact(c.guards, canon(n.sons[0].sons[0]))
+  let oldFacts = c.guards.s.len
+  addFact(c.guards, canon(n.sons[0].sons[0], c.guards.o))
 
   analyse(c, n.sons[0].sons[1])
-  for i in 1.. <n.len:
+  for i in 1..<n.len:
     let branch = n.sons[i]
-    setLen(c.guards, oldFacts)
+    setLen(c.guards.s, oldFacts)
     for j in 0..i-1:
-      addFactNeg(c.guards, canon(n.sons[j].sons[0]))
+      addFactNeg(c.guards, canon(n.sons[j].sons[0], c.guards.o))
     if branch.len > 1:
-      addFact(c.guards, canon(branch.sons[0]))
-    for i in 0 .. <branch.len:
+      addFact(c.guards, canon(branch.sons[0], c.guards.o))
+    for i in 0 ..< branch.len:
       analyse(c, branch.sons[i])
-  setLen(c.guards, oldFacts)
+  setLen(c.guards.s, oldFacts)
 
 proc analyse(c: var AnalysisCtx; n: PNode) =
   case n.kind
@@ -345,10 +349,11 @@ proc analyse(c: var AnalysisCtx; n: PNode) =
     if n[0].kind == nkSym: analyseCall(c, n, n[0].sym)
     else: analyseSons(c, n)
   of nkBracketExpr:
-    c.addSlice(n, n[0], n[1], n[1])
+    if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple:
+      c.addSlice(n, n[0], n[1], n[1])
     analyseSons(c, n)
   of nkReturnStmt, nkRaiseStmt, nkTryStmt:
-    localError(n.info, "invalid control flow for 'parallel'")
+    localError(c.graph.config, n.info, "invalid control flow for 'parallel'")
     # 'break' that leaves the 'parallel' section is not valid either
     # or maybe we should generate a 'try' XXX
   of nkVarSection, nkLetSection:
@@ -364,7 +369,7 @@ proc analyse(c: var AnalysisCtx; n: PNode) =
           if it[j].isLocal:
             let slot = c.getSlot(it[j].sym)
             if slot.lower.isNil: slot.lower = value
-            else: internalError(it.info, "slot already has a lower bound")
+            else: internalError(c.graph.config, it.info, "slot already has a lower bound")
         if not isSpawned: analyse(c, value)
   of nkCaseStmt: analyseCase(c, n)
   of nkWhen, nkIfStmt, nkIfExpr: analyseIf(c, n)
@@ -377,28 +382,28 @@ proc analyse(c: var AnalysisCtx; n: PNode) =
     else:
       # loop may never execute:
       let oldState = c.locals.len
-      let oldFacts = c.guards.len
-      addFact(c.guards, canon(n.sons[0]))
+      let oldFacts = c.guards.s.len
+      addFact(c.guards, canon(n.sons[0], c.guards.o))
       analyse(c, n.sons[1])
       setLen(c.locals, oldState)
-      setLen(c.guards, oldFacts)
+      setLen(c.guards.s, oldFacts)
       # we know after the loop the negation holds:
       if not hasSubnodeWith(n.sons[1], nkBreakStmt):
-        addFactNeg(c.guards, canon(n.sons[0]))
+        addFactNeg(c.guards, canon(n.sons[0], c.guards.o))
     dec c.inLoop
   of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
-      nkMacroDef, nkTemplateDef, nkConstSection, nkPragma:
+      nkMacroDef, nkTemplateDef, nkConstSection, nkPragma, nkFuncDef:
     discard
   else:
     analyseSons(c, n)
 
-proc transformSlices(n: PNode): PNode =
+proc transformSlices(g: ModuleGraph; n: PNode): PNode =
   if n.kind in nkCallKinds and n[0].kind == nkSym:
     let op = n[0].sym
     if op.name.s == "[]" and op.fromSystem:
       result = copyNode(n)
-      let opSlice = newSymNode(createMagic("slice", mSlice))
-      opSlice.typ = getSysType(tyInt)
+      let opSlice = newSymNode(createMagic(g, "slice", mSlice))
+      opSlice.typ = getSysType(g, n.info, tyInt)
       result.add opSlice
       result.add n[1]
       let slice = n[2].skipStmtList
@@ -407,50 +412,50 @@ proc transformSlices(n: PNode): PNode =
       return result
   if n.safeLen > 0:
     result = shallowCopy(n)
-    for i in 0 .. < n.len:
-      result.sons[i] = transformSlices(n.sons[i])
+    for i in 0 ..< n.len:
+      result.sons[i] = transformSlices(g, n.sons[i])
   else:
     result = n
 
-proc transformSpawn(owner: PSym; n, barrier: PNode): PNode
-proc transformSpawnSons(owner: PSym; n, barrier: PNode): PNode =
+proc transformSpawn(g: ModuleGraph; owner: PSym; n, barrier: PNode): PNode
+proc transformSpawnSons(g: ModuleGraph; owner: PSym; n, barrier: PNode): PNode =
   result = shallowCopy(n)
-  for i in 0 .. < n.len:
-    result.sons[i] = transformSpawn(owner, n.sons[i], barrier)
+  for i in 0 ..< n.len:
+    result.sons[i] = transformSpawn(g, owner, n.sons[i], barrier)
 
-proc transformSpawn(owner: PSym; n, barrier: PNode): PNode =
+proc transformSpawn(g: ModuleGraph; owner: PSym; n, barrier: PNode): PNode =
   case n.kind
   of nkVarSection, nkLetSection:
     result = nil
     for it in n:
       let b = it.lastSon
       if getMagic(b) == mSpawn:
-        if it.len != 3: localError(it.info, "invalid context for 'spawn'")
-        let m = transformSlices(b)
+        if it.len != 3: localError(g.config, it.info, "invalid context for 'spawn'")
+        let m = transformSlices(g, b)
         if result.isNil:
           result = newNodeI(nkStmtList, n.info)
           result.add n
         let t = b[1][0].typ.sons[0]
         if spawnResult(t, true) == srByVar:
-          result.add wrapProcForSpawn(owner, m, b.typ, barrier, it[0])
-          it.sons[it.len-1] = emptyNode
+          result.add wrapProcForSpawn(g, owner, m, b.typ, barrier, it[0])
+          it.sons[it.len-1] = newNodeI(nkEmpty, it.info)
         else:
-          it.sons[it.len-1] = wrapProcForSpawn(owner, m, b.typ, barrier, nil)
+          it.sons[it.len-1] = wrapProcForSpawn(g, owner, m, b.typ, barrier, nil)
     if result.isNil: result = n
   of nkAsgn, nkFastAsgn:
     let b = n[1]
     if getMagic(b) == mSpawn and (let t = b[1][0].typ.sons[0];
         spawnResult(t, true) == srByVar):
-      let m = transformSlices(b)
-      return wrapProcForSpawn(owner, m, b.typ, barrier, n[0])
-    result = transformSpawnSons(owner, n, barrier)
+      let m = transformSlices(g, b)
+      return wrapProcForSpawn(g, owner, m, b.typ, barrier, n[0])
+    result = transformSpawnSons(g, owner, n, barrier)
   of nkCallKinds:
     if getMagic(n) == mSpawn:
-      result = transformSlices(n)
-      return wrapProcForSpawn(owner, result, n.typ, barrier, nil)
-    result = transformSpawnSons(owner, n, barrier)
+      result = transformSlices(g, n)
+      return wrapProcForSpawn(g, owner, result, n.typ, barrier, nil)
+    result = transformSpawnSons(g, owner, n, barrier)
   elif n.safeLen > 0:
-    result = transformSpawnSons(owner, n, barrier)
+    result = transformSpawnSons(g, owner, n, barrier)
   else:
     result = n
 
@@ -460,7 +465,7 @@ proc checkArgs(a: var AnalysisCtx; n: PNode) =
 proc generateAliasChecks(a: AnalysisCtx; result: PNode) =
   discard "too implement"
 
-proc liftParallel*(owner: PSym; n: PNode): PNode =
+proc liftParallel*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
   # this needs to be called after the 'for' loop elimination
 
   # first pass:
@@ -469,17 +474,17 @@ proc liftParallel*(owner: PSym; n: PNode): PNode =
   # - detect used arguments
   #echo "PAR ", renderTree(n)
 
-  var a = initAnalysisCtx()
+  var a = initAnalysisCtx(g)
   let body = n.lastSon
   analyse(a, body)
   if a.spawns == 0:
-    localError(n.info, "'parallel' section without 'spawn'")
+    localError(g.config, n.info, "'parallel' section without 'spawn'")
   checkSlicesAreDisjoint(a)
   checkArgs(a, body)
 
   var varSection = newNodeI(nkVarSection, n.info)
-  var temp = newSym(skTemp, getIdent"barrier", owner, n.info)
-  temp.typ = magicsys.getCompilerProc("Barrier").typ
+  var temp = newSym(skTemp, getIdent(g.cache, "barrier"), owner, n.info)
+  temp.typ = magicsys.getCompilerProc(g, "Barrier").typ
   incl(temp.flags, sfFromGeneric)
   let tempNode = newSymNode(temp)
   varSection.addVar tempNode
@@ -488,6 +493,6 @@ proc liftParallel*(owner: PSym; n: PNode): PNode =
   result = newNodeI(nkStmtList, n.info)
   generateAliasChecks(a, result)
   result.add varSection
-  result.add callCodegenProc("openBarrier", barrier)
-  result.add transformSpawn(owner, body, barrier)
-  result.add callCodegenProc("closeBarrier", barrier)
+  result.add callCodegenProc(g, "openBarrier", barrier.info, barrier)
+  result.add transformSpawn(g, owner, body, barrier)
+  result.add callCodegenProc(g, "closeBarrier", barrier.info, barrier)
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 8193a3537..75dea069f 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -9,25 +9,20 @@
 
 import
   intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
-  wordrecg, strutils, options, guards, writetracking
+  wordrecg, strutils, options, guards, lineinfos, semfold,
+  modulegraphs
+
+when not defined(leanCompiler):
+  import writetracking
+
+when defined(useDfa):
+  import dfa
 
 # Second semantic checking pass over the AST. Necessary because the old
 # way had some inherent problems. Performs:
 #
 # * effect+exception tracking
 # * "usage before definition" checking
-# * checks for invalid usages of compiletime magics (not implemented)
-# * checks for invalid usages of NimNode (not implemented)
-# * later: will do an escape analysis for closures at least
-
-# Predefined effects:
-#   io, time (time dependent), gc (performs GC'ed allocation), exceptions,
-#   side effect (accesses global), store (stores into *type*),
-#   store_unknown (performs some store) --> store(any)|store(x)
-#   load (loads from *type*), recursive (recursive call), unsafe,
-#   endless (has endless loops), --> user effects are defined over *patterns*
-#   --> a TR macro can annotate the proc with user defined annotations
-#   --> the effect system can access these
 
 # ------------------------ exception and tag tracking -------------------------
 
@@ -56,11 +51,15 @@ type
     tags: PNode # list of tags
     bottom, inTryStmt: int
     owner: PSym
+    owner_module: PSym
     init: seq[int] # list of initialized variables
     guards: TModel # nested guards
     locked: seq[PNode] # locked locations
     gcUnsafe, isRecursive, isToplevel, hasSideEffect, inEnforcedGcSafe: bool
+    inEnforcedNoSideEffects: bool
     maxLockLevel, currLockLevel: TLockLevel
+    config: ConfigRef
+    graph: ModuleGraph
   PEffects = var TEffects
 
 proc `<`(a, b: TLockLevel): bool {.borrow.}
@@ -69,7 +68,8 @@ proc `==`(a, b: TLockLevel): bool {.borrow.}
 proc max(a, b: TLockLevel): TLockLevel {.borrow.}
 
 proc isLocalVar(a: PEffects, s: PSym): bool =
-  s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner
+  s.kind in {skVar, skResult} and sfGlobal notin s.flags and
+    s.owner == a.owner and s.typ != nil
 
 proc getLockLevel(t: PType): TLockLevel =
   var t = t
@@ -80,24 +80,23 @@ proc getLockLevel(t: PType): TLockLevel =
 
 proc lockLocations(a: PEffects; pragma: PNode) =
   if pragma.kind != nkExprColonExpr:
-    localError(pragma.info, errGenerated, "locks pragma without argument")
+    localError(a.config, pragma.info, "locks pragma without argument")
     return
   var firstLL = TLockLevel(-1'i16)
   for x in pragma[1]:
     let thisLL = getLockLevel(x.typ)
     if thisLL != 0.TLockLevel:
       if thisLL < 0.TLockLevel or thisLL > MaxLockLevel.TLockLevel:
-        localError(x.info, "invalid lock level: " & $thisLL)
+        localError(a.config, x.info, "invalid lock level: " & $thisLL)
       elif firstLL < 0.TLockLevel: firstLL = thisLL
       elif firstLL != thisLL:
-        localError(x.info, errGenerated,
+        localError(a.config, x.info,
           "multi-lock requires the same static lock level for every operand")
       a.maxLockLevel = max(a.maxLockLevel, firstLL)
     a.locked.add x
   if firstLL >= 0.TLockLevel and firstLL != a.currLockLevel:
     if a.currLockLevel > 0.TLockLevel and a.currLockLevel <= firstLL:
-      localError(pragma.info, errGenerated,
-        "invalid nested locking")
+      localError(a.config, pragma.info, "invalid nested locking")
     a.currLockLevel = firstLL
 
 proc guardGlobal(a: PEffects; n: PNode; guard: PSym) =
@@ -110,7 +109,7 @@ proc guardGlobal(a: PEffects; n: PNode; guard: PSym) =
   #  message(n.info, warnUnguardedAccess, renderTree(n))
   #else:
   if not a.isTopLevel:
-    localError(n.info, errGenerated, "unguarded access: " & renderTree(n))
+    localError(a.config, n.info, "unguarded access: " & renderTree(n))
 
 # 'guard*' are checks which are concerned with 'guard' annotations
 # (var x{.guard: y.}: int)
@@ -133,7 +132,7 @@ proc guardDotAccess(a: PEffects; n: PNode) =
         if ty == nil: break
         ty = ty.skipTypes(skipPtrs)
     if field == nil:
-      localError(n.info, errGenerated, "invalid guard field: " & g.name.s)
+      localError(a.config, n.info, "invalid guard field: " & g.name.s)
       return
     g = field
     #ri.sym.guard = field
@@ -146,13 +145,13 @@ proc guardDotAccess(a: PEffects; n: PNode) =
     for L in a.locked:
       #if a.guards.sameSubexprs(dot, L): return
       if guards.sameTree(dot, L): return
-    localError(n.info, errGenerated, "unguarded access: " & renderTree(n))
+    localError(a.config, n.info, "unguarded access: " & renderTree(n))
   else:
     guardGlobal(a, n, g)
 
 proc makeVolatile(a: PEffects; s: PSym) {.inline.} =
   template compileToCpp(a): untyped =
-    gCmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags
+    a.config.cmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags
   if a.inTryStmt > 0 and not compileToCpp(a):
     incl(s.flags, sfVolatile)
 
@@ -175,9 +174,9 @@ proc initVarViaNew(a: PEffects, n: PNode) =
   elif isLocalVar(a, s):
     makeVolatile(a, s)
 
-proc warnAboutGcUnsafe(n: PNode) =
+proc warnAboutGcUnsafe(n: PNode; conf: ConfigRef) =
   #assert false
-  message(n.info, warnGcUnsafe, renderTree(n))
+  message(conf, n.info, warnGcUnsafe, renderTree(n))
 
 proc markGcUnsafe(a: PEffects; reason: PSym) =
   if not a.inEnforcedGcSafe:
@@ -191,54 +190,53 @@ proc markGcUnsafe(a: PEffects; reason: PNode) =
       if reason.kind == nkSym:
         a.owner.gcUnsafetyReason = reason.sym
       else:
-        a.owner.gcUnsafetyReason = newSym(skUnknown, getIdent("<unknown>"),
-                                          a.owner, reason.info)
+        a.owner.gcUnsafetyReason = newSym(skUnknown, a.owner.name,
+                                          a.owner, reason.info, {})
 
 when true:
   template markSideEffect(a: PEffects; reason: typed) =
-    a.hasSideEffect = true
+    if not a.inEnforcedNoSideEffects: a.hasSideEffect = true
 else:
   template markSideEffect(a: PEffects; reason: typed) =
-    a.hasSideEffect = true
+    if not a.inEnforcedNoSideEffects: a.hasSideEffect = true
     markGcUnsafe(a, reason)
 
-proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet) =
+proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet; conf: ConfigRef) =
   let u = s.gcUnsafetyReason
   if u != nil and not cycleCheck.containsOrIncl(u.id):
     let msgKind = if onlyWarning: warnGcUnsafe2 else: errGenerated
     case u.kind
     of skLet, skVar:
-      message(s.info, msgKind,
+      message(conf, s.info, msgKind,
         ("'$#' is not GC-safe as it accesses '$#'" &
         " which is a global using GC'ed memory") % [s.name.s, u.name.s])
     of routineKinds:
       # recursive call *always* produces only a warning so the full error
       # message is printed:
-      listGcUnsafety(u, true, cycleCheck)
-      message(s.info, msgKind,
+      listGcUnsafety(u, true, cycleCheck, conf)
+      message(conf, s.info, msgKind,
         "'$#' is not GC-safe as it calls '$#'" %
         [s.name.s, u.name.s])
-    of skParam:
-      message(s.info, msgKind,
+    of skParam, skForVar:
+      message(conf, s.info, msgKind,
         "'$#' is not GC-safe as it performs an indirect call via '$#'" %
         [s.name.s, u.name.s])
     else:
-      internalAssert u.kind == skUnknown
-      message(u.info, msgKind,
+      message(conf, u.info, msgKind,
         "'$#' is not GC-safe as it performs an indirect call here" % s.name.s)
 
-proc listGcUnsafety(s: PSym; onlyWarning: bool) =
+proc listGcUnsafety(s: PSym; onlyWarning: bool; conf: ConfigRef) =
   var cycleCheck = initIntSet()
-  listGcUnsafety(s, onlyWarning, cycleCheck)
+  listGcUnsafety(s, onlyWarning, cycleCheck, conf)
 
 proc useVar(a: PEffects, n: PNode) =
   let s = n.sym
   if isLocalVar(a, s):
     if s.id notin a.init:
       if {tfNeedsInit, tfNotNil} * s.typ.flags != {}:
-        message(n.info, warnProveInit, s.name.s)
+        message(a.config, n.info, warnProveInit, s.name.s)
       else:
-        message(n.info, warnUninit, s.name.s)
+        message(a.config, n.info, warnUninit, s.name.s)
       # prevent superfluous warnings about the same variable:
       a.init.add s.id
   if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
@@ -248,6 +246,7 @@ proc useVar(a: PEffects, n: PNode) =
         (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
       #if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
       markGcUnsafe(a, s)
+      markSideEffect(a, s)
     else:
       markSideEffect(a, s)
 
@@ -256,7 +255,7 @@ type
   TIntersection = seq[tuple[id, count: int]] # a simple count table
 
 proc addToIntersection(inter: var TIntersection, s: int) =
-  for j in 0.. <inter.len:
+  for j in 0..<inter.len:
     if s == inter[j].id:
       inc inter[j].count
       return
@@ -265,60 +264,56 @@ proc addToIntersection(inter: var TIntersection, s: int) =
 proc throws(tracked, n: PNode) =
   if n.typ == nil or n.typ.kind != tyError: tracked.add n
 
-proc getEbase(): PType =
-  result = if getCompilerProc("Exception") != nil: sysTypeFromName"Exception"
-           else: sysTypeFromName"E_Base"
+proc getEbase(g: ModuleGraph; info: TLineInfo): PType =
+  result = g.sysTypeFromName(info, "Exception")
 
-proc excType(n: PNode): PType =
+proc excType(g: ModuleGraph; n: PNode): PType =
   # reraise is like raising E_Base:
-  let t = if n.kind == nkEmpty or n.typ.isNil: getEbase() else: n.typ
+  let t = if n.kind == nkEmpty or n.typ.isNil: getEbase(g, n.info) else: n.typ
   result = skipTypes(t, skipPtrs)
 
-proc createRaise(n: PNode): PNode =
+proc createRaise(g: ModuleGraph; n: PNode): PNode =
   result = newNode(nkType)
-  result.typ = getEbase()
+  result.typ = getEbase(g, n.info)
   if not n.isNil: result.info = n.info
 
-proc createTag(n: PNode): PNode =
+proc createTag(g: ModuleGraph; n: PNode): PNode =
   result = newNode(nkType)
-  if getCompilerProc("RootEffect") != nil:
-    result.typ = sysTypeFromName"RootEffect"
-  else:
-    result.typ = sysTypeFromName"TEffect"
+  result.typ = g.sysTypeFromName(n.info, "RootEffect")
   if not n.isNil: result.info = n.info
 
 proc addEffect(a: PEffects, e: PNode, useLineInfo=true) =
   assert e.kind != nkRaiseStmt
   var aa = a.exc
-  for i in a.bottom .. <aa.len:
-    if sameType(aa[i].excType, e.excType):
-      if not useLineInfo or gCmd == cmdDoc: return
+  for i in a.bottom ..< aa.len:
+    if sameType(a.graph.excType(aa[i]), a.graph.excType(e)):
+      if not useLineInfo or a.config.cmd == cmdDoc: return
       elif aa[i].info == e.info: return
   throws(a.exc, e)
 
 proc addTag(a: PEffects, e: PNode, useLineInfo=true) =
   var aa = a.tags
-  for i in 0 .. <aa.len:
+  for i in 0 ..< aa.len:
     if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)):
-      if not useLineInfo or gCmd == cmdDoc: return
+      if not useLineInfo or a.config.cmd == cmdDoc: return
       elif aa[i].info == e.info: return
   throws(a.tags, e)
 
 proc mergeEffects(a: PEffects, b, comesFrom: PNode) =
   if b.isNil:
-    addEffect(a, createRaise(comesFrom))
+    addEffect(a, createRaise(a.graph, comesFrom))
   else:
     for effect in items(b): addEffect(a, effect, useLineInfo=comesFrom != nil)
 
 proc mergeTags(a: PEffects, b, comesFrom: PNode) =
   if b.isNil:
-    addTag(a, createTag(comesFrom))
+    addTag(a, createTag(a.graph, comesFrom))
   else:
     for effect in items(b): addTag(a, effect, useLineInfo=comesFrom != nil)
 
 proc listEffects(a: PEffects) =
-  for e in items(a.exc):  message(e.info, hintUser, typeToString(e.typ))
-  for e in items(a.tags): message(e.info, hintUser, typeToString(e.typ))
+  for e in items(a.exc):  message(a.config, e.info, hintUser, typeToString(e.typ))
+  for e in items(a.tags): message(a.config, e.info, hintUser, typeToString(e.typ))
   #if a.maxLockLevel != 0:
   #  message(e.info, hintUser, "lockLevel: " & a.maxLockLevel)
 
@@ -328,18 +323,18 @@ proc catches(tracked: PEffects, e: PType) =
   var i = tracked.bottom
   while i < L:
     # r supertype of e?
-    if safeInheritanceDiff(tracked.exc[i].excType, e) <= 0:
+    if safeInheritanceDiff(tracked.graph.excType(tracked.exc[i]), e) <= 0:
       tracked.exc.sons[i] = tracked.exc.sons[L-1]
       dec L
     else:
       inc i
-  if not isNil(tracked.exc.sons):
+  if tracked.exc.len > 0:
     setLen(tracked.exc.sons, L)
   else:
     assert L == 0
 
 proc catchesAll(tracked: PEffects) =
-  if not isNil(tracked.exc.sons):
+  if tracked.exc.len > 0:
     setLen(tracked.exc.sons, tracked.bottom)
 
 proc track(tracked: PEffects, n: PNode)
@@ -353,12 +348,12 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
   inc tracked.inTryStmt
   track(tracked, n.sons[0])
   dec tracked.inTryStmt
-  for i in oldState.. <tracked.init.len:
+  for i in oldState..<tracked.init.len:
     addToIntersection(inter, tracked.init[i])
 
   var branches = 1
   var hasFinally = false
-  for i in 1 .. < n.len:
+  for i in 1 ..< n.len:
     let b = n.sons[i]
     let blen = sonsLen(b)
     if b.kind == nkExceptBranch:
@@ -367,12 +362,15 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
         catchesAll(tracked)
       else:
         for j in countup(0, blen - 2):
-          assert(b.sons[j].kind == nkType)
-          catches(tracked, b.sons[j].typ)
-
+          if b.sons[j].isInfixAs():
+            assert(b.sons[j][1].kind == nkType)
+            catches(tracked, b.sons[j][1].typ)
+          else:
+            assert(b.sons[j].kind == nkType)
+            catches(tracked, b.sons[j].typ)
       setLen(tracked.init, oldState)
       track(tracked, b.sons[blen-1])
-      for i in oldState.. <tracked.init.len:
+      for i in oldState..<tracked.init.len:
         addToIntersection(inter, tracked.init[i])
     else:
       assert b.kind == nkFinally
@@ -417,7 +415,7 @@ proc effectSpec(n: PNode, effectType: TSpecialWord): PNode =
         result.add(it.sons[1])
       return
 
-proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
+proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
   let spec = effectSpec(x, effectType)
   if isNil(spec):
     let s = n.sons[namePos].sym
@@ -428,17 +426,17 @@ proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
 
     # warning: hack ahead:
     var effects = newNodeI(nkBracket, n.info, real.len)
-    for i in 0 .. <real.len:
+    for i in 0 ..< real.len:
       var t = typeToString(real[i].typ)
       if t.startsWith("ref "): t = substr(t, 4)
-      effects.sons[i] = newIdentNode(getIdent(t), n.info)
+      effects.sons[i] = newIdentNode(getIdent(cache, t), n.info)
       # set the type so that the following analysis doesn't screw up:
       effects.sons[i].typ = real[i].typ
 
     result = newNode(nkExprColonExpr, n.info, @[
-      newIdentNode(getIdent(specialWords[effectType]), n.info), effects])
+      newIdentNode(getIdent(cache, specialWords[effectType]), n.info), effects])
 
-proc documentWriteEffect(n: PNode; flag: TSymFlag; pragmaName: string): PNode =
+proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode =
   let s = n.sons[namePos].sym
   let params = s.typ.n
 
@@ -449,21 +447,21 @@ proc documentWriteEffect(n: PNode; flag: TSymFlag; pragmaName: string): PNode =
 
   if effects.len > 0:
     result = newNode(nkExprColonExpr, n.info, @[
-      newIdentNode(getIdent(pragmaName), n.info), effects])
+      newIdentNode(getIdent(cache, pragmaName), n.info), effects])
 
-proc documentNewEffect(n: PNode): PNode =
+proc documentNewEffect(cache: IdentCache; n: PNode): PNode =
   let s = n.sons[namePos].sym
   if tfReturnsNew in s.typ.flags:
-    result = newIdentNode(getIdent("new"), n.info)
+    result = newIdentNode(getIdent(cache, "new"), n.info)
 
-proc documentRaises*(n: PNode) =
+proc documentRaises*(cache: IdentCache; n: PNode) =
   if n.sons[namePos].kind != nkSym: return
   let pragmas = n.sons[pragmasPos]
-  let p1 = documentEffect(n, pragmas, wRaises, exceptionEffects)
-  let p2 = documentEffect(n, pragmas, wTags, tagEffects)
-  let p3 = documentWriteEffect(n, sfWrittenTo, "writes")
-  let p4 = documentNewEffect(n)
-  let p5 = documentWriteEffect(n, sfEscapes, "escapes")
+  let p1 = documentEffect(cache, n, pragmas, wRaises, exceptionEffects)
+  let p2 = documentEffect(cache, n, pragmas, wTags, tagEffects)
+  let p3 = documentWriteEffect(cache, n, sfWrittenTo, "writes")
+  let p4 = documentNewEffect(cache, n)
+  let p5 = documentWriteEffect(cache, n, sfEscapes, "escapes")
 
   if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil:
     if pragmas.kind == nkEmpty:
@@ -494,7 +492,7 @@ proc mergeLockLevels(tracked: PEffects, n: PNode, lockLevel: TLockLevel) =
   if lockLevel >= tracked.currLockLevel:
     # if in lock section:
     if tracked.currLockLevel > 0.TLockLevel:
-      localError n.info, errGenerated,
+      localError tracked.config, n.info, errGenerated,
         "expected lock level < " & $tracked.currLockLevel &
         " but got lock level " & $lockLevel
     tracked.maxLockLevel = max(tracked.maxLockLevel, lockLevel)
@@ -508,34 +506,47 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
   mergeTags(tracked, tagSpec, n)
 
   if notGcSafe(s.typ) and sfImportc notin s.flags:
-    if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
+    if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
     markGcUnsafe(tracked, s)
   if tfNoSideEffect notin s.typ.flags:
     markSideEffect(tracked, s)
   mergeLockLevels(tracked, n, s.getLockLevel)
 
+proc procVarcheck(n: PNode; conf: ConfigRef) =
+  if n.kind in nkSymChoices:
+    for x in n: procVarCheck(x, conf)
+  elif n.kind == nkSym and n.sym.magic != mNone and n.sym.kind in routineKinds:
+    localError(conf, n.info, "'$1' cannot be passed to a procvar" % n.sym.name.s)
+
 proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
   let n = n.skipConv
+  if paramType.isNil or paramType.kind != tyTypeDesc:
+    procVarcheck skipConvAndClosure(n), tracked.config
+  #elif n.kind in nkSymChoices:
+  #  echo "came here"
+  let paramType = paramType.skipTypesOrNil(abstractInst)
   if paramType != nil and tfNotNil in paramType.flags and
       n.typ != nil and tfNotNil notin n.typ.flags:
     if n.kind == nkAddr:
       # addr(x[]) can't be proven, but addr(x) can:
       if not containsNode(n, {nkDerefExpr, nkHiddenDeref}): return
     elif (n.kind == nkSym and n.sym.kind in routineKinds) or
-         n.kind in procDefs+{nkObjConstr, nkBracket}:
+         (n.kind in procDefs+{nkObjConstr, nkBracket, nkClosure, nkStrLit..nkTripleStrLit}) or
+         (n.kind in nkCallKinds and n[0].kind == nkSym and n[0].sym.magic == mArrToSeq):
       # 'p' is not nil obviously:
       return
     case impliesNotNil(tracked.guards, n)
     of impUnknown:
-      message(n.info, errGenerated,
+      message(tracked.config, n.info, errGenerated,
               "cannot prove '$1' is not nil" % n.renderTree)
     of impNo:
-      message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
+      message(tracked.config, n.info, errGenerated,
+              "'$1' is provably nil" % n.renderTree)
     of impYes: discard
 
 proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) =
-  addEffect(tracked, createRaise(n))
-  addTag(tracked, createTag(n))
+  addEffect(tracked, createRaise(tracked.graph, n))
+  addTag(tracked, createTag(tracked.graph, n))
   let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel
                   else: op.lockLevel
   #if lockLevel == UnknownLockLevel:
@@ -546,16 +557,22 @@ proc isOwnedProcVar(n: PNode; owner: PSym): bool =
   # XXX prove the soundness of this effect system rule
   result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner
 
+proc isNoEffectList(n: PNode): bool {.inline.} =
+  assert n.kind == nkEffectList
+  n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil)
+
 proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
   let a = skipConvAndClosure(n)
   let op = a.typ
   if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit:
-    internalAssert op.n.sons[0].kind == nkEffectList
+    internalAssert tracked.config, op.n.sons[0].kind == nkEffectList
     var effectList = op.n.sons[0]
-    let s = n.skipConv
-    if s.kind == nkSym and s.sym.kind in routineKinds:
+    var s = n.skipConv
+    if s.kind == nkCast and s[1].typ.kind == tyProc:
+      s = s[1]
+    if s.kind == nkSym and s.sym.kind in routineKinds and isNoEffectList(effectList):
       propagateEffects(tracked, n, s.sym)
-    elif effectList.len == 0:
+    elif isNoEffectList(effectList):
       if isForwardedProc(n):
         # we have no explicit effects but it's a forward declaration and so it's
         # stated there are no additional effects, so simply propagate them:
@@ -565,7 +582,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
         assumeTheWorst(tracked, n, op)
       # assume GcUnsafe unless in its type; 'forward' does not matter:
       if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner):
-        if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
+        if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
         markGcUnsafe(tracked, a)
       elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
         markSideEffect(tracked, a)
@@ -573,48 +590,51 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
       mergeEffects(tracked, effectList.sons[exceptionEffects], n)
       mergeTags(tracked, effectList.sons[tagEffects], n)
       if notGcSafe(op):
-        if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
+        if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
         markGcUnsafe(tracked, a)
       elif tfNoSideEffect notin op.flags:
         markSideEffect(tracked, a)
   if paramType != nil and paramType.kind == tyVar:
     if n.kind == nkSym and isLocalVar(tracked, n.sym):
       makeVolatile(tracked, n.sym)
+  if paramType != nil and paramType.kind == tyProc and tfGcSafe in paramType.flags:
+    let argtype = skipTypes(a.typ, abstractInst)
+    # XXX figure out why this can be a non tyProc here. See httpclient.nim for an
+    # example that triggers it.
+    if argtype.kind == tyProc and notGcSafe(argtype) and not tracked.inEnforcedGcSafe:
+      localError(tracked.config, n.info, $n & " is not GC safe")
   notNilCheck(tracked, n, paramType)
 
 proc breaksBlock(n: PNode): bool =
-  case n.kind
-  of nkStmtList, nkStmtListExpr:
-    for c in n:
-      if breaksBlock(c): return true
-  of nkBreakStmt, nkReturnStmt, nkRaiseStmt:
-    return true
-  of nkCallKinds:
-    if n.sons[0].kind == nkSym and sfNoReturn in n.sons[0].sym.flags:
-      return true
-  else:
-    discard
+  # sematic check doesn't allow statements after raise, break, return or
+  # call to noreturn proc, so it is safe to check just the last statements
+  var it = n
+  while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0:
+    it = it.lastSon
+
+  result = it.kind in {nkBreakStmt, nkReturnStmt, nkRaiseStmt} or
+    it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags
 
 proc trackCase(tracked: PEffects, n: PNode) =
   track(tracked, n.sons[0])
   let oldState = tracked.init.len
-  let oldFacts = tracked.guards.len
+  let oldFacts = tracked.guards.s.len
   let stringCase = skipTypes(n.sons[0].typ,
         abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString}
   let interesting = not stringCase and interestingCaseExpr(n.sons[0]) and
-        warnProveField in gNotes
+        warnProveField in tracked.config.notes
   var inter: TIntersection = @[]
   var toCover = 0
-  for i in 1.. <n.len:
+  for i in 1..<n.len:
     let branch = n.sons[i]
     setLen(tracked.init, oldState)
     if interesting:
-      setLen(tracked.guards, oldFacts)
+      setLen(tracked.guards.s, oldFacts)
       addCaseBranchFacts(tracked.guards, n, i)
-    for i in 0 .. <branch.len:
+    for i in 0 ..< branch.len:
       track(tracked, branch.sons[i])
     if not breaksBlock(branch.lastSon): inc toCover
-    for i in oldState.. <tracked.init.len:
+    for i in oldState..<tracked.init.len:
       addToIntersection(inter, tracked.init[i])
 
   setLen(tracked.init, oldState)
@@ -622,11 +642,11 @@ proc trackCase(tracked: PEffects, n: PNode) =
     for id, count in items(inter):
       if count >= toCover: tracked.init.add id
     # else we can't merge
-  setLen(tracked.guards, oldFacts)
+  setLen(tracked.guards.s, oldFacts)
 
 proc trackIf(tracked: PEffects, n: PNode) =
   track(tracked, n.sons[0].sons[0])
-  let oldFacts = tracked.guards.len
+  let oldFacts = tracked.guards.s.len
   addFact(tracked.guards, n.sons[0].sons[0])
   let oldState = tracked.init.len
 
@@ -634,33 +654,33 @@ proc trackIf(tracked: PEffects, n: PNode) =
   var toCover = 0
   track(tracked, n.sons[0].sons[1])
   if not breaksBlock(n.sons[0].sons[1]): inc toCover
-  for i in oldState.. <tracked.init.len:
+  for i in oldState..<tracked.init.len:
     addToIntersection(inter, tracked.init[i])
 
-  for i in 1.. <n.len:
+  for i in 1..<n.len:
     let branch = n.sons[i]
-    setLen(tracked.guards, oldFacts)
+    setLen(tracked.guards.s, oldFacts)
     for j in 0..i-1:
       addFactNeg(tracked.guards, n.sons[j].sons[0])
     if branch.len > 1:
       addFact(tracked.guards, branch.sons[0])
     setLen(tracked.init, oldState)
-    for i in 0 .. <branch.len:
+    for i in 0 ..< branch.len:
       track(tracked, branch.sons[i])
     if not breaksBlock(branch.lastSon): inc toCover
-    for i in oldState.. <tracked.init.len:
+    for i in oldState..<tracked.init.len:
       addToIntersection(inter, tracked.init[i])
   setLen(tracked.init, oldState)
   if lastSon(n).len == 1:
     for id, count in items(inter):
       if count >= toCover: tracked.init.add id
     # else we can't merge as it is not exhaustive
-  setLen(tracked.guards, oldFacts)
+  setLen(tracked.guards.s, oldFacts)
 
 proc trackBlock(tracked: PEffects, n: PNode) =
   if n.kind in {nkStmtList, nkStmtListExpr}:
     var oldState = -1
-    for i in 0.. <n.len:
+    for i in 0..<n.len:
       if hasSubnodeWith(n.sons[i], nkBreakStmt):
         # block:
         #   x = def
@@ -683,7 +703,7 @@ proc paramType(op: PType, i: int): PType =
 proc cstringCheck(tracked: PEffects; n: PNode) =
   if n.sons[0].typ.kind == tyCString and (let a = skipConv(n[1]);
       a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
-    message(n.info, warnUnsafeCode, renderTree(n))
+    message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
 
 proc track(tracked: PEffects, n: PNode) =
   case n.kind
@@ -693,12 +713,16 @@ proc track(tracked: PEffects, n: PNode) =
     n.sons[0].info = n.info
     #throws(tracked.exc, n.sons[0])
     addEffect(tracked, n.sons[0], useLineInfo=false)
-    for i in 0 .. <safeLen(n):
+    for i in 0 ..< safeLen(n):
       track(tracked, n.sons[i])
   of nkCallKinds:
+    if getConstExpr(tracked.owner_module, n, tracked.graph) != nil:
+      return
     # p's effects are ours too:
-    let a = n.sons[0]
+    var a = n.sons[0]
     let op = a.typ
+    if a.kind == nkCast and a[1].typ.kind == tyProc:
+      a = a[1]
     # XXX: in rare situations, templates and macros will reach here after
     # calling getAst(templateOrMacro()). Currently, templates and macros
     # are indistinguishable from normal procs (both have tyProc type) and
@@ -714,7 +738,7 @@ proc track(tracked: PEffects, n: PNode) =
       var effectList = op.n.sons[0]
       if a.kind == nkSym and a.sym.kind == skMethod:
         propagateEffects(tracked, n, a.sym)
-      elif effectList.len == 0:
+      elif isNoEffectList(effectList):
         if isForwardedProc(a):
           propagateEffects(tracked, n, a.sym)
         elif isIndirectCall(a, tracked.owner):
@@ -725,33 +749,34 @@ proc track(tracked: PEffects, n: PNode) =
         if notGcSafe(op) and not importedFromC(a):
           # and it's not a recursive call:
           if not (a.kind == nkSym and a.sym == tracked.owner):
-            if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
+            if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
             markGcUnsafe(tracked, a)
         if tfNoSideEffect notin op.flags and not importedFromC(a):
           # and it's not a recursive call:
           if not (a.kind == nkSym and a.sym == tracked.owner):
             markSideEffect(tracked, a)
     if a.kind != nkSym or a.sym.magic != mNBindSym:
-      for i in 1 .. <len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
+      for i in 1 ..< len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
     if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
       # may not look like an assignment, but it is:
       let arg = n.sons[1]
       initVarViaNew(tracked, arg)
-      if {tfNeedsInit} * arg.typ.lastSon.flags != {}:
+      if arg.typ.len != 0 and {tfNeedsInit} * arg.typ.lastSon.flags != {}:
         if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and
             n[2].intVal == 0:
           # var s: seq[notnil];  newSeq(s, 0)  is a special case!
           discard
         else:
-          message(arg.info, warnProveInit, $arg)
-    for i in 0 .. <safeLen(n):
+          message(tracked.config, arg.info, warnProveInit, $arg)
+    for i in 0 ..< safeLen(n):
       track(tracked, n.sons[i])
   of nkDotExpr:
     guardDotAccess(tracked, n)
-    for i in 0 .. <len(n): track(tracked, n.sons[i])
+    for i in 0 ..< len(n): track(tracked, n.sons[i])
   of nkCheckedFieldExpr:
     track(tracked, n.sons[0])
-    if warnProveField in gNotes: checkFieldAccess(tracked.guards, n)
+    if warnProveField in tracked.config.notes:
+      checkFieldAccess(tracked.guards, n, tracked.config)
   of nkTryStmt: trackTryStmt(tracked, n)
   of nkPragma: trackPragmaStmt(tracked, n)
   of nkAsgn, nkFastAsgn:
@@ -771,8 +796,21 @@ proc track(tracked: PEffects, n: PNode) =
           initVar(tracked, child.sons[i], volatileCheck=false)
           addAsgnFact(tracked.guards, child.sons[i], last)
           notNilCheck(tracked, last, child.sons[i].typ)
+      elif child.kind == nkVarTuple and last.kind != nkEmpty:
+        for i in 0 .. child.len-2:
+          if child[i].kind == nkEmpty or
+            child[i].kind == nkSym and child[i].sym.name.s == "_":
+            continue
+          initVar(tracked, child[i], volatileCheck=false)
+          if last.kind in {nkPar, nkTupleConstr}:
+            addAsgnFact(tracked.guards, child[i], last[i])
+            notNilCheck(tracked, last[i], child[i].typ)
       # since 'var (a, b): T = ()' is not even allowed, there is always type
       # inference for (a, b) and thus no nil checking is necessary.
+  of nkConstSection:
+    for child in n:
+      let last = lastSon(child)
+      track(tracked, last)
   of nkCaseStmt: trackCase(tracked, n)
   of nkWhen, nkIfStmt, nkIfExpr: trackIf(tracked, n)
   of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1])
@@ -784,73 +822,82 @@ proc track(tracked: PEffects, n: PNode) =
     else:
       # loop may never execute:
       let oldState = tracked.init.len
-      let oldFacts = tracked.guards.len
+      let oldFacts = tracked.guards.s.len
       addFact(tracked.guards, n.sons[0])
       track(tracked, n.sons[1])
       setLen(tracked.init, oldState)
-      setLen(tracked.guards, oldFacts)
+      setLen(tracked.guards.s, oldFacts)
   of nkForStmt, nkParForStmt:
     # we are very conservative here and assume the loop is never executed:
     let oldState = tracked.init.len
-    for i in 0 .. <len(n):
+    for i in 0 ..< len(n):
       track(tracked, n.sons[i])
     setLen(tracked.init, oldState)
   of nkObjConstr:
-    track(tracked, n.sons[0])
-    let oldFacts = tracked.guards.len
-    for i in 1 .. <len(n):
+    when false: track(tracked, n.sons[0])
+    let oldFacts = tracked.guards.s.len
+    for i in 1 ..< len(n):
       let x = n.sons[i]
       track(tracked, x)
       if x.sons[0].kind == nkSym and sfDiscriminant in x.sons[0].sym.flags:
         addDiscriminantFact(tracked.guards, x)
-    setLen(tracked.guards, oldFacts)
+    setLen(tracked.guards.s, oldFacts)
   of nkPragmaBlock:
     let pragmaList = n.sons[0]
     let oldLocked = tracked.locked.len
     let oldLockLevel = tracked.currLockLevel
     var enforcedGcSafety = false
-    for i in 0 .. <pragmaList.len:
+    var enforceNoSideEffects = false
+    for i in 0 ..< pragmaList.len:
       let pragma = whichPragma(pragmaList.sons[i])
       if pragma == wLocks:
         lockLocations(tracked, pragmaList.sons[i])
       elif pragma == wGcSafe:
         enforcedGcSafety = true
+      elif pragma == wNosideeffect:
+        enforceNoSideEffects = true
     if enforcedGcSafety: tracked.inEnforcedGcSafe = true
+    if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
     track(tracked, n.lastSon)
     if enforcedGcSafety: tracked.inEnforcedGcSafe = false
+    if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
     setLen(tracked.locked, oldLocked)
     tracked.currLockLevel = oldLockLevel
   of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
-      nkMacroDef, nkTemplateDef, nkLambda, nkDo:
+      nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
     discard
+  of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv:
+    if n.len == 2: track(tracked, n.sons[1])
+  of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
+    if n.len == 1: track(tracked, n.sons[0])
   else:
-    for i in 0 .. <safeLen(n): track(tracked, n.sons[i])
+    for i in 0 ..< safeLen(n): track(tracked, n.sons[i])
 
-proc subtypeRelation(spec, real: PNode): bool =
-  result = safeInheritanceDiff(real.excType, spec.typ) <= 0
+proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
+  result = safeInheritanceDiff(g.excType(real), spec.typ) <= 0
 
-proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool;
-                     effectPredicate: proc (a, b: PNode): bool {.nimcall.}) =
+proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool;
+                     effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.}) =
   # check that any real exception is listed in 'spec'; mark those as used;
   # report any unused exception
   var used = initIntSet()
   for r in items(real):
     block search:
-      for s in 0 .. <spec.len:
-        if effectPredicate(spec[s], r):
+      for s in 0 ..< spec.len:
+        if effectPredicate(g, spec[s], r):
           used.incl(s)
           break search
       # XXX call graph analysis would be nice here!
-      pushInfoContext(spec.info)
-      localError(r.info, errGenerated, msg & typeToString(r.typ))
-      popInfoContext()
+      pushInfoContext(g.config, spec.info)
+      localError(g.config, r.info, errGenerated, msg & typeToString(r.typ))
+      popInfoContext(g.config)
   # hint about unnecessarily listed exception types:
   if hints:
-    for s in 0 .. <spec.len:
+    for s in 0 ..< spec.len:
       if not used.contains(s):
-        message(spec[s].info, hintXDeclaredButNotUsed, renderTree(spec[s]))
+        message(g.config, spec[s].info, hintXDeclaredButNotUsed, renderTree(spec[s]))
 
-proc checkMethodEffects*(disp, branch: PSym) =
+proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
   ## checks for consistent effects for multi methods.
   let actual = branch.typ.n.sons[0]
   if actual.len != effectListLen: return
@@ -858,98 +905,102 @@ proc checkMethodEffects*(disp, branch: PSym) =
   let p = disp.ast.sons[pragmasPos]
   let raisesSpec = effectSpec(p, wRaises)
   if not isNil(raisesSpec):
-    checkRaisesSpec(raisesSpec, actual.sons[exceptionEffects],
+    checkRaisesSpec(g, raisesSpec, actual.sons[exceptionEffects],
       "can raise an unlisted exception: ", hints=off, subtypeRelation)
   let tagsSpec = effectSpec(p, wTags)
   if not isNil(tagsSpec):
-    checkRaisesSpec(tagsSpec, actual.sons[tagEffects],
+    checkRaisesSpec(g, tagsSpec, actual.sons[tagEffects],
       "can have an unlisted effect: ", hints=off, subtypeRelation)
   if sfThread in disp.flags and notGcSafe(branch.typ):
-    localError(branch.info, "base method is GC-safe, but '$1' is not" %
+    localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
                                 branch.name.s)
   if branch.typ.lockLevel > disp.typ.lockLevel:
     when true:
-      message(branch.info, warnLockLevel,
+      message(g.config, branch.info, warnLockLevel,
         "base method has lock level $1, but dispatcher has $2" %
           [$branch.typ.lockLevel, $disp.typ.lockLevel])
     else:
       # XXX make this an error after bigbreak has been released:
-      localError(branch.info,
+      localError(g.config, branch.info,
         "base method has lock level $1, but dispatcher has $2" %
           [$branch.typ.lockLevel, $disp.typ.lockLevel])
 
-proc setEffectsForProcType*(t: PType, n: PNode) =
-  var effects = t.n.sons[0]
-  internalAssert t.kind == tyProc and effects.kind == nkEffectList
-
-  let
-    raisesSpec = effectSpec(n, wRaises)
-    tagsSpec = effectSpec(n, wTags)
-  if not isNil(raisesSpec) or not isNil(tagsSpec):
-    internalAssert effects.len == 0
+proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
+  var effects = t.n[0]
+  if t.kind != tyProc or effects.kind != nkEffectList: return
+  if n.kind != nkEmpty:
+    internalAssert g.config, effects.len == 0
     newSeq(effects.sons, effectListLen)
+    let raisesSpec = effectSpec(n, wRaises)
     if not isNil(raisesSpec):
-      effects.sons[exceptionEffects] = raisesSpec
+      effects[exceptionEffects] = raisesSpec
+    let tagsSpec = effectSpec(n, wTags)
     if not isNil(tagsSpec):
-      effects.sons[tagEffects] = tagsSpec
+      effects[tagEffects] = tagsSpec
+    effects[pragmasEffects] = n
 
-proc initEffects(effects: PNode; s: PSym; t: var TEffects) =
+proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) =
   newSeq(effects.sons, effectListLen)
   effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info)
   effects.sons[tagEffects] = newNodeI(nkArgList, s.info)
-  effects.sons[usesEffects] = ast.emptyNode
-  effects.sons[writeEffects] = ast.emptyNode
+  effects.sons[usesEffects] = g.emptyNode
+  effects.sons[writeEffects] = g.emptyNode
+  effects.sons[pragmasEffects] = g.emptyNode
 
   t.exc = effects.sons[exceptionEffects]
   t.tags = effects.sons[tagEffects]
   t.owner = s
+  t.owner_module = s.getModule
   t.init = @[]
-  t.guards = @[]
+  t.guards.s = @[]
+  t.guards.o = initOperators(g)
   t.locked = @[]
+  t.graph = g
+  t.config = g.config
 
-proc trackProc*(s: PSym, body: PNode) =
+proc trackProc*(g: ModuleGraph; s: PSym, body: PNode) =
   var effects = s.typ.n.sons[0]
-  internalAssert effects.kind == nkEffectList
+  if effects.kind != nkEffectList: return
   # effects already computed?
   if sfForward in s.flags: return
   if effects.len == effectListLen: return
 
   var t: TEffects
-  initEffects(effects, s, t)
+  initEffects(g, effects, s, t)
   track(t, body)
   if not isEmptyType(s.typ.sons[0]) and
       {tfNeedsInit, tfNotNil} * s.typ.sons[0].flags != {} and
-      s.kind in {skProc, skConverter, skMethod}:
+      s.kind in {skProc, skFunc, skConverter, skMethod}:
     var res = s.ast.sons[resultPos].sym # get result symbol
     if res.id notin t.init:
-      message(body.info, warnProveInit, "result")
+      message(g.config, body.info, warnProveInit, "result")
   let p = s.ast.sons[pragmasPos]
   let raisesSpec = effectSpec(p, wRaises)
   if not isNil(raisesSpec):
-    checkRaisesSpec(raisesSpec, t.exc, "can raise an unlisted exception: ",
+    checkRaisesSpec(g, raisesSpec, t.exc, "can raise an unlisted exception: ",
                     hints=on, subtypeRelation)
     # after the check, use the formal spec:
     effects.sons[exceptionEffects] = raisesSpec
 
   let tagsSpec = effectSpec(p, wTags)
   if not isNil(tagsSpec):
-    checkRaisesSpec(tagsSpec, t.tags, "can have an unlisted effect: ",
+    checkRaisesSpec(g, tagsSpec, t.tags, "can have an unlisted effect: ",
                     hints=off, subtypeRelation)
     # after the check, use the formal spec:
     effects.sons[tagEffects] = tagsSpec
 
   if sfThread in s.flags and t.gcUnsafe:
-    if optThreads in gGlobalOptions and optThreadAnalysis in gGlobalOptions:
+    if optThreads in g.config.globalOptions and optThreadAnalysis in g.config.globalOptions:
       #localError(s.info, "'$1' is not GC-safe" % s.name.s)
-      listGcUnsafety(s, onlyWarning=false)
+      listGcUnsafety(s, onlyWarning=false, g.config)
     else:
-      listGcUnsafety(s, onlyWarning=true)
+      listGcUnsafety(s, onlyWarning=true, g.config)
       #localError(s.info, warnGcUnsafe2, s.name.s)
   if sfNoSideEffect in s.flags and t.hasSideEffect:
     when false:
-      listGcUnsafety(s, onlyWarning=false)
+      listGcUnsafety(s, onlyWarning=false, g.config)
     else:
-      localError(s.info, errXhasSideEffects, s.name.s)
+      localError(g.config, s.info, "'$1' can have side effects" % s.name.s)
   if not t.gcUnsafe:
     s.typ.flags.incl tfGcSafe
   if not t.hasSideEffect and sfSideEffect notin s.flags:
@@ -958,17 +1009,20 @@ proc trackProc*(s: PSym, body: PNode) =
     s.typ.lockLevel = t.maxLockLevel
   elif t.maxLockLevel > s.typ.lockLevel:
     #localError(s.info,
-    message(s.info, warnLockLevel,
+    message(g.config, s.info, warnLockLevel,
       "declared lock level is $1, but real lock level is $2" %
         [$s.typ.lockLevel, $t.maxLockLevel])
-  when useWriteTracking: trackWrites(s, body)
+  when defined(useDfa):
+    if s.kind == skFunc:
+      dataflowAnalysis(s, body)
+      when false: trackWrites(s, body)
 
-proc trackTopLevelStmt*(module: PSym; n: PNode) =
-  if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef,
+proc trackTopLevelStmt*(g: ModuleGraph; module: PSym; n: PNode) =
+  if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
                 nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:
     return
   var effects = newNode(nkEffectList, n.info)
   var t: TEffects
-  initEffects(effects, module, t)
+  initEffects(g, effects, module, t)
   t.isToplevel = true
   track(t, n)
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 9a1850932..aec03b492 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -10,111 +10,105 @@
 ## this module does the semantic checking of statements
 #  included from sem.nim
 
-var enforceVoidContext = PType(kind: tyStmt)
+const
+  errNoSymbolToBorrowFromFound = "no symbol to borrow from found"
+  errDiscardValueX = "value of type '$1' has to be discarded"
+  errInvalidDiscard = "statement returns no value that can be discarded"
+  errInvalidControlFlowX = "invalid control flow: $1"
+  errSelectorMustBeOfCertainTypes = "selector must be of an ordinal type, float or string"
+  errExprCannotBeRaised = "only a 'ref object' can be raised"
+  errBreakOnlyInLoop = "'break' only allowed in loop construct"
+  errExceptionAlreadyHandled = "exception already handled"
+  errYieldNotAllowedHere = "'yield' only allowed in an iterator"
+  errYieldNotAllowedInTryStmt = "'yield' cannot be used within 'try' in a non-inlined iterator"
+  errInvalidNumberOfYieldExpr = "invalid number of 'yield' expressions"
+  errCannotReturnExpr = "current routine cannot return an expression"
+  errGenericLambdaNotAllowed = "A nested proc can have generic parameters only when " &
+    "it is used as an operand to another routine and the types " &
+    "of the generic paramers can be inferred from the expected signature."
+  errCannotInferTypeOfTheLiteral = "cannot infer the type of the $1"
+  errCannotInferReturnType = "cannot infer the return type of the proc"
+  errCannotInferStaticParam = "cannot infer the value of the static param '$1'"
+  errProcHasNoConcreteType = "'$1' doesn't have a concrete type, due to unspecified generic parameters."
+  errLetNeedsInit = "'let' symbol requires an initialization"
+  errThreadvarCannotInit = "a thread var cannot be initialized explicitly; this would only run for the main thread"
+  errImplOfXexpected = "implementation of '$1' expected"
+  errRecursiveDependencyX = "recursive dependency: '$1'"
+  errPragmaOnlyInHeaderOfProcX = "pragmas are only allowed in the header of a proc; redefinition of $1"
 
 proc semDiscard(c: PContext, n: PNode): PNode =
   result = n
-  checkSonsLen(n, 1)
+  checkSonsLen(n, 1, c.config)
   if n.sons[0].kind != nkEmpty:
     n.sons[0] = semExprWithType(c, n.sons[0])
-    if isEmptyType(n.sons[0].typ) or n.sons[0].typ.kind == tyNone:
-      localError(n.info, errInvalidDiscard)
+    let sonType = n.sons[0].typ
+    let sonKind = n.sons[0].kind
+    if isEmptyType(sonType) or sonType.kind == tyNone or n.sons[0].kind == nkTypeOfExpr:
+      localError(c.config, n.info, errInvalidDiscard)
+    if sonType.kind == tyProc and sonKind notin nkCallKinds:
+      # tyProc is disallowed to prevent ``discard foo`` to be valid, when ``discard foo()`` is meant.
+      localError(c.config, n.info, "illegal discard proc, did you mean: " & $n[0] & "()")
 
 proc semBreakOrContinue(c: PContext, n: PNode): PNode =
   result = n
-  checkSonsLen(n, 1)
+  checkSonsLen(n, 1, c.config)
   if n.sons[0].kind != nkEmpty:
     if n.kind != nkContinueStmt:
       var s: PSym
       case n.sons[0].kind
       of nkIdent: s = lookUp(c, n.sons[0])
       of nkSym: s = n.sons[0].sym
-      else: illFormedAst(n)
+      else: illFormedAst(n, c.config)
       s = getGenSym(c, s)
       if s.kind == skLabel and s.owner.id == c.p.owner.id:
         var x = newSymNode(s)
         x.info = n.info
         incl(s.flags, sfUsed)
         n.sons[0] = x
-        suggestSym(x.info, s, c.graph.usageSym)
-        styleCheckUse(x.info, s)
+        suggestSym(c.config, x.info, s, c.graph.usageSym)
+        onUse(x.info, s)
       else:
-        localError(n.info, errInvalidControlFlowX, s.name.s)
+        localError(c.config, n.info, errInvalidControlFlowX % s.name.s)
     else:
-      localError(n.info, errGenerated, "'continue' cannot have a label")
-  elif (c.p.nestedLoopCounter <= 0) and (c.p.nestedBlockCounter <= 0):
-    localError(n.info, errInvalidControlFlowX,
+      localError(c.config, n.info, errGenerated, "'continue' cannot have a label")
+  elif (c.p.nestedLoopCounter <= 0) and ((c.p.nestedBlockCounter <= 0) or n.kind == nkContinueStmt):
+    localError(c.config, n.info, errInvalidControlFlowX %
                renderTree(n, {renderNoComments}))
 
-proc semAsm(con: PContext, n: PNode): PNode =
-  checkSonsLen(n, 2)
-  var marker = pragmaAsm(con, n.sons[0])
+proc semAsm(c: PContext, n: PNode): PNode =
+  checkSonsLen(n, 2, c.config)
+  var marker = pragmaAsm(c, n.sons[0])
   if marker == '\0': marker = '`' # default marker
-  result = semAsmOrEmit(con, n, marker)
+  result = semAsmOrEmit(c, n, marker)
 
-proc semWhile(c: PContext, n: PNode): PNode =
+proc semWhile(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   openScope(c)
   n.sons[0] = forceBool(c, semExprWithType(c, n.sons[0]))
   inc(c.p.nestedLoopCounter)
-  n.sons[1] = semStmt(c, n.sons[1])
+  n.sons[1] = semStmt(c, n.sons[1], flags)
   dec(c.p.nestedLoopCounter)
   closeScope(c)
-  if n.sons[1].typ == enforceVoidContext:
-    result.typ = enforceVoidContext
+  if n.sons[1].typ == c.enforceVoidContext:
+    result.typ = c.enforceVoidContext
+  elif efInTypeof in flags:
+    result.typ = n[1].typ
 
-proc toCover(t: PType): BiggestInt =
-  var t2 = skipTypes(t, abstractVarRange-{tyTypeDesc})
+proc toCover(c: PContext, t: PType): BiggestInt =
+  let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc})
   if t2.kind == tyEnum and enumHasHoles(t2):
     result = sonsLen(t2.n)
   else:
-    result = lengthOrd(skipTypes(t, abstractVar-{tyTypeDesc}))
-
-proc performProcvarCheck(c: PContext, info: TLineInfo, s: PSym) =
-  ## Checks that the given symbol is a proper procedure variable, meaning
-  ## that it
-  var smoduleId = getModule(s).id
-  if sfProcvar notin s.flags and s.typ.callConv == ccDefault and
-      smoduleId != c.module.id:
-    block outer:
-      for module in c.friendModules:
-        if smoduleId == module.id:
-          break outer
-      localError(info, errXCannotBePassedToProcVar, s.name.s)
-
-proc semProcvarCheck(c: PContext, n: PNode) =
-  var n = n.skipConv
-  if n.kind in nkSymChoices:
-    for x in n:
-      if x.sym.kind in {skProc, skMethod, skConverter, skIterator}:
-        performProcvarCheck(c, n.info, x.sym)
-  elif n.kind == nkSym and n.sym.kind in {skProc, skMethod, skConverter,
-                                        skIterator}:
-    performProcvarCheck(c, n.info, n.sym)
+    result = lengthOrd(c.config, skipTypes(t, abstractVar-{tyTypeDesc}))
 
 proc semProc(c: PContext, n: PNode): PNode
 
-include semdestruct
-
-proc semDestructorCheck(c: PContext, n: PNode, flags: TExprFlags) {.inline.} =
-  if efAllowDestructor notin flags and
-      n.kind in nkCallKinds+{nkObjConstr,nkBracket}:
-    if instantiateDestructor(c, n.typ) != nil:
-      localError(n.info, warnDestructor)
-  # This still breaks too many things:
-  when false:
-    if efDetermineType notin flags and n.typ.kind == tyTypeDesc and
-        c.p.owner.kind notin {skTemplate, skMacro}:
-      localError(n.info, errGenerated, "value expected, but got a type")
-
-proc semExprBranch(c: PContext, n: PNode): PNode =
-  result = semExpr(c, n)
+proc semExprBranch(c: PContext, n: PNode; flags: TExprFlags = {}): PNode =
+  result = semExpr(c, n, flags)
   if result.typ != nil:
     # XXX tyGenericInst here?
-    semProcvarCheck(c, result)
-    if result.typ.kind == tyVar: result = newDeref(result)
-    semDestructorCheck(c, result, {})
+    if result.typ.kind in {tyVar, tyLent}: result = newDeref(result)
 
 proc semExprBranchScope(c: PContext, n: PNode): PNode =
   openScope(c)
@@ -124,216 +118,167 @@ proc semExprBranchScope(c: PContext, n: PNode): PNode =
 const
   skipForDiscardable = {nkIfStmt, nkIfExpr, nkCaseStmt, nkOfBranch,
     nkElse, nkStmtListExpr, nkTryStmt, nkFinally, nkExceptBranch,
-    nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr}
+    nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr,
+    nkHiddenStdConv, nkHiddenDeref}
 
 proc implicitlyDiscardable(n: PNode): bool =
   var n = n
   while n.kind in skipForDiscardable: n = n.lastSon
-  result = isCallExpr(n) and n.sons[0].kind == nkSym and
-           sfDiscardable in n.sons[0].sym.flags
+  result = n.kind == nkRaiseStmt or
+           (isCallExpr(n) and n.sons[0].kind == nkSym and
+           sfDiscardable in n.sons[0].sym.flags)
 
-proc fixNilType(n: PNode) =
+proc fixNilType(c: PContext; n: PNode) =
   if isAtom(n):
     if n.kind != nkNilLit and n.typ != nil:
-      localError(n.info, errDiscardValueX, n.typ.typeToString)
+      localError(c.config, n.info, errDiscardValueX % n.typ.typeToString)
   elif n.kind in {nkStmtList, nkStmtListExpr}:
     n.kind = nkStmtList
-    for it in n: fixNilType(it)
+    for it in n: fixNilType(c, it)
   n.typ = nil
 
-proc discardCheck(c: PContext, result: PNode) =
-  if c.inTypeClass > 0: return
+proc discardCheck(c: PContext, result: PNode, flags: TExprFlags) =
+  if c.matchedConcept != nil or efInTypeof in flags: return
+
   if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}:
-    if result.kind == nkNilLit:
-      result.typ = nil
-      message(result.info, warnNilStatement)
-    elif implicitlyDiscardable(result):
+    if implicitlyDiscardable(result):
+      var n = newNodeI(nkDiscardStmt, result.info, 1)
+      n[0] = result
+    elif result.typ.kind != tyError and c.config.cmd != cmdInteractive:
       var n = result
-      result.typ = nil
-      while n.kind in skipForDiscardable:
-        n = n.lastSon
-        n.typ = nil
-    elif result.typ.kind != tyError and gCmd != cmdInteractive:
-      if result.typ.kind == tyNil:
-        fixNilType(result)
-        message(result.info, warnNilStatement)
-      else:
-        var n = result
-        while n.kind in skipForDiscardable: n = n.lastSon
-        if result.typ.kind == tyProc:
-          localError(n.info, "value of type '" & result.typ.typeToString & "' has to be discarded; for a function call use ()")
-        else:
-          localError(n.info, errDiscardValueX, result.typ.typeToString)
-
-proc semIf(c: PContext, n: PNode): PNode =
+      while n.kind in skipForDiscardable: n = n.lastSon
+      var s = "expression '" & $n & "' is of type '" &
+          result.typ.typeToString & "' and has to be discarded"
+      if result.info.line != n.info.line or
+          result.info.fileIndex != n.info.fileIndex:
+        s.add "; start of expression here: " & c.config$result.info
+      if result.typ.kind == tyProc:
+        s.add "; for a function call use ()"
+      localError(c.config, n.info, s)
+
+proc semIf(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
   var typ = commonTypeBegin
   var hasElse = false
   for i in countup(0, sonsLen(n) - 1):
     var it = n.sons[i]
     if it.len == 2:
-      when newScopeForIf: openScope(c)
+      openScope(c)
       it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0]))
-      when not newScopeForIf: openScope(c)
       it.sons[1] = semExprBranch(c, it.sons[1])
-      typ = commonType(typ, it.sons[1].typ)
+      typ = commonType(typ, it.sons[1])
       closeScope(c)
     elif it.len == 1:
       hasElse = true
       it.sons[0] = semExprBranchScope(c, it.sons[0])
-      typ = commonType(typ, it.sons[0].typ)
-    else: illFormedAst(it)
-  if isEmptyType(typ) or typ.kind == tyNil or not hasElse:
-    for it in n: discardCheck(c, it.lastSon)
+      typ = commonType(typ, it.sons[0])
+    else: illFormedAst(it, c.config)
+  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or
+      (not hasElse and efInTypeof notin flags):
+    for it in n: discardCheck(c, it.lastSon, flags)
     result.kind = nkIfStmt
     # propagate any enforced VoidContext:
-    if typ == enforceVoidContext: result.typ = enforceVoidContext
+    if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext
   else:
     for it in n:
       let j = it.len-1
-      it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
+      if not endsInNoReturn(it.sons[j]):
+        it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
     result.kind = nkIfExpr
     result.typ = typ
 
-proc semCase(c: PContext, n: PNode): PNode =
-  result = n
-  checkMinSonsLen(n, 2)
-  openScope(c)
-  n.sons[0] = semExprWithType(c, n.sons[0])
-  var chckCovered = false
-  var covered: BiggestInt = 0
-  var typ = commonTypeBegin
-  var hasElse = false
-  var notOrdinal = false
-  let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc})
-  case caseTyp.kind
-  of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool:
-    chckCovered = true
-  of tyFloat..tyFloat128, tyString, tyError:
-    notOrdinal = true
-  else:
-    localError(n.info, errSelectorMustBeOfCertainTypes)
-    return
-  for i in countup(1, sonsLen(n) - 1):
-    var x = n.sons[i]
-    when defined(nimsuggest):
-      if gIdeCmd == ideSug and exactEquals(gTrackPos, x.info) and caseTyp.kind == tyEnum:
-        suggestEnum(c, x, caseTyp)
-    case x.kind
-    of nkOfBranch:
-      checkMinSonsLen(x, 2)
-      semCaseBranch(c, n, x, i, covered)
-      var last = sonsLen(x)-1
-      x.sons[last] = semExprBranchScope(c, x.sons[last])
-      typ = commonType(typ, x.sons[last].typ)
-    of nkElifBranch:
-      chckCovered = false
-      checkSonsLen(x, 2)
-      when newScopeForIf: openScope(c)
-      x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0]))
-      when not newScopeForIf: openScope(c)
-      x.sons[1] = semExprBranch(c, x.sons[1])
-      typ = commonType(typ, x.sons[1].typ)
-      closeScope(c)
-    of nkElse:
-      chckCovered = false
-      checkSonsLen(x, 1)
-      x.sons[0] = semExprBranchScope(c, x.sons[0])
-      typ = commonType(typ, x.sons[0].typ)
-      hasElse = true
-    else:
-      illFormedAst(x)
-  if notOrdinal and not hasElse:
-    message(n.info, warnDeprecated,
-            "use 'else: discard'; non-ordinal case without 'else'")
-  if chckCovered:
-    if covered == toCover(n.sons[0].typ):
-      hasElse = true
-    else:
-      localError(n.info, errNotAllCasesCovered)
-  closeScope(c)
-  if isEmptyType(typ) or typ.kind == tyNil or not hasElse:
-    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
-    # propagate any enforced VoidContext:
-    if typ == enforceVoidContext:
-      result.typ = enforceVoidContext
-  else:
-    for i in 1..n.len-1:
-      var it = n.sons[i]
-      let j = it.len-1
-      it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
-    result.typ = typ
+proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode =
+  var check = initIntSet()
+  template semExceptBranchType(typeNode: PNode): bool =
+    # returns true if exception type is imported type
+    let typ = semTypeNode(c, typeNode, nil).toObject()
+    var is_imported = false
+    if isImportedException(typ, c.config):
+      is_imported = true
+    elif not isException(typ):
+      localError(c.config, typeNode.info, errExprCannotBeRaised)
+
+    if containsOrIncl(check, typ.id):
+      localError(c.config, typeNode.info, errExceptionAlreadyHandled)
+    typeNode = newNodeIT(nkType, typeNode.info, typ)
+    is_imported
 
-proc semTry(c: PContext, n: PNode): PNode =
   result = n
   inc c.p.inTryStmt
-  checkMinSonsLen(n, 2)
+  checkMinSonsLen(n, 2, c.config)
 
   var typ = commonTypeBegin
-  n.sons[0] = semExprBranchScope(c, n.sons[0])
-  typ = commonType(typ, n.sons[0].typ)
+  n[0] = semExprBranchScope(c, n[0])
+  typ = commonType(typ, n[0].typ)
 
-  var check = initIntSet()
   var last = sonsLen(n) - 1
+  var catchAllExcepts = 0
+
   for i in countup(1, last):
-    var a = n.sons[i]
-    checkMinSonsLen(a, 1)
-    var length = sonsLen(a)
+    let a = n.sons[i]
+    checkMinSonsLen(a, 1, c.config)
     openScope(c)
     if a.kind == nkExceptBranch:
-      # so that ``except [a, b, c]`` is supported:
-      if length == 2 and a.sons[0].kind == nkBracket:
-        a.sons[0..0] = a.sons[0].sons
-        length = a.sonsLen
-
-      # Iterate through each exception type in the except branch.
-      for j in countup(0, length-2):
-        var typeNode = a.sons[j] # e.g. `Exception`
-        var symbolNode: PNode = nil # e.g. `foobar`
-        # Handle the case where the `Exception as foobar` syntax is used.
-        if typeNode.isInfixAs():
-          typeNode = a.sons[j].sons[1]
-          symbolNode = a.sons[j].sons[2]
-
-        # Resolve the type ident into a PType.
-        var typ = semTypeNode(c, typeNode, nil).toObject()
-        if typ.kind != tyObject:
-          localError(a.sons[j].info, errExprCannotBeRaised)
-
-        let newTypeNode = newNodeI(nkType, typeNode.info)
-        newTypeNode.typ = typ
-        if symbolNode.isNil:
-          a.sons[j] = newTypeNode
-        else:
-          a.sons[j].sons[1] = newTypeNode
-          # Add the exception ident to the symbol table.
-          let symbol = newSymG(skLet, symbolNode, c)
-          symbol.typ = typ.toRef()
-          addDecl(c, symbol)
-          # Overwrite symbol in AST with the symbol in the symbol table.
-          let symNode = newNodeI(nkSym, typeNode.info)
-          symNode.sym = symbol
-          a.sons[j].sons[2] = symNode
-
-        if containsOrIncl(check, typ.id):
-          localError(a.sons[j].info, errExceptionAlreadyHandled)
-    elif a.kind != nkFinally:
-      illFormedAst(n)
+
+      if a.len == 2 and a[0].kind == nkBracket:
+        # rewrite ``except [a, b, c]: body`` -> ```except a, b, c: body```
+        a.sons[0..0] = a[0].sons
+
+      if a.len == 2 and a[0].isInfixAs():
+        # support ``except Exception as ex: body``
+        let is_imported = semExceptBranchType(a[0][1])
+        let symbol = newSymG(skLet, a[0][2], c)
+        symbol.typ = if is_imported: a[0][1].typ
+                     else: a[0][1].typ.toRef()
+        addDecl(c, symbol)
+        # Overwrite symbol in AST with the symbol in the symbol table.
+        a[0][2] = newSymNode(symbol, a[0][2].info)
+
+      elif a.len == 1:
+          # count number of ``except: body`` blocks
+          inc catchAllExcepts
+
+      else:
+        # support ``except KeyError, ValueError, ... : body``
+        if catchAllExcepts > 0:
+          # if ``except: body`` already encountered,
+          # cannot be followed by a ``except KeyError, ... : body`` block
+          inc catchAllExcepts
+        var is_native, is_imported: bool
+        for j in 0..a.len-2:
+          let tmp = semExceptBranchType(a[j])
+          if tmp: is_imported = true
+          else: is_native = true
+
+        if is_native and is_imported:
+          localError(c.config, a[0].info, "Mix of imported and native exception types is not allowed in one except branch")
+
+    elif a.kind == nkFinally:
+      if i != n.len-1:
+        localError(c.config, a.info, "Only one finally is allowed after all other branches")
+
+    else:
+      illFormedAst(n, c.config)
+
+    if catchAllExcepts > 1:
+      # if number of ``except: body`` blocks is greater than 1
+      # or more specific exception follows a general except block, it is invalid
+      localError(c.config, a.info, "Only one general except clause is allowed after more specific exceptions")
 
     # last child of an nkExcept/nkFinally branch is a statement:
-    a.sons[length-1] = semExprBranchScope(c, a.sons[length-1])
-    if a.kind != nkFinally: typ = commonType(typ, a.sons[length-1].typ)
+    a[^1] = semExprBranchScope(c, a[^1])
+    if a.kind != nkFinally: typ = commonType(typ, a[^1])
     else: dec last
     closeScope(c)
 
   dec c.p.inTryStmt
-  if isEmptyType(typ) or typ.kind == tyNil:
-    discardCheck(c, n.sons[0])
-    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
-    if typ == enforceVoidContext:
-      result.typ = enforceVoidContext
+  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr}:
+    discardCheck(c, n.sons[0], flags)
+    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags)
+    if typ == c.enforceVoidContext:
+      result.typ = c.enforceVoidContext
   else:
-    if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon)
+    if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon, flags)
     n.sons[0] = fitNode(c, typ, n.sons[0], n.sons[0].info)
     for i in 1..last:
       var it = n.sons[i]
@@ -350,10 +295,10 @@ proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode =
       result.info = n.info
       result.typ = typ
     else:
-      changeType(r1, typ, check=true)
+      changeType(c, r1, typ, check=true)
       result = r1
   elif not sameType(result.typ, typ):
-    changeType(result, typ, check=false)
+    changeType(c, result, typ, check=false)
 
 proc findShadowedVar(c: PContext, v: PSym): PSym =
   for scope in walkScopes(c.currentScope.parent):
@@ -377,87 +322,56 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
     result = semIdentWithPragma(c, kind, n, {})
     if result.owner.kind == skModule:
       incl(result.flags, sfGlobal)
-  suggestSym(n.info, result, c.graph.usageSym)
-  styleCheckDef(result)
+  suggestSym(c.config, n.info, result, c.graph.usageSym)
 
-proc checkNilable(v: PSym) =
+proc checkNilable(c: PContext; v: PSym) =
   if {sfGlobal, sfImportC} * v.flags == {sfGlobal} and
       {tfNotNil, tfNeedsInit} * v.typ.flags != {}:
     if v.ast.isNil:
-      message(v.info, warnProveInit, v.name.s)
-    elif tfNeedsInit in v.typ.flags and tfNotNil notin v.ast.typ.flags:
-      message(v.info, warnProveInit, v.name.s)
+      message(c.config, v.info, warnProveInit, v.name.s)
+    elif tfNotNil in v.typ.flags and tfNotNil notin v.ast.typ.flags:
+      message(c.config, v.info, warnProveInit, v.name.s)
 
 include semasgn
 
-proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) =
-  # consider this:
-  #   var
-  #     x = 0
-  #     withOverloadedAssignment = foo()
-  #     y = use(withOverloadedAssignment)
-  # We need to split this into a statement list with multiple 'var' sections
-  # in order for this transformation to be correct.
+proc addToVarSection(c: PContext; result: PNode; orig, identDefs: PNode) =
   let L = identDefs.len
   let value = identDefs[L-1]
-  if value.typ != nil and tfHasAsgn in value.typ.flags:
-    # the spec says we need to rewrite 'var x = T()' to 'var x: T; x = T()':
-    identDefs.sons[L-1] = emptyNode
-    if result.kind != nkStmtList:
-      let oldResult = result
-      oldResult.add identDefs
-      result = newNodeI(nkStmtList, result.info)
-      result.add oldResult
-    else:
-      let o = copyNode(orig)
-      o.add identDefs
-      result.add o
-    for i in 0 .. L-3:
-      result.add overloadedAsgn(c, identDefs[i], value)
-  elif result.kind == nkStmtList:
+  if result.kind == nkStmtList:
     let o = copyNode(orig)
     o.add identDefs
     result.add o
   else:
     result.add identDefs
 
-proc addDefer(c: PContext; result: var PNode; s: PSym) =
-  let deferDestructorCall = createDestructorCall(c, s)
-  if deferDestructorCall != nil:
-    if result.kind != nkStmtList:
-      let oldResult = result
-      result = newNodeI(nkStmtList, result.info)
-      result.add oldResult
-    result.add deferDestructorCall
-
 proc isDiscardUnderscore(v: PSym): bool =
   if v.name.s == "_":
     v.flags.incl(sfGenSym)
     result = true
 
 proc semUsing(c: PContext; n: PNode): PNode =
-  result = ast.emptyNode
-  if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "using")
-  if not experimentalMode(c):
-    localError(n.info, "use the {.experimental.} pragma to enable 'using'")
+  result = c.graph.emptyNode
+  if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "using")
   for i in countup(0, sonsLen(n)-1):
     var a = n.sons[i]
-    if gCmd == cmdIdeTools: suggestStmt(c, a)
+    if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
     if a.kind == nkCommentStmt: continue
-    if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a)
-    checkMinSonsLen(a, 3)
+    if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
+    checkMinSonsLen(a, 3, c.config)
     var length = sonsLen(a)
     if a.sons[length-2].kind != nkEmpty:
       let typ = semTypeNode(c, a.sons[length-2], nil)
       for j in countup(0, length-3):
         let v = semIdentDef(c, a.sons[j], skParam)
+        styleCheckDef(c.config, v)
+        onDef(a[j].info, v)
         v.typ = typ
         strTableIncl(c.signatures, v)
     else:
-      localError(a.info, "'using' section must have a type")
+      localError(c.config, a.info, "'using' section must have a type")
     var def: PNode
     if a.sons[length-1].kind != nkEmpty:
-      localError(a.info, "'using' sections cannot contain assignments")
+      localError(c.config, a.info, "'using' sections cannot contain assignments")
 
 proc hasEmpty(typ: PType): bool =
   if typ.kind in {tySequence, tyArray, tySet}:
@@ -466,28 +380,71 @@ proc hasEmpty(typ: PType): bool =
     for s in typ.sons:
       result = result or hasEmpty(s)
 
+proc makeDeref(n: PNode): PNode =
+  var t = n.typ
+  if t.kind in tyUserTypeClasses and t.isResolvedUserTypeClass:
+    t = t.lastSon
+  t = skipTypes(t, {tyGenericInst, tyAlias, tySink})
+  result = n
+  if t.kind in {tyVar, tyLent}:
+    result = newNodeIT(nkHiddenDeref, n.info, t.sons[0])
+    addSon(result, n)
+    t = skipTypes(t.sons[0], {tyGenericInst, tyAlias, tySink})
+  while t.kind in {tyPtr, tyRef}:
+    var a = result
+    let baseTyp = t.lastSon
+    result = newNodeIT(nkHiddenDeref, n.info, baseTyp)
+    addSon(result, a)
+    t = skipTypes(baseTyp, {tyGenericInst, tyAlias, tySink})
+
+proc fillPartialObject(c: PContext; n: PNode; typ: PType) =
+  if n.len == 2:
+    let x = semExprWithType(c, n[0])
+    let y = considerQuotedIdent(c, n[1])
+    let obj = x.typ.skipTypes(abstractPtrs)
+    if obj.kind == tyObject and tfPartial in obj.flags:
+      let field = newSym(skField, getIdent(c.cache, y.s), obj.sym, n[1].info)
+      field.typ = skipIntLit(typ)
+      field.position = sonsLen(obj.n)
+      addSon(obj.n, newSymNode(field))
+      n.sons[0] = makeDeref x
+      n.sons[1] = newSymNode(field)
+      n.typ = field.typ
+    else:
+      localError(c.config, n.info, "implicit object field construction " &
+        "requires a .partial object, but got " & typeToString(obj))
+  else:
+    localError(c.config, n.info, "nkDotNode requires 2 children")
+
+proc setVarType(c: PContext; v: PSym, typ: PType) =
+  if v.typ != nil and not sameTypeOrNil(v.typ, typ):
+    localError(c.config, v.info, "inconsistent typing for reintroduced symbol '" &
+        v.name.s & "': previous type was: " & typeToString(v.typ) &
+        "; new type is: " & typeToString(typ))
+  v.typ = typ
+
 proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
   var b: PNode
   result = copyNode(n)
   var hasCompileTime = false
   for i in countup(0, sonsLen(n)-1):
     var a = n.sons[i]
-    if gCmd == cmdIdeTools: suggestStmt(c, a)
+    if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
     if a.kind == nkCommentStmt: continue
-    if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a)
-    checkMinSonsLen(a, 3)
+    if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config)
+    checkMinSonsLen(a, 3, c.config)
     var length = sonsLen(a)
     var typ: PType
     if a.sons[length-2].kind != nkEmpty:
       typ = semTypeNode(c, a.sons[length-2], nil)
     else:
       typ = nil
-    var def: PNode = ast.emptyNode
+    var def: PNode = c.graph.emptyNode
     if a.sons[length-1].kind != nkEmpty:
       def = semExprWithType(c, a.sons[length-1], {efAllowDestructor})
       if def.typ.kind == tyTypeDesc and c.p.owner.kind != skMacro:
         # prevent the all too common 'var x = int' bug:
-        localError(def.info, "'typedesc' metatype is not valid here; typed '=' instead of ':'?")
+        localError(c.config, def.info, "'typedesc' metatype is not valid here; typed '=' instead of ':'?")
         def.typ = errorType(c)
       if typ != nil:
         if typ.isMetaType:
@@ -499,38 +456,51 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
           def = fitNode(c, typ, def, def.info)
           #changeType(def.skipConv, typ, check=true)
       else:
-        typ = skipIntLit(def.typ)
+        typ = def.typ.skipTypes({tyStatic}).skipIntLit
+        if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass:
+          typ = typ.lastSon
         if hasEmpty(typ):
-          localError(def.info, errCannotInferTypeOfTheLiteral,
+          localError(c.config, def.info, errCannotInferTypeOfTheLiteral %
                      ($typ.kind).substr(2).toLowerAscii)
         elif typ.kind == tyProc and tfUnresolved in typ.flags:
-          localError(def.info, errProcHasNoConcreteType, def.renderTree)
+          localError(c.config, def.info, errProcHasNoConcreteType % def.renderTree)
     else:
-      if symkind == skLet: localError(a.info, errLetNeedsInit)
+      if symkind == skLet: localError(c.config, a.info, errLetNeedsInit)
 
     # this can only happen for errornous var statements:
     if typ == nil: continue
-    typeAllowedCheck(a.info, typ, symkind)
-    var tup = skipTypes(typ, {tyGenericInst, tyAlias})
+    typeAllowedCheck(c.config, a.info, typ, symkind, if c.matchedConcept != nil: {taConcept} else: {})
+    liftTypeBoundOps(c, typ, a.info)
+    var tup = skipTypes(typ, {tyGenericInst, tyAlias, tySink})
     if a.kind == nkVarTuple:
       if tup.kind != tyTuple:
-        localError(a.info, errXExpected, "tuple")
+        localError(c.config, a.info, errXExpected, "tuple")
       elif length-2 != sonsLen(tup):
-        localError(a.info, errWrongNumberOfVariables)
-      else:
-        b = newNodeI(nkVarTuple, a.info)
-        newSons(b, length)
-        b.sons[length-2] = a.sons[length-2] # keep type desc for doc generator
-        b.sons[length-1] = def
-        addToVarSection(c, result, n, b)
-    elif tup.kind == tyTuple and def.kind == nkPar and
+        localError(c.config, a.info, errWrongNumberOfVariables)
+      b = newNodeI(nkVarTuple, a.info)
+      newSons(b, length)
+      # keep type desc for doc generator
+      # NOTE: at the moment this is always ast.emptyNode, see parser.nim
+      b.sons[length-2] = a.sons[length-2]
+      b.sons[length-1] = def
+      addToVarSection(c, result, n, b)
+    elif tup.kind == tyTuple and def.kind in {nkPar, nkTupleConstr} and
         a.kind == nkIdentDefs and a.len > 3:
-      message(a.info, warnEachIdentIsTuple)
+      message(c.config, a.info, warnEachIdentIsTuple)
 
     for j in countup(0, length-3):
+      if a[j].kind == nkDotExpr:
+        fillPartialObject(c, a[j],
+          if a.kind != nkVarTuple: typ else: tup.sons[j])
+        addToVarSection(c, result, n, a)
+        continue
       var v = semIdentDef(c, a.sons[j], symkind)
-      if sfGenSym notin v.flags and not isDiscardUnderscore(v):
-        addInterfaceDecl(c, v)
+      styleCheckDef(c.config, v)
+      onDef(a[j].info, v)
+      if sfGenSym notin v.flags:
+        if not isDiscardUnderscore(v): addInterfaceDecl(c, v)
+      else:
+        if v.owner == nil: v.owner = c.p.owner
       when oKeepVariableNames:
         if c.inUnrolledContext > 0: v.flags.incl(sfShadowed)
         else:
@@ -538,49 +508,58 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
           if shadowed != nil:
             shadowed.flags.incl(sfShadowed)
             if shadowed.kind == skResult and sfGenSym notin v.flags:
-              message(a.info, warnResultShadowed)
+              message(c.config, a.info, warnResultShadowed)
             # a shadowed variable is an error unless it appears on the right
             # side of the '=':
-            if warnShadowIdent in gNotes and not identWithin(def, v.name):
-              message(a.info, warnShadowIdent, v.name.s)
+            if warnShadowIdent in c.config.notes and not identWithin(def, v.name):
+              message(c.config, a.info, warnShadowIdent, v.name.s)
       if a.kind != nkVarTuple:
         if def.kind != nkEmpty:
           # this is needed for the evaluation pass and for the guard checking:
           v.ast = def
-          if sfThread in v.flags: localError(def.info, errThreadvarCannotInit)
-        v.typ = typ
+          if sfThread in v.flags: localError(c.config, def.info, errThreadvarCannotInit)
+        setVarType(c, v, typ)
         b = newNodeI(nkIdentDefs, a.info)
-        if importantComments():
+        if importantComments(c.config):
           # keep documentation information:
           b.comment = a.comment
         addSon(b, newSymNode(v))
-        addSon(b, a.sons[length-2])      # keep type desc for doc generator
+        # keep type desc for doc generator
+        addSon(b, a.sons[length-2])
         addSon(b, copyTree(def))
         addToVarSection(c, result, n, b)
       else:
-        if def.kind == nkPar: v.ast = def[j]
-        v.typ = tup.sons[j]
+        if def.kind in {nkPar, nkTupleConstr}: v.ast = def[j]
+        # bug #7663, for 'nim check' this can be a non-tuple:
+        if tup.kind == tyTuple: setVarType(c, v, tup.sons[j])
+        else: v.typ = tup
         b.sons[j] = newSymNode(v)
-      addDefer(c, result, v)
-      checkNilable(v)
+      checkNilable(c, v)
       if sfCompileTime in v.flags: hasCompileTime = true
-  if hasCompileTime: vm.setupCompileTimeVar(c.module, c.cache, result)
+      if v.flags * {sfGlobal, sfThread} == {sfGlobal}:
+        message(c.config, v.info, hintGlobalVar)
+  if hasCompileTime:
+    vm.setupCompileTimeVar(c.module, c.graph, result)
+    # handled by the VM codegen:
+    #c.graph.recordStmt(c.graph, c.module, result)
 
 proc semConst(c: PContext, n: PNode): PNode =
   result = copyNode(n)
   for i in countup(0, sonsLen(n) - 1):
     var a = n.sons[i]
-    if gCmd == cmdIdeTools: suggestStmt(c, a)
+    if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
     if a.kind == nkCommentStmt: continue
-    if (a.kind != nkConstDef): illFormedAst(a)
-    checkSonsLen(a, 3)
+    if a.kind != nkConstDef: illFormedAst(a, c.config)
+    checkSonsLen(a, 3, c.config)
     var v = semIdentDef(c, a.sons[0], skConst)
+    styleCheckDef(c.config, v)
+    onDef(a[0].info, v)
     var typ: PType = nil
     if a.sons[1].kind != nkEmpty: typ = semTypeNode(c, a.sons[1], nil)
 
     var def = semConstExpr(c, a.sons[2])
     if def == nil:
-      localError(a.sons[2].info, errConstExprExpected)
+      localError(c.config, a.sons[2].info, errConstExprExpected)
       continue
     # check type compatibility between def.typ and typ:
     if typ != nil:
@@ -588,16 +567,17 @@ proc semConst(c: PContext, n: PNode): PNode =
     else:
       typ = def.typ
     if typ == nil:
-      localError(a.sons[2].info, errConstExprExpected)
+      localError(c.config, a.sons[2].info, errConstExprExpected)
       continue
     if typeAllowed(typ, skConst) != nil and def.kind != nkNilLit:
-      localError(a.info, "invalid type for const: " & typeToString(typ))
+      localError(c.config, a.info, "invalid type for const: " & typeToString(typ))
       continue
-    v.typ = typ
+    setVarType(c, v, typ)
     v.ast = def               # no need to copy
     if sfGenSym notin v.flags: addInterfaceDecl(c, v)
+    elif v.owner == nil: v.owner = getCurrOwner(c)
     var b = newNodeI(nkConstDef, a.info)
-    if importantComments(): b.comment = a.comment
+    if importantComments(c.config): b.comment = a.comment
     addSon(b, newSymNode(v))
     addSon(b, a.sons[1])
     addSon(b, copyTree(def))
@@ -606,24 +586,27 @@ proc semConst(c: PContext, n: PNode): PNode =
 include semfields
 
 proc addForVarDecl(c: PContext, v: PSym) =
-  if warnShadowIdent in gNotes:
+  if warnShadowIdent in c.config.notes:
     let shadowed = findShadowedVar(c, v)
     if shadowed != nil:
       # XXX should we do this here?
       #shadowed.flags.incl(sfShadowed)
-      message(v.info, warnShadowIdent, v.name.s)
+      message(c.config, v.info, warnShadowIdent, v.name.s)
   addDecl(c, v)
 
 proc symForVar(c: PContext, n: PNode): PSym =
   let m = if n.kind == nkPragmaExpr: n.sons[0] else: n
   result = newSymG(skForVar, m, c)
-  styleCheckDef(result)
+  styleCheckDef(c.config, result)
+  onDef(n.info, result)
+  if n.kind == nkPragmaExpr:
+    pragma(c, result, n.sons[1], forVarPragmas)
 
-proc semForVars(c: PContext, n: PNode): PNode =
+proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
   var length = sonsLen(n)
   let iterBase = n.sons[length-2].typ
-  var iter = skipTypes(iterBase, {tyGenericInst, tyAlias})
+  var iter = skipTypes(iterBase, {tyGenericInst, tyAlias, tySink})
   # length == 3 means that there is one for loop variable
   # and thus no tuple unpacking:
   if iter.kind != tyTuple or length == 3:
@@ -636,50 +619,133 @@ proc semForVars(c: PContext, n: PNode): PNode =
       v.typ = iterBase
       n.sons[0] = newSymNode(v)
       if sfGenSym notin v.flags: addForVarDecl(c, v)
+      elif v.owner == nil: v.owner = getCurrOwner(c)
     else:
-      localError(n.info, errWrongNumberOfVariables)
+      localError(c.config, n.info, errWrongNumberOfVariables)
   elif length-2 != sonsLen(iter):
-    localError(n.info, errWrongNumberOfVariables)
+    localError(c.config, n.info, errWrongNumberOfVariables)
   else:
     for i in countup(0, length - 3):
       var v = symForVar(c, n.sons[i])
       if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal)
       v.typ = iter.sons[i]
       n.sons[i] = newSymNode(v)
-      if sfGenSym notin v.flags and not isDiscardUnderscore(v):
-        addForVarDecl(c, v)
+      if sfGenSym notin v.flags:
+        if not isDiscardUnderscore(v): addForVarDecl(c, v)
+      elif v.owner == nil: v.owner = getCurrOwner(c)
   inc(c.p.nestedLoopCounter)
-  n.sons[length-1] = semStmt(c, n.sons[length-1])
+  openScope(c)
+  n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags)
+  if efInTypeof notin flags:
+    discardCheck(c, n.sons[length-1], flags)
+  closeScope(c)
   dec(c.p.nestedLoopCounter)
 
 proc implicitIterator(c: PContext, it: string, arg: PNode): PNode =
   result = newNodeI(nkCall, arg.info)
-  result.add(newIdentNode(it.getIdent, arg.info))
-  if arg.typ != nil and arg.typ.kind == tyVar:
+  result.add(newIdentNode(getIdent(c.cache, it), arg.info))
+  if arg.typ != nil and arg.typ.kind in {tyVar, tyLent}:
     result.add newDeref(arg)
   else:
     result.add arg
   result = semExprNoDeref(c, result, {efWantIterator})
 
-proc semFor(c: PContext, n: PNode): PNode =
-  result = n
-  checkMinSonsLen(n, 3)
+proc isTrivalStmtExpr(n: PNode): bool =
+  for i in 0 .. n.len-2:
+    if n[i].kind notin {nkEmpty, nkCommentStmt}:
+      return false
+  result = true
+
+proc handleStmtMacro(c: PContext; n, selector: PNode; magicType: string): PNode =
+  if selector.kind in nkCallKinds:
+    # we transform
+    # n := for a, b, c in m(x, y, z): Y
+    # to
+    # m(n)
+    let maType = magicsys.getCompilerProc(c.graph, magicType)
+    if maType == nil: return
+
+    let headSymbol = selector[0]
+    var o: TOverloadIter
+    var match: PSym = nil
+    var symx = initOverloadIter(o, c, headSymbol)
+    while symx != nil:
+      if symx.kind in {skTemplate, skMacro}:
+        if symx.typ.len == 2 and symx.typ[1] == maType.typ:
+          if match == nil:
+            match = symx
+          else:
+            localError(c.config, n.info, errAmbiguousCallXYZ % [
+              getProcHeader(c.config, match),
+              getProcHeader(c.config, symx), $selector])
+      symx = nextOverloadIter(o, c, headSymbol)
+
+    if match == nil: return
+    var callExpr = newNodeI(nkCall, n.info)
+    callExpr.add newSymNode(match)
+    callExpr.add n
+    case match.kind
+    of skMacro: result = semMacroExpr(c, callExpr, callExpr, match, {})
+    of skTemplate: result = semTemplateExpr(c, callExpr, match, {})
+    else: result = nil
+
+proc handleForLoopMacro(c: PContext; n: PNode): PNode =
+  result = handleStmtMacro(c, n, n[^2], "ForLoopStmt")
+
+proc handleCaseStmtMacro(c: PContext; n: PNode): PNode =
+  # n[0] has been sem'checked and has a type. We use this to resolve
+  # 'match(n[0])' but then we pass 'n' to the 'match' macro. This seems to
+  # be the best solution.
+  var toResolve = newNodeI(nkCall, n.info)
+  toResolve.add newIdentNode(getIdent(c.cache, "match"), n.info)
+  toResolve.add n[0]
+
+  var errors: CandidateErrors
+  var r = resolveOverloads(c, toResolve, toResolve, {skTemplate, skMacro}, {},
+                           errors, false)
+  if r.state == csMatch:
+    var match = r.calleeSym
+    markUsed(c.config, n[0].info, match, c.graph.usageSym)
+    onUse(n[0].info, match)
+
+    # but pass 'n' to the 'match' macro, not 'n[0]':
+    r.call.sons[1] = n
+    let toExpand = semResolvedCall(c, r, r.call, {})
+    case match.kind
+    of skMacro: result = semMacroExpr(c, toExpand, toExpand, match, {})
+    of skTemplate: result = semTemplateExpr(c, toExpand, match, {})
+    else: result = nil
+  # this would be the perfectly consistent solution with 'for loop macros',
+  # but it kinda sucks for pattern matching as the matcher is not attached to
+  # a type then:
+  when false:
+    result = handleStmtMacro(c, n, n[0], "CaseStmt")
+
+proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
+  checkMinSonsLen(n, 3, c.config)
   var length = sonsLen(n)
+  if forLoopMacros in c.features:
+    result = handleForLoopMacro(c, n)
+    if result != nil: return result
   openScope(c)
+  result = n
   n.sons[length-2] = semExprNoDeref(c, n.sons[length-2], {efWantIterator})
   var call = n.sons[length-2]
+  if call.kind == nkStmtListExpr and isTrivalStmtExpr(call):
+    call = call.lastSon
+    n.sons[length-2] = call
   let isCallExpr = call.kind in nkCallKinds
   if isCallExpr and call[0].kind == nkSym and
       call[0].sym.magic in {mFields, mFieldPairs, mOmpParFor}:
     if call.sons[0].sym.magic == mOmpParFor:
-      result = semForVars(c, n)
+      result = semForVars(c, n, flags)
       result.kind = nkParForStmt
     else:
       result = semForFields(c, n, call.sons[0].sym.magic)
   elif isCallExpr and call.sons[0].typ.callConv == ccClosure and
       tfIterator in call.sons[0].typ.flags:
     # first class iterator:
-    result = semForVars(c, n)
+    result = semForVars(c, n, flags)
   elif not isCallExpr or call.sons[0].kind != nkSym or
       call.sons[0].sym.kind != skIterator:
     if length == 3:
@@ -687,30 +753,116 @@ proc semFor(c: PContext, n: PNode): PNode =
     elif length == 4:
       n.sons[length-2] = implicitIterator(c, "pairs", n.sons[length-2])
     else:
-      localError(n.sons[length-2].info, errIteratorExpected)
-    result = semForVars(c, n)
+      localError(c.config, n.sons[length-2].info, "iterator within for loop context expected")
+    result = semForVars(c, n, flags)
   else:
-    result = semForVars(c, n)
+    result = semForVars(c, n, flags)
   # propagate any enforced VoidContext:
-  if n.sons[length-1].typ == enforceVoidContext:
-    result.typ = enforceVoidContext
+  if n.sons[length-1].typ == c.enforceVoidContext:
+    result.typ = c.enforceVoidContext
+  elif efInTypeof in flags:
+    result.typ = result.lastSon.typ
   closeScope(c)
 
+proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
+  result = n
+  checkMinSonsLen(n, 2, c.config)
+  openScope(c)
+  n.sons[0] = semExprWithType(c, n.sons[0])
+  var chckCovered = false
+  var covered: BiggestInt = 0
+  var typ = commonTypeBegin
+  var hasElse = false
+  let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc})
+  case caseTyp.kind
+  of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool:
+    chckCovered = true
+  of tyFloat..tyFloat128, tyString, tyError:
+    discard
+  else:
+    if caseStmtMacros in c.features:
+      result = handleCaseStmtMacro(c, n)
+      if result != nil: return result
+
+    localError(c.config, n.info, errSelectorMustBeOfCertainTypes)
+    return
+  for i in countup(1, sonsLen(n) - 1):
+    var x = n.sons[i]
+    when defined(nimsuggest):
+      if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum:
+        suggestEnum(c, x, caseTyp)
+    case x.kind
+    of nkOfBranch:
+      checkMinSonsLen(x, 2, c.config)
+      semCaseBranch(c, n, x, i, covered)
+      var last = sonsLen(x)-1
+      x.sons[last] = semExprBranchScope(c, x.sons[last])
+      typ = commonType(typ, x.sons[last])
+    of nkElifBranch:
+      chckCovered = false
+      checkSonsLen(x, 2, c.config)
+      openScope(c)
+      x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0]))
+      x.sons[1] = semExprBranch(c, x.sons[1])
+      typ = commonType(typ, x.sons[1])
+      closeScope(c)
+    of nkElse:
+      chckCovered = false
+      checkSonsLen(x, 1, c.config)
+      x.sons[0] = semExprBranchScope(c, x.sons[0])
+      typ = commonType(typ, x.sons[0])
+      hasElse = true
+    else:
+      illFormedAst(x, c.config)
+  if chckCovered:
+    if covered == toCover(c, n.sons[0].typ):
+      hasElse = true
+    else:
+      localError(c.config, n.info, "not all cases are covered")
+  closeScope(c)
+  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or
+      (not hasElse and efInTypeof notin flags):
+    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags)
+    # propagate any enforced VoidContext:
+    if typ == c.enforceVoidContext:
+      result.typ = c.enforceVoidContext
+  else:
+    for i in 1..n.len-1:
+      var it = n.sons[i]
+      let j = it.len-1
+      if not endsInNoReturn(it.sons[j]):
+        it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
+    result.typ = typ
+
 proc semRaise(c: PContext, n: PNode): PNode =
   result = n
-  checkSonsLen(n, 1)
-  if n.sons[0].kind != nkEmpty:
-    n.sons[0] = semExprWithType(c, n.sons[0])
-    var typ = n.sons[0].typ
-    if typ.kind != tyRef or typ.lastSon.kind != tyObject:
-      localError(n.info, errExprCannotBeRaised)
+  checkSonsLen(n, 1, c.config)
+  if n[0].kind != nkEmpty:
+    n[0] = semExprWithType(c, n[0])
+    var typ = n[0].typ
+    if not isImportedException(typ, c.config):
+      typ = typ.skipTypes({tyAlias, tyGenericInst})
+      if typ.kind != tyRef:
+        localError(c.config, n.info, errExprCannotBeRaised)
+      if typ.len > 0 and not isException(typ.lastSon):
+        localError(c.config, n.info, "raised object of type $1 does not inherit from Exception",
+                          [typeToString(typ)])
 
 proc addGenericParamListToScope(c: PContext, n: PNode) =
-  if n.kind != nkGenericParams: illFormedAst(n)
+  if n.kind != nkGenericParams: illFormedAst(n, c.config)
   for i in countup(0, sonsLen(n)-1):
     var a = n.sons[i]
     if a.kind == nkSym: addDecl(c, a.sym)
-    else: illFormedAst(a)
+    else: illFormedAst(a, c.config)
+
+proc typeSectionTypeName(c: PContext; n: PNode): PNode =
+  if n.kind == nkPragmaExpr:
+    if n.len == 0: illFormedAst(n, c.config)
+    result = n.sons[0]
+  else:
+    result = n
+  if result.kind != nkSym: illFormedAst(n, c.config)
+
 
 proc typeSectionLeftSidePass(c: PContext, n: PNode) =
   # process the symbols on the left side for the whole type section, before
@@ -718,40 +870,146 @@ proc typeSectionLeftSidePass(c: PContext, n: PNode) =
   for i in countup(0, sonsLen(n) - 1):
     var a = n.sons[i]
     when defined(nimsuggest):
-      if gCmd == cmdIdeTools:
+      if c.config.cmd == cmdIdeTools:
         inc c.inTypeContext
         suggestStmt(c, a)
         dec c.inTypeContext
     if a.kind == nkCommentStmt: continue
-    if a.kind != nkTypeDef: illFormedAst(a)
-    checkSonsLen(a, 3)
+    if a.kind != nkTypeDef: illFormedAst(a, c.config)
+    checkSonsLen(a, 3, c.config)
     let name = a.sons[0]
     var s: PSym
-    if name.kind == nkDotExpr:
-      s = qualifiedLookUp(c, name, {checkUndeclared, checkModule})
-      if s.kind != skType or s.typ.skipTypes(abstractPtrs).kind != tyObject or tfPartial notin s.typ.skipTypes(abstractPtrs).flags:
-        localError(name.info, "only .partial objects can be extended")
+    if name.kind == nkDotExpr and a[2].kind == nkObjectTy:
+      let pkgName = considerQuotedIdent(c, name[0])
+      let typName = considerQuotedIdent(c, name[1])
+      let pkg = c.graph.packageSyms.strTableGet(pkgName)
+      if pkg.isNil or pkg.kind != skPackage:
+        localError(c.config, name.info, "unknown package name: " & pkgName.s)
+      else:
+        let typsym = pkg.tab.strTableGet(typName)
+        if typsym.isNil:
+          s = semIdentDef(c, name[1], skType)
+          styleCheckDef(c.config, s)
+          onDef(name[1].info, s)
+          s.typ = newTypeS(tyObject, c)
+          s.typ.sym = s
+          s.flags.incl sfForward
+          pkg.tab.strTableAdd s
+          addInterfaceDecl(c, s)
+        elif typsym.kind == skType and sfForward in typsym.flags:
+          s = typsym
+          addInterfaceDecl(c, s)
+        else:
+          localError(c.config, name.info, typsym.name.s & " is not a type that can be forwarded")
+          s = typsym
     else:
       s = semIdentDef(c, name, skType)
+      styleCheckDef(c.config, s)
+      onDef(name.info, s)
       s.typ = newTypeS(tyForward, c)
       s.typ.sym = s             # process pragmas:
       if name.kind == nkPragmaExpr:
         pragma(c, s, name.sons[1], typePragmas)
+      if sfForward in s.flags:
+        # check if the symbol already exists:
+        let pkg = c.module.owner
+        if not isTopLevel(c) or pkg.isNil:
+          localError(c.config, name.info, "only top level types in a package can be 'package'")
+        else:
+          let typsym = pkg.tab.strTableGet(s.name)
+          if typsym != nil:
+            if sfForward notin typsym.flags or sfNoForward notin typsym.flags:
+              typeCompleted(typsym)
+              typsym.info = s.info
+            else:
+              localError(c.config, name.info, "cannot complete type '" & s.name.s & "' twice; " &
+                      "previous type completion was here: " & c.config$typsym.info)
+            s = typsym
       # add it here, so that recursive types are possible:
       if sfGenSym notin s.flags: addInterfaceDecl(c, s)
-    a.sons[0] = newSymNode(s)
+      elif s.owner == nil: s.owner = getCurrOwner(c)
+
+    if name.kind == nkPragmaExpr:
+      a.sons[0].sons[0] = newSymNode(s)
+    else:
+      a.sons[0] = newSymNode(s)
+
+proc checkCovariantParamsUsages(c: PContext; genericType: PType) =
+  var body = genericType[^1]
+
+  proc traverseSubTypes(c: PContext; t: PType): bool =
+    template error(msg) = localError(c.config, genericType.sym.info, msg)
+    result = false
+    template subresult(r) =
+      let sub = r
+      result = result or sub
+
+    case t.kind
+    of tyGenericParam:
+      t.flags.incl tfWeakCovariant
+      return true
+    of tyObject:
+      for field in t.n:
+        subresult traverseSubTypes(c, field.typ)
+    of tyArray:
+      return traverseSubTypes(c, t[1])
+    of tyProc:
+      for subType in t.sons:
+        if subType != nil:
+          subresult traverseSubTypes(c, subType)
+      if result:
+        error("non-invariant type param used in a proc type: " & $t)
+    of tySequence:
+      return traverseSubTypes(c, t[0])
+    of tyGenericInvocation:
+      let targetBody = t[0]
+      for i in 1 ..< t.len:
+        let param = t[i]
+        if param.kind == tyGenericParam:
+          if tfCovariant in param.flags:
+            let formalFlags = targetBody[i-1].flags
+            if tfCovariant notin formalFlags:
+              error("covariant param '" & param.sym.name.s &
+                    "' used in a non-covariant position")
+            elif tfWeakCovariant in formalFlags:
+              param.flags.incl tfWeakCovariant
+            result = true
+          elif tfContravariant in param.flags:
+            let formalParam = targetBody[i-1].sym
+            if tfContravariant notin formalParam.typ.flags:
+              error("contravariant param '" & param.sym.name.s &
+                    "' used in a non-contravariant position")
+            result = true
+        else:
+          subresult traverseSubTypes(c, param)
+    of tyAnd, tyOr, tyNot, tyStatic, tyBuiltInTypeClass, tyCompositeTypeClass:
+      error("non-invariant type parameters cannot be used with types such '" & $t & "'")
+    of tyUserTypeClass, tyUserTypeClassInst:
+      error("non-invariant type parameters are not supported in concepts")
+    of tyTuple:
+      for fieldType in t.sons:
+        subresult traverseSubTypes(c, fieldType)
+    of tyPtr, tyRef, tyVar, tyLent:
+      if t.base.kind == tyGenericParam: return true
+      return traverseSubTypes(c, t.base)
+    of tyDistinct, tyAlias, tySink:
+      return traverseSubTypes(c, t.lastSon)
+    of tyGenericInst:
+      internalAssert c.config, false
+    else:
+      discard
+  discard traverseSubTypes(c, body)
 
 proc typeSectionRightSidePass(c: PContext, n: PNode) =
   for i in countup(0, sonsLen(n) - 1):
     var a = n.sons[i]
     if a.kind == nkCommentStmt: continue
-    if (a.kind != nkTypeDef): illFormedAst(a)
-    checkSonsLen(a, 3)
-    let name = a.sons[0]
-    if (name.kind != nkSym): illFormedAst(a)
+    if a.kind != nkTypeDef: illFormedAst(a, c.config)
+    checkSonsLen(a, 3, c.config)
+    let name = typeSectionTypeName(c, a.sons[0])
     var s = name.sym
     if s.magic == mNone and a.sons[2].kind == nkEmpty:
-      localError(a.info, errImplOfXexpected, s.name.s)
+      localError(c.config, a.info, errImplOfXexpected % s.name.s)
     if s.magic != mNone: processMagicType(c, s)
     if a.sons[1].kind != nkEmpty:
       # We have a generic type declaration here. In generic types,
@@ -781,6 +1039,25 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
         body.sym = s
         body.size = -1 # could not be computed properly
         s.typ.sons[sonsLen(s.typ) - 1] = body
+        if tfCovariant in s.typ.flags:
+          checkCovariantParamsUsages(c, s.typ)
+          # XXX: This is a temporary limitation:
+          # The codegen currently produces various failures with
+          # generic imported types that have fields, but we need
+          # the fields specified in order to detect weak covariance.
+          # The proper solution is to teach the codegen how to handle
+          # such types, because this would offer various interesting
+          # possibilities such as instantiating C++ generic types with
+          # garbage collected Nim types.
+          if sfImportc in s.flags:
+            var body = s.typ.lastSon
+            if body.kind == tyObject:
+              # erases all declared fields
+              when defined(nimNoNilSeqs):
+                body.n.sons = @[]
+              else:
+                body.n.sons = nil
+
       popOwner(c)
       closeScope(c)
     elif a.sons[2].kind != nkEmpty:
@@ -801,45 +1078,45 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
       # give anonymous object a dummy symbol:
       var st = s.typ
       if st.kind == tyGenericBody: st = st.lastSon
-      internalAssert st.kind in {tyPtr, tyRef}
-      internalAssert st.lastSon.sym == nil
+      internalAssert c.config, st.kind in {tyPtr, tyRef}
+      internalAssert c.config, st.lastSon.sym == nil
       incl st.flags, tfRefsAnonObj
-      let obj = newSym(skType, getIdent(s.name.s & ":ObjectType"),
+      let obj = newSym(skType, getIdent(c.cache, s.name.s & ":ObjectType"),
                               getCurrOwner(c), s.info)
       obj.typ = st.lastSon
       st.lastSon.sym = obj
 
 
-proc checkForMetaFields(n: PNode) =
+proc checkForMetaFields(c: PContext; n: PNode) =
   template checkMeta(t) =
     if t != nil and t.isMetaType and tfGenericTypeParam notin t.flags:
-      localError(n.info, errTIsNotAConcreteType, t.typeToString)
+      localError(c.config, n.info, errTIsNotAConcreteType % t.typeToString)
 
   if n.isNil: return
   case n.kind
   of nkRecList, nkRecCase:
-    for s in n: checkForMetaFields(s)
+    for s in n: checkForMetaFields(c, s)
   of nkOfBranch, nkElse:
-    checkForMetaFields(n.lastSon)
+    checkForMetaFields(c, n.lastSon)
   of nkSym:
     let t = n.sym.typ
     case t.kind
-    of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyPtr, tyRef,
-       tyProc, tyGenericInvocation, tyGenericInst, tyAlias:
+    of tySequence, tySet, tyArray, tyOpenArray, tyVar, tyLent, tyPtr, tyRef,
+       tyProc, tyGenericInvocation, tyGenericInst, tyAlias, tySink:
       let start = ord(t.kind in {tyGenericInvocation, tyGenericInst})
-      for i in start .. <t.sons.len:
+      for i in start ..< t.len:
         checkMeta(t.sons[i])
     else:
       checkMeta(t)
   else:
-    internalAssert false
+    internalAssert c.config, false
 
 proc typeSectionFinalPass(c: PContext, n: PNode) =
   for i in countup(0, sonsLen(n) - 1):
     var a = n.sons[i]
     if a.kind == nkCommentStmt: continue
-    if a.sons[0].kind != nkSym: illFormedAst(a)
-    var s = a.sons[0].sym
+    let name = typeSectionTypeName(c, a.sons[0])
+    var s = name.sym
     # compute the type's size and check for illegal recursions:
     if a.sons[1].kind == nkEmpty:
       var x = a[2]
@@ -850,28 +1127,33 @@ proc typeSectionFinalPass(c: PContext, n: PNode) =
         # type aliases are hard:
         var t = semTypeNode(c, x, nil)
         assert t != nil
-        if t.kind in {tyObject, tyEnum, tyDistinct}:
-          assert s.typ != nil
-          if s.typ.kind != tyAlias:
+        if s.typ != nil and s.typ.kind notin {tyAlias, tySink}:
+          if t.kind in {tyProc, tyGenericInst} and not t.isMetaType:
+            assignType(s.typ, t)
+            s.typ.id = t.id
+          elif t.kind in {tyObject, tyEnum, tyDistinct}:
+            assert s.typ != nil
             assignType(s.typ, t)
             s.typ.id = t.id     # same id
-      checkConstructedType(s.info, s.typ)
+      checkConstructedType(c.config, s.info, s.typ)
       if s.typ.kind in {tyObject, tyTuple} and not s.typ.n.isNil:
-        checkForMetaFields(s.typ.n)
+        checkForMetaFields(c, s.typ.n)
+  instAllTypeBoundOp(c, n.info)
+
 
 proc semAllTypeSections(c: PContext; n: PNode): PNode =
   proc gatherStmts(c: PContext; n: PNode; result: PNode) {.nimcall.} =
     case n.kind
     of nkIncludeStmt:
       for i in 0..<n.len:
-        var f = checkModuleName(n.sons[i])
+        var f = checkModuleName(c.config, n.sons[i])
         if f != InvalidFileIDX:
-          if containsOrIncl(c.includedFiles, f):
-            localError(n.info, errRecursiveDependencyX, f.toFilename)
+          if containsOrIncl(c.includedFiles, f.int):
+            localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f))
           else:
-            let code = gIncludeFile(c.graph, c.module, f, c.cache)
+            let code = c.graph.includeFileCallback(c.graph, c.module, f)
             gatherStmts c, code, result
-            excl(c.includedFiles, f)
+            excl(c.includedFiles, f.int)
     of nkStmtList:
       for i in 0 ..< n.len:
         gatherStmts(c, n.sons[i], result)
@@ -912,21 +1194,23 @@ proc semTypeSection(c: PContext, n: PNode): PNode =
   ## to allow the type definitions in the section to reference each other
   ## without regard for the order of their definitions.
   if sfNoForward notin c.module.flags or nfSem notin n.flags:
+    inc c.inTypeContext
     typeSectionLeftSidePass(c, n)
     typeSectionRightSidePass(c, n)
     typeSectionFinalPass(c, n)
+    dec c.inTypeContext
   result = n
 
 proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) =
   s.typ = semProcTypeNode(c, n, genericParams, nil, s.kind)
   if s.kind notin {skMacro, skTemplate}:
     if s.typ.sons[0] != nil and s.typ.sons[0].kind == tyStmt:
-      localError(n.info, errGenerated, "invalid return type: 'stmt'")
+      localError(c.config, n.info, "invalid return type: 'stmt'")
 
 proc addParams(c: PContext, n: PNode, kind: TSymKind) =
   for i in countup(1, sonsLen(n)-1):
     if n.sons[i].kind == nkSym: addParamOrResult(c, n.sons[i].sym, kind)
-    else: illFormedAst(n)
+    else: illFormedAst(n, c.config)
 
 proc semBorrow(c: PContext, n: PNode, s: PSym) =
   # search for the correct alias:
@@ -934,12 +1218,15 @@ proc semBorrow(c: PContext, n: PNode, s: PSym) =
   if b != nil:
     # store the alias:
     n.sons[bodyPos] = newSymNode(b)
+    # Carry over the original symbol magic, this is necessary in order to ensure
+    # the semantic pass is correct
+    s.magic = b.magic
   else:
-    localError(n.info, errNoSymbolToBorrowFromFound)
+    localError(c.config, n.info, errNoSymbolToBorrowFromFound)
 
 proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) =
   if t != nil:
-    var s = newSym(skResult, getIdent"result", getCurrOwner(c), info)
+    var s = newSym(skResult, getIdent(c.cache, "result"), getCurrOwner(c), info)
     s.typ = t
     incl(s.flags, sfUsed)
     addParamOrResult(c, s, owner)
@@ -950,48 +1237,63 @@ proc addResultNode(c: PContext, n: PNode) =
 
 proc copyExcept(n: PNode, i: int): PNode =
   result = copyNode(n)
-  for j in 0.. <n.len:
+  for j in 0..<n.len:
     if j != i: result.add(n.sons[j])
 
-proc lookupMacro(c: PContext, n: PNode): PSym =
-  if n.kind == nkSym:
-    result = n.sym
-    if result.kind notin {skMacro, skTemplate}: result = nil
-  else:
-    result = searchInScopes(c, considerQuotedIdent(n), {skMacro, skTemplate})
-
 proc semProcAnnotation(c: PContext, prc: PNode;
                        validPragmas: TSpecialWords): PNode =
   var n = prc.sons[pragmasPos]
   if n == nil or n.kind == nkEmpty: return
-  for i in countup(0, <n.len):
+  for i in countup(0, n.len-1):
     var it = n.sons[i]
-    var key = if it.kind == nkExprColonExpr: it.sons[0] else: it
-    let m = lookupMacro(c, key)
-    if m == nil:
-      if key.kind == nkIdent and key.ident.id == ord(wDelegator):
-        if considerQuotedIdent(prc.sons[namePos]).s == "()":
-          prc.sons[namePos] = newIdentNode(c.cache.idDelegator, prc.info)
-          prc.sons[pragmasPos] = copyExcept(n, i)
-        else:
-          localError(prc.info, errOnlyACallOpCanBeDelegator)
+    var key = if it.kind in nkPragmaCallKinds and it.len >= 1: it.sons[0] else: it
+
+    if whichPragma(it) != wInvalid:
+      # Not a custom pragma
+      continue
+    elif strTableGet(c.userPragmas, considerQuotedIdent(c, key)) != nil:
+      # User-defined pragma
       continue
+
     # we transform ``proc p {.m, rest.}`` into ``m(do: proc p {.rest.})`` and
     # let the semantic checker deal with it:
-    var x = newNodeI(nkCall, n.info)
-    x.add(newSymNode(m))
+    var x = newNodeI(nkCall, key.info)
+    x.add(key)
+
+    if it.kind in nkPragmaCallKinds and it.len > 1:
+      # pass pragma arguments to the macro too:
+      for i in 1..<it.len:
+        x.add(it.sons[i])
+
+    # Drop the pragma from the list, this prevents getting caught in endless
+    # recursion when the nkCall is semanticized
     prc.sons[pragmasPos] = copyExcept(n, i)
-    if it.kind == nkExprColonExpr:
-      # pass pragma argument to the macro too:
-      x.add(it.sons[1])
+    if prc[pragmasPos].kind != nkEmpty and prc[pragmasPos].len == 0:
+      prc.sons[pragmasPos] = c.graph.emptyNode
+
     x.add(prc)
+
     # recursion assures that this works for multiple macro annotations too:
-    result = semStmt(c, x)
+    var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared})
+    if r == nil:
+      # Restore the old list of pragmas since we couldn't process this
+      prc.sons[pragmasPos] = n
+      # No matching macro was found but there's always the possibility this may
+      # be a .pragma. template instead
+      continue
+
+    doAssert r.sons[0].kind == nkSym
+    # Expand the macro here
+    result = semMacroExpr(c, r, r, r.sons[0].sym, {})
+
+    doAssert result != nil
+
     # since a proc annotation can set pragmas, we process these here again.
     # This is required for SqueakNim-like export pragmas.
     if result.kind in procDefs and result[namePos].kind == nkSym and
         result[pragmasPos].kind != nkEmpty:
       pragma(c, result[namePos].sym, result[pragmasPos], validPragmas)
+
     return
 
 proc setGenericParamsMisc(c: PContext; n: PNode): PNode =
@@ -1000,7 +1302,7 @@ proc setGenericParamsMisc(c: PContext; n: PNode): PNode =
   # issue https://github.com/nim-lang/Nim/issues/1713
   result = semGenericParamList(c, orig)
   if n.sons[miscPos].kind == nkEmpty:
-    n.sons[miscPos] = newTree(nkBracket, ast.emptyNode, orig)
+    n.sons[miscPos] = newTree(nkBracket, c.graph.emptyNode, orig)
   else:
     n.sons[miscPos].sons[1] = orig
   n.sons[genericParamsPos] = result
@@ -1011,7 +1313,7 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
   result = semProcAnnotation(c, n, lambdaPragmas)
   if result != nil: return result
   result = n
-  checkSonsLen(n, bodyPos + 1)
+  checkSonsLen(n, bodyPos + 1, c.config)
   var s: PSym
   if n[namePos].kind != nkSym:
     s = newSym(skProc, c.cache.idAnon, getCurrOwner(c), n.info)
@@ -1028,9 +1330,6 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
     gp = newNodeI(nkGenericParams, n.info)
 
   if n.sons[paramsPos].kind != nkEmpty:
-    #if n.kind == nkDo and not experimentalMode(c):
-    #  localError(n.sons[paramsPos].info,
-    #      "use the {.experimental.} pragma to enable 'do' with parameters")
     semParamList(c, n.sons[paramsPos], gp, s)
     # paramsTypeCheck(c, s.typ)
     if sonsLen(gp) > 0 and n.sons[genericParamsPos].kind == nkEmpty:
@@ -1040,35 +1339,28 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode =
     s.typ = newProcType(c, n.info)
   if n.sons[pragmasPos].kind != nkEmpty:
     pragma(c, s, n.sons[pragmasPos], lambdaPragmas)
-  s.options = gOptions
+  s.options = c.config.options
   if n.sons[bodyPos].kind != nkEmpty:
     if sfImportc in s.flags:
-      localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s)
+      localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s)
     #if efDetermineType notin flags:
     # XXX not good enough; see tnamedparamanonproc.nim
     if gp.len == 0 or (gp.len == 1 and tfRetType in gp[0].typ.flags):
       pushProcCon(c, s)
       addResult(c, s.typ.sons[0], n.info, skProc)
       addResultNode(c, n)
-      let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
-      n.sons[bodyPos] = transformBody(c.module, semBody, s)
+      s.ast[bodyPos] = hloBody(c, semProcBody(c, n.sons[bodyPos]))
+      trackProc(c.graph, s, s.ast[bodyPos])
       popProcCon(c)
     elif efOperand notin flags:
-      localError(n.info, errGenericLambdaNotAllowed)
+      localError(c.config, n.info, errGenericLambdaNotAllowed)
     sideEffectsCheck(c, s)
   else:
-    localError(n.info, errImplOfXexpected, s.name.s)
+    localError(c.config, n.info, errImplOfXexpected % s.name.s)
   closeScope(c)           # close scope for parameters
   popOwner(c)
   result.typ = s.typ
 
-proc semDo(c: PContext, n: PNode, flags: TExprFlags): PNode =
-  # 'do' without params produces a stmt:
-  if n[genericParamsPos].kind == nkEmpty and n[paramsPos].kind == nkEmpty:
-    result = semStmt(c, n[bodyPos])
-  else:
-    result = semLambda(c, n, flags)
-
 proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
   var n = n
 
@@ -1081,15 +1373,15 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
   result = n
   s.ast = result
   n.sons[namePos].sym = s
-  n.sons[genericParamsPos] = emptyNode
+  n.sons[genericParamsPos] = c.graph.emptyNode
   # for LL we need to avoid wrong aliasing
   let params = copyTree n.typ.n
   n.sons[paramsPos] = params
   s.typ = n.typ
   for i in 1..<params.len:
     if params[i].typ.kind in {tyTypeDesc, tyGenericParam,
-                              tyFromExpr, tyFieldAccessor}+tyTypeClasses:
-      localError(params[i].info, "cannot infer type of parameter: " &
+                              tyFromExpr}+tyTypeClasses:
+      localError(c.config, params[i].info, "cannot infer type of parameter: " &
                  params[i].sym.name.s)
     #params[i].sym.owner = s
   openScope(c)
@@ -1098,8 +1390,8 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode =
   pushProcCon(c, s)
   addResult(c, n.typ.sons[0], n.info, skProc)
   addResultNode(c, n)
-  let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
-  n.sons[bodyPos] = transformBody(c.module, semBody, s)
+  s.ast[bodyPos] = hloBody(c, semProcBody(c, n.sons[bodyPos]))
+  trackProc(c.graph, s, s.ast[bodyPos])
   popProcCon(c)
   popOwner(c)
   closeScope(c)
@@ -1119,7 +1411,7 @@ proc activate(c: PContext, n: PNode) =
     of nkLambdaKinds:
       discard semLambda(c, n, {})
     of nkCallKinds:
-      for i in 1 .. <n.len: activate(c, n[i])
+      for i in 1 ..< n.len: activate(c, n[i])
     else:
       discard
 
@@ -1131,10 +1423,26 @@ proc maybeAddResult(c: PContext, s: PSym, n: PNode) =
 
 proc semOverride(c: PContext, s: PSym, n: PNode) =
   case s.name.s.normalize
-  of "destroy", "=destroy":
-    doDestructorStuff(c, s, n)
-    if not experimentalMode(c):
-      localError n.info, "use the {.experimental.} pragma to enable destructors"
+  of "=destroy":
+    let t = s.typ
+    var noError = false
+    if t.len == 2 and t.sons[0] == nil and t.sons[1].kind == tyVar:
+      var obj = t.sons[1].sons[0]
+      while true:
+        incl(obj.flags, tfHasAsgn)
+        if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon
+        elif obj.kind == tyGenericInvocation: obj = obj.sons[0]
+        else: break
+      if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
+        if obj.destructor.isNil:
+          obj.destructor = s
+        else:
+          localError(c.config, n.info, errGenerated,
+            "cannot bind another '" & s.name.s & "' to: " & typeToString(obj))
+        noError = true
+    if not noError and sfSystemModule notin s.owner.flags:
+      localError(c.config, n.info, errGenerated,
+        "signature for '" & s.name.s & "' must be proc[T: object](x: var T)")
     incl(s.flags, sfUsed)
   of "deepcopy", "=deepcopy":
     if s.typ.len == 2 and
@@ -1147,19 +1455,19 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
         if t.kind == tyGenericBody: t = t.lastSon
         elif t.kind == tyGenericInvocation: t = t.sons[0]
         else: break
-      if t.kind in {tyObject, tyDistinct, tyEnum}:
+      if t.kind in {tyObject, tyDistinct, tyEnum, tySequence, tyString}:
         if t.deepCopy.isNil: t.deepCopy = s
         else:
-          localError(n.info, errGenerated,
+          localError(c.config, n.info, errGenerated,
                      "cannot bind another 'deepCopy' to: " & typeToString(t))
       else:
-        localError(n.info, errGenerated,
+        localError(c.config, n.info, errGenerated,
                    "cannot bind 'deepCopy' to: " & typeToString(t))
     else:
-      localError(n.info, errGenerated,
+      localError(c.config, n.info, errGenerated,
                  "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T")
     incl(s.flags, sfUsed)
-  of "=":
+  of "=", "=sink":
     if s.magic == mAsgn: return
     incl(s.flags, sfUsed)
     let t = s.typ
@@ -1173,30 +1481,33 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
       var objB = t.sons[2]
       while true:
         if objB.kind == tyGenericBody: objB = objB.lastSon
-        elif objB.kind == tyGenericInvocation: objB = objB.sons[0]
+        elif objB.kind in {tyGenericInvocation, tyGenericInst}:
+          objB = objB.sons[0]
         else: break
-      if obj.kind in {tyObject, tyDistinct} and sameType(obj, objB):
-        if obj.assignment.isNil:
-          obj.assignment = s
+      if obj.kind in {tyObject, tyDistinct, tySequence, tyString} and sameType(obj, objB):
+        let opr = if s.name.s == "=": addr(obj.assignment) else: addr(obj.sink)
+        if opr[].isNil:
+          opr[] = s
         else:
-          localError(n.info, errGenerated,
-                     "cannot bind another '=' to: " & typeToString(obj))
+          localError(c.config, n.info, errGenerated,
+                     "cannot bind another '" & s.name.s & "' to: " & typeToString(obj))
         return
-    localError(n.info, errGenerated,
-               "signature for '=' must be proc[T: object](x: var T; y: T)")
+    if sfSystemModule notin s.owner.flags:
+      localError(c.config, n.info, errGenerated,
+                "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
   else:
     if sfOverriden in s.flags:
-      localError(n.info, errGenerated,
+      localError(c.config, n.info, errGenerated,
                  "'destroy' or 'deepCopy' expected for 'override'")
 
-proc cursorInProcAux(n: PNode): bool =
-  if inCheckpoint(n.info) != cpNone: return true
+proc cursorInProcAux(conf: ConfigRef; n: PNode): bool =
+  if inCheckpoint(n.info, conf.m.trackPos) != cpNone: return true
   for i in 0..<n.safeLen:
-    if cursorInProcAux(n[i]): return true
+    if cursorInProcAux(conf, n[i]): return true
 
-proc cursorInProc(n: PNode): bool =
-  if n.info.fileIndex == gTrackPos.fileIndex:
-    result = cursorInProcAux(n)
+proc cursorInProc(conf: ConfigRef; n: PNode): bool =
+  if n.info.fileIndex == conf.m.trackPos.fileIndex:
+    result = cursorInProcAux(conf, n)
 
 type
   TProcCompilationSteps = enum
@@ -1222,14 +1533,14 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
     for col in countup(1, sonsLen(tt)-1):
       let t = tt.sons[col]
       if t != nil and t.kind == tyGenericInvocation:
-        var x = skipTypes(t.sons[0], {tyVar, tyPtr, tyRef, tyGenericInst,
+        var x = skipTypes(t.sons[0], {tyVar, tyLent, tyPtr, tyRef, tyGenericInst,
                                       tyGenericInvocation, tyGenericBody,
-                                      tyAlias})
+                                      tyAlias, tySink})
         if x.kind == tyObject and t.len-1 == n.sons[genericParamsPos].len:
           foundObj = true
-          x.methods.safeAdd((col,s))
+          x.methods.add((col,s))
     if not foundObj:
-      message(n.info, warnDeprecated, "generic method not attachable to object type")
+      message(c.config, n.info, warnDeprecated, "generic method not attachable to object type")
   else:
     # why check for the body? bug #2400 has none. Checking for sfForward makes
     # no sense either.
@@ -1237,7 +1548,7 @@ proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
     if hasObjParam(s):
       methodDef(c.graph, s, fromCache=false)
     else:
-      localError(n.info, errXNeedsParamObjectType, "method")
+      localError(c.config, n.info, "'method' needs a parameter that has an object type")
 
 proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
                 validPragmas: TSpecialWords,
@@ -1245,7 +1556,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
   result = semProcAnnotation(c, n, validPragmas)
   if result != nil: return result
   result = n
-  checkSonsLen(n, bodyPos + 1)
+  checkSonsLen(n, bodyPos + 1, c.config)
   var s: PSym
   var typeIsDetermined = false
   var isAnon = false
@@ -1275,6 +1586,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     s.ast = n
     #s.scope = c.currentScope
 
+  s.options = c.config.options
+
   # before compiling the proc body, set as current the scope
   # where the proc was declared
   let oldScope = c.currentScope
@@ -1302,7 +1615,9 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     n.sons[patternPos] = semPattern(c, n.sons[patternPos])
   if s.kind == skIterator:
     s.typ.flags.incl(tfIterator)
-
+  elif s.kind == skFunc:
+    incl(s.flags, sfNoSideEffect)
+    incl(s.typ.flags, tfNoSideEffect)
   var proto = searchForProc(c, oldScope, s)
   if proto == nil or isAnon:
     if s.kind == skIterator:
@@ -1311,7 +1626,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     else:
       s.typ.callConv = lastOptionEntry(c).defaultCC
     # add it here, so that recursive procs are possible:
-    if sfGenSym in s.flags: discard
+    if sfGenSym in s.flags:
+      if s.owner == nil: s.owner = getCurrOwner(c)
     elif kind in OverloadableSyms:
       if not typeIsDetermined:
         addInterfaceOverloadableSymAt(c, oldScope, s)
@@ -1322,12 +1638,24 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
       pragma(c, s, n.sons[pragmasPos], validPragmas)
     else:
       implicitPragmas(c, s, n, validPragmas)
+    styleCheckDef(c.config, s)
+    onDef(n[namePos].info, s)
   else:
     if n.sons[pragmasPos].kind != nkEmpty:
-      localError(n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX,
-        "'" & proto.name.s & "' from " & $proto.info)
-    if sfForward notin proto.flags:
-      wrongRedefinition(n.info, proto.name.s)
+      pragma(c, s, n.sons[pragmasPos], validPragmas)
+      # To ease macro generation that produce forwarded .async procs we now
+      # allow a bit redudancy in the pragma declarations. The rule is
+      # a prototype's pragma list must be a superset of the current pragma
+      # list.
+      # XXX This needs more checks eventually, for example that external
+      # linking names do agree:
+      if proto.typ.callConv != s.typ.callConv or proto.typ.flags < s.typ.flags:
+        localError(c.config, n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX %
+          ("'" & proto.name.s & "' from " & c.config$proto.info))
+    styleCheckDef(c.config, s)
+    onDefResolveForward(n[namePos].info, proto)
+    if sfForward notin proto.flags and proto.magic == mNone:
+      wrongRedefinition(c, n.info, proto.name.s, proto.info)
     excl(proto.flags, sfForward)
     closeScope(c)         # close scope with wrong parameter symbols
     openScope(c)          # open scope for old (correct) parameter symbols
@@ -1336,32 +1664,38 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
     addParams(c, proto.typ.n, proto.kind)
     proto.info = s.info       # more accurate line information
     s.typ = proto.typ
+    proto.options = s.options
     s = proto
     n.sons[genericParamsPos] = proto.ast.sons[genericParamsPos]
     n.sons[paramsPos] = proto.ast.sons[paramsPos]
     n.sons[pragmasPos] = proto.ast.sons[pragmasPos]
-    if n.sons[namePos].kind != nkSym: internalError(n.info, "semProcAux")
+    if n.sons[namePos].kind != nkSym: internalError(c.config, n.info, "semProcAux")
     n.sons[namePos].sym = proto
-    if importantComments() and not isNil(proto.ast.comment):
+    if importantComments(c.config) and proto.ast.comment.len > 0:
       n.comment = proto.ast.comment
     proto.ast = n             # needed for code generation
     popOwner(c)
     pushOwner(c, s)
-  s.options = gOptions
+
   if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n)
   if s.name.s[0] in {'.', '('}:
-    if s.name.s in [".", ".()", ".=", "()"] and not experimentalMode(c):
-      message(n.info, warnDeprecated, "overloaded '.' and '()' operators are now .experimental; " & s.name.s)
-  if n.sons[bodyPos].kind != nkEmpty:
+    if s.name.s in [".", ".()", ".="] and {destructor, dotOperators} * c.features == {}:
+      localError(c.config, n.info, "the overloaded " & s.name.s &
+        " operator has to be enabled with {.experimental: \"dotOperators\".}")
+    elif s.name.s == "()" and callOperator notin c.features:
+      localError(c.config, n.info, "the overloaded " & s.name.s &
+        " operator has to be enabled with {.experimental: \"callOperator\".}")
+
+  if n.sons[bodyPos].kind != nkEmpty and sfError notin s.flags:
     # for DLL generation it is annoying to check for sfImportc!
     if sfBorrow in s.flags:
-      localError(n.sons[bodyPos].info, errImplOfXNotAllowed, s.name.s)
+      localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s)
     let usePseudoGenerics = kind in {skMacro, skTemplate}
     # Macros and Templates can have generic parameters, but they are
     # only used for overload resolution (there is no instantiation of
     # the symbol, so we must process the body now)
-    if not usePseudoGenerics and gIdeCmd in {ideSug, ideCon} and not
-        cursorInProc(n.sons[bodyPos]):
+    if not usePseudoGenerics and c.config.ideCmd in {ideSug, ideCon} and not
+        cursorInProc(c.config, n.sons[bodyPos]):
       discard "speed up nimsuggest"
       if s.kind == skMethod: semMethodPrototype(c, s, n)
     else:
@@ -1375,27 +1709,28 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
 
         if lfDynamicLib notin s.loc.flags:
           # no semantic checking for importc:
-          let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos]))
+          s.ast[bodyPos] = hloBody(c, semProcBody(c, n.sons[bodyPos]))
           # unfortunately we cannot skip this step when in 'system.compiles'
           # context as it may even be evaluated in 'system.compiles':
-          n.sons[bodyPos] = transformBody(c.module, semBody, s)
+          trackProc(c.graph, s, s.ast[bodyPos])
       else:
         if s.typ.sons[0] != nil and kind != skIterator:
-          addDecl(c, newSym(skUnknown, getIdent"result", nil, n.info))
+          addDecl(c, newSym(skUnknown, getIdent(c.cache, "result"), nil, n.info))
 
         openScope(c)
         n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos])
         closeScope(c)
-        fixupInstantiatedSymbols(c, s)
+        if s.magic == mNone:
+          fixupInstantiatedSymbols(c, s)
         if s.kind == skMethod: semMethodPrototype(c, s, n)
       if sfImportc in s.flags:
         # so we just ignore the body after semantic checking for importc:
-        n.sons[bodyPos] = ast.emptyNode
+        n.sons[bodyPos] = c.graph.emptyNode
       popProcCon(c)
   else:
     if s.kind == skMethod: semMethodPrototype(c, s, n)
-    if proto != nil: localError(n.info, errImplOfXexpected, proto.name.s)
-    if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone:
+    if proto != nil: localError(c.config, n.info, errImplOfXexpected % proto.name.s)
+    if {sfImportc, sfBorrow, sfError} * s.flags == {} and s.magic == mNone:
       incl(s.flags, sfForward)
     elif sfBorrow in s.flags: semBorrow(c, n, s)
   sideEffectsCheck(c, s)
@@ -1404,10 +1739,12 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
   popOwner(c)
   if n.sons[patternPos].kind != nkEmpty:
     c.patterns.add(s)
-  if isAnon: result.typ = s.typ
+  if isAnon:
+    n.kind = nkLambda
+    result.typ = s.typ
   if isTopLevel(c) and s.kind != skIterator and
       s.typ.callConv == ccClosure:
-    localError(s.info, "'.closure' calling convention for top level routines is invalid")
+    localError(c.config, s.info, "'.closure' calling convention for top level routines is invalid")
 
 proc determineType(c: PContext, s: PSym) =
   if s.typ != nil: return
@@ -1423,12 +1760,16 @@ proc semIterator(c: PContext, n: PNode): PNode =
     n[namePos].sym.owner = getCurrOwner(c)
     n[namePos].sym.kind = skIterator
   result = semProcAux(c, n, skIterator, iteratorPragmas)
+  # bug #7093: if after a macro transformation we don't have an
+  # nkIteratorDef aynmore, return. The iterator then might have been
+  # sem'checked already. (Or not, if the macro skips it.)
+  if result.kind != n.kind: return
   var s = result.sons[namePos].sym
   var t = s.typ
   if t.sons[0] == nil and s.typ.callConv != ccClosure:
-    localError(n.info, errXNeedsReturnType, "iterator")
+    localError(c.config, n.info, "iterator needs a return type")
   if isAnon and s.typ.callConv == ccInline:
-    localError(n.info, "inline iterators are not first-class / cannot be assigned to variables")
+    localError(c.config, n.info, "inline iterators are not first-class / cannot be assigned to variables")
   # iterators are either 'inline' or 'closure'; for backwards compatibility,
   # we require first class iterators to be marked with 'closure' explicitly
   # -- at least for 0.9.2.
@@ -1436,22 +1777,24 @@ proc semIterator(c: PContext, n: PNode): PNode =
     incl(s.typ.flags, tfCapturesEnv)
   else:
     s.typ.callConv = ccInline
-  when false:
-    if s.typ.callConv != ccInline:
-      s.typ.callConv = ccClosure
-      # and they always at least use the 'env' for the state field:
-      incl(s.typ.flags, tfCapturesEnv)
   if n.sons[bodyPos].kind == nkEmpty and s.magic == mNone:
-    localError(n.info, errImplOfXexpected, s.name.s)
+    localError(c.config, n.info, errImplOfXexpected % s.name.s)
 
 proc semProc(c: PContext, n: PNode): PNode =
   result = semProcAux(c, n, skProc, procPragmas)
 
+proc semFunc(c: PContext, n: PNode): PNode =
+  result = semProcAux(c, n, skFunc, procPragmas)
+
 proc semMethod(c: PContext, n: PNode): PNode =
-  if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "method")
+  if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "method")
   result = semProcAux(c, n, skMethod, methodPragmas)
   # macros can transform converters to nothing:
   if namePos >= result.safeLen: return result
+  # bug #7093: if after a macro transformation we don't have an
+  # nkIteratorDef aynmore, return. The iterator then might have been
+  # sem'checked already. (Or not, if the macro skips it.)
+  if result.kind != nkMethodDef: return
   var s = result.sons[namePos].sym
   # we need to fix the 'auto' return type for the dispatcher here (see tautonotgeneric
   # test case):
@@ -1461,26 +1804,34 @@ proc semMethod(c: PContext, n: PNode): PNode =
     let ret = s.typ.sons[0]
     disp.typ.sons[0] = ret
     if disp.ast[resultPos].kind == nkSym:
-      if isEmptyType(ret): disp.ast.sons[resultPos] = emptyNode
+      if isEmptyType(ret): disp.ast.sons[resultPos] = c.graph.emptyNode
       else: disp.ast[resultPos].sym.typ = ret
 
 proc semConverterDef(c: PContext, n: PNode): PNode =
-  if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "converter")
-  checkSonsLen(n, bodyPos + 1)
+  if not isTopLevel(c): localError(c.config, n.info, errXOnlyAtModuleScope % "converter")
+  checkSonsLen(n, bodyPos + 1, c.config)
   result = semProcAux(c, n, skConverter, converterPragmas)
   # macros can transform converters to nothing:
   if namePos >= result.safeLen: return result
+  # bug #7093: if after a macro transformation we don't have an
+  # nkIteratorDef aynmore, return. The iterator then might have been
+  # sem'checked already. (Or not, if the macro skips it.)
+  if result.kind != nkConverterDef: return
   var s = result.sons[namePos].sym
   var t = s.typ
-  if t.sons[0] == nil: localError(n.info, errXNeedsReturnType, "converter")
-  if sonsLen(t) != 2: localError(n.info, errXRequiresOneArgument, "converter")
+  if t.sons[0] == nil: localError(c.config, n.info, errXNeedsReturnType % "converter")
+  if sonsLen(t) != 2: localError(c.config, n.info, "a converter takes exactly one argument")
   addConverter(c, s)
 
 proc semMacroDef(c: PContext, n: PNode): PNode =
-  checkSonsLen(n, bodyPos + 1)
+  checkSonsLen(n, bodyPos + 1, c.config)
   result = semProcAux(c, n, skMacro, macroPragmas)
   # macros can transform macros to nothing:
   if namePos >= result.safeLen: return result
+  # bug #7093: if after a macro transformation we don't have an
+  # nkIteratorDef aynmore, return. The iterator then might have been
+  # sem'checked already. (Or not, if the macro skips it.)
+  if result.kind != nkMacroDef: return
   var s = result.sons[namePos].sym
   var t = s.typ
   var allUntyped = true
@@ -1488,57 +1839,71 @@ proc semMacroDef(c: PContext, n: PNode): PNode =
     let param = t.n.sons[i].sym
     if param.typ.kind != tyExpr: allUntyped = false
   if allUntyped: incl(s.flags, sfAllUntyped)
-  if t.sons[0] == nil: localError(n.info, errXNeedsReturnType, "macro")
+  if t.sons[0] == nil: localError(c.config, n.info, "macro needs a return type")
   if n.sons[bodyPos].kind == nkEmpty:
-    localError(n.info, errImplOfXexpected, s.name.s)
+    localError(c.config, n.info, errImplOfXexpected % s.name.s)
+
+proc incMod(c: PContext, n: PNode, it: PNode, includeStmtResult: PNode) =
+  var f = checkModuleName(c.config, it)
+  if f != InvalidFileIDX:
+    if containsOrIncl(c.includedFiles, f.int):
+      localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f))
+    else:
+      addSon(includeStmtResult, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f), {}))
+      excl(c.includedFiles, f.int)
 
 proc evalInclude(c: PContext, n: PNode): PNode =
   result = newNodeI(nkStmtList, n.info)
   addSon(result, n)
   for i in countup(0, sonsLen(n) - 1):
-    var f = checkModuleName(n.sons[i])
-    if f != InvalidFileIDX:
-      if containsOrIncl(c.includedFiles, f):
-        localError(n.info, errRecursiveDependencyX, f.toFilename)
-      else:
-        addSon(result, semStmt(c, gIncludeFile(c.graph, c.module, f, c.cache)))
-        excl(c.includedFiles, f)
+    var imp: PNode
+    let it = n.sons[i]
+    if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
+      let sep = it[0]
+      let dir = it[1]
+      imp = newNodeI(nkInfix, it.info)
+      imp.add sep
+      imp.add dir
+      imp.add sep # dummy entry, replaced in the loop
+      for x in it[2]:
+        imp.sons[2] = x
+        incMod(c, n, imp, result)
+    else:
+      incMod(c, n, it, result)
 
 proc setLine(n: PNode, info: TLineInfo) =
-  for i in 0 .. <safeLen(n): setLine(n.sons[i], info)
+  for i in 0 ..< safeLen(n): setLine(n.sons[i], info)
   n.info = info
 
 proc semPragmaBlock(c: PContext, n: PNode): PNode =
+  checkSonsLen(n, 2, c.config)
   let pragmaList = n.sons[0]
   pragma(c, nil, pragmaList, exprPragmas)
-  result = semExpr(c, n.sons[1])
-  n.sons[1] = result
-  for i in 0 .. <pragmaList.len:
+  n[1] = semExpr(c, n[1])
+  result = n
+  result.typ = n[1].typ
+  for i in 0 ..< pragmaList.len:
     case whichPragma(pragmaList.sons[i])
     of wLine: setLine(result, pragmaList.sons[i].info)
-    of wLocks, wGcSafe:
-      result = n
-      result.typ = n.sons[1].typ
-    of wNoRewrite:
-      incl(result.flags, nfNoRewrite)
+    of wNoRewrite: incl(result.flags, nfNoRewrite)
     else: discard
 
 proc semStaticStmt(c: PContext, n: PNode): PNode =
   #echo "semStaticStmt"
   #writeStackTrace()
-  let a = semStmt(c, n.sons[0])
+  inc c.inStaticContext
+  openScope(c)
+  let a = semStmt(c, n.sons[0], {})
+  closeScope(c)
+  dec c.inStaticContext
   n.sons[0] = a
-  evalStaticStmt(c.module, c.cache, a, c.p.owner)
-  result = newNodeI(nkDiscardStmt, n.info, 1)
-  result.sons[0] = emptyNode
+  evalStaticStmt(c.module, c.graph, a, c.p.owner)
   when false:
-    result = evalStaticStmt(c.module, a, c.p.owner)
-    if result.isNil:
-      LocalError(n.info, errCannotInterpretNodeX, renderTree(n))
-      result = emptyNode
-    elif result.kind == nkEmpty:
-      result = newNodeI(nkDiscardStmt, n.info, 1)
-      result.sons[0] = emptyNode
+    # for incremental replays, keep the AST as required for replays:
+    result = n
+  else:
+    result = newNodeI(nkDiscardStmt, n.info, 1)
+    result.sons[0] = c.graph.emptyNode
 
 proc usesResult(n: PNode): bool =
   # nkStmtList(expr) properly propagates the void context,
@@ -1553,6 +1918,16 @@ proc usesResult(n: PNode): bool =
       for c in n:
         if usesResult(c): return true
 
+proc inferConceptStaticParam(c: PContext, inferred, n: PNode) =
+  var typ = inferred.typ
+  let res = semConstExpr(c, n)
+  if not sameType(res.typ, typ.base):
+    localError(c.config, n.info,
+      "cannot infer the concept parameter '%s', due to a type mismatch. " &
+      "attempt to equate '%s' and '%s'.",
+      [inferred.renderTree, $res.typ, $typ.base])
+  typ.n = res
+
 proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # these must be last statements in a block:
   const
@@ -1570,84 +1945,66 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
   #                                         nkNilLit, nkEmpty}:
   #  dec last
   for i in countup(0, length - 1):
-    let k = n.sons[i].kind
-    case k
-    of nkFinally, nkExceptBranch:
-      # stand-alone finally and except blocks are
-      # transformed into regular try blocks:
-      #
-      # var f = fopen("somefile") | var f = fopen("somefile")
-      # finally: fclose(f)        | try:
-      # ...                       |   ...
-      #                           | finally:
-      #                           |   fclose(f)
-      var deferPart: PNode
-      if k == nkDefer:
-        deferPart = newNodeI(nkFinally, n.sons[i].info)
-        deferPart.add n.sons[i].sons[0]
-      elif k == nkFinally:
-        message(n.info, warnDeprecated,
-                "use 'defer'; standalone 'finally'")
-        deferPart = n.sons[i]
-      else:
-        message(n.info, warnDeprecated,
-                "use an explicit 'try'; standalone 'except'")
-        deferPart = n.sons[i]
-      var tryStmt = newNodeI(nkTryStmt, n.sons[i].info)
-      var body = newNodeI(nkStmtList, n.sons[i].info)
-      if i < n.sonsLen - 1:
-        body.sons = n.sons[(i+1)..n.len-1]
-      tryStmt.addSon(body)
-      tryStmt.addSon(deferPart)
-      n.sons[i] = semTry(c, tryStmt)
-      n.sons.setLen(i+1)
+    var expr = semExpr(c, n.sons[i], flags)
+    n.sons[i] = expr
+    if c.matchedConcept != nil and expr.typ != nil and
+        (nfFromTemplate notin n.flags or i != last):
+      case expr.typ.kind
+      of tyBool:
+        if expr.kind == nkInfix and
+            expr[0].kind == nkSym and
+            expr[0].sym.name.s == "==":
+          if expr[1].typ.isUnresolvedStatic:
+            inferConceptStaticParam(c, expr[1], expr[2])
+            continue
+          elif expr[2].typ.isUnresolvedStatic:
+            inferConceptStaticParam(c, expr[2], expr[1])
+            continue
+
+        let verdict = semConstExpr(c, n[i])
+        if verdict.intVal == 0:
+          localError(c.config, result.info, "concept predicate failed")
+      of tyUnknown: continue
+      else: discard
+    if n.sons[i].typ == c.enforceVoidContext: #or usesResult(n.sons[i]):
+      voidContext = true
+      n.typ = c.enforceVoidContext
+    if i == last and (length == 1 or ({efWantValue, efInTypeof} * flags != {})):
       n.typ = n.sons[i].typ
-      return
+      if not isEmptyType(n.typ): n.kind = nkStmtListExpr
+    elif i != last or voidContext:
+      discardCheck(c, n.sons[i], flags)
     else:
-      n.sons[i] = semExpr(c, n.sons[i])
-      if c.inTypeClass > 0 and n[i].typ != nil:
-        case n[i].typ.kind
-        of tyBool:
-          let verdict = semConstExpr(c, n[i])
-          if verdict.intVal == 0:
-            localError(result.info, "type class predicate failed")
-        of tyUnknown: continue
-        else: discard
-      if n.sons[i].typ == enforceVoidContext: #or usesResult(n.sons[i]):
-        voidContext = true
-        n.typ = enforceVoidContext
-      if i == last and (length == 1 or efWantValue in flags):
-        n.typ = n.sons[i].typ
-        if not isEmptyType(n.typ): n.kind = nkStmtListExpr
-      elif i != last or voidContext:
-        discardCheck(c, n.sons[i])
-      else:
-        n.typ = n.sons[i].typ
-        if not isEmptyType(n.typ): n.kind = nkStmtListExpr
-      case n.sons[i].kind
-      of LastBlockStmts:
-        for j in countup(i + 1, length - 1):
-          case n.sons[j].kind
-          of nkPragma, nkCommentStmt, nkNilLit, nkEmpty: discard
-          else: localError(n.sons[j].info, errStmtInvalidAfterReturn)
-      else: discard
-  if result.len == 1 and result.sons[0].kind != nkDefer:
+      n.typ = n.sons[i].typ
+      if not isEmptyType(n.typ): n.kind = nkStmtListExpr
+    if n.sons[i].kind in LastBlockStmts or
+        n.sons[i].kind in nkCallKinds and n.sons[i][0].kind == nkSym and
+        sfNoReturn in n.sons[i][0].sym.flags:
+      for j in countup(i + 1, length - 1):
+        case n.sons[j].kind
+        of nkPragma, nkCommentStmt, nkNilLit, nkEmpty, nkBlockExpr,
+            nkBlockStmt, nkState: discard
+        else: localError(c.config, n.sons[j].info,
+          "unreachable statement after 'return' statement or '{.noReturn.}' proc")
+    else: discard
+
+  if result.len == 1 and
+     # concept bodies should be preserved as a stmt list:
+     c.matchedConcept == nil and
+     # also, don't make life complicated for macros.
+     # they will always expect a proper stmtlist:
+     nfBlockArg notin n.flags and
+     result.sons[0].kind != nkDefer:
     result = result.sons[0]
+
   when defined(nimfix):
     if result.kind == nkCommentStmt and not result.comment.isNil and
         not (result.comment[0] == '#' and result.comment[1] == '#'):
       # it is an old-style comment statement: we replace it with 'discard ""':
       prettybase.replaceComment(result.info)
-  when false:
-    # a statement list (s; e) has the type 'e':
-    if result.kind == nkStmtList and result.len > 0:
-      var lastStmt = lastSon(result)
-      if lastStmt.kind != nkNilLit and not implicitlyDiscardable(lastStmt):
-        result.typ = lastStmt.typ
-        #localError(lastStmt.info, errGenerated,
-        #  "Last expression must be explicitly returned if it " &
-        #  "is discardable or discarded")
-
-proc semStmt(c: PContext, n: PNode): PNode =
-  # now: simply an alias:
-  result = semExprNoType(c, n)
+
+proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode =
+  if efInTypeof notin flags:
+    result = semExprNoType(c, n)
+  else:
+    result = semExpr(c, n, flags)
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 5ac2e678a..14507cf9d 100644
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -12,20 +12,23 @@
 discard """
   hygienic templates:
 
-    template `||` (a, b: expr): expr =
+    template `||` (a, b: untyped): untyped =
       let aa = a
       if aa: aa else: b
 
     var
       a, b: T
 
-    a || b || a
+    echo a || b || a
 
   Each evaluation context has to be different and we need to perform
   some form of preliminary symbol lookup in template definitions. Hygiene is
   a way to achieve lexical scoping at compile time.
 """
 
+const
+  errImplOfXNotAllowed = "implementation of '$1' is not allowed"
+
 type
   TSymBinding = enum
     spNone, spGenSym, spInject
@@ -60,7 +63,8 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode =
     # (s.kind notin routineKinds or s.magic != mNone):
     # for instance 'nextTry' is both in tables.nim and astalgo.nim ...
     result = newSymNode(s, n.info)
-    markUsed(n.info, s, c.graph.usageSym)
+    markUsed(c.config, n.info, s, c.graph.usageSym)
+    onUse(n.info, s)
   else:
     # semantic checking requires a type; ``fitNode`` deals with it
     # appropriately
@@ -72,10 +76,11 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode =
       if a.kind != skModule:
         incl(a.flags, sfUsed)
         addSon(result, newSymNode(a, n.info))
+        onUse(n.info, a)
       a = nextOverloadIter(o, c, n)
 
 proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode =
-  for i in 0 .. < n.len:
+  for i in 0 ..< n.len:
     var a = n.sons[i]
     # If 'a' is an overloaded symbol, we used to use the first symbol
     # as a 'witness' and use the fact that subsequent lookups will yield
@@ -91,20 +96,20 @@ proc semBindStmt(c: PContext, n: PNode, toBind: var IntSet): PNode =
       else:
         for x in items(sc): toBind.incl(x.sym.id)
     else:
-      illFormedAst(a)
+      illFormedAst(a, c.config)
   result = newNodeI(nkEmpty, n.info)
 
 proc semMixinStmt(c: PContext, n: PNode, toMixin: var IntSet): PNode =
-  for i in 0 .. < n.len:
-    toMixin.incl(considerQuotedIdent(n.sons[i]).id)
+  for i in 0 ..< n.len:
+    toMixin.incl(considerQuotedIdent(c, n.sons[i]).id)
   result = newNodeI(nkEmpty, n.info)
 
-proc replaceIdentBySym(n: var PNode, s: PNode) =
+proc replaceIdentBySym(c: PContext; n: var PNode, s: PNode) =
   case n.kind
-  of nkPostfix: replaceIdentBySym(n.sons[1], s)
-  of nkPragmaExpr: replaceIdentBySym(n.sons[0], s)
+  of nkPostfix: replaceIdentBySym(c, n.sons[1], s)
+  of nkPragmaExpr: replaceIdentBySym(c, n.sons[0], s)
   of nkIdent, nkAccQuoted, nkSym: n = s
-  else: illFormedAst(n)
+  else: illFormedAst(n, c.config)
 
 type
   TemplCtx = object
@@ -113,13 +118,9 @@ type
     owner: PSym
     cursorInBody: bool # only for nimsuggest
     scopeN: int
-    bracketExpr: PNode
 
 template withBracketExpr(ctx, x, body: untyped) =
-  let old = ctx.bracketExpr
-  ctx.bracketExpr = x
   body
-  ctx.bracketExpr = old
 
 proc getIdentNode(c: var TemplCtx, n: PNode): PNode =
   case n.kind
@@ -133,7 +134,7 @@ proc getIdentNode(c: var TemplCtx, n: PNode): PNode =
         result = newSymNode(s, n.info)
   of nkAccQuoted, nkSym: result = n
   else:
-    illFormedAst(n)
+    illFormedAst(n, c.c.config)
     result = n
 
 proc isTemplParam(c: TemplCtx, n: PNode): bool {.inline.} =
@@ -161,13 +162,13 @@ proc onlyReplaceParams(c: var TemplCtx, n: PNode): PNode =
       if s.owner == c.owner and s.kind == skParam:
         incl(s.flags, sfUsed)
         result = newSymNode(s, n.info)
-        styleCheckUse(n.info, s)
+        onUse(n.info, s)
   else:
-    for i in 0 .. <n.safeLen:
+    for i in 0 ..< n.safeLen:
       result.sons[i] = onlyReplaceParams(c, n.sons[i])
 
 proc newGenSym(kind: TSymKind, n: PNode, c: var TemplCtx): PSym =
-  result = newSym(kind, considerQuotedIdent(n), c.owner, n.info)
+  result = newSym(kind, considerQuotedIdent(c.c, n), c.owner, n.info)
   incl(result.flags, sfGenSym)
   incl(result.flags, sfShadowed)
 
@@ -188,12 +189,12 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) =
         n = onlyReplaceParams(c, n)
         return
       else:
-        illFormedAst(x)
+        illFormedAst(x, c.c.config)
     let ident = getIdentNode(c, x)
     if not isTemplParam(c, ident):
       c.toInject.incl(x.ident.id)
     else:
-      replaceIdentBySym(n, ident)
+      replaceIdentBySym(c.c, n, ident)
   else:
     let ident = getIdentNode(c, n)
     if not isTemplParam(c, ident):
@@ -207,21 +208,22 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) =
       #
       # We need to ensure that both 'a' produce the same gensym'ed symbol.
       # So we need only check the *current* scope.
-      let s = localSearchInScope(c.c, considerQuotedIdent ident)
+      let s = localSearchInScope(c.c, considerQuotedIdent(c.c, ident))
       if s != nil and s.owner == c.owner and sfGenSym in s.flags:
-        styleCheckUse(n.info, s)
-        replaceIdentBySym(n, newSymNode(s, n.info))
-      else:
+        onUse(n.info, s)
+        replaceIdentBySym(c.c, n, newSymNode(s, n.info))
+      elif not (n.kind == nkSym and sfGenSym in n.sym.flags):
         let local = newGenSym(k, ident, c)
         addPrelimDecl(c.c, local)
-        styleCheckDef(n.info, local)
-        replaceIdentBySym(n, newSymNode(local, n.info))
+        styleCheckDef(c.c.config, n.info, local)
+        onDef(n.info, local)
+        replaceIdentBySym(c.c, n, newSymNode(local, n.info))
     else:
-      replaceIdentBySym(n, ident)
+      replaceIdentBySym(c.c, n, ident)
 
 proc semTemplSymbol(c: PContext, n: PNode, s: PSym): PNode =
   incl(s.flags, sfUsed)
-  # we do not call styleCheckUse here, as the identifier is not really
+  # we do not call onUse here, as the identifier is not really
   # resolved here. We will fixup the used identifiers later.
   case s.kind
   of skUnknown:
@@ -246,14 +248,14 @@ proc semRoutineInTemplName(c: var TemplCtx, n: PNode): PNode =
       if s.owner == c.owner and (s.kind == skParam or sfGenSym in s.flags):
         incl(s.flags, sfUsed)
         result = newSymNode(s, n.info)
-        styleCheckUse(n.info, s)
+        onUse(n.info, s)
   else:
     for i in countup(0, safeLen(n) - 1):
       result.sons[i] = semRoutineInTemplName(c, n.sons[i])
 
 proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode =
   result = n
-  checkSonsLen(n, bodyPos + 1)
+  checkSonsLen(n, bodyPos + 1, c.c.config)
   # routines default to 'inject':
   if n.kind notin nkLambdaKinds and symBinding(n.sons[pragmasPos]) == spGenSym:
     let ident = getIdentNode(c, n.sons[namePos])
@@ -261,7 +263,8 @@ proc semRoutineInTemplBody(c: var TemplCtx, n: PNode, k: TSymKind): PNode =
       var s = newGenSym(k, ident, c)
       s.ast = n
       addPrelimDecl(c.c, s)
-      styleCheckDef(n.info, s)
+      styleCheckDef(c.c.config, n.info, s)
+      onDef(n.info, s)
       n.sons[namePos] = newSymNode(s, n.sons[namePos].info)
     else:
       n.sons[namePos] = ident
@@ -285,8 +288,8 @@ proc semTemplSomeDecl(c: var TemplCtx, n: PNode, symKind: TSymKind; start=0) =
   for i in countup(start, sonsLen(n) - 1):
     var a = n.sons[i]
     if a.kind == nkCommentStmt: continue
-    if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a)
-    checkMinSonsLen(a, 3)
+    if (a.kind != nkIdentDefs) and (a.kind != nkVarTuple): illFormedAst(a, c.c.config)
+    checkMinSonsLen(a, 3, c.c.config)
     var L = sonsLen(a)
     when defined(nimsuggest):
       inc c.c.inTypeContext
@@ -301,24 +304,12 @@ proc semPattern(c: PContext, n: PNode): PNode
 
 proc semTemplBodySons(c: var TemplCtx, n: PNode): PNode =
   result = n
-  for i in 0.. < n.len:
+  for i in 0 ..< n.len:
     result.sons[i] = semTemplBody(c, n.sons[i])
 
-proc oprIsRoof(n: PNode): bool =
-  const roof = "^"
-  case n.kind
-  of nkIdent: result = n.ident.s == roof
-  of nkSym: result = n.sym.name.s == roof
-  of nkAccQuoted:
-    if n.len == 1:
-      result = oprIsRoof(n.sons[0])
-  of nkOpenSymChoice, nkClosedSymChoice:
-    result = oprIsRoof(n.sons[0])
-  else: discard
-
 proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
   result = n
-  semIdeForTemplateOrGenericCheck(n, c.cursorInBody)
+  semIdeForTemplateOrGenericCheck(c.c.config, n, c.cursorInBody)
   case n.kind
   of nkIdent:
     if n.ident.id in c.toInject: return n
@@ -327,7 +318,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
       if s.owner == c.owner and s.kind == skParam:
         incl(s.flags, sfUsed)
         result = newSymNode(s, n.info)
-        styleCheckUse(n.info, s)
+        onUse(n.info, s)
       elif contains(c.toBind, s.id):
         result = symChoice(c.c, n, s, scClosed)
       elif contains(c.toMixin, s.name.id):
@@ -337,7 +328,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
         # var yz: T
         incl(s.flags, sfUsed)
         result = newSymNode(s, n.info)
-        styleCheckUse(n.info, s)
+        onUse(n.info, s)
       else:
         result = semTemplSymbol(c.c, n, s)
   of nkBind:
@@ -347,15 +338,14 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
   of nkMixinStmt:
     if c.scopeN > 0: result = semTemplBodySons(c, n)
     else: result = semMixinStmt(c.c, n, c.toMixin)
-  of nkEmpty, nkSym..nkNilLit:
+  of nkEmpty, nkSym..nkNilLit, nkComesFrom:
     discard
   of nkIfStmt:
     for i in countup(0, sonsLen(n)-1):
       var it = n.sons[i]
       if it.len == 2:
-        when newScopeForIf: openScope(c)
+        openScope(c)
         it.sons[0] = semTemplBody(c, it.sons[0])
-        when not newScopeForIf: openScope(c)
         it.sons[1] = semTemplBody(c, it.sons[1])
         closeScope(c)
       else:
@@ -370,7 +360,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
     n.sons[0] = semTemplBody(c, n.sons[0])
     for i in countup(1, sonsLen(n)-1):
       var a = n.sons[i]
-      checkMinSonsLen(a, 1)
+      checkMinSonsLen(a, 1, c.c.config)
       var L = sonsLen(a)
       for j in countup(0, L-2):
         a.sons[j] = semTemplBody(c, a.sons[j])
@@ -382,10 +372,12 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
     n.sons[L-2] = semTemplBody(c, n.sons[L-2])
     for i in countup(0, L - 3):
       addLocalDecl(c, n.sons[i], skForVar)
+    openScope(c)
     n.sons[L-1] = semTemplBody(c, n.sons[L-1])
     closeScope(c)
+    closeScope(c)
   of nkBlockStmt, nkBlockExpr, nkBlockType:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.c.config)
     openScope(c)
     if n.sons[0].kind != nkEmpty:
       addLocalDecl(c, n.sons[0], skLabel)
@@ -393,16 +385,17 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
         # labels are always 'gensym'ed:
         let s = newGenSym(skLabel, n.sons[0], c)
         addPrelimDecl(c.c, s)
-        styleCheckDef(s)
+        styleCheckDef(c.c.config, s)
+        onDef(n[0].info, s)
         n.sons[0] = newSymNode(s, n.sons[0].info)
     n.sons[1] = semTemplBody(c, n.sons[1])
     closeScope(c)
   of nkTryStmt:
-    checkMinSonsLen(n, 2)
+    checkMinSonsLen(n, 2, c.c.config)
     n.sons[0] = semTemplBodyScope(c, n.sons[0])
     for i in countup(1, sonsLen(n)-1):
       var a = n.sons[i]
-      checkMinSonsLen(a, 1)
+      checkMinSonsLen(a, 1, c.c.config)
       var L = sonsLen(a)
       openScope(c)
       for j in countup(0, L-2):
@@ -416,15 +409,15 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
   of nkVarSection: semTemplSomeDecl(c, n, skVar)
   of nkLetSection: semTemplSomeDecl(c, n, skLet)
   of nkFormalParams:
-    checkMinSonsLen(n, 1)
+    checkMinSonsLen(n, 1, c.c.config)
     n.sons[0] = semTemplBody(c, n.sons[0])
     semTemplSomeDecl(c, n, skParam, 1)
   of nkConstSection:
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
       if a.kind == nkCommentStmt: continue
-      if (a.kind != nkConstDef): illFormedAst(a)
-      checkSonsLen(a, 3)
+      if (a.kind != nkConstDef): illFormedAst(a, c.c.config)
+      checkSonsLen(a, 3, c.c.config)
       addLocalDecl(c, a.sons[0], skConst)
       a.sons[1] = semTemplBody(c, a.sons[1])
       a.sons[2] = semTemplBody(c, a.sons[2])
@@ -432,14 +425,14 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
       if a.kind == nkCommentStmt: continue
-      if (a.kind != nkTypeDef): illFormedAst(a)
-      checkSonsLen(a, 3)
+      if (a.kind != nkTypeDef): illFormedAst(a, c.c.config)
+      checkSonsLen(a, 3, c.c.config)
       addLocalDecl(c, a.sons[0], skType)
     for i in countup(0, sonsLen(n) - 1):
       var a = n.sons[i]
       if a.kind == nkCommentStmt: continue
-      if (a.kind != nkTypeDef): illFormedAst(a)
-      checkSonsLen(a, 3)
+      if (a.kind != nkTypeDef): illFormedAst(a, c.c.config)
+      checkSonsLen(a, 3, c.c.config)
       if a.sons[1].kind != nkEmpty:
         openScope(c)
         a.sons[1] = semTemplBody(c, a.sons[1])
@@ -449,6 +442,8 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
         a.sons[2] = semTemplBody(c, a.sons[2])
   of nkProcDef, nkLambdaKinds:
     result = semRoutineInTemplBody(c, n, skProc)
+  of nkFuncDef:
+    result = semRoutineInTemplBody(c, n, skFunc)
   of nkMethodDef:
     result = semRoutineInTemplBody(c, n, skMethod)
   of nkIteratorDef:
@@ -469,18 +464,18 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
         x.sons[1] = semTemplBody(c, x.sons[1])
   of nkBracketExpr:
     result = newNodeI(nkCall, n.info)
-    result.add newIdentNode(getIdent("[]"), n.info)
+    result.add newIdentNode(getIdent(c.c.cache, "[]"), n.info)
     for i in 0 ..< n.len: result.add(n[i])
     let n0 = semTemplBody(c, n.sons[0])
     withBracketExpr c, n0:
       result = semTemplBodySons(c, result)
   of nkCurlyExpr:
     result = newNodeI(nkCall, n.info)
-    result.add newIdentNode(getIdent("{}"), n.info)
+    result.add newIdentNode(getIdent(c.c.cache, "{}"), n.info)
     for i in 0 ..< n.len: result.add(n[i])
     result = semTemplBodySons(c, result)
   of nkAsgn, nkFastAsgn:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.c.config)
     let a = n.sons[0]
     let b = n.sons[1]
 
@@ -488,7 +483,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
     case k
     of nkBracketExpr:
       result = newNodeI(nkCall, n.info)
-      result.add newIdentNode(getIdent("[]="), n.info)
+      result.add newIdentNode(getIdent(c.c.cache, "[]="), n.info)
       for i in 0 ..< a.len: result.add(a[i])
       result.add(b)
       let a0 = semTemplBody(c, a.sons[0])
@@ -496,16 +491,16 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
         result = semTemplBodySons(c, result)
     of nkCurlyExpr:
       result = newNodeI(nkCall, n.info)
-      result.add newIdentNode(getIdent("{}="), n.info)
+      result.add newIdentNode(getIdent(c.c.cache, "{}="), n.info)
       for i in 0 ..< a.len: result.add(a[i])
       result.add(b)
       result = semTemplBodySons(c, result)
     else:
       result = semTemplBodySons(c, n)
   of nkCallKinds-{nkPostfix}:
-    result = semTemplBodySons(c, n)
-    if c.bracketExpr != nil and n.len == 2 and oprIsRoof(n.sons[0]):
-      result.add c.bracketExpr
+    # do not transform runnableExamples (bug #9143)
+    if not isRunnableExamples(n[0]):
+      result = semTemplBodySons(c, n)
   of nkDotExpr, nkAccQuoted:
     # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam',
     # so we use the generic code for nkDotExpr too
@@ -515,7 +510,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
       if s.owner == c.owner and s.kind == skParam and
           n.kind == nkAccQuoted and n.len == 1:
         incl(s.flags, sfUsed)
-        styleCheckUse(n.info, s)
+        onUse(n.info, s)
         return newSymNode(s, n.info)
       elif contains(c.toBind, s.id):
         return symChoice(c.c, n, s, scClosed)
@@ -529,7 +524,7 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
 
 proc semTemplBodyDirty(c: var TemplCtx, n: PNode): PNode =
   result = n
-  semIdeForTemplateOrGenericCheck(n, c.cursorInBody)
+  semIdeForTemplateOrGenericCheck(c.c.config, n, c.cursorInBody)
   case n.kind
   of nkIdent:
     let s = qualifiedLookUp(c.c, n, {})
@@ -542,7 +537,7 @@ proc semTemplBodyDirty(c: var TemplCtx, n: PNode): PNode =
     result = semTemplBodyDirty(c, n.sons[0])
   of nkBindStmt:
     result = semBindStmt(c.c, n, c.toBind)
-  of nkEmpty, nkSym..nkNilLit:
+  of nkEmpty, nkSym..nkNilLit, nkComesFrom:
     discard
   else:
     # dotExpr is ambiguous: note that we explicitly allow 'x.TemplateParam',
@@ -562,7 +557,8 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
     incl(s.flags, sfGlobal)
   else:
     s = semIdentVis(c, skTemplate, n.sons[0], {})
-  styleCheckDef(s)
+  styleCheckDef(c.config, s)
+  onDef(n[0].info, s)
   # check parameter list:
   #s.scope = c.currentScope
   pushOwner(c, s)
@@ -622,8 +618,11 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
   popOwner(c)
   s.ast = n
   result = n
-  if n.sons[bodyPos].kind == nkEmpty:
-    localError(n.info, errImplOfXexpected, s.name.s)
+  if sfCustomPragma in s.flags:
+    if n.sons[bodyPos].kind != nkEmpty:
+      localError(c.config, n.sons[bodyPos].info, errImplOfXNotAllowed % s.name.s)
+  elif n.sons[bodyPos].kind == nkEmpty:
+    localError(c.config, n.info, "implementation of '$1' expected" % s.name.s)
   var proto = searchForProc(c, c.currentScope, s)
   if proto == nil:
     addInterfaceOverloadableSymAt(c, c.currentScope, s)
@@ -642,7 +641,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode =
     # semtypes.addParamOrResult). Within the pattern we have to ensure
     # to use the param with the proper type though:
     incl(s.flags, sfUsed)
-    styleCheckUse(n.info, s)
+    onUse(n.info, s)
     let x = c.owner.typ.n.sons[s.position+1].sym
     assert x.name == s.name
     result = newSymNode(x, n.info)
@@ -666,7 +665,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode =
     if s != nil and s.owner == c.owner and s.kind == skParam:
       result = newParam(c, n, s)
     else:
-      localError(n.info, errInvalidExpression)
+      localError(c.c.config, n.info, "invalid expression")
       result = n
 
   proc stupidStmtListExpr(n: PNode): bool =
@@ -686,7 +685,7 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode =
     # we support '(pattern){x}' to bind a subpattern to a parameter 'x';
     # '(pattern){|x}' does the same but the matches will be gathered in 'x'
     if n.len != 2:
-      localError(n.info, errInvalidExpression)
+      localError(c.c.config, n.info, "invalid expression")
     elif n.sons[1].kind == nkIdent:
       n.sons[0] = semPatternBody(c, n.sons[0])
       n.sons[1] = expectParam(c, n.sons[1])
@@ -696,9 +695,9 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode =
         n.sons[0] = semPatternBody(c, n.sons[0])
         n.sons[1].sons[1] = expectParam(c, n.sons[1].sons[1])
       else:
-        localError(n.info, errInvalidExpression)
+        localError(c.c.config, n.info, "invalid expression")
     else:
-      localError(n.info, errInvalidExpression)
+      localError(c.c.config, n.info, "invalid expression")
   of nkStmtList, nkStmtListExpr:
     if stupidStmtListExpr(n):
       result = semPatternBody(c, n.lastSon)
@@ -713,28 +712,26 @@ proc semPatternBody(c: var TemplCtx, n: PNode): PNode =
       elif templToExpand(s):
         return semPatternBody(c, semTemplateExpr(c.c, n, s, {efNoSemCheck}))
 
-    if n.kind == nkInfix and n.sons[0].kind == nkIdent:
+    if n.kind == nkInfix and (let id = considerQuotedIdent(c.c, n[0]); id != nil):
       # we interpret `*` and `|` only as pattern operators if they occur in
       # infix notation, so that '`*`(a, b)' can be used for verbatim matching:
-      let opr = n.sons[0]
-      if opr.ident.s == "*" or opr.ident.s == "**":
+      if id.s == "*" or id.s == "**":
         result = newNodeI(nkPattern, n.info, n.len)
-        result.sons[0] = opr
+        result.sons[0] = newIdentNode(id, n.info)
         result.sons[1] = semPatternBody(c, n.sons[1])
         result.sons[2] = expectParam(c, n.sons[2])
         return
-      elif opr.ident.s == "|":
+      elif id.s == "|":
         result = newNodeI(nkPattern, n.info, n.len)
-        result.sons[0] = opr
+        result.sons[0] = newIdentNode(id, n.info)
         result.sons[1] = semPatternBody(c, n.sons[1])
         result.sons[2] = semPatternBody(c, n.sons[2])
         return
 
-    if n.kind == nkPrefix and n.sons[0].kind == nkIdent:
-      let opr = n.sons[0]
-      if opr.ident.s == "~":
+    if n.kind == nkPrefix and (let id = considerQuotedIdent(c.c, n[0]); id != nil):
+      if id.s == "~":
         result = newNodeI(nkPattern, n.info, n.len)
-        result.sons[0] = opr
+        result.sons[0] = newIdentNode(id, n.info)
         result.sons[1] = semPatternBody(c, n.sons[1])
         return
 
@@ -770,5 +767,5 @@ proc semPattern(c: PContext, n: PNode): PNode =
     if result.len == 1:
       result = result.sons[0]
     elif result.len == 0:
-      localError(n.info, errInvalidExpression)
+      localError(c.config, n.info, "a pattern cannot be empty")
   closeScope(c)
diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim
index e86b527d6..f4ff97ba4 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -10,6 +10,40 @@
 # this module does the semantic checking of type declarations
 # included from sem.nim
 
+const
+  errStringOrIdentNodeExpected = "string or ident node expected"
+  errStringLiteralExpected = "string literal expected"
+  errIntLiteralExpected = "integer literal expected"
+  errWrongNumberOfVariables = "wrong number of variables"
+  errInvalidOrderInEnumX = "invalid order in enum '$1'"
+  errOrdinalTypeExpected = "ordinal type expected"
+  errSetTooBig = "set is too large"
+  errBaseTypeMustBeOrdinal = "base type of a set must be an ordinal"
+  errInheritanceOnlyWithNonFinalObjects = "inheritance only works with non-final objects"
+  errXExpectsOneTypeParam = "'$1' expects one type parameter"
+  errArrayExpectsTwoTypeParams = "array expects two type parameters"
+  errInvalidVisibilityX = "invalid visibility: '$1'"
+  errInitHereNotAllowed = "initialization not allowed here"
+  errXCannotBeAssignedTo = "'$1' cannot be assigned to"
+  errIteratorNotAllowed = "iterators can only be defined at the module's top level"
+  errXNeedsReturnType = "$1 needs a return type"
+  errNoReturnTypeDeclared = "no return type declared"
+  errTIsNotAConcreteType = "'$1' is not a concrete type"
+  errTypeExpected = "type expected"
+  errXOnlyAtModuleScope = "'$1' is only allowed at top level"
+  errDuplicateCaseLabel = "duplicate case label"
+  errMacroBodyDependsOnGenericTypes = "the macro body cannot be compiled, " &
+    "because the parameter '$1' has a generic type"
+  errIllegalRecursionInTypeX = "illegal recursion in type '$1'"
+  errNoGenericParamsAllowedForX = "no generic parameters allowed for $1"
+  errInOutFlagNotExtern = "the '$1' modifier can be used only with imported types"
+
+const
+  mStaticTy = {mStatic}
+  mTypeTy = {mType, mTypeOf}
+  # XXX: This should be needed only temporarily until the C
+  # sources are rebuilt
+
 proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType =
   if prev == nil:
     result = newTypeS(kind, c)
@@ -34,14 +68,16 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
   base = nil
   result = newOrPrevType(tyEnum, prev, c)
   result.n = newNodeI(nkEnumTy, n.info)
-  checkMinSonsLen(n, 1)
+  checkMinSonsLen(n, 1, c.config)
   if n.sons[0].kind != nkEmpty:
     base = semTypeNode(c, n.sons[0].sons[0], nil)
     if base.kind != tyEnum:
-      localError(n.sons[0].info, errInheritanceOnlyWithEnums)
-    counter = lastOrd(base) + 1
+      localError(c.config, n.sons[0].info, "inheritance only works with an enum")
+    counter = lastOrd(c.config, base) + 1
   rawAddSon(result, base)
   let isPure = result.sym != nil and sfPure in result.sym.flags
+  var symbols: TStrTable
+  if isPure: initStrTable(symbols)
   var hasNull = false
   for i in countup(1, sonsLen(n) - 1):
     case n.sons[i].kind
@@ -54,20 +90,24 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
         if sonsLen(v) == 2:
           strVal = v.sons[1] # second tuple part is the string value
           if skipTypes(strVal.typ, abstractInst).kind in {tyString, tyCString}:
+            if not isOrdinalType(v.sons[0].typ):
+              localError(c.config, v.sons[0].info, errOrdinalTypeExpected)
             x = getOrdValue(v.sons[0]) # first tuple part is the ordinal
           else:
-            localError(strVal.info, errStringLiteralExpected)
+            localError(c.config, strVal.info, errStringLiteralExpected)
         else:
-          localError(v.info, errWrongNumberOfVariables)
+          localError(c.config, v.info, errWrongNumberOfVariables)
       of tyString, tyCString:
         strVal = v
         x = counter
       else:
+        if not isOrdinalType(v.typ):
+          localError(c.config, v.info, errOrdinalTypeExpected)
         x = getOrdValue(v)
       if i != 1:
         if x != counter: incl(result.flags, tfEnumHasHoles)
         if x < counter:
-          localError(n.sons[i].info, errInvalidOrderInEnumX, e.name.s)
+          localError(c.config, n.sons[i].info, errInvalidOrderInEnumX % e.name.s)
           x = counter
       e.ast = strVal # might be nil
       counter = x
@@ -76,7 +116,7 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
     of nkIdent, nkAccQuoted:
       e = newSymS(skEnumField, n.sons[i], c)
     else:
-      illFormedAst(n[i])
+      illFormedAst(n[i], c.config)
     e.typ = result
     e.position = int(counter)
     if e.position == 0: hasNull = true
@@ -85,8 +125,13 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
       incl(e.flags, sfExported)
       if not isPure: strTableAdd(c.module.tab, e)
     addSon(result.n, newSymNode(e))
-    styleCheckDef(e)
-    if sfGenSym notin e.flags and not isPure: addDecl(c, e)
+    styleCheckDef(c.config, e)
+    onDef(e.info, e)
+    if sfGenSym notin e.flags:
+      if not isPure: addDecl(c, e)
+      else: importPureEnumField(c, e)
+    if isPure and (let conflict = strTableInclReportConflict(symbols, e); conflict != nil):
+      wrongRedefinition(c, e.info, e.name.s, conflict.info)
     inc(counter)
   if not hasNull: incl(result.flags, tfNeedsInit)
 
@@ -95,47 +140,55 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
   if sonsLen(n) == 2:
     var base = semTypeNode(c, n.sons[1], nil)
     addSonSkipIntLit(result, base)
-    if base.kind in {tyGenericInst, tyAlias}: base = lastSon(base)
+    if base.kind in {tyGenericInst, tyAlias, tySink}: base = lastSon(base)
     if base.kind != tyGenericParam:
-      if not isOrdinalType(base):
-        localError(n.info, errOrdinalTypeExpected)
-      elif lengthOrd(base) > MaxSetElements:
-        localError(n.info, errSetTooBig)
+      if not isOrdinalType(base, allowEnumWithHoles = true):
+        localError(c.config, n.info, errOrdinalTypeExpected)
+      elif lengthOrd(c.config, base) > MaxSetElements:
+        localError(c.config, n.info, errSetTooBig)
   else:
-    localError(n.info, errXExpectsOneTypeParam, "set")
+    localError(c.config, n.info, errXExpectsOneTypeParam % "set")
     addSonSkipIntLit(result, errorType(c))
 
-proc semContainer(c: PContext, n: PNode, kind: TTypeKind, kindStr: string,
-                  prev: PType): PType =
-  result = newOrPrevType(kind, prev, c)
+proc semContainerArg(c: PContext; n: PNode, kindStr: string; result: PType) =
   if sonsLen(n) == 2:
     var base = semTypeNode(c, n.sons[1], nil)
     if base.kind == tyVoid:
-      localError(n.info, errTIsNotAConcreteType, typeToString(base))
+      localError(c.config, n.info, errTIsNotAConcreteType % typeToString(base))
     addSonSkipIntLit(result, base)
   else:
-    localError(n.info, errXExpectsOneTypeParam, kindStr)
+    localError(c.config, n.info, errXExpectsOneTypeParam % kindStr)
     addSonSkipIntLit(result, errorType(c))
 
+proc semContainer(c: PContext, n: PNode, kind: TTypeKind, kindStr: string,
+                  prev: PType): PType =
+  result = newOrPrevType(kind, prev, c)
+  semContainerArg(c, n, kindStr, result)
+
 proc semVarargs(c: PContext, n: PNode, prev: PType): PType =
   result = newOrPrevType(tyVarargs, prev, c)
   if sonsLen(n) == 2 or sonsLen(n) == 3:
     var base = semTypeNode(c, n.sons[1], nil)
     addSonSkipIntLit(result, base)
     if sonsLen(n) == 3:
-      result.n = newIdentNode(considerQuotedIdent(n.sons[2]), n.sons[2].info)
+      result.n = newIdentNode(considerQuotedIdent(c, n.sons[2]), n.sons[2].info)
   else:
-    localError(n.info, errXExpectsOneTypeParam, "varargs")
+    localError(c.config, n.info, errXExpectsOneTypeParam % "varargs")
     addSonSkipIntLit(result, errorType(c))
 
 proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
   if n.len < 1:
     result = newConstraint(c, kind)
   else:
-    let isCall = ord(n.kind in nkCallKinds+{nkBracketExpr})
+    let isCall = int ord(n.kind in nkCallKinds+{nkBracketExpr})
     let n = if n[0].kind == nkBracket: n[0] else: n
-    checkMinSonsLen(n, 1)
-    var base = semTypeNode(c, n.lastSon, nil)
+    checkMinSonsLen(n, 1, c.config)
+    var t = semTypeNode(c, n.lastSon, nil)
+    if t.kind == tyTypeDesc and tfUnresolved notin t.flags:
+      t = t.base
+    if t.kind == tyVoid:
+      const kindToStr: array[tyPtr..tyRef, string] = ["ptr", "ref"]
+      localError(c.config, n.info, "type '$1 void' is not allowed" % kindToStr[kind])
     result = newOrPrevType(kind, prev, c)
     var isNilable = false
     # check every except the last is an object:
@@ -145,19 +198,23 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType =
         isNilable = true
       else:
         let region = semTypeNode(c, ni, nil)
-        if region.skipTypes({tyGenericInst, tyAlias}).kind notin {
+        if region.skipTypes({tyGenericInst, tyAlias, tySink}).kind notin {
               tyError, tyObject}:
-          message n[i].info, errGenerated, "region needs to be an object type"
+          message c.config, n[i].info, errGenerated, "region needs to be an object type"
+        else:
+          message(c.config, n.info, warnDeprecated, "region for pointer types")
         addSonSkipIntLit(result, region)
-    addSonSkipIntLit(result, base)
+    addSonSkipIntLit(result, t)
+    if tfPartial in result.flags:
+      if result.lastSon.kind == tyObject: incl(result.lastSon.flags, tfPartial)
     #if not isNilable: result.flags.incl tfNotNil
 
 proc semVarType(c: PContext, n: PNode, prev: PType): PType =
   if sonsLen(n) == 1:
     result = newOrPrevType(tyVar, prev, c)
-    var base = semTypeNode(c, n.sons[0], nil)
+    var base = semTypeNode(c, n.sons[0], nil).skipTypes({tyTypeDesc})
     if base.kind == tyVar:
-      localError(n.info, errVarVarTypeNotAllowed)
+      localError(c.config, n.info, "type 'var var' is not allowed")
       base = base.sons[0]
     addSonSkipIntLit(result, base)
   else:
@@ -171,11 +228,15 @@ proc semDistinct(c: PContext, n: PNode, prev: PType): PType =
 
 proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
   assert isRange(n)
-  checkSonsLen(n, 3)
+  checkSonsLen(n, 3, c.config)
   result = newOrPrevType(tyRange, prev, c)
   result.n = newNodeI(nkRange, n.info)
+  # always create a 'valid' range type, but overwrite it later
+  # because 'semExprWithType' can raise an exception. See bug #6895.
+  addSonSkipIntLit(result, errorType(c))
+
   if (n[1].kind == nkEmpty) or (n[2].kind == nkEmpty):
-    localError(n.info, errRangeIsEmpty)
+    localError(c.config, n.info, "range is empty")
 
   var range: array[2, PNode]
   range[0] = semExprWithType(c, n[1], {efDetermineType})
@@ -185,23 +246,28 @@ proc semRangeAux(c: PContext, n: PNode, prev: PType): PType =
   for i in 0..1:
     rangeT[i] = range[i].typ.skipTypes({tyStatic}).skipIntLit
 
-  if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})):
-    localError(n.info, errPureTypeMismatch)
-  elif not rangeT[0].isOrdinalType:
-    localError(n.info, errOrdinalTypeExpected)
-  elif enumHasHoles(rangeT[0]):
-    localError(n.info, errEnumXHasHoles, rangeT[0].sym.name.s)
+  let hasUnknownTypes = c.inGenericContext > 0 and
+    rangeT[0].kind == tyFromExpr or rangeT[1].kind == tyFromExpr
+
+  if not hasUnknownTypes:
+    if not sameType(rangeT[0].skipTypes({tyRange}), rangeT[1].skipTypes({tyRange})):
+      localError(c.config, n.info, "type mismatch")
+    elif not rangeT[0].isOrdinalType and rangeT[0].kind notin tyFloat..tyFloat128:
+      localError(c.config, n.info, "ordinal or float type expected")
+    elif enumHasHoles(rangeT[0]):
+      localError(c.config, n.info, "enum '$1' has holes" % typeToString(rangeT[0]))
 
   for i in 0..1:
-    if hasGenericArguments(range[i]):
+    if hasUnresolvedArgs(c, range[i]):
       result.n.addSon makeStaticExpr(c, range[i])
+      result.flags.incl tfUnresolved
     else:
       result.n.addSon semConstExpr(c, range[i])
 
   if weakLeValue(result.n[0], result.n[1]) == impNo:
-    localError(n.info, errRangeIsEmpty)
+    localError(c.config, n.info, "range is empty")
 
-  addSonSkipIntLit(result, rangeT[0])
+  result[0] = rangeT[0]
 
 proc semRange(c: PContext, n: PNode, prev: PType): PType =
   result = nil
@@ -220,30 +286,38 @@ proc semRange(c: PContext, n: PNode, prev: PType): PType =
           n.sons[1].floatVal < 0.0:
         incl(result.flags, tfNeedsInit)
     else:
-      localError(n.sons[0].info, errRangeExpected)
+      if n[1].kind == nkInfix and considerQuotedIdent(c, n[1][0]).s == "..<":
+        localError(c.config, n[0].info, "range types need to be constructed with '..', '..<' is not supported")
+      else:
+        localError(c.config, n.sons[0].info, "expected range")
       result = newOrPrevType(tyError, prev, c)
   else:
-    localError(n.info, errXExpectsOneTypeParam, "range")
+    localError(c.config, n.info, errXExpectsOneTypeParam % "range")
     result = newOrPrevType(tyError, prev, c)
 
 proc semArrayIndex(c: PContext, n: PNode): PType =
-  if isRange(n): result = semRangeAux(c, n, nil)
+  if isRange(n):
+    result = semRangeAux(c, n, nil)
   else:
     let e = semExprWithType(c, n, {efDetermineType})
     if e.typ.kind == tyFromExpr:
       result = makeRangeWithStaticExpr(c, e.typ.n)
     elif e.kind in {nkIntLit..nkUInt64Lit}:
+      if e.intVal < 0:
+        localError(c.config, n[1].info,
+          "Array length can't be negative, but was " & $e.intVal)
       result = makeRangeType(c, 0, e.intVal-1, n.info, e.typ)
     elif e.kind == nkSym and e.typ.kind == tyStatic:
       if e.sym.ast != nil:
         return semArrayIndex(c, e.sym.ast)
       if not isOrdinalType(e.typ.lastSon):
-        localError(n[1].info, errOrdinalTypeExpected)
+        let info = if n.safeLen > 1: n[1].info else: n.info
+        localError(c.config, info, errOrdinalTypeExpected)
       result = makeRangeWithStaticExpr(c, e)
       if c.inGenericContext > 0: result.flags.incl tfUnresolved
-    elif e.kind in nkCallKinds and hasGenericArguments(e):
+    elif e.kind in (nkCallKinds + {nkBracketExpr}) and hasUnresolvedArgs(c, e):
       if not isOrdinalType(e.typ):
-        localError(n[1].info, errOrdinalTypeExpected)
+        localError(c.config, n[1].info, errOrdinalTypeExpected)
       # This is an int returning call, depending on an
       # yet unknown generic param (see tgenericshardcases).
       # We are going to construct a range type that will be
@@ -259,7 +333,7 @@ proc semArrayIndex(c: PContext, n: PNode): PType =
                              x.typ.skipTypes({tyTypeDesc}))
       else:
         result = x.typ.skipTypes({tyTypeDesc})
-        #localError(n[1].info, errConstExprExpected)
+        #localError(c.config, n[1].info, errConstExprExpected)
 
 proc semArray(c: PContext, n: PNode, prev: PType): PType =
   var base: PType
@@ -267,20 +341,24 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
     # 3 = length(array indx base)
     let indx = semArrayIndex(c, n[1])
     var indxB = indx
-    if indxB.kind in {tyGenericInst, tyAlias}: indxB = lastSon(indxB)
+    if indxB.kind in {tyGenericInst, tyAlias, tySink}: indxB = lastSon(indxB)
     if indxB.kind notin {tyGenericParam, tyStatic, tyFromExpr}:
-      if not isOrdinalType(indxB):
-        localError(n.sons[1].info, errOrdinalTypeExpected)
+      if indxB.skipTypes({tyRange}).kind in {tyUInt, tyUInt64}:
+        discard
+      elif not isOrdinalType(indxB):
+        localError(c.config, n.sons[1].info, errOrdinalTypeExpected)
       elif enumHasHoles(indxB):
-        localError(n.sons[1].info, errEnumXHasHoles,
+        localError(c.config, n.sons[1].info, "enum '$1' has holes" %
                    typeToString(indxB.skipTypes({tyRange})))
     base = semTypeNode(c, n.sons[2], nil)
     # ensure we only construct a tyArray when there was no error (bug #3048):
     result = newOrPrevType(tyArray, prev, c)
-    addSonSkipIntLit(result, indx)
+    # bug #6682: Do not propagate initialization requirements etc for the
+    # index type:
+    rawAddSonNoPropagationOfTypeFlags(result, indx)
     addSonSkipIntLit(result, base)
   else:
-    localError(n.info, errArrayExpectsTwoTypeParams)
+    localError(c.config, n.info, errArrayExpectsTwoTypeParams)
     result = newOrPrevType(tyError, prev, c)
 
 proc semOrdinal(c: PContext, n: PNode, prev: PType): PType =
@@ -289,25 +367,23 @@ proc semOrdinal(c: PContext, n: PNode, prev: PType): PType =
     var base = semTypeNode(c, n.sons[1], nil)
     if base.kind != tyGenericParam:
       if not isOrdinalType(base):
-        localError(n.sons[1].info, errOrdinalTypeExpected)
+        localError(c.config, n.sons[1].info, errOrdinalTypeExpected)
     addSonSkipIntLit(result, base)
   else:
-    localError(n.info, errXExpectsOneTypeParam, "ordinal")
+    localError(c.config, n.info, errXExpectsOneTypeParam % "ordinal")
     result = newOrPrevType(tyError, prev, c)
 
 proc semTypeIdent(c: PContext, n: PNode): PSym =
   if n.kind == nkSym:
     result = getGenSym(c, n.sym)
   else:
-    when defined(nimfix):
-      result = pickSym(c, n, skType)
-      if result.isNil:
-        result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared})
-    else:
+    result = pickSym(c, n, {skType, skGenericParam, skParam})
+    if result.isNil:
       result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared})
     if result != nil:
-      markUsed(n.info, result, c.graph.usageSym)
-      styleCheckUse(n.info, result)
+      markUsed(c.config, n.info, result, c.graph.usageSym)
+      onUse(n.info, result)
+
       if result.kind == skParam and result.typ.kind == tyTypeDesc:
         # This is a typedesc param. is it already bound?
         # it's not bound when it's used multiple times in the
@@ -317,10 +393,10 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
           if bound != nil: return bound
           return result
         if result.typ.sym == nil:
-          localError(n.info, errTypeExpected)
+          localError(c.config, n.info, errTypeExpected)
           return errorSym(c, n)
         result = result.typ.sym.copySym
-        result.typ = copyType(result.typ, result.typ.owner, true)
+        result.typ = exactReplica(result.typ)
         result.typ.flags.incl tfUnresolved
 
       if result.kind == skGenericParam:
@@ -331,10 +407,9 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
           result.typ.flags.excl tfWildcard
           return
         else:
-          localError(n.info, errTypeExpected)
+          localError(c.config, n.info, errTypeExpected)
           return errorSym(c, n)
-
-      if result.kind != skType:
+      if result.kind != skType and result.magic notin (mStaticTy + mTypeTy):
         # this implements the wanted ``var v: V, x: V`` feature ...
         var ov: TOverloadIter
         var amb = initOverloadIter(ov, c, n)
@@ -342,7 +417,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
           amb = nextOverloadIter(ov, c, n)
         if amb != nil: result = amb
         else:
-          if result.kind != skError: localError(n.info, errTypeExpected)
+          if result.kind != skError: localError(c.config, n.info, errTypeExpected)
           return errorSym(c, n)
       if result.typ.kind != tyGenericParam:
         # XXX get rid of this hack!
@@ -357,15 +432,15 @@ proc semTypeIdent(c: PContext, n: PNode): PSym =
         n.info = oldInfo
         n.typ = result.typ
     else:
-      localError(n.info, errIdentifierExpected)
+      localError(c.config, n.info, "identifier expected")
       result = errorSym(c, n)
 
 proc semAnonTuple(c: PContext, n: PNode, prev: PType): PType =
   if sonsLen(n) == 0:
-    localError(n.info, errTypeExpected)
+    localError(c.config, n.info, errTypeExpected)
   result = newOrPrevType(tyTuple, prev, c)
-  for i in countup(0, sonsLen(n) - 1):
-    addSonSkipIntLit(result, semTypeNode(c, n.sons[i], nil))
+  for it in n:
+    addSonSkipIntLit(result, semTypeNode(c, it, nil))
 
 proc semTuple(c: PContext, n: PNode, prev: PType): PType =
   var typ: PType
@@ -375,27 +450,28 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType =
   var counter = 0
   for i in countup(ord(n.kind == nkBracketExpr), sonsLen(n) - 1):
     var a = n.sons[i]
-    if (a.kind != nkIdentDefs): illFormedAst(a)
-    checkMinSonsLen(a, 3)
+    if (a.kind != nkIdentDefs): illFormedAst(a, c.config)
+    checkMinSonsLen(a, 3, c.config)
     var length = sonsLen(a)
     if a.sons[length - 2].kind != nkEmpty:
       typ = semTypeNode(c, a.sons[length - 2], nil)
     else:
-      localError(a.info, errTypeExpected)
+      localError(c.config, a.info, errTypeExpected)
       typ = errorType(c)
     if a.sons[length - 1].kind != nkEmpty:
-      localError(a.sons[length - 1].info, errInitHereNotAllowed)
+      localError(c.config, a.sons[length - 1].info, errInitHereNotAllowed)
     for j in countup(0, length - 3):
       var field = newSymG(skField, a.sons[j], c)
       field.typ = typ
       field.position = counter
       inc(counter)
       if containsOrIncl(check, field.name.id):
-        localError(a.sons[j].info, errAttemptToRedefine, field.name.s)
+        localError(c.config, a.sons[j].info, "attempt to redefine: '" & field.name.s & "'")
       else:
         addSon(result.n, newSymNode(field))
         addSonSkipIntLit(result, typ)
-      if gCmd == cmdPretty: styleCheckDef(a.sons[j].info, field)
+      styleCheckDef(c.config, a.sons[j].info, field)
+      onDef(field.info, field)
   if result.n.len == 0: result.n = nil
 
 proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
@@ -406,23 +482,23 @@ proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
       # for gensym'ed identifiers the identifier may already have been
       # transformed to a symbol and we need to use that here:
       result = newSymG(kind, n.sons[1], c)
-      var v = considerQuotedIdent(n.sons[0])
+      var v = considerQuotedIdent(c, n.sons[0])
       if sfExported in allowed and v.id == ord(wStar):
         incl(result.flags, sfExported)
       else:
         if not (sfExported in allowed):
-          localError(n.sons[0].info, errXOnlyAtModuleScope, "export")
+          localError(c.config, n.sons[0].info, errXOnlyAtModuleScope % "export")
         else:
-          localError(n.sons[0].info, errInvalidVisibilityX, renderTree(n[0]))
+          localError(c.config, n.sons[0].info, errInvalidVisibilityX % renderTree(n[0]))
     else:
-      illFormedAst(n)
+      illFormedAst(n, c.config)
   else:
     result = newSymG(kind, n, c)
 
 proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode,
                         allowed: TSymFlags): PSym =
   if n.kind == nkPragmaExpr:
-    checkSonsLen(n, 2)
+    checkSonsLen(n, 2, c.config)
     result = semIdentVis(c, kind, n.sons[0], allowed)
     case kind
     of skType:
@@ -435,7 +511,6 @@ proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode,
     else: discard
   else:
     result = semIdentVis(c, kind, n, allowed)
-  if gCmd == cmdPretty: styleCheckDef(n.info, result)
 
 proc checkForOverlap(c: PContext, t: PNode, currentEx, branchIndex: int) =
   let ex = t[branchIndex][currentEx].skipConv
@@ -443,10 +518,10 @@ proc checkForOverlap(c: PContext, t: PNode, currentEx, branchIndex: int) =
     for j in countup(0, sonsLen(t.sons[i]) - 2):
       if i == branchIndex and j == currentEx: break
       if overlap(t.sons[i].sons[j].skipConv, ex):
-        localError(ex.info, errDuplicateCaseLabel)
+        localError(c.config, ex.info, errDuplicateCaseLabel)
 
 proc semBranchRange(c: PContext, t, a, b: PNode, covered: var BiggestInt): PNode =
-  checkMinSonsLen(t, 1)
+  checkMinSonsLen(t, 1, c.config)
   let ac = semConstExpr(c, a)
   let bc = semConstExpr(c, b)
   let at = fitNode(c, t.sons[0].typ, ac, ac.info).skipConvTakeType
@@ -455,21 +530,21 @@ proc semBranchRange(c: PContext, t, a, b: PNode, covered: var BiggestInt): PNode
   result = newNodeI(nkRange, a.info)
   result.add(at)
   result.add(bt)
-  if emptyRange(ac, bc): localError(b.info, errRangeIsEmpty)
+  if emptyRange(ac, bc): localError(c.config, b.info, "range is empty")
   else: covered = covered + getOrdValue(bc) - getOrdValue(ac) + 1
 
 proc semCaseBranchRange(c: PContext, t, b: PNode,
                         covered: var BiggestInt): PNode =
-  checkSonsLen(b, 3)
+  checkSonsLen(b, 3, c.config)
   result = semBranchRange(c, t, b.sons[1], b.sons[2], covered)
 
 proc semCaseBranchSetElem(c: PContext, t, b: PNode,
                           covered: var BiggestInt): PNode =
   if isRange(b):
-    checkSonsLen(b, 3)
+    checkSonsLen(b, 3, c.config)
     result = semBranchRange(c, t, b.sons[1], b.sons[2], covered)
   elif b.kind == nkRange:
-    checkSonsLen(b, 2)
+    checkSonsLen(b, 2, c.config)
     result = semBranchRange(c, t, b.sons[0], b.sons[1], covered)
   else:
     result = fitNode(c, t.sons[0].typ, b, b.info)
@@ -477,8 +552,8 @@ proc semCaseBranchSetElem(c: PContext, t, b: PNode,
 
 proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int,
                    covered: var BiggestInt) =
-
-  for i in countup(0, sonsLen(branch) - 2):
+  let lastIndex = sonsLen(branch) - 2
+  for i in 0..lastIndex:
     var b = branch.sons[i]
     if b.kind == nkRange:
       branch.sons[i] = b
@@ -492,82 +567,92 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int,
         delSon(branch, 0)
         return
       elif r.kind notin {nkCurly, nkBracket} or len(r) == 0:
-        checkMinSonsLen(t, 1)
-        branch.sons[i] = skipConv(fitNode(c, t.sons[0].typ, r, r.info))
+        checkMinSonsLen(t, 1, c.config)
+        var tmp = fitNode(c, t.sons[0].typ, r, r.info)
+        # the call to fitNode may introduce a call to a converter
+        if tmp.kind in {nkHiddenCallConv}: tmp = semConstExpr(c, tmp)
+        branch.sons[i] = skipConv(tmp)
         inc(covered)
       else:
+        if r.kind == nkCurly:
+          r = deduplicate(c.config, r)
+
         # first element is special and will overwrite: branch.sons[i]:
         branch.sons[i] = semCaseBranchSetElem(c, t, r[0], covered)
+
         # other elements have to be added to ``branch``
-        for j in 1 .. <r.len:
+        for j in 1 ..< r.len:
           branch.add(semCaseBranchSetElem(c, t, r[j], covered))
           # caution! last son of branch must be the actions to execute:
-          var L = branch.len
-          swap(branch.sons[L-2], branch.sons[L-1])
+          swap(branch.sons[^2], branch.sons[^1])
+    checkForOverlap(c, t, i, branchIndex)
+
+  # Elements added above needs to be checked for overlaps.
+  for i in lastIndex.succ..(sonsLen(branch) - 2):
     checkForOverlap(c, t, i, branchIndex)
 
 proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
-                      father: PNode, rectype: PType)
+                      father: PNode, rectype: PType, hasCaseFields = false)
 proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int,
                    father: PNode, rectype: PType) =
   var a = copyNode(n)
-  checkMinSonsLen(n, 2)
-  semRecordNodeAux(c, n.sons[0], check, pos, a, rectype)
+  checkMinSonsLen(n, 2, c.config)
+  semRecordNodeAux(c, n.sons[0], check, pos, a, rectype, hasCaseFields = true)
   if a.sons[0].kind != nkSym:
-    internalError("semRecordCase: discriminant is no symbol")
+    internalError(c.config, "semRecordCase: discriminant is no symbol")
     return
   incl(a.sons[0].sym.flags, sfDiscriminant)
   var covered: BiggestInt = 0
   var typ = skipTypes(a.sons[0].typ, abstractVar-{tyTypeDesc})
   if not isOrdinalType(typ):
-    localError(n.info, errSelectorMustBeOrdinal)
-  elif firstOrd(typ) != 0:
-    localError(n.info, errGenerated, "low(" & $a.sons[0].sym.name.s &
+    localError(c.config, n.info, "selector must be of an ordinal type")
+  elif firstOrd(c.config, typ) != 0:
+    localError(c.config, n.info, "low(" & $a.sons[0].sym.name.s &
                                      ") must be 0 for discriminant")
-  elif lengthOrd(typ) > 0x00007FFF:
-    localError(n.info, errLenXinvalid, a.sons[0].sym.name.s)
+  elif lengthOrd(c.config, typ) > 0x00007FFF:
+    localError(c.config, n.info, "len($1) must be less than 32768" % a.sons[0].sym.name.s)
   var chckCovered = true
   for i in countup(1, sonsLen(n) - 1):
     var b = copyTree(n.sons[i])
     addSon(a, b)
     case n.sons[i].kind
     of nkOfBranch:
-      checkMinSonsLen(b, 2)
+      checkMinSonsLen(b, 2, c.config)
       semCaseBranch(c, a, b, i, covered)
     of nkElse:
       chckCovered = false
-      checkSonsLen(b, 1)
-    else: illFormedAst(n)
+      checkSonsLen(b, 1, c.config)
+    else: illFormedAst(n, c.config)
     delSon(b, sonsLen(b) - 1)
-    semRecordNodeAux(c, lastSon(n.sons[i]), check, pos, b, rectype)
-  if chckCovered and (covered != lengthOrd(a.sons[0].typ)):
-    localError(a.info, errNotAllCasesCovered)
+    semRecordNodeAux(c, lastSon(n.sons[i]), check, pos, b, rectype, hasCaseFields = true)
+  if chckCovered and covered != lengthOrd(c.config, a.sons[0].typ):
+    localError(c.config, a.info, "not all cases are covered")
   addSon(father, a)
 
 proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
-                      father: PNode, rectype: PType) =
+                      father: PNode, rectype: PType, hasCaseFields = false) =
   if n == nil: return
   case n.kind
   of nkRecWhen:
     var branch: PNode = nil   # the branch to take
     for i in countup(0, sonsLen(n) - 1):
       var it = n.sons[i]
-      if it == nil: illFormedAst(n)
+      if it == nil: illFormedAst(n, c.config)
       var idx = 1
       case it.kind
       of nkElifBranch:
-        checkSonsLen(it, 2)
+        checkSonsLen(it, 2, c.config)
         if c.inGenericContext == 0:
           var e = semConstBoolExpr(c, it.sons[0])
-          if e.kind != nkIntLit: internalError(e.info, "semRecordNodeAux")
+          if e.kind != nkIntLit: internalError(c.config, e.info, "semRecordNodeAux")
           elif e.intVal != 0 and branch == nil: branch = it.sons[1]
         else:
           it.sons[0] = forceBool(c, semExprWithType(c, it.sons[0]))
       of nkElse:
-        checkSonsLen(it, 1)
+        checkSonsLen(it, 1, c.config)
         if branch == nil: branch = it.sons[0]
         idx = 0
-      else: illFormedAst(n)
+      else: illFormedAst(n, c.config)
       if c.inGenericContext > 0:
         # use a new check intset here for each branch:
         var newCheck: IntSet
@@ -591,71 +676,74 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
       semRecordNodeAux(c, n.sons[i], check, pos, a, rectype)
     if a != father: addSon(father, a)
   of nkIdentDefs:
-    checkMinSonsLen(n, 3)
+    checkMinSonsLen(n, 3, c.config)
     var length = sonsLen(n)
     var a: PNode
     if father.kind != nkRecList and length>=4: a = newNodeI(nkRecList, n.info)
-    else: a = ast.emptyNode
+    else: a = newNodeI(nkEmpty, n.info)
     if n.sons[length-1].kind != nkEmpty:
-      localError(n.sons[length-1].info, errInitHereNotAllowed)
+      localError(c.config, n.sons[length-1].info, errInitHereNotAllowed)
     var typ: PType
     if n.sons[length-2].kind == nkEmpty:
-      localError(n.info, errTypeExpected)
+      localError(c.config, n.info, errTypeExpected)
       typ = errorType(c)
     else:
       typ = semTypeNode(c, n.sons[length-2], nil)
       propagateToOwner(rectype, typ)
-    let rec = rectype.sym
+    var fieldOwner = if c.inGenericContext > 0: c.getCurrOwner
+                     else: rectype.sym
     for i in countup(0, sonsLen(n)-3):
       var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported})
-      suggestSym(n.sons[i].info, f, c.graph.usageSym)
+      suggestSym(c.config, n.sons[i].info, f, c.graph.usageSym)
       f.typ = typ
       f.position = pos
-      if (rec != nil) and ({sfImportc, sfExportc} * rec.flags != {}) and
-          (f.loc.r == nil):
+      if fieldOwner != nil and
+         {sfImportc, sfExportc} * fieldOwner.flags != {} and
+         not hasCaseFields and f.loc.r == nil:
         f.loc.r = rope(f.name.s)
-        f.flags = f.flags + ({sfImportc, sfExportc} * rec.flags)
+        f.flags = f.flags + ({sfImportc, sfExportc} * fieldOwner.flags)
       inc(pos)
       if containsOrIncl(check, f.name.id):
-        localError(n.sons[i].info, errAttemptToRedefine, f.name.s)
+        localError(c.config, n.sons[i].info, "attempt to redefine: '" & f.name.s & "'")
       if a.kind == nkEmpty: addSon(father, newSymNode(f))
       else: addSon(a, newSymNode(f))
-      styleCheckDef(f)
+      styleCheckDef(c.config, f)
+      onDef(f.info, f)
     if a.kind != nkEmpty: addSon(father, a)
   of nkSym:
     # This branch only valid during generic object
     # inherited from generic/partial specialized parent second check.
     # There is no branch validity check here
     if containsOrIncl(check, n.sym.name.id):
-      localError(n.info, errAttemptToRedefine, n.sym.name.s)
+      localError(c.config, n.info, "attempt to redefine: '" & n.sym.name.s & "'")
     addSon(father, n)
   of nkEmpty: discard
-  else: illFormedAst(n)
+  else: illFormedAst(n, c.config)
 
 proc addInheritedFieldsAux(c: PContext, check: var IntSet, pos: var int,
                            n: PNode) =
   case n.kind
   of nkRecCase:
-    if (n.sons[0].kind != nkSym): internalError(n.info, "addInheritedFieldsAux")
+    if (n.sons[0].kind != nkSym): internalError(c.config, n.info, "addInheritedFieldsAux")
     addInheritedFieldsAux(c, check, pos, n.sons[0])
     for i in countup(1, sonsLen(n) - 1):
       case n.sons[i].kind
       of nkOfBranch, nkElse:
         addInheritedFieldsAux(c, check, pos, lastSon(n.sons[i]))
-      else: internalError(n.info, "addInheritedFieldsAux(record case branch)")
+      else: internalError(c.config, n.info, "addInheritedFieldsAux(record case branch)")
   of nkRecList:
     for i in countup(0, sonsLen(n) - 1):
       addInheritedFieldsAux(c, check, pos, n.sons[i])
   of nkSym:
     incl(check, n.sym.name.id)
     inc(pos)
-  else: internalError(n.info, "addInheritedFieldsAux()")
+  else: internalError(c.config, n.info, "addInheritedFieldsAux()")
 
 proc skipGenericInvocation(t: PType): PType {.inline.} =
   result = t
   if result.kind == tyGenericInvocation:
     result = result.sons[0]
-  while result.kind in {tyGenericInst, tyGenericBody, tyRef, tyPtr, tyAlias}:
+  while result.kind in {tyGenericInst, tyGenericBody, tyRef, tyPtr, tyAlias, tySink}:
     result = lastSon(result)
 
 proc addInheritedFields(c: PContext, check: var IntSet, pos: var int,
@@ -672,12 +760,12 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
   var pos = 0
   var base, realBase: PType = nil
   # n.sons[0] contains the pragmas (if any). We process these later...
-  checkSonsLen(n, 3)
+  checkSonsLen(n, 3, c.config)
   if n.sons[1].kind != nkEmpty:
     realBase = semTypeNode(c, n.sons[1].sons[0], nil)
     base = skipTypesOrNil(realBase, skipPtrs)
     if base.isNil:
-      localError(n.info, errIllegalRecursionInTypeX, "object")
+      localError(c.config, n.info, "cannot inherit from a type that is not an object type")
     else:
       var concreteBase = skipGenericInvocation(base)
       if concreteBase.kind in {tyObject, tyGenericParam,
@@ -690,10 +778,11 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
           addInheritedFields(c, check, pos, concreteBase)
       else:
         if concreteBase.kind != tyError:
-          localError(n.sons[1].info, errInheritanceOnlyWithNonFinalObjects)
+          localError(c.config, n.sons[1].info, "inheritance only works with non-final objects; " &
+             "to enable inheritance write '" & typeToString(realBase) & " of RootObj'")
         base = nil
         realBase = nil
-  if n.kind != nkObjectTy: internalError(n.info, "semObjectNode")
+  if n.kind != nkObjectTy: internalError(c.config, n.info, "semObjectNode")
   result = newOrPrevType(tyObject, prev, c)
   rawAddSon(result, realBase)
   if result.n.isNil:
@@ -704,7 +793,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
   semRecordNodeAux(c, n.sons[2], check, pos, result.n, result)
   if n.sons[0].kind != nkEmpty:
     # dummy symbol for `pragma`:
-    var s = newSymS(skType, newIdentNode(getIdent("dummy"), n.info), c)
+    var s = newSymS(skType, newIdentNode(getIdent(c.cache, "dummy"), n.info), c)
     s.typ = result
     pragma(c, s, n.sons[0], typePragmas)
   if base == nil and tfInheritable notin result.flags:
@@ -731,18 +820,19 @@ proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) =
       addDecl(c, param)
     else:
       # within a macro, every param has the type NimNode!
-      let nn = if getCompilerProc("NimNode") != nil: getSysSym"NimNode"
-               else: getSysSym"PNimrodNode"
+      let nn = getSysSym(c.graph, param.info, "NimNode")
       var a = copySym(param)
       a.typ = nn.typ
       addDecl(c, a)
   else:
-    if sfGenSym notin param.flags: addDecl(c, param)
-
-let typedescId = getIdent"typedesc"
+    if sfGenSym in param.flags:
+      # bug #XXX, fix the gensym'ed parameters owner:
+      if param.owner == nil:
+        param.owner = getCurrOwner(c)
+    else: addDecl(c, param)
 
 template shouldHaveMeta(t) =
-  internalAssert tfHasMeta in t.flags
+  internalAssert c.config, tfHasMeta in t.flags
   # result.lastSon.flags.incl tfHasMeta
 
 proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
@@ -750,13 +840,13 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
                    info: TLineInfo, anon = false): PType =
   if paramType == nil: return # (e.g. proc return type)
 
-  proc addImplicitGenericImpl(typeClass: PType, typId: PIdent): PType =
+  proc addImplicitGenericImpl(c: PContext; typeClass: PType, typId: PIdent): PType =
     if genericParams == nil:
       # This happens with anonymous proc types appearing in signatures
       # XXX: we need to lift these earlier
       return
     let finalTypId = if typId != nil: typId
-                     else: getIdent(paramName & ":type")
+                     else: getIdent(c.cache, paramName & ":type")
     # is this a bindOnce type class already present in the param list?
     for i in countup(0, genericParams.len - 1):
       if genericParams.sons[i].sym.name.id == finalTypId.id:
@@ -765,6 +855,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
     let owner = if typeClass.sym != nil: typeClass.sym
                 else: getCurrOwner(c)
     var s = newSym(skType, finalTypId, owner, info)
+    if sfExplain in owner.flags: s.flags.incl sfExplain
     if typId == nil: s.flags.incl(sfAnon)
     s.linkTo(typeClass)
     typeClass.flags.incl tfImplicitTypeParam
@@ -786,38 +877,43 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
     (if lifted != nil: lifted else: typ)
 
   template addImplicitGeneric(e): untyped =
-    addImplicitGenericImpl(e, paramTypId)
+    addImplicitGenericImpl(c, e, paramTypId)
 
   case paramType.kind:
   of tyAnything:
-    result = addImplicitGenericImpl(newTypeS(tyGenericParam, c), nil)
+    result = addImplicitGenericImpl(c, newTypeS(tyGenericParam, c), nil)
 
   of tyStatic:
-    # proc(a: expr{string}, b: expr{nkLambda})
-    # overload on compile time values and AST trees
-    if paramType.n != nil: return # this is a concrete type
+    if paramType.base.kind != tyNone and paramType.n != nil:
+      # this is a concrete static value
+      return
     if tfUnresolved in paramType.flags: return # already lifted
     let base = paramType.base.maybeLift
     if base.isMetaType and procKind == skMacro:
-      localError(info, errMacroBodyDependsOnGenericTypes, paramName)
+      localError(c.config, info, errMacroBodyDependsOnGenericTypes % paramName)
     result = addImplicitGeneric(c.newTypeWithSons(tyStatic, @[base]))
-    result.flags.incl({tfHasStatic, tfUnresolved})
+    if result != nil: result.flags.incl({tfHasStatic, tfUnresolved})
 
   of tyTypeDesc:
     if tfUnresolved notin paramType.flags:
       # naked typedescs are not bindOnce types
       if paramType.base.kind == tyNone and paramTypId != nil and
-         paramTypId.id == typedescId.id: paramTypId = nil
+          paramTypId.id == getIdent(c.cache, "typedesc").id:
+        # XXX Why doesn't this check for tyTypeDesc instead?
+        paramTypId = nil
       result = addImplicitGeneric(
         c.newTypeWithSons(tyTypeDesc, @[paramType.base]))
 
   of tyDistinct:
     if paramType.sonsLen == 1:
       # disable the bindOnce behavior for the type class
-      result = liftingWalk(paramType.sons[0], true)
+      result = liftingWalk(paramType.base, true)
+
+  of tyAlias:
+    result = liftingWalk(paramType.base)
 
   of tySequence, tySet, tyArray, tyOpenArray,
-     tyVar, tyPtr, tyRef, tyProc:
+     tyVar, tyLent, tyPtr, tyRef, tyProc:
     # XXX: this is a bit strange, but proc(s: seq)
     # produces tySequence(tyGenericParam, tyNone).
     # This also seems to be true when creating aliases
@@ -829,9 +925,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
                                   @[newTypeS(paramType.kind, c)])
       result = addImplicitGeneric(typ)
     else:
-      for i in 0 .. <paramType.len:
+      for i in 0 ..< paramType.len:
         if paramType.sons[i] == paramType:
-          globalError(info, errIllegalRecursionInTypeX, typeToString(paramType))
+          globalError(c.config, info, errIllegalRecursionInTypeX % typeToString(paramType))
         var lifted = liftingWalk(paramType.sons[i])
         if lifted != nil:
           paramType.sons[i] = lifted
@@ -843,9 +939,9 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
 
     for i in 0 .. paramType.sonsLen - 2:
       if paramType.sons[i].kind == tyStatic:
-        var x = copyNode(ast.emptyNode)
-        x.typ = paramType.sons[i]
-        result.rawAddSon makeTypeFromExpr(c, x) # aka 'tyUnknown'
+        var staticCopy = paramType.sons[i].exactReplica
+        staticCopy.flags.incl tfInferrableStatic
+        result.rawAddSon staticCopy
       else:
         result.rawAddSon newTypeS(tyAnything, c)
 
@@ -877,23 +973,32 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
     let liftBody = liftingWalk(paramType.lastSon, true)
     if liftBody != nil:
       result = liftBody
-      result.shouldHaveMeta
+      result.flags.incl tfHasMeta
+      #result.shouldHaveMeta
 
   of tyGenericInvocation:
-    for i in 1 .. <paramType.len:
+    for i in 1 ..< paramType.len:
       let lifted = liftingWalk(paramType.sons[i])
       if lifted != nil: paramType.sons[i] = lifted
-    when false:
+
+    let body = paramType.base
+    if body.kind == tyForward:
+      # this may happen for proc type appearing in a type section
+      # before one of its param types
+      return
+
+    if body.lastSon.kind == tyUserTypeClass:
       let expanded = instGenericContainer(c, info, paramType,
                                           allowMetaTypes = true)
       result = liftingWalk(expanded, true)
 
-  of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot:
-    result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), true))
+  of tyUserTypeClasses, tyBuiltInTypeClass, tyCompositeTypeClass,
+     tyAnd, tyOr, tyNot:
+    result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), false))
 
   of tyGenericParam:
-    markUsed(info, paramType.sym, c.graph.usageSym)
-    styleCheckUse(info, paramType.sym)
+    markUsed(c.config, info, paramType.sym, c.graph.usageSym)
+    onUse(info, paramType.sym)
     if tfWildcard in paramType.flags:
       paramType.flags.excl tfWildcard
       paramType.sym.kind = skType
@@ -905,7 +1010,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
 proc semParamType(c: PContext, n: PNode, constraint: var PNode): PType =
   if n.kind == nkCurlyExpr:
     result = semTypeNode(c, n.sons[0], nil)
-    constraint = semNodeKindConstraints(n)
+    constraint = semNodeKindConstraints(n, c.config)
   else:
     result = semTypeNode(c, n, nil)
 
@@ -923,13 +1028,11 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
                      prev: PType, kind: TSymKind; isType=false): PType =
   # for historical reasons (code grows) this is invoked for parameter
   # lists too and then 'isType' is false.
-  var cl: IntSet
-  checkMinSonsLen(n, 1)
+  checkMinSonsLen(n, 1, c.config)
   result = newProcType(c, n.info, prev)
-  if genericParams != nil and sonsLen(genericParams) == 0:
-    cl = initIntSet()
   var check = initIntSet()
   var counter = 0
+
   for i in countup(1, n.len - 1):
     var a = n.sons[i]
     if a.kind != nkIdentDefs:
@@ -938,8 +1041,9 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
       # skip this parameter here. It'll then be re-generated in another LL
       # pass over this instantiation:
       if a.kind == nkSym and sfFromGeneric in a.sym.flags: continue
-      illFormedAst(a)
-    checkMinSonsLen(a, 3)
+      illFormedAst(a, c.config)
+
+    checkMinSonsLen(a, 3, c.config)
     var
       typ: PType = nil
       def: PNode = nil
@@ -947,33 +1051,59 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
       length = sonsLen(a)
       hasType = a.sons[length-2].kind != nkEmpty
       hasDefault = a.sons[length-1].kind != nkEmpty
+
     if hasType:
       typ = semParamType(c, a.sons[length-2], constraint)
 
     if hasDefault:
-      def = semExprWithType(c, a.sons[length-1])
-      # check type compatibility between def.typ and typ:
+      def = a[^1]
+      block determineType:
+        if genericParams != nil and genericParams.len > 0:
+          def = semGenericStmt(c, def)
+          if hasUnresolvedArgs(c, def):
+            def.typ = makeTypeFromExpr(c, def.copyTree)
+            break determineType
+
+        def = semExprWithType(c, def, {efDetermineType})
+        if def.referencesAnotherParam(getCurrOwner(c)):
+          def.flags.incl nfDefaultRefsParam
+
       if typ == nil:
         typ = def.typ
-      elif def != nil:
-        # and def.typ != nil and def.typ.kind != tyNone:
+        if typ.kind == tyTypeDesc:
+          # consider a proc such as:
+          # proc takesType(T = int)
+          # a naive analysis may conclude that the proc type is type[int]
+          # which will prevent other types from matching - clearly a very
+          # surprising behavior. We must instead fix the expected type of
+          # the proc to be the unbound typedesc type:
+          typ = newTypeWithSons(c, tyTypeDesc, @[newTypeS(tyNone, c)])
+
+      else:
+        # if def.typ != nil and def.typ.kind != tyNone:
         # example code that triggers it:
         # proc sort[T](cmp: proc(a, b: T): int = cmp)
         if not containsGenericType(typ):
+          # check type compatibility between def.typ and typ:
+          def = fitNode(c, typ, def, def.info)
+        elif typ.kind == tyStatic:
+          def = semConstExpr(c, def)
           def = fitNode(c, typ, def, def.info)
+
     if not hasType and not hasDefault:
-      if isType: localError(a.info, "':' expected")
+      if isType: localError(c.config, a.info, "':' expected")
       if kind in {skTemplate, skMacro}:
         typ = newTypeS(tyExpr, c)
-    elif skipTypes(typ, {tyGenericInst, tyAlias}).kind == tyVoid:
+    elif skipTypes(typ, {tyGenericInst, tyAlias, tySink}).kind == tyVoid:
       continue
+
     for j in countup(0, length-3):
       var arg = newSymG(skParam, a.sons[j], c)
       if not hasType and not hasDefault and kind notin {skTemplate, skMacro}:
         let param = strTableGet(c.signatures, arg.name)
         if param != nil: typ = param.typ
         else:
-          localError(a.info, "typeless parameters are obsolete")
+          localError(c.config, a.info, "typeless parameters are obsolete")
           typ = errorType(c)
       let lifted = liftParamType(c, kind, genericParams, typ,
                                  arg.name.s, arg.info)
@@ -982,13 +1112,15 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
       arg.position = counter
       arg.constraint = constraint
       inc(counter)
-      if def != nil and def.kind != nkEmpty: arg.ast = copyTree(def)
+      if def != nil and def.kind != nkEmpty:
+        arg.ast = copyTree(def)
       if containsOrIncl(check, arg.name.id):
-        localError(a.sons[j].info, errAttemptToRedefine, arg.name.s)
+        localError(c.config, a.sons[j].info, "attempt to redefine: '" & arg.name.s & "'")
       addSon(result.n, newSymNode(arg))
       rawAddSon(result, finalType)
       addParamOrResult(c, arg, kind)
-      if gCmd == cmdPretty: styleCheckDef(a.sons[j].info, arg)
+      styleCheckDef(c.config, a.sons[j].info, arg)
+      onDef(a[j].info, arg)
 
   var r: PType
   if n.sons[0].kind != nkEmpty:
@@ -997,7 +1129,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
   if r != nil:
     # turn explicit 'void' return type into 'nil' because the rest of the
     # compiler only checks for 'nil':
-    if skipTypes(r, {tyGenericInst, tyAlias}).kind != tyVoid:
+    if skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid:
       # 'auto' as a return type does not imply a generic:
       if r.kind == tyAnything:
         # 'p(): auto' and 'p(): expr' are equivalent, but the rest of the
@@ -1020,6 +1152,12 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
           result.flags.incl tfIterator
           # XXX Would be nice if we could get rid of this
       result.sons[0] = r
+      let oldFlags = result.flags
+      propagateToOwner(result, r)
+      if oldFlags != result.flags:
+        # XXX This rather hacky way keeps 'tflatmap' compiling:
+        if tfHasMeta notin oldFlags:
+          result.flags.excl tfHasMeta
       result.n.typ = r
 
   if genericParams != nil and genericParams.len > 0:
@@ -1032,10 +1170,10 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
         n.sym.typ.flags.excl tfWildcard
 
 proc semStmtListType(c: PContext, n: PNode, prev: PType): PType =
-  checkMinSonsLen(n, 1)
+  checkMinSonsLen(n, 1, c.config)
   var length = sonsLen(n)
   for i in countup(0, length - 2):
-    n.sons[i] = semStmt(c, n.sons[i])
+    n.sons[i] = semStmt(c, n.sons[i], {})
   if length > 0:
     result = semTypeNode(c, n.sons[length - 1], prev)
     n.typ = result
@@ -1045,7 +1183,7 @@ proc semStmtListType(c: PContext, n: PNode, prev: PType): PType =
 
 proc semBlockType(c: PContext, n: PNode, prev: PType): PType =
   inc(c.p.nestedBlockCounter)
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   openScope(c)
   if n.sons[0].kind notin {nkEmpty, nkSym}:
     addDecl(c, newSymS(skLabel, n.sons[0], c))
@@ -1057,6 +1195,7 @@ proc semBlockType(c: PContext, n: PNode, prev: PType): PType =
 
 proc semGenericParamInInvocation(c: PContext, n: PNode): PType =
   result = semTypeNode(c, n, nil)
+  n.typ = makeTypeDesc(c, result)
 
 proc semObjectTypeForInheritedGenericInst(c: PContext, n: PNode, t: PType) =
   var
@@ -1066,20 +1205,20 @@ proc semObjectTypeForInheritedGenericInst(c: PContext, n: PNode, t: PType) =
     realBase = t.sons[0]
     base = skipTypesOrNil(realBase, skipPtrs)
   if base.isNil:
-    localError(n.info, errIllegalRecursionInTypeX, "object")
+    localError(c.config, n.info, errIllegalRecursionInTypeX % "object")
   else:
     let concreteBase = skipGenericInvocation(base)
     if concreteBase.kind == tyObject and tfFinal notin concreteBase.flags:
       addInheritedFields(c, check, pos, concreteBase)
     else:
       if concreteBase.kind != tyError:
-        localError(n.info, errInheritanceOnlyWithNonFinalObjects)
+        localError(c.config, n.info, errInheritanceOnlyWithNonFinalObjects)
   var newf = newNodeI(nkRecList, n.info)
   semRecordNodeAux(c, t.n, check, pos, newf, t)
 
 proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
   if s.typ == nil:
-    localError(n.info, "cannot instantiate the '$1' $2" %
+    localError(c.config, n.info, "cannot instantiate the '$1' $2" %
                        [s.name.s, ($s.kind).substr(2).toLowerAscii])
     return newOrPrevType(tyError, prev, c)
 
@@ -1092,7 +1231,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
 
   template addToResult(typ) =
     if typ.isNil:
-      internalAssert false
+      internalAssert c.config, false
       rawAddSon(result, typ)
     else: addSonSkipIntLit(result, typ)
 
@@ -1104,23 +1243,23 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
   elif t.kind != tyGenericBody:
     # we likely got code of the form TypeA[TypeB] where TypeA is
     # not generic.
-    localError(n.info, errNoGenericParamsAllowedForX, s.name.s)
+    localError(c.config, n.info, errNoGenericParamsAllowedForX % s.name.s)
     return newOrPrevType(tyError, prev, c)
   else:
     var m = newCandidate(c, t)
     m.isNoCall = true
     matches(c, n, copyTree(n), m)
 
-    if m.state != csMatch and not m.typedescMatched:
+    if m.state != csMatch:
       let err = "cannot instantiate " & typeToString(t) & "\n" &
-                "got: (" & describeArgs(c, n) & ")\n" &
-                "but expected: (" & describeArgs(c, t.n, 0) & ")"
-      localError(n.info, errGenerated, err)
+                "got: <" & describeArgs(c, n) & ">\n" &
+                "but expected: <" & describeArgs(c, t.n, 0) & ">"
+      localError(c.config, n.info, errGenerated, err)
       return newOrPrevType(tyError, prev, c)
 
     var isConcrete = true
 
-    for i in 1 .. <m.call.len:
+    for i in 1 ..< m.call.len:
       var typ = m.call[i].typ
       if typ.kind == tyTypeDesc and typ.sons[0].kind == tyNone:
         isConcrete = false
@@ -1133,7 +1272,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
     if isConcrete:
       if s.ast == nil and s.typ.kind != tyCompositeTypeClass:
         # XXX: What kind of error is this? is it still relevant?
-        localError(n.info, errCannotInstantiateX, s.name.s)
+        localError(c.config, n.info, errCannotInstantiateX % s.name.s)
         result = newOrPrevType(tyError, prev, c)
       else:
         result = instGenericContainer(c, n.info, result,
@@ -1141,7 +1280,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
 
   # special check for generic object with
   # generic/partial specialized parent
-  let tx = result.skipTypes(abstractPtrs)
+  let tx = result.skipTypes(abstractPtrs, 50)
+  if tx.isNil:
+    localError(c.config, n.info, "invalid recursion in type '$1'" % typeToString(result[0]))
+    return errorType(c)
   if tx != result and tx.kind == tyObject and tx.sons[0] != nil:
     semObjectTypeForInheritedGenericInst(c, n, tx)
 
@@ -1169,7 +1311,7 @@ proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType =
         let alias = maybeAliasType(c, result, prev)
         if alias != nil: result = alias
   else:
-    localError(n.info, errTypeExpected, n.renderTree)
+    localError(c.config, n.info, "expected type, but got: " & n.renderTree)
     result = errorType(c)
 
 proc freshType(res, prev: PType): PType {.inline.} =
@@ -1178,35 +1320,75 @@ proc freshType(res, prev: PType): PType {.inline.} =
   else:
     result = res
 
+template modifierTypeKindOfNode(n: PNode): TTypeKind =
+  case n.kind
+  of nkVarTy: tyVar
+  of nkRefTy: tyRef
+  of nkPtrTy: tyPtr
+  of nkStaticTy: tyStatic
+  of nkTypeOfExpr: tyTypeDesc
+  else: tyNone
+
 proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
   # if n.sonsLen == 0: return newConstraint(c, tyTypeClass)
-  if nfBase2 in n.flags:
-    message(n.info, warnDeprecated, "use 'concept' instead; 'generic'")
-  result = newOrPrevType(tyUserTypeClass, prev, c)
-  result.n = n
-
   let
     pragmas = n[1]
     inherited = n[2]
 
+  result = newOrPrevType(tyUserTypeClass, prev, c)
+  var owner = getCurrOwner(c)
+  var candidateTypeSlot = newTypeWithSons(owner, tyAlias, @[c.errorType])
+  result.sons = @[candidateTypeSlot]
+  result.n = n
+
   if inherited.kind != nkEmpty:
     for n in inherited.sons:
       let typ = semTypeNode(c, n, nil)
-      result.sons.safeAdd(typ)
+      result.sons.add(typ)
+
+  openScope(c)
+  for param in n[0]:
+    var
+      dummyName: PNode
+      dummyType: PType
+
+    let modifier = param.modifierTypeKindOfNode
+
+    if modifier != tyNone:
+      dummyName = param[0]
+      dummyType = c.makeTypeWithModifier(modifier, candidateTypeSlot)
+      if modifier == tyTypeDesc: dummyType.flags.incl tfConceptMatchedTypeSym
+    else:
+      dummyName = param
+      dummyType = candidateTypeSlot
+
+    # this can be true for 'nim check' on incomplete concepts,
+    # see bug #8230
+    if dummyName.kind == nkEmpty: continue
+
+    internalAssert c.config, dummyName.kind == nkIdent
+    var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar,
+                            dummyName.ident, owner, param.info)
+    dummyParam.typ = dummyType
+    incl dummyParam.flags, sfUsed
+    addDecl(c, dummyParam)
+
+  result.n[3] = semConceptBody(c, n[3])
+  closeScope(c)
 
 proc semProcTypeWithScope(c: PContext, n: PNode,
                         prev: PType, kind: TSymKind): PType =
-  checkSonsLen(n, 2)
+  checkSonsLen(n, 2, c.config)
   openScope(c)
   result = semProcTypeNode(c, n.sons[0], nil, prev, kind, isType=true)
   # start with 'ccClosure', but of course pragmas can overwrite this:
   result.callConv = ccClosure
   # dummy symbol for `pragma`:
-  var s = newSymS(kind, newIdentNode(getIdent("dummy"), n.info), c)
+  var s = newSymS(kind, newIdentNode(getIdent(c.cache, "dummy"), n.info), c)
   s.typ = result
   if n.sons[1].kind != nkEmpty and n.sons[1].len > 0:
     pragma(c, s, n.sons[1], procTypePragmas)
-    when useEffectSystem: setEffectsForProcType(result, n.sons[1])
+    when useEffectSystem: setEffectsForProcType(c.graph, result, n.sons[1])
   closeScope(c)
 
 proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType =
@@ -1223,24 +1405,57 @@ proc fixupTypeOf(c: PContext, prev: PType, typExpr: PNode) =
     result.sym = prev.sym
     assignType(prev, result)
 
+proc symFromExpectedTypeNode(c: PContext, n: PNode): PSym =
+  if n.kind == nkType:
+    result = symFromType(c, n.typ, n.info)
+  else:
+    localError(c.config, n.info, errTypeExpected)
+    result = errorSym(c, n)
+
+proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType =
+  result = newOrPrevType(tyStatic, prev, c)
+  var base = semTypeNode(c, childNode, nil).skipTypes({tyTypeDesc, tyAlias})
+  result.rawAddSon(base)
+  result.flags.incl tfHasStatic
+
+proc semTypeof(c: PContext; n: PNode; prev: PType): PType =
+  openScope(c)
+  let t = semExprWithType(c, n, {efInTypeof})
+  closeScope(c)
+  fixupTypeOf(c, prev, t)
+  result = t.typ
+
+proc semTypeof2(c: PContext; n: PNode; prev: PType): PType =
+  openScope(c)
+  var m = BiggestInt 1 # typeOfIter
+  if n.len == 3:
+    let mode = semConstExpr(c, n[2])
+    if mode.kind != nkIntLit:
+      localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
+    else:
+      m = mode.intVal
+  let t = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
+  closeScope(c)
+  fixupTypeOf(c, prev, t)
+  result = t.typ
+
 proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   result = nil
-  when defined(nimsuggest):
-    inc c.inTypeContext
+  inc c.inTypeContext
 
-  if gCmd == cmdIdeTools: suggestExpr(c, n)
+  if c.config.cmd == cmdIdeTools: suggestExpr(c, n)
   case n.kind
   of nkEmpty: discard
   of nkTypeOfExpr:
     # for ``type(countup(1,3))``, see ``tests/ttoseq``.
-    checkSonsLen(n, 1)
-    let typExpr = semExprWithType(c, n.sons[0], {efInTypeof})
-    fixupTypeOf(c, prev, typExpr)
-    result = typExpr.typ
+    checkSonsLen(n, 1, c.config)
+    result = semTypeof(c, n.sons[0], prev)
+    if result.kind == tyTypeDesc: result.flags.incl tfExplicit
   of nkPar:
     if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev)
     else:
       result = semAnonTuple(c, n, prev)
+  of nkTupleConstr: result = semAnonTuple(c, n, prev)
   of nkCallKinds:
     let x = n[0]
     let ident = case x.kind
@@ -1256,26 +1471,26 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
       result = semRangeAux(c, n, prev)
     elif n[0].kind == nkNilLit and n.len == 2:
       result = semTypeNode(c, n.sons[1], prev)
-      if result.skipTypes({tyGenericInst, tyAlias}).kind in NilableTypes+GenericTypes:
+      if result.skipTypes({tyGenericInst, tyAlias, tySink}).kind in NilableTypes+GenericTypes:
         if tfNotNil in result.flags:
           result = freshType(result, prev)
           result.flags.excl(tfNotNil)
       else:
-        localError(n.info, errGenerated, "invalid type")
+        localError(c.config, n.info, errGenerated, "invalid type")
     elif n[0].kind notin nkIdentKinds:
       result = semTypeExpr(c, n, prev)
     else:
-      let op = considerQuotedIdent(n.sons[0])
+      let op = considerQuotedIdent(c, n.sons[0])
       if op.id in {ord(wAnd), ord(wOr)} or op.s == "|":
-        checkSonsLen(n, 3)
+        checkSonsLen(n, 3, c.config)
         var
           t1 = semTypeNode(c, n.sons[1], nil)
           t2 = semTypeNode(c, n.sons[2], nil)
         if t1 == nil:
-          localError(n.sons[1].info, errTypeExpected)
+          localError(c.config, n.sons[1].info, errTypeExpected)
           result = newOrPrevType(tyError, prev, c)
         elif t2 == nil:
-          localError(n.sons[2].info, errTypeExpected)
+          localError(c.config, n.sons[2].info, errTypeExpected)
           result = newOrPrevType(tyError, prev, c)
         else:
           result = if op.id == ord(wAnd): makeAndType(c, t1, t2)
@@ -1284,44 +1499,74 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
         case n.len
         of 3:
           result = semTypeNode(c, n.sons[1], prev)
-          if result.skipTypes({tyGenericInst, tyAlias}).kind in NilableTypes+GenericTypes and
+          if result.skipTypes({tyGenericInst, tyAlias, tySink}).kind in NilableTypes+GenericTypes+{tyForward} and
               n.sons[2].kind == nkNilLit:
             result = freshType(result, prev)
             result.flags.incl(tfNotNil)
+            if notnil notin c.features:
+              localError(c.config, n.info, "enable the 'not nil' annotation with {.experimental: \"notnil\".}")
           else:
-            localError(n.info, errGenerated, "invalid type")
+            localError(c.config, n.info, errGenerated, "invalid type")
         of 2:
           let negated = semTypeNode(c, n.sons[1], prev)
           result = makeNotType(c, negated)
         else:
-          localError(n.info, errGenerated, "invalid type")
+          localError(c.config, n.info, errGenerated, "invalid type")
       elif op.id == ord(wPtr):
         result = semAnyRef(c, n, tyPtr, prev)
       elif op.id == ord(wRef):
         result = semAnyRef(c, n, tyRef, prev)
       elif op.id == ord(wType):
-        checkSonsLen(n, 2)
-        let typExpr = semExprWithType(c, n.sons[1], {efInTypeof})
-        fixupTypeOf(c, prev, typExpr)
-        result = typExpr.typ
+        checkSonsLen(n, 2, c.config)
+        result = semTypeof(c, n[1], prev)
+      elif op.s == "typeof" and n[0].kind == nkSym and n[0].sym.magic == mTypeof:
+        result = semTypeOf2(c, n, prev)
       else:
-        result = semTypeExpr(c, n, prev)
+        if c.inGenericContext > 0 and n.kind == nkCall:
+          result = makeTypeFromExpr(c, n.copyTree)
+        else:
+          result = semTypeExpr(c, n, prev)
   of nkWhenStmt:
     var whenResult = semWhen(c, n, false)
     if whenResult.kind == nkStmtList: whenResult.kind = nkStmtListType
     result = semTypeNode(c, whenResult, prev)
   of nkBracketExpr:
-    checkMinSonsLen(n, 2)
-    var s = semTypeIdent(c, n.sons[0])
+    checkMinSonsLen(n, 2, c.config)
+    var head = n.sons[0]
+    var s = if head.kind notin nkCallKinds: semTypeIdent(c, head)
+            else: symFromExpectedTypeNode(c, semExpr(c, head))
     case s.magic
     of mArray: result = semArray(c, n, prev)
     of mOpenArray: result = semContainer(c, n, tyOpenArray, "openarray", prev)
+    of mUncheckedArray: result = semContainer(c, n, tyUncheckedArray, "UncheckedArray", prev)
     of mRange: result = semRange(c, n, prev)
     of mSet: result = semSet(c, n, prev)
     of mOrdinal: result = semOrdinal(c, n, prev)
-    of mSeq: result = semContainer(c, n, tySequence, "seq", prev)
+    of mSeq:
+      if c.config.selectedGc == gcDestructors:
+        let s = c.graph.sysTypes[tySequence]
+        assert s != nil
+        assert prev == nil
+        result = copyType(s, s.owner, keepId=false)
+        # XXX figure out why this has children already...
+        result.sons.setLen 0
+        result.n = nil
+        if c.config.selectedGc == gcDestructors:
+          result.flags = {tfHasAsgn}
+        else:
+          result.flags = {}
+        semContainerArg(c, n, "seq", result)
+      else:
+        result = semContainer(c, n, tySequence, "seq", prev)
+        if c.config.selectedGc == gcDestructors:
+          incl result.flags, tfHasAsgn
+    of mOpt: result = semContainer(c, n, tyOpt, "opt", prev)
     of mVarargs: result = semVarargs(c, n, prev)
-    of mTypeDesc: result = makeTypeDesc(c, semTypeNode(c, n[1], nil))
+    of mTypeDesc, mTypeTy:
+      result = makeTypeDesc(c, semTypeNode(c, n[1], nil))
+      result.flags.incl tfExplicit
+    of mStaticTy:
+      result = semStaticType(c, n[1], prev)
     of mExpr:
       result = semTypeNode(c, n.sons[0], nil)
       if result != nil:
@@ -1334,8 +1579,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
     of mVar:
       result = newOrPrevType(tyVar, prev, c)
       var base = semTypeNode(c, n.sons[1], nil)
-      if base.kind == tyVar:
-        localError(n.info, errVarVarTypeNotAllowed)
+      if base.kind in {tyVar, tyLent}:
+        localError(c.config, n.info, "type 'var var' is not allowed")
         base = base.sons[0]
       addSonSkipIntLit(result, base)
     of mRef: result = semAnyRef(c, n, tyRef, prev)
@@ -1344,12 +1589,21 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
     else: result = semGeneric(c, n, s, prev)
   of nkDotExpr:
     let typeExpr = semExpr(c, n)
-    if typeExpr.typ.kind != tyTypeDesc:
-      localError(n.info, errTypeExpected)
+    if typeExpr.typ.isNil:
+      localError(c.config, n.info, "object constructor needs an object type;" &
+          " for named arguments use '=' instead of ':'")
+      result = errorType(c)
+    elif typeExpr.typ.kind == tyFromExpr:
+      result = typeExpr.typ
+    elif typeExpr.typ.kind != tyTypeDesc:
+      localError(c.config, n.info, errTypeExpected)
       result = errorType(c)
     else:
       result = typeExpr.typ.base
-      if result.isMetaType:
+      if result.isMetaType and
+         result.kind != tyUserTypeClass:
+           # the dot expression may refer to a concept type in
+           # a different module. allow a normal alias then.
         let preprocessed = semGenericStmt(c, n)
         result = makeTypeFromExpr(c, preprocessed.copyTree)
       else:
@@ -1358,10 +1612,10 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   of nkIdent, nkAccQuoted:
     var s = semTypeIdent(c, n)
     if s.typ == nil:
-      if s.kind != skError: localError(n.info, errTypeExpected)
+      if s.kind != skError: localError(c.config, n.info, errTypeExpected)
       result = newOrPrevType(tyError, prev, c)
     elif s.kind == skParam and s.typ.kind == tyTypeDesc:
-      internalAssert s.typ.base.kind != tyNone and prev == nil
+      internalAssert c.config, s.typ.base.kind != tyNone and prev == nil
       result = s.typ.base
     elif prev == nil:
       result = s.typ
@@ -1378,8 +1632,13 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
         result = prev
   of nkSym:
     let s = getGenSym(c, n.sym)
-    if s.kind == skType and s.typ != nil:
-      var t = s.typ
+    if s.typ != nil and (s.kind == skType or s.typ.kind == tyTypeDesc):
+      var t =
+        if s.kind == skType:
+          s.typ
+        else:
+          internalAssert c.config, s.typ.base.kind != tyNone and prev == nil
+          s.typ.base
       let alias = maybeAliasType(c, t, prev)
       if alias != nil:
         result = alias
@@ -1388,10 +1647,10 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
       else:
         assignType(prev, t)
         result = prev
-      markUsed(n.info, n.sym, c.graph.usageSym)
-      styleCheckUse(n.info, n.sym)
+      markUsed(c.config, n.info, n.sym, c.graph.usageSym)
+      onUse(n.info, n.sym)
     else:
-      if s.kind != skError: localError(n.info, errTypeExpected)
+      if s.kind != skError: localError(c.config, n.info, errTypeExpected)
       result = newOrPrevType(tyError, prev, c)
   of nkObjectTy: result = semObjectNode(c, n, prev)
   of nkTupleTy: result = semTuple(c, n, prev)
@@ -1401,11 +1660,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   of nkPtrTy: result = semAnyRef(c, n, tyPtr, prev)
   of nkVarTy: result = semVarType(c, n, prev)
   of nkDistinctTy: result = semDistinct(c, n, prev)
-  of nkStaticTy:
-    result = newOrPrevType(tyStatic, prev, c)
-    var base = semTypeNode(c, n.sons[0], nil)
-    result.rawAddSon(base)
-    result.flags.incl tfHasStatic
+  of nkStaticTy: result = semStaticType(c, n[0], prev)
   of nkIteratorTy:
     if n.sonsLen == 0:
       result = newTypeS(tyBuiltInTypeClass, c)
@@ -1429,80 +1684,120 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   of nkStmtListType: result = semStmtListType(c, n, prev)
   of nkBlockType: result = semBlockType(c, n, prev)
   else:
-    localError(n.info, errTypeExpected)
+    localError(c.config, n.info, errTypeExpected)
     result = newOrPrevType(tyError, prev, c)
   n.typ = result
-  when defined(nimsuggest):
-    dec c.inTypeContext
+  dec c.inTypeContext
+  if c.inTypeContext == 0: instAllTypeBoundOp(c, n.info)
 
-proc setMagicType(m: PSym, kind: TTypeKind, size: int) =
+when false:
+  proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
+    result = semTypeNodeInner(c, n, prev)
+    instAllTypeBoundOp(c, n.info)
+
+proc setMagicType(conf: ConfigRef; m: PSym, kind: TTypeKind, size: int) =
+  # source : https://en.wikipedia.org/wiki/Data_structure_alignment#x86
   m.typ.kind = kind
-  m.typ.align = size.int16
   m.typ.size = size
+  # this usually works for most basic types
+  # Assuming that since ARM, ARM64  don't support unaligned access
+  # data is aligned to type size
+  m.typ.align = size.int16
+
+  # FIXME: proper support for clongdouble should be added.
+  # long double size can be 8, 10, 12, 16 bytes depending on platform & compiler
+  if conf.target.targetCPU == cpuI386 and size == 8:
+    #on Linux/BSD i386, double are aligned to 4bytes (except with -malign-double)
+    if kind in {tyFloat64, tyFloat} and
+        conf.target.targetOS in {osLinux, osAndroid, osNetbsd, osFreebsd, osOpenbsd, osDragonfly}:
+      m.typ.align = 4
+    # on i386, all known compiler, 64bits ints are aligned to 4bytes (except with -malign-double)
+    elif kind in {tyInt, tyUInt, tyInt64, tyUInt64}:
+      m.typ.align = 4
+  else:
+    discard
 
 proc processMagicType(c: PContext, m: PSym) =
   case m.magic
-  of mInt: setMagicType(m, tyInt, intSize)
-  of mInt8: setMagicType(m, tyInt8, 1)
-  of mInt16: setMagicType(m, tyInt16, 2)
-  of mInt32: setMagicType(m, tyInt32, 4)
-  of mInt64: setMagicType(m, tyInt64, 8)
-  of mUInt: setMagicType(m, tyUInt, intSize)
-  of mUInt8: setMagicType(m, tyUInt8, 1)
-  of mUInt16: setMagicType(m, tyUInt16, 2)
-  of mUInt32: setMagicType(m, tyUInt32, 4)
-  of mUInt64: setMagicType(m, tyUInt64, 8)
-  of mFloat: setMagicType(m, tyFloat, floatSize)
-  of mFloat32: setMagicType(m, tyFloat32, 4)
-  of mFloat64: setMagicType(m, tyFloat64, 8)
-  of mFloat128: setMagicType(m, tyFloat128, 16)
-  of mBool: setMagicType(m, tyBool, 1)
-  of mChar: setMagicType(m, tyChar, 1)
+  of mInt: setMagicType(c.config, m, tyInt, c.config.target.intSize)
+  of mInt8: setMagicType(c.config, m, tyInt8, 1)
+  of mInt16: setMagicType(c.config, m, tyInt16, 2)
+  of mInt32: setMagicType(c.config, m, tyInt32, 4)
+  of mInt64: setMagicType(c.config, m, tyInt64, 8)
+  of mUInt: setMagicType(c.config, m, tyUInt, c.config.target.intSize)
+  of mUInt8: setMagicType(c.config, m, tyUInt8, 1)
+  of mUInt16: setMagicType(c.config, m, tyUInt16, 2)
+  of mUInt32: setMagicType(c.config, m, tyUInt32, 4)
+  of mUInt64: setMagicType(c.config, m, tyUInt64, 8)
+  of mFloat: setMagicType(c.config, m, tyFloat, c.config.target.floatSize)
+  of mFloat32: setMagicType(c.config, m, tyFloat32, 4)
+  of mFloat64: setMagicType(c.config, m, tyFloat64, 8)
+  of mFloat128: setMagicType(c.config, m, tyFloat128, 16)
+  of mBool: setMagicType(c.config, m, tyBool, 1)
+  of mChar: setMagicType(c.config, m, tyChar, 1)
   of mString:
-    setMagicType(m, tyString, ptrSize)
-    rawAddSon(m.typ, getSysType(tyChar))
+    setMagicType(c.config, m, tyString, szUncomputedSize)
+    rawAddSon(m.typ, getSysType(c.graph, m.info, tyChar))
+    when false:
+      if c.config.selectedGc == gcDestructors:
+        incl m.typ.flags, tfHasAsgn
   of mCstring:
-    setMagicType(m, tyCString, ptrSize)
-    rawAddSon(m.typ, getSysType(tyChar))
-  of mPointer: setMagicType(m, tyPointer, ptrSize)
+    setMagicType(c.config, m, tyCString, c.config.target.ptrSize)
+    rawAddSon(m.typ, getSysType(c.graph, m.info, tyChar))
+  of mPointer: setMagicType(c.config, m, tyPointer, c.config.target.ptrSize)
   of mEmptySet:
-    setMagicType(m, tySet, 1)
+    setMagicType(c.config, m, tySet, 1)
     rawAddSon(m.typ, newTypeS(tyEmpty, c))
-  of mIntSetBaseType: setMagicType(m, tyRange, intSize)
-  of mNil: setMagicType(m, tyNil, ptrSize)
+  of mIntSetBaseType: setMagicType(c.config, m, tyRange, c.config.target.intSize)
+  of mNil: setMagicType(c.config, m, tyNil, c.config.target.ptrSize)
   of mExpr:
     if m.name.s == "auto":
-      setMagicType(m, tyAnything, 0)
+      setMagicType(c.config, m, tyAnything, 0)
     else:
-      setMagicType(m, tyExpr, 0)
-      if m.name.s == "expr": m.typ.flags.incl tfOldSchoolExprStmt
+      setMagicType(c.config, m, tyExpr, 0)
   of mStmt:
-    setMagicType(m, tyStmt, 0)
-    if m.name.s == "stmt": m.typ.flags.incl tfOldSchoolExprStmt
-  of mTypeDesc:
-    setMagicType(m, tyTypeDesc, 0)
+    setMagicType(c.config, m, tyStmt, 0)
+  of mTypeDesc, mType:
+    setMagicType(c.config, m, tyTypeDesc, 0)
+    rawAddSon(m.typ, newTypeS(tyNone, c))
+  of mStatic:
+    setMagicType(c.config, m, tyStatic, 0)
     rawAddSon(m.typ, newTypeS(tyNone, c))
   of mVoidType:
-    setMagicType(m, tyVoid, 0)
+    setMagicType(c.config, m, tyVoid, 0)
   of mArray:
-    setMagicType(m, tyArray, 0)
+    setMagicType(c.config, m, tyArray, szUncomputedSize)
   of mOpenArray:
-    setMagicType(m, tyOpenArray, 0)
+    setMagicType(c.config, m, tyOpenArray, szUncomputedSize)
   of mVarargs:
-    setMagicType(m, tyVarargs, 0)
+    setMagicType(c.config, m, tyVarargs, szUncomputedSize)
   of mRange:
-    setMagicType(m, tyRange, 0)
+    setMagicType(c.config, m, tyRange, szUncomputedSize)
     rawAddSon(m.typ, newTypeS(tyNone, c))
   of mSet:
-    setMagicType(m, tySet, 0)
+    setMagicType(c.config, m, tySet, szUncomputedSize)
+  of mUncheckedArray:
+    setMagicType(c.config, m, tyUncheckedArray, szUncomputedSize)
   of mSeq:
-    setMagicType(m, tySequence, 0)
+    setMagicType(c.config, m, tySequence, szUncomputedSize)
+    if c.config.selectedGc == gcDestructors:
+      incl m.typ.flags, tfHasAsgn
+    assert c.graph.sysTypes[tySequence] == nil
+    c.graph.sysTypes[tySequence] = m.typ
+  of mOpt:
+    setMagicType(c.config, m, tyOpt, szUncomputedSize)
   of mOrdinal:
-    setMagicType(m, tyOrdinal, 0)
+    setMagicType(c.config, m, tyOrdinal, szUncomputedSize)
     rawAddSon(m.typ, newTypeS(tyNone, c))
   of mPNimrodNode:
     incl m.typ.flags, tfTriggersCompileTime
-  else: localError(m.info, errTypeExpected)
+  of mException: discard
+  of mBuiltinType:
+    case m.name.s
+    of "lent": setMagicType(c.config, m, tyLent, c.config.target.ptrSize)
+    of "sink": setMagicType(c.config, m, tySink, szUncomputedSize)
+    else: localError(c.config, m.info, errTypeExpected)
+  else: localError(c.config, m.info, errTypeExpected)
 
 proc semGenericConstraints(c: PContext, x: PType): PType =
   result = newTypeWithSons(c, tyGenericParam, @[x])
@@ -1510,14 +1805,14 @@ proc semGenericConstraints(c: PContext, x: PType): PType =
 proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
   result = copyNode(n)
   if n.kind != nkGenericParams:
-    illFormedAst(n)
+    illFormedAst(n, c.config)
     return
   for i in countup(0, sonsLen(n)-1):
     var a = n.sons[i]
-    if a.kind != nkIdentDefs: illFormedAst(n)
+    if a.kind != nkIdentDefs: illFormedAst(n, c.config)
     let L = a.len
-    var def = a{-1}
-    let constraint = a{-2}
+    var def = a[^1]
+    let constraint = a[^2]
     var typ: PType
 
     if constraint.kind != nkEmpty:
@@ -1554,13 +1849,26 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode =
                       # type for each generic param. the index
                       # of the parameter will be stored in the
                       # attached symbol.
+      var paramName = a.sons[j]
+      var covarianceFlag = tfUnresolved
+
+      if paramName.safeLen == 2:
+        if not nimEnableCovariance or paramName[0].ident.s == "in":
+          if father == nil or sfImportc notin father.sym.flags:
+            localError(c.config, paramName.info, errInOutFlagNotExtern % $paramName[0])
+        covarianceFlag = if paramName[0].ident.s == "in": tfContravariant
+                         else: tfCovariant
+        if father != nil: father.flags.incl tfCovariant
+        paramName = paramName[1]
+
       var s = if finalType.kind == tyStatic or tfWildcard in typ.flags:
-          newSymG(skGenericParam, a.sons[j], c).linkTo(finalType)
+          newSymG(skGenericParam, paramName, c).linkTo(finalType)
         else:
-          newSymG(skType, a.sons[j], c).linkTo(finalType)
+          newSymG(skType, paramName, c).linkTo(finalType)
+
+      if covarianceFlag != tfUnresolved: s.typ.flags.incl(covarianceFlag)
       if def.kind != nkEmpty: s.ast = def
       if father != nil: addSonSkipIntLit(father, s.typ)
       s.position = result.len
       addSon(result, newSymNode(s))
       if sfGenSym notin s.flags: addDecl(c, s)
-
diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim
index 75dffb67f..ffa913f1d 100644
--- a/compiler/semtypinst.nim
+++ b/compiler/semtypinst.nim
@@ -9,26 +9,27 @@
 
 # This module does the instantiation of generic types.
 
-import ast, astalgo, msgs, types, magicsys, semdata, renderer
+import ast, astalgo, msgs, types, magicsys, semdata, renderer, options,
+  lineinfos
 
 const
-  tfInstClearedFlags = {tfHasMeta}
+  tfInstClearedFlags = {tfHasMeta, tfUnresolved}
 
-proc checkPartialConstructedType(info: TLineInfo, t: PType) =
+proc checkPartialConstructedType(conf: ConfigRef; info: TLineInfo, t: PType) =
   if tfAcyclic in t.flags and skipTypes(t, abstractInst).kind != tyObject:
-    localError(info, errInvalidPragmaX, "acyclic")
-  elif t.kind == tyVar and t.sons[0].kind == tyVar:
-    localError(info, errVarVarTypeNotAllowed)
+    localError(conf, info, "invalid pragma: acyclic")
+  elif t.kind in {tyVar, tyLent} and t.sons[0].kind in {tyVar, tyLent}:
+    localError(conf, info, "type 'var var' is not allowed")
 
-proc checkConstructedType*(info: TLineInfo, typ: PType) =
+proc checkConstructedType*(conf: ConfigRef; info: TLineInfo, typ: PType) =
   var t = typ.skipTypes({tyDistinct})
   if t.kind in tyTypeClasses: discard
   elif tfAcyclic in t.flags and skipTypes(t, abstractInst).kind != tyObject:
-    localError(info, errInvalidPragmaX, "acyclic")
-  elif t.kind == tyVar and t.sons[0].kind == tyVar:
-    localError(info, errVarVarTypeNotAllowed)
-  elif computeSize(t) == szIllegalRecursion:
-    localError(info, errIllegalRecursionInTypeX, typeToString(t))
+    localError(conf, info, "invalid pragma: acyclic")
+  elif t.kind in {tyVar, tyLent} and t.sons[0].kind in {tyVar, tyLent}:
+    localError(conf, info, "type 'var var' is not allowed")
+  elif computeSize(conf, t) == szIllegalRecursion:
+    localError(conf, info,  "illegal recursion in type '" & typeToString(t) & "'")
   when false:
     if t.kind == tyObject and t.sons[0] != nil:
       if t.sons[0].kind != tyObject or tfFinal in t.sons[0].flags:
@@ -36,12 +37,11 @@ proc checkConstructedType*(info: TLineInfo, typ: PType) =
 
 proc searchInstTypes*(key: PType): PType =
   let genericTyp = key.sons[0]
-  internalAssert genericTyp.kind == tyGenericBody and
-                 key.sons[0] == genericTyp and
-                 genericTyp.sym != nil
+  if not (genericTyp.kind == tyGenericBody and
+      key.sons[0] == genericTyp and genericTyp.sym != nil): return
 
-  if genericTyp.sym.typeInstCache == nil:
-    return
+  when not defined(nimNoNilSeqs):
+    if genericTyp.sym.typeInstCache == nil: return
 
   for inst in genericTyp.sym.typeInstCache:
     if inst.id == key.id: return inst
@@ -50,6 +50,9 @@ proc searchInstTypes*(key: PType): PType =
       # types such as Channel[empty]. Why?
       # See the notes for PActor in handleGenericInvocation
       return
+    if not sameFlags(inst, key):
+      continue
+
     block matchType:
       for j in 1 .. high(key.sons):
         # XXX sameType is not really correct for nested generics?
@@ -66,13 +69,17 @@ proc cacheTypeInst*(inst: PType) =
   let t = if gt.kind == tyGenericBody: gt.lastSon else: gt
   if t.kind in {tyStatic, tyGenericParam} + tyTypeClasses:
     return
-  gt.sym.typeInstCache.safeAdd(inst)
+  gt.sym.typeInstCache.add(inst)
 
 
 type
-  TReplTypeVars* {.final.} = object
+  LayeredIdTable* = object
+    topLayer*: TIdTable
+    nextLayer*: ptr LayeredIdTable
+
+  TReplTypeVars* = object
     c*: PContext
-    typeMap*: TIdTable        # map PType to PType
+    typeMap*: ptr LayeredIdTable # map PType to PType
     symMap*: TIdTable         # map PSym to PSym
     localCache*: TIdTable     # local cache for remembering alraedy replaced
                               # types during instantiation of meta types
@@ -88,6 +95,23 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType
 proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym
 proc replaceTypeVarsN*(cl: var TReplTypeVars, n: PNode; start=0): PNode
 
+proc initLayeredTypeMap*(pt: TIdTable): LayeredIdTable =
+  copyIdTable(result.topLayer, pt)
+
+proc newTypeMapLayer*(cl: var TReplTypeVars): LayeredIdTable =
+  result.nextLayer = cl.typeMap
+  initIdTable(result.topLayer)
+
+proc lookup(typeMap: ptr LayeredIdTable, key: PType): PType =
+  var tm = typeMap
+  while tm != nil:
+    result = PType(idTableGet(tm.topLayer, key))
+    if result != nil: return
+    tm = tm.nextLayer
+
+template put(typeMap: ptr LayeredIdTable, key, value: PType) =
+  idTablePut(typeMap.topLayer, key, value)
+
 template checkMetaInvariants(cl: TReplTypeVars, t: PType) =
   when false:
     if t != nil and tfHasMeta in t.flags and
@@ -103,12 +127,13 @@ proc replaceTypeVarsT*(cl: var TReplTypeVars, t: PType): PType =
 proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode =
   let t = replaceTypeVarsT(cl, n.typ)
   if t != nil and t.kind == tyStatic and t.n != nil:
-    return t.n
+    return if tfUnresolved in t.flags: prepareNode(cl, t.n)
+           else: t.n
   result = copyNode(n)
   result.typ = t
   if result.kind == nkSym: result.sym = replaceTypeVarsS(cl, n.sym)
   let isCall = result.kind in nkCallKinds
-  for i in 0 .. <n.safeLen:
+  for i in 0 ..< n.safeLen:
     # XXX HACK: ``f(a, b)``, avoid to instantiate `f`
     if isCall and i == 0: result.add(n[i])
     else: result.add(prepareNode(cl, n[i]))
@@ -119,16 +144,6 @@ proc isTypeParam(n: PNode): bool =
          (n.sym.kind == skGenericParam or
            (n.sym.kind == skType and sfFromGeneric in n.sym.flags))
 
-proc hasGenericArguments*(n: PNode): bool =
-  if n.kind == nkSym:
-    return n.sym.kind == skGenericParam or
-           (n.sym.kind == skType and
-            n.sym.typ.flags * {tfGenericTypeParam, tfImplicitTypeParam} != {})
-  else:
-    for i in 0.. <n.safeLen:
-      if hasGenericArguments(n.sons[i]): return true
-    return false
-
 proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
   # This is needed for tgenericshardcases
   # It's possible that a generic param will be used in a proc call to a
@@ -140,13 +155,13 @@ proc reResolveCallsWithTypedescParams(cl: var TReplTypeVars, n: PNode): PNode =
   # overload resolution is executed again (which may trigger generateInstance).
   if n.kind in nkCallKinds and sfFromGeneric in n[0].sym.flags:
     var needsFixing = false
-    for i in 1 .. <n.safeLen:
+    for i in 1 ..< n.safeLen:
       if isTypeParam(n[i]): needsFixing = true
     if needsFixing:
       n.sons[0] = newSymNode(n.sons[0].sym.owner)
-      return cl.c.semOverloadedCall(cl.c, n, n, {skProc})
+      return cl.c.semOverloadedCall(cl.c, n, n, {skProc, skFunc}, {})
 
-  for i in 0 .. <n.safeLen:
+  for i in 0 ..< n.safeLen:
     n.sons[i] = reResolveCallsWithTypedescParams(cl, n[i])
 
   return n
@@ -169,19 +184,19 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0): PNode =
     var branch: PNode = nil              # the branch to take
     for i in countup(0, sonsLen(n) - 1):
       var it = n.sons[i]
-      if it == nil: illFormedAst(n)
+      if it == nil: illFormedAst(n, cl.c.config)
       case it.kind
       of nkElifBranch:
-        checkSonsLen(it, 2)
+        checkSonsLen(it, 2, cl.c.config)
         var cond = prepareNode(cl, it.sons[0])
         var e = cl.c.semConstExpr(cl.c, cond)
         if e.kind != nkIntLit:
-          internalError(e.info, "ReplaceTypeVarsN: when condition not a bool")
+          internalError(cl.c.config, e.info, "ReplaceTypeVarsN: when condition not a bool")
         if e.intVal != 0 and branch == nil: branch = it.sons[1]
       of nkElse:
-        checkSonsLen(it, 1)
+        checkSonsLen(it, 1, cl.c.config)
         if branch == nil: branch = it.sons[0]
-      else: illFormedAst(n)
+      else: illFormedAst(n, cl.c.config)
     if branch != nil:
       result = replaceTypeVarsN(cl, branch)
     else:
@@ -205,9 +220,20 @@ proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym =
   # symbol is not our business:
   if cl.owner != nil and s.owner != cl.owner:
     return s
+
+  # XXX: Bound symbols in default parameter expressions may reach here.
+  # We cannot process them, becase `sym.n` may point to a proc body with
+  # cyclic references that will lead to an infinite recursion.
+  # Perhaps we should not use a black-list here, but a whitelist instead
+  # (e.g. skGenericParam and skType).
+  # Note: `s.magic` may be `mType` in an example such as:
+  # proc foo[T](a: T, b = myDefault(type(a)))
+  if s.kind == skProc or s.magic != mNone:
+    return s
+
   #result = PSym(idTableGet(cl.symMap, s))
   #if result == nil:
-  result = copySym(s, false)
+  result = copySym(s)
   incl(result.flags, sfFromGeneric)
   #idTablePut(cl.symMap, s, result)
   result.owner = s.owner
@@ -215,41 +241,56 @@ proc replaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym =
   result.ast = replaceTypeVarsN(cl, s.ast)
 
 proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
-  result = PType(idTableGet(cl.typeMap, t))
+  result = cl.typeMap.lookup(t)
   if result == nil:
     if cl.allowMetaTypes or tfRetType in t.flags: return
-    localError(t.sym.info, errCannotInstantiateX, typeToString(t))
+    localError(cl.c.config, t.sym.info, "cannot instantiate: '" & typeToString(t) & "'")
     result = errorType(cl.c)
     # In order to prevent endless recursions, we must remember
     # this bad lookup and replace it with errorType everywhere.
     # These code paths are only active in "nim check"
-    idTablePut(cl.typeMap, t, result)
+    cl.typeMap.put(t, result)
   elif result.kind == tyGenericParam and not cl.allowMetaTypes:
-    internalError(cl.info, "substitution with generic parameter")
+    internalError(cl.c.config, cl.info, "substitution with generic parameter")
 
 proc instCopyType*(cl: var TReplTypeVars, t: PType): PType =
   # XXX: relying on allowMetaTypes is a kludge
   result = copyType(t, t.owner, cl.allowMetaTypes)
+  if cl.allowMetaTypes: return
   result.flags.incl tfFromGeneric
   if not (t.kind in tyMetaTypes or
          (t.kind == tyStatic and t.n == nil)):
     result.flags.excl tfInstClearedFlags
+  when false:
+    if newDestructors:
+      result.assignment = nil
+      #result.destructor = nil
+      result.sink = nil
+
+template typeBound(c, newty, oldty, field, info) =
+  let opr = newty.field
+  if opr != nil and sfFromGeneric notin opr.flags:
+    # '=' needs to be instantiated for generics when the type is constructed:
+    newty.field = c.instTypeBoundOp(c, opr, oldty, info, attachedAsgn, 1)
 
 proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
   # tyGenericInvocation[A, tyGenericInvocation[A, B]]
   # is difficult to handle:
+  const eqFlags = eqTypeFlags + {tfGcSafe}
   var body = t.sons[0]
-  if body.kind != tyGenericBody: internalError(cl.info, "no generic body")
+  if body.kind != tyGenericBody:
+    internalError(cl.c.config, cl.info, "no generic body")
   var header: PType = t
   # search for some instantiation here:
   if cl.allowMetaTypes:
     result = PType(idTableGet(cl.localCache, t))
   else:
     result = searchInstTypes(t)
-  if result != nil and eqTypeFlags*result.flags == eqTypeFlags*t.flags: return
+
+  if result != nil and eqFlags*result.flags == eqFlags*t.flags: return
   for i in countup(1, sonsLen(t) - 1):
     var x = t.sons[i]
-    if x.kind == tyGenericParam:
+    if x.kind in {tyGenericParam}:
       x = lookupTypeVar(cl, x)
       if x != nil:
         if header == t: header = instCopyType(cl, t)
@@ -261,7 +302,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
   if header != t:
     # search again after first pass:
     result = searchInstTypes(header)
-    if result != nil and eqTypeFlags*result.flags == eqTypeFlags*t.flags: return
+    if result != nil and eqFlags*result.flags == eqFlags*t.flags: return
   else:
     header = instCopyType(cl, t)
 
@@ -279,12 +320,16 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
 
   let oldSkipTypedesc = cl.skipTypedesc
   cl.skipTypedesc = true
+
+  var typeMapLayer = newTypeMapLayer(cl)
+  cl.typeMap = addr(typeMapLayer)
+
   for i in countup(1, sonsLen(t) - 1):
     var x = replaceTypeVarsT(cl, t.sons[i])
     assert x.kind != tyGenericInvocation
     header.sons[i] = x
     propagateToOwner(header, x)
-    idTablePut(cl.typeMap, body.sons[i-1], x)
+    cl.typeMap.put(body.sons[i-1], x)
 
   for i in countup(1, sonsLen(t) - 1):
     # if one of the params is not concrete, we cannot do anything
@@ -297,6 +342,9 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
   cl.skipTypedesc = oldSkipTypedesc
   newbody.flags = newbody.flags + (t.flags + body.flags - tfInstClearedFlags)
   result.flags = result.flags + newbody.flags - tfInstClearedFlags
+
+  cl.typeMap = cl.typeMap.nextLayer
+
   # This is actually wrong: tgeneric_closure fails with this line:
   #newbody.callConv = body.callConv
   # This type may be a generic alias and we want to resolve it here.
@@ -304,33 +352,38 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
   # handleGenericInvocation will handle the alias-to-alias-to-alias case
   if newbody.isGenericAlias: newbody = newbody.skipGenericAlias
   rawAddSon(result, newbody)
-  checkPartialConstructedType(cl.info, newbody)
+  checkPartialConstructedType(cl.c.config, cl.info, newbody)
   let dc = newbody.deepCopy
-  if dc != nil and sfFromGeneric notin newbody.deepCopy.flags:
-    # 'deepCopy' needs to be instantiated for
-    # generics *when the type is constructed*:
-    newbody.deepCopy = cl.c.instTypeBoundOp(cl.c, dc, result, cl.info,
-                                            attachedDeepCopy, 1)
-  if bodyIsNew and newbody.typeInst == nil:
-    #doassert newbody.typeInst == nil
-    newbody.typeInst = result
-    if tfRefsAnonObj in newbody.flags and newbody.kind != tyGenericInst:
-      # can come here for tyGenericInst too, see tests/metatype/ttypeor.nim
-      # need to look into this issue later
-      assert newbody.kind in {tyRef, tyPtr}
-      assert newbody.lastSon.typeInst == nil
-      newbody.lastSon.typeInst = result
-  let asgn = newbody.assignment
-  if asgn != nil and sfFromGeneric notin asgn.flags:
-    # '=' needs to be instantiated for generics when the type is constructed:
-    newbody.assignment = cl.c.instTypeBoundOp(cl.c, asgn, result, cl.info,
-                                              attachedAsgn, 1)
-  let methods = skipTypes(bbody, abstractPtrs).methods
-  for col, meth in items(methods):
-    # we instantiate the known methods belonging to that type, this causes
-    # them to be registered and that's enough, so we 'discard' the result.
-    discard cl.c.instTypeBoundOp(cl.c, meth, result, cl.info,
-      attachedAsgn, col)
+  if cl.allowMetaTypes == false:
+    if dc != nil and sfFromGeneric notin newbody.deepCopy.flags:
+      # 'deepCopy' needs to be instantiated for
+      # generics *when the type is constructed*:
+      newbody.deepCopy = cl.c.instTypeBoundOp(cl.c, dc, result, cl.info,
+                                              attachedDeepCopy, 1)
+    if bodyIsNew and newbody.typeInst == nil:
+      #doassert newbody.typeInst == nil
+      newbody.typeInst = result
+      if tfRefsAnonObj in newbody.flags and newbody.kind != tyGenericInst:
+        # can come here for tyGenericInst too, see tests/metatype/ttypeor.nim
+        # need to look into this issue later
+        assert newbody.kind in {tyRef, tyPtr}
+        if newbody.lastSon.typeInst != nil:
+          #internalError(cl.c.config, cl.info, "ref already has a 'typeInst' field")
+          discard
+        else:
+          newbody.lastSon.typeInst = result
+    cl.c.typesWithOps.add((newbody, result))
+    let mm = skipTypes(bbody, abstractPtrs)
+    if tfFromGeneric notin mm.flags:
+      # bug #5479, prevent endless recursions here:
+      incl mm.flags, tfFromGeneric
+      let methods = mm.methods
+      for col, meth in items(methods):
+        # we instantiate the known methods belonging to that type, this causes
+        # them to be registered and that's enough, so we 'discard' the result.
+        discard cl.c.instTypeBoundOp(cl.c, meth, result, cl.info,
+          attachedAsgn, col)
+      excl mm.flags, tfFromGeneric
 
 proc eraseVoidParams*(t: PType) =
   # transform '(): void' into '()' because old parts of the compiler really
@@ -338,11 +391,11 @@ proc eraseVoidParams*(t: PType) =
   if t.sons[0] != nil and t.sons[0].kind == tyVoid:
     t.sons[0] = nil
 
-  for i in 1 .. <t.sonsLen:
+  for i in 1 ..< t.sonsLen:
     # don't touch any memory unless necessary
     if t.sons[i].kind == tyVoid:
       var pos = i
-      for j in i+1 .. <t.sonsLen:
+      for j in i+1 ..< t.sonsLen:
         if t.sons[j].kind != tyVoid:
           t.sons[pos] = t.sons[j]
           t.n.sons[pos] = t.n.sons[j]
@@ -352,7 +405,7 @@ proc eraseVoidParams*(t: PType) =
       return
 
 proc skipIntLiteralParams*(t: PType) =
-  for i in 0 .. <t.sonsLen:
+  for i in 0 ..< t.sonsLen:
     let p = t.sons[i]
     if p == nil: continue
     let skipped = p.skipIntLit
@@ -370,7 +423,7 @@ proc propagateFieldFlags(t: PType, n: PNode) =
   # The type must be fully instantiated!
   if n.isNil:
     return
-  internalAssert n.kind != nkRecWhen
+  #internalAssert n.kind != nkRecWhen
   case n.kind
   of nkSym:
     propagateToOwner(t, n.sym.typ)
@@ -397,20 +450,26 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
   if t == nil: return
 
   if t.kind in {tyStatic, tyGenericParam} + tyTypeClasses:
-    let lookup = PType(idTableGet(cl.typeMap, t))
+    let lookup = cl.typeMap.lookup(t)
     if lookup != nil: return lookup
 
   case t.kind
   of tyGenericInvocation:
     result = handleGenericInvocation(cl, t)
+    if result.lastSon.kind == tyUserTypeClass:
+      result.kind = tyUserTypeClassInst
 
   of tyGenericBody:
-    localError(cl.info, errCannotInstantiateX, typeToString(t))
+    localError(cl.c.config, cl.info, "cannot instantiate: '" & typeToString(t) & "'")
     result = errorType(cl.c)
     #result = replaceTypeVarsT(cl, lastSon(t))
 
   of tyFromExpr:
     if cl.allowMetaTypes: return
+    # This assert is triggered when a tyFromExpr was created in a cyclic
+    # way. You should break the cycle at the point of creation by introducing
+    # a call such as: `n.typ = makeTypeFromExpr(c, n.copyTree)`
+    # Otherwise, the cycle will be fatal for the prepareNode call below
     assert t.n.typ != t
     var n = prepareNode(cl, t.n)
     if n.kind != nkEmpty:
@@ -437,21 +496,21 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
     result = skipIntLit(t)
 
   of tyTypeDesc:
-    let lookup = PType(idTableGet(cl.typeMap, t)) # lookupTypeVar(cl, t)
+    let lookup = cl.typeMap.lookup(t)
     if lookup != nil:
       result = lookup
       if tfUnresolved in t.flags or cl.skipTypedesc: result = result.base
     elif t.sons[0].kind != tyNone:
       result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t.sons[0]))
 
-  of tyUserTypeClass:
+  of tyUserTypeClass, tyStatic:
     result = t
 
-  of tyGenericInst:
+  of tyGenericInst, tyUserTypeClassInst:
     bailout()
     result = instCopyType(cl, t)
     idTablePut(cl.localCache, t, result)
-    for i in 1 .. <result.sonsLen:
+    for i in 1 ..< result.sonsLen:
       result.sons[i] = replaceTypeVarsT(cl, result.sons[i])
     propagateToOwner(result, result.lastSon)
 
@@ -469,18 +528,18 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
           var r = replaceTypeVarsT(cl, result.sons[i])
           if result.kind == tyObject:
             # carefully coded to not skip the precious tyGenericInst:
-            let r2 = r.skipTypes({tyGenericInst, tyAlias})
+            let r2 = r.skipTypes({tyAlias, tySink})
             if r2.kind in {tyPtr, tyRef}:
               r = skipTypes(r2, {tyPtr, tyRef})
           result.sons[i] = r
-          propagateToOwner(result, r)
+          if result.kind != tyArray or i != 0:
+            propagateToOwner(result, r)
       # bug #4677: Do not instantiate effect lists
       result.n = replaceTypeVarsN(cl, result.n, ord(result.kind==tyProc))
-
       case result.kind
       of tyArray:
         let idx = result.sons[0]
-        internalAssert idx.kind != tyStatic
+        internalAssert cl.c.config, idx.kind != tyStatic
 
       of tyObject, tyTuple:
         propagateFieldFlags(result, result.n)
@@ -491,36 +550,59 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
 
       else: discard
 
-proc initTypeVars*(p: PContext, pt: TIdTable, info: TLineInfo;
+proc instAllTypeBoundOp*(c: PContext, info: TLineInfo) =
+  var i = 0
+  while i < c.typesWithOps.len:
+    let (newty, oldty) = c.typesWithOps[i]
+    typeBound(c, newty, oldty, destructor, info)
+    typeBound(c, newty, oldty, sink, info)
+    typeBound(c, newty, oldty, assignment, info)
+    inc i
+  setLen(c.typesWithOps, 0)
+
+proc initTypeVars*(p: PContext, typeMap: ptr LayeredIdTable, info: TLineInfo;
                    owner: PSym): TReplTypeVars =
   initIdTable(result.symMap)
-  copyIdTable(result.typeMap, pt)
   initIdTable(result.localCache)
+  result.typeMap = typeMap
   result.info = info
   result.c = p
   result.owner = owner
 
 proc replaceTypesInBody*(p: PContext, pt: TIdTable, n: PNode;
-                         owner: PSym): PNode =
-  var cl = initTypeVars(p, pt, n.info, owner)
-  pushInfoContext(n.info)
+                         owner: PSym, allowMetaTypes = false): PNode =
+  var typeMap = initLayeredTypeMap(pt)
+  var cl = initTypeVars(p, addr(typeMap), n.info, owner)
+  cl.allowMetaTypes = allowMetaTypes
+  pushInfoContext(p.config, n.info)
   result = replaceTypeVarsN(cl, n)
-  popInfoContext()
+  popInfoContext(p.config)
 
 proc replaceTypesForLambda*(p: PContext, pt: TIdTable, n: PNode;
                             original, new: PSym): PNode =
-  var cl = initTypeVars(p, pt, n.info, original)
+  var typeMap = initLayeredTypeMap(pt)
+  var cl = initTypeVars(p, addr(typeMap), n.info, original)
   idTablePut(cl.symMap, original, new)
-  pushInfoContext(n.info)
+  pushInfoContext(p.config, n.info)
   result = replaceTypeVarsN(cl, n)
-  popInfoContext()
+  popInfoContext(p.config)
 
 proc generateTypeInstance*(p: PContext, pt: TIdTable, info: TLineInfo,
                            t: PType): PType =
-  var cl = initTypeVars(p, pt, info, nil)
-  pushInfoContext(info)
+  var typeMap = initLayeredTypeMap(pt)
+  var cl = initTypeVars(p, addr(typeMap), info, nil)
+  pushInfoContext(p.config, info)
+  result = replaceTypeVarsT(cl, t)
+  popInfoContext(p.config)
+
+proc prepareMetatypeForSigmatch*(p: PContext, pt: TIdTable, info: TLineInfo,
+                                 t: PType): PType =
+  var typeMap = initLayeredTypeMap(pt)
+  var cl = initTypeVars(p, addr(typeMap), info, nil)
+  cl.allowMetaTypes = true
+  pushInfoContext(p.config, info)
   result = replaceTypeVarsT(cl, t)
-  popInfoContext()
+  popInfoContext(p.config)
 
 template generateTypeInstance*(p: PContext, pt: TIdTable, arg: PNode,
                                t: PType): untyped =
diff --git a/compiler/service.nim b/compiler/service.nim
deleted file mode 100644
index ac04b7860..000000000
--- a/compiler/service.nim
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-#
-#           The Nim Compiler
-#        (c) Copyright 2015 Andreas Rumpf
-#
-#    See the file "copying.txt", included in this
-#    distribution, for details about the copyright.
-#
-
-## Implements the "compiler as a service" feature.
-
-import
-  times, commands, options, msgs, nimconf,
-  extccomp, strutils, os, platform, parseopt, idents
-
-when useCaas:
-  import net
-
-# We cache modules and the dependency graph. However, we don't check for
-# file changes but expect the client to tell us about them, otherwise the
-# repeated hash calculations may turn out to be too slow.
-
-var
-  curCaasCmd* = ""
-  lastCaasCmd* = ""
-    # in caas mode, the list of defines and options will be given at start-up?
-    # it's enough to check that the previous compilation command is the same?
-
-proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
-  var p = parseopt.initOptParser(cmd)
-  var argsCount = 0
-  while true:
-    parseopt.next(p)
-    case p.kind
-    of cmdEnd: break
-    of cmdLongoption, cmdShortOption:
-      if p.key == " ":
-        p.key = "-"
-        if processArgument(pass, p, argsCount): break
-      else:
-        processSwitch(pass, p)
-    of cmdArgument:
-      if processArgument(pass, p, argsCount): break
-  if pass == passCmd2:
-    if optRun notin gGlobalOptions and arguments != "" and options.command.normalize != "run":
-      rawMessage(errArgsNeedRunOption, [])
-
-proc serve*(cache: IdentCache; action: proc (cache: IdentCache){.nimcall.}) =
-  template execute(cmd) =
-    curCaasCmd = cmd
-    processCmdLine(passCmd2, cmd)
-    action(cache)
-    gErrorCounter = 0
-
-  let typ = getConfigVar("server.type")
-  case typ
-  of "stdin":
-    while true:
-      var line = stdin.readLine.string
-      if line == "quit": quit()
-      execute line
-      echo ""
-      flushFile(stdout)
-
-  of "tcp", "":
-    when useCaas:
-      var server = newSocket()
-      let p = getConfigVar("server.port")
-      let port = if p.len > 0: parseInt(p).Port else: 6000.Port
-      server.bindAddr(port, getConfigVar("server.address"))
-      var inp = "".TaintedString
-      server.listen()
-      var stdoutSocket = newSocket()
-      msgs.writelnHook = proc (line: string) =
-        stdoutSocket.send(line & "\c\L")
-
-      while true:
-        accept(server, stdoutSocket)
-        stdoutSocket.readLine(inp)
-        execute inp.string
-        stdoutSocket.send("\c\L")
-        stdoutSocket.close()
-    else:
-      msgQuit "server.type not supported; compiler built without caas support"
-  else:
-    echo "Invalid server.type:", typ
-    msgQuit 1
diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim
index fd703a433..218011b1d 100644
--- a/compiler/sighashes.nim
+++ b/compiler/sighashes.nim
@@ -9,10 +9,10 @@
 
 ## Computes hash values for routine (proc, method etc) signatures.
 
-import ast, md5
+import ast, md5, tables, ropes
 from hashes import Hash
 from astalgo import debug
-from types import typeToString, preferDesc
+import types
 from strutils import startsWith, contains
 
 when false:
@@ -68,6 +68,8 @@ else:
     toBase64a(cast[cstring](unsafeAddr u), sizeof(u))
   proc `&=`(c: var MD5Context, s: string) = md5Update(c, s, s.len)
   proc `&=`(c: var MD5Context, ch: char) = md5Update(c, unsafeAddr ch, 1)
+  proc `&=`(c: var MD5Context, r: Rope) =
+    for l in leaves(r): md5Update(c, l, l.len)
   proc `&=`(c: var MD5Context, i: BiggestInt) =
     md5Update(c, cast[cstring](unsafeAddr i), sizeof(i))
 
@@ -87,6 +89,7 @@ type
     CoProc
     CoType
     CoOwnerSig
+    CoIgnoreRange
 
 proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag])
 
@@ -136,7 +139,7 @@ proc hashTree(c: var MD5Context, n: PNode) =
   of nkStrLit..nkTripleStrLit:
     c &= n.strVal
   else:
-    for i in 0.. <n.len: hashTree(c, n.sons[i])
+    for i in 0..<n.len: hashTree(c, n.sons[i])
 
 proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) =
   if t == nil:
@@ -147,88 +150,98 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) =
   of tyGenericInvocation:
     for i in countup(0, sonsLen(t) - 1):
       c.hashType t.sons[i], flags
-    return
   of tyDistinct:
     if CoType in flags:
       c.hashType t.lastSon, flags
     else:
       c.hashSym(t.sym)
-    return
-  of tyAlias, tyGenericInst:
+  of tyGenericInst:
+    if sfInfixCall in t.base.sym.flags:
+      # This is an imported C++ generic type.
+      # We cannot trust the `lastSon` to hold a properly populated and unique
+      # value for each instantiation, so we hash the generic parameters here:
+      let normalizedType = t.skipGenericAlias
+      for i in 0 .. normalizedType.len - 2:
+        c.hashType t.sons[i], flags
+    else:
+      c.hashType t.lastSon, flags
+  of tyAlias, tySink, tyUserTypeClasses, tyInferred:
     c.hashType t.lastSon, flags
-    return
-  else:
-    discard
-  c &= char(t.kind)
-  case t.kind
   of tyBool, tyChar, tyInt..tyUInt64:
     # no canonicalization for integral types, so that e.g. ``pid_t`` is
     # produced instead of ``NI``:
+    c &= char(t.kind)
     if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}:
       c.hashSym(t.sym)
   of tyObject, tyEnum:
     if t.typeInst != nil:
-      assert t.typeInst.kind == tyGenericInst
-      for i in countup(1, sonsLen(t.typeInst) - 2):
-        c.hashType t.typeInst.sons[i], flags
+      # prevent against infinite recursions here, see bug #8883:
+      let inst = t.typeInst
+      t.typeInst = nil
+      assert inst.kind == tyGenericInst
+      for i in countup(0, inst.len - 2):
+        c.hashType inst.sons[i], flags
+      t.typeInst = inst
+      return
+    c &= char(t.kind)
     # Every cyclic type in Nim need to be constructed via some 't.sym', so this
     # is actually safe without an infinite recursion check:
     if t.sym != nil:
-      #if "Future:" in t.sym.name.s and t.typeInst == nil:
-      #  writeStackTrace()
-      #  echo "yes ", t.sym.name.s
-      #  #quit 1
-      if CoOwnerSig in flags:
+      if {sfCompilerProc} * t.sym.flags != {}:
+        doAssert t.sym.loc.r != nil
+        # The user has set a specific name for this type
+        c &= t.sym.loc.r
+      elif CoOwnerSig in flags:
         c.hashTypeSym(t.sym)
       else:
         c.hashSym(t.sym)
-      if sfAnon in t.sym.flags:
+      if {sfAnon, sfGenSym} * t.sym.flags != {}:
         # generated object names can be identical, so we need to
         # disambiguate furthermore by hashing the field types and names:
         # mild hack to prevent endless recursions (makes nimforum compile again):
-        excl t.sym.flags, sfAnon
+        let oldFlags = t.sym.flags
+        t.sym.flags = t.sym.flags - {sfAnon, sfGenSym}
         let n = t.n
         for i in 0 ..< n.len:
           assert n[i].kind == nkSym
           let s = n[i].sym
           c.hashSym s
           c.hashType s.typ, flags
-        incl t.sym.flags, sfAnon
+        t.sym.flags = oldFlags
     else:
       c &= t.id
     if t.len > 0 and t.sons[0] != nil:
       hashType c, t.sons[0], flags
   of tyRef, tyPtr, tyGenericBody, tyVar:
+    c &= char(t.kind)
     c.hashType t.lastSon, flags
     if tfVarIsPtr in t.flags: c &= ".varisptr"
-  of tyUserTypeClass:
-    if t.sym != nil and t.sym.owner != nil:
-      c &= t.sym.owner.name.s
-    else:
-      c &= "unknown typeclass"
-  of tyUserTypeClassInst:
-    let body = t.sons[0]
-    c.hashSym body.sym
-    for i in countup(1, sonsLen(t) - 2):
-      c.hashType t.sons[i], flags
-  of tyFromExpr, tyFieldAccessor:
+  of tyFromExpr:
+    c &= char(t.kind)
     c.hashTree(t.n)
   of tyTuple:
+    c &= char(t.kind)
     if t.n != nil and CoType notin flags:
       assert(sonsLen(t.n) == sonsLen(t))
       for i in countup(0, sonsLen(t.n) - 1):
         assert(t.n.sons[i].kind == nkSym)
         c &= t.n.sons[i].sym.name.s
         c &= ':'
-        c.hashType(t.sons[i], flags)
+        c.hashType(t.sons[i], flags+{CoIgnoreRange})
         c &= ','
     else:
-      for i in countup(0, sonsLen(t) - 1): c.hashType t.sons[i], flags
-  of tyRange, tyStatic:
-    #if CoType notin flags:
+      for i in countup(0, sonsLen(t) - 1): c.hashType t.sons[i], flags+{CoIgnoreRange}
+  of tyRange:
+    if CoIgnoreRange notin flags:
+      c &= char(t.kind)
+      c.hashTree(t.n)
+    c.hashType(t.sons[0], flags)
+  of tyStatic:
+    c &= char(t.kind)
     c.hashTree(t.n)
     c.hashType(t.sons[0], flags)
   of tyProc:
+    c &= char(t.kind)
     c &= (if tfIterator in t.flags: "iterator " else: "proc ")
     if CoProc in flags and t.n != nil:
       let params = t.n
@@ -240,14 +253,18 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) =
         c &= ','
       c.hashType(t.sons[0], flags)
     else:
-      for i in 0.. <t.len: c.hashType(t.sons[i], flags)
+      for i in 0..<t.len: c.hashType(t.sons[i], flags)
     c &= char(t.callConv)
     if CoType notin flags:
       if tfNoSideEffect in t.flags: c &= ".noSideEffect"
       if tfThread in t.flags: c &= ".thread"
     if tfVarargs in t.flags: c &= ".varargs"
+  of tyArray:
+    c &= char(t.kind)
+    for i in 0..<t.len: c.hashType(t.sons[i], flags-{CoIgnoreRange})
   else:
-    for i in 0.. <t.len: c.hashType(t.sons[i], flags)
+    c &= char(t.kind)
+    for i in 0..<t.len: c.hashType(t.sons[i], flags)
   if tfNotNil in t.flags and CoType notin flags: c &= "not nil"
 
 when defined(debugSigHashes):
@@ -321,3 +338,32 @@ proc hashOwner*(s: PSym): SigHash =
   c &= m.name.s
 
   md5Final c, result.Md5Digest
+
+proc idOrSig*(s: PSym, currentModule: string,
+              sigCollisions: var CountTable[SigHash]): Rope =
+  if s.kind in routineKinds and s.typ != nil:
+    # signatures for exported routines are reliable enough to
+    # produce a unique name and this means produced C++ is more stable wrt
+    # Nim changes:
+    let sig = hashProc(s)
+    result = rope($sig)
+    #let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m
+    let counter = sigCollisions.getOrDefault(sig)
+    #if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains":
+    #  echo "counter ", counter, " ", s.id
+    if counter != 0:
+      result.add "_" & rope(counter+1)
+    # this minor hack is necessary to make tests/collections/thashes compile.
+    # The inlined hash function's original module is ambiguous so we end up
+    # generating duplicate names otherwise:
+    if s.typ.callConv == ccInline:
+      result.add rope(currentModule)
+    sigCollisions.inc(sig)
+  else:
+    let sig = hashNonProc(s)
+    result = rope($sig)
+    let counter = sigCollisions.getOrDefault(sig)
+    if counter != 0:
+      result.add "_" & rope(counter+1)
+    sigCollisions.inc(sig)
+
diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim
index ae7be3c6d..d66e8d121 100644
--- a/compiler/sigmatch.nim
+++ b/compiler/sigmatch.nim
@@ -13,16 +13,23 @@
 import
   intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst,
   magicsys, condsyms, idents, lexer, options, parampatterns, strutils, trees,
-  nimfix.pretty
+  linter, lineinfos, lowerings, modulegraphs
 
-when not defined(noDocgen):
+when (defined(booting) or defined(nimsuggest)) and not defined(leanCompiler):
   import docgen
 
 type
   TCandidateState* = enum
     csEmpty, csMatch, csNoMatch
 
-  CandidateErrors* = seq[(PSym,int)]
+  CandidateError* = object
+    sym*: PSym
+    unmatchedVarParam*, firstMismatch*: int
+    diagnostics*: seq[string]
+    enabled*: bool
+
+  CandidateErrors* = seq[CandidateError]
+
   TCandidate* = object
     c*: PContext
     exactMatches*: int       # also misused to prefer iters over procs
@@ -50,9 +57,28 @@ type
     typedescMatched*: bool
     isNoCall*: bool          # misused for generic type instantiations C[T]
     mutabilityProblem*: uint8 # tyVar mismatch
-    inheritancePenalty: int  # to prefer closest father object type
-    errors*: CandidateErrors # additional clarifications to be displayed to the
-                             # user if overload resolution fails
+    inferredTypes: seq[PType] # inferred types during the current signature
+                              # matching. they will be reset if the matching
+                              # is not successful. may replace the bindings
+                              # table in the future.
+    diagnostics*: seq[string] # \
+                              # when diagnosticsEnabled, the matching process
+                              # will collect extra diagnostics that will be
+                              # displayed to the user.
+                              # triggered when overload resolution fails
+                              # or when the explain pragma is used. may be
+                              # triggered with an idetools command in the
+                              # future.
+    inheritancePenalty: int   # to prefer closest father object type
+    firstMismatch*: int       # position of the first type mismatch for
+                              # better error messages
+    diagnosticsEnabled*: bool
+
+  TTypeRelFlag* = enum
+    trDontBind
+    trNoCovariance
+
+  TTypeRelFlags* = set[TTypeRelFlag]
 
   TTypeRelation* = enum      # order is important!
     isNone, isConvertible,
@@ -73,7 +99,7 @@ type
 const
   isNilConversion = isConvertible # maybe 'isIntConv' fits better?
 
-proc markUsed*(info: TLineInfo, s: PSym; usageSym: var PSym)
+proc markUsed*(conf: ConfigRef; info: TLineInfo, s: PSym; usageSym: var PSym)
 
 template hasFauxMatch*(c: TCandidate): bool = c.fauxMatch != tyNone
 
@@ -101,7 +127,8 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
   idTablePut(c.bindings, key, val.skipIntLit)
 
 proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
-                    binding: PNode, calleeScope = -1) =
+                    binding: PNode, calleeScope = -1,
+                    diagnosticsEnabled = false) =
   initCandidateAux(ctx, c, callee.typ)
   c.calleeSym = callee
   if callee.kind in skProcKinds and calleeScope == -1:
@@ -116,21 +143,22 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
       c.calleeScope = 1
   else:
     c.calleeScope = calleeScope
+  c.diagnostics = @[] # if diagnosticsEnabled: @[] else: nil
+  c.diagnosticsEnabled = diagnosticsEnabled
   c.magic = c.calleeSym.magic
   initIdTable(c.bindings)
-  c.errors = nil
   if binding != nil and callee.kind in routineKinds:
     var typeParams = callee.ast[genericParamsPos]
     for i in 1..min(sonsLen(typeParams), sonsLen(binding)-1):
       var formalTypeParam = typeParams.sons[i-1].typ
       var bound = binding[i].typ
-      internalAssert bound != nil
-      if formalTypeParam.kind == tyTypeDesc:
-        if bound.kind != tyTypeDesc:
-          bound = makeTypeDesc(ctx, bound)
-      else:
-        bound = bound.skipTypes({tyTypeDesc})
-      put(c, formalTypeParam, bound)
+      if bound != nil:
+        if formalTypeParam.kind == tyTypeDesc:
+          if bound.kind != tyTypeDesc:
+            bound = makeTypeDesc(ctx, bound)
+        else:
+          bound = bound.skipTypes({tyTypeDesc})
+        put(c, formalTypeParam, bound)
 
 proc newCandidate*(ctx: PContext, callee: PSym,
                    binding: PNode, calleeScope = -1): TCandidate =
@@ -158,10 +186,18 @@ proc sumGeneric(t: PType): int =
   var isvar = 1
   while true:
     case t.kind
-    of tyGenericInst, tyArray, tyRef, tyPtr, tyDistinct,
-        tyOpenArray, tyVarargs, tySet, tyRange, tySequence, tyGenericBody:
+    of tyGenericInst, tyArray, tyRef, tyPtr, tyDistinct, tyUncheckedArray,
+        tyOpenArray, tyVarargs, tySet, tyRange, tySequence, tyGenericBody,
+        tyLent:
       t = t.lastSon
       inc result
+    of tyOr:
+      var maxBranch = 0
+      for branch in t.sons:
+        let branchSum = branch.sumGeneric
+        if branchSum > maxBranch: maxBranch = branchSum
+      inc result, maxBranch + 1
+      break
     of tyVar:
       t = t.sons[0]
       inc result
@@ -170,14 +206,16 @@ proc sumGeneric(t: PType): int =
       t = t.lastSon
       if t.kind == tyEmpty: break
       inc result
-    of tyGenericInvocation, tyTuple, tyProc:
-      result += ord(t.kind == tyGenericInvocation)
-      for i in 0 .. <t.len:
+    of tyGenericInvocation, tyTuple, tyProc, tyAnd:
+      result += ord(t.kind in {tyGenericInvocation, tyAnd})
+      for i in 0 ..< t.len:
         if t.sons[i] != nil:
           result += t.sons[i].sumGeneric
       break
-    of tyGenericParam, tyExpr, tyStatic, tyStmt: break
-    of tyAlias: t = t.lastSon
+    of tyStatic:
+      return t.sons[0].sumGeneric + 1
+    of tyGenericParam, tyExpr, tyStmt: break
+    of tyAlias, tySink: t = t.lastSon
     of tyBool, tyChar, tyEnum, tyObject, tyPointer,
         tyString, tyCString, tyInt..tyInt64, tyFloat..tyFloat128,
         tyUInt..tyUInt64, tyCompositeTypeClass:
@@ -190,11 +228,12 @@ proc sumGeneric(t: PType): int =
 proc complexDisambiguation(a, b: PType): int =
   # 'a' matches better if *every* argument matches better or equal than 'b'.
   var winner = 0
-  for i in 1 .. <min(a.len, b.len):
+  for i in 1 ..< min(a.len, b.len):
     let x = a.sons[i].sumGeneric
     let y = b.sons[i].sumGeneric
     #if ggDebug:
-    #  echo "came her ", typeToString(a.sons[i]), " ", typeToString(b.sons[i])
+    #echo "came herA ", typeToString(a.sons[i]), " ", x
+    #echo "came herB ", typeToString(b.sons[i]), " ", y
     if x != y:
       if winner == 0:
         if x > y: winner = 1
@@ -209,10 +248,19 @@ proc complexDisambiguation(a, b: PType): int =
   result = winner
   when false:
     var x, y: int
-    for i in 1 .. <a.len: x += a.sons[i].sumGeneric
-    for i in 1 .. <b.len: y += b.sons[i].sumGeneric
+    for i in 1 ..< a.len: x += a.sons[i].sumGeneric
+    for i in 1 ..< b.len: y += b.sons[i].sumGeneric
     result = x - y
 
+proc writeMatches*(c: TCandidate) =
+  echo "Candidate '", c.calleeSym.name.s, "' at ", c.c.config $ c.calleeSym.info
+  echo "  exact matches: ", c.exactMatches
+  echo "  generic matches: ", c.genericMatches
+  echo "  subtype matches: ", c.subtypeMatches
+  echo "  intconv matches: ", c.intConvMatches
+  echo "  conv matches: ", c.convMatches
+  echo "  inheritance: ", c.inheritancePenalty
+
 proc cmpCandidates*(a, b: TCandidate): int =
   result = a.exactMatches - b.exactMatches
   if result != 0: return
@@ -233,18 +281,10 @@ proc cmpCandidates*(a, b: TCandidate): int =
   if result != 0: return
   result = a.calleeScope - b.calleeScope
 
-proc writeMatches*(c: TCandidate) =
-  writeLine(stdout, "exact matches: " & $c.exactMatches)
-  writeLine(stdout, "generic matches: " & $c.genericMatches)
-  writeLine(stdout, "subtype matches: " & $c.subtypeMatches)
-  writeLine(stdout, "intconv matches: " & $c.intConvMatches)
-  writeLine(stdout, "conv matches: " & $c.convMatches)
-  writeLine(stdout, "inheritance: " & $c.inheritancePenalty)
-
 proc argTypeToString(arg: PNode; prefer: TPreferedDesc): string =
   if arg.kind in nkSymChoices:
     result = typeToString(arg[0].typ, prefer)
-    for i in 1 .. <arg.len:
+    for i in 1 ..< arg.len:
       result.add(" | ")
       result.add typeToString(arg[i].typ, prefer)
   elif arg.typ == nil:
@@ -266,14 +306,42 @@ proc describeArgs*(c: PContext, n: PNode, startIdx = 1;
         n.sons[i].typ = arg.typ
         n.sons[i].sons[1] = arg
     else:
-      if arg.typ.isNil and arg.kind notin {nkStmtList, nkDo}:
+      if arg.typ.isNil and arg.kind notin {nkStmtList, nkDo, nkElse,
+                                           nkOfBranch, nkElifBranch,
+                                           nkExceptBranch}:
         arg = c.semOperand(c, n.sons[i])
         n.sons[i] = arg
     if arg.typ != nil and arg.typ.kind == tyError: return
     add(result, argTypeToString(arg, prefer))
     if i != sonsLen(n) - 1: add(result, ", ")
 
-proc typeRel*(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation
+proc typeRelImpl*(c: var TCandidate, f, aOrig: PType,
+                  flags: TTypeRelFlags = {}): TTypeRelation
+
+const traceTypeRel = false
+
+when traceTypeRel:
+  var nextTypeRel = 0
+
+template typeRel*(c: var TCandidate, f, aOrig: PType,
+                 flags: TTypeRelFlags = {}): TTypeRelation =
+  when traceTypeRel:
+    var enteringAt = nextTypeRel
+    if mdbg:
+      inc nextTypeRel
+      echo "----- TYPE REL ", enteringAt
+      debug f
+      debug aOrig
+      # writeStackTrace()
+
+  let r = typeRelImpl(c, f, aOrig, flags)
+
+  when traceTypeRel:
+    if enteringAt != nextTypeRel:
+      echo "----- TYPE REL ", enteringAt, " RESULT: ", r
+
+  r
+
 proc concreteType(c: TCandidate, t: PType): PType =
   case t.kind
   of tyNil:
@@ -294,8 +362,8 @@ proc concreteType(c: TCandidate, t: PType): PType =
         # proc sort[T](cmp: proc(a, b: T): int = cmp)
       if result.kind != tyGenericParam: break
   of tyGenericInvocation:
-    internalError("cannot resolve type: " & typeToString(t))
     result = t
+    doAssert(false, "cannot resolve type: " & typeToString(t))
   else:
     result = t                # Note: empty is valid here
 
@@ -308,8 +376,10 @@ proc handleRange(f, a: PType, min, max: TTypeKind): TTypeRelation =
     if k == f.kind: result = isSubrange
     elif k == tyInt and f.kind in {tyRange, tyInt8..tyInt64,
                                    tyUInt..tyUInt64} and
-        isIntLit(ab) and ab.n.intVal >= firstOrd(f) and
-                         ab.n.intVal <= lastOrd(f):
+        isIntLit(ab) and ab.n.intVal >= firstOrd(nil, f) and
+                         ab.n.intVal <= lastOrd(nil, f):
+      # passing 'nil' to firstOrd/lastOrd here as type checking rules should
+      # not depent on the target integer size configurations!
       # integer literal in the proper range; we want ``i16 + 4`` to stay an
       # ``int16`` operation so we declare the ``4`` pseudo-equal to int16
       result = isFromIntLit
@@ -319,8 +389,10 @@ proc handleRange(f, a: PType, min, max: TTypeKind): TTypeRelation =
       result = isConvertible
     elif a.kind == tyRange and a.sons[0].kind in {tyInt..tyInt64,
                                                   tyUInt8..tyUInt32} and
-                         a.n[0].intVal >= firstOrd(f) and
-                         a.n[1].intVal <= lastOrd(f):
+                         a.n[0].intVal >= firstOrd(nil, f) and
+                         a.n[1].intVal <= lastOrd(nil, f):
+      # passing 'nil' to firstOrd/lastOrd here as type checking rules should
+      # not depent on the target integer size configurations!
       result = isConvertible
     else: result = isNone
     #elif f.kind == tyInt and k in {tyInt..tyInt32}: result = isIntConv
@@ -330,7 +402,16 @@ proc isConvertibleToRange(f, a: PType): bool =
   # be less picky for tyRange, as that it is used for array indexing:
   if f.kind in {tyInt..tyInt64, tyUInt..tyUInt64} and
      a.kind in {tyInt..tyInt64, tyUInt..tyUInt64}:
-    result = true
+    case f.kind
+    of tyInt, tyInt64: result = true
+    of tyInt8: result = a.kind in {tyInt8, tyInt}
+    of tyInt16: result = a.kind in {tyInt8, tyInt16, tyInt}
+    of tyInt32: result = a.kind in {tyInt8, tyInt16, tyInt32, tyInt}
+    of tyUInt, tyUInt64: result = true
+    of tyUInt8: result = a.kind in {tyUInt8, tyUInt}
+    of tyUInt16: result = a.kind in {tyUInt8, tyUInt16, tyUInt}
+    of tyUInt32: result = a.kind in {tyUInt8, tyUInt16, tyUInt32, tyUInt}
+    else: result = false
   elif f.kind in {tyFloat..tyFloat128} and
        a.kind in {tyFloat..tyFloat128}:
     result = true
@@ -350,6 +431,14 @@ proc handleFloatRange(f, a: PType): TTypeRelation =
       else: result = isIntConv
     else: result = isNone
 
+proc genericParamPut(c: var TCandidate; last, fGenericOrigin: PType) =
+  if fGenericOrigin != nil and last.kind == tyGenericInst and
+     last.len-1 == fGenericOrigin.len:
+   for i in countup(1, sonsLen(fGenericOrigin) - 1):
+     let x = PType(idTableGet(c.bindings, fGenericOrigin.sons[i]))
+     if x == nil:
+       put(c, fGenericOrigin.sons[i], last.sons[i])
+
 proc isObjectSubtype(c: var TCandidate; a, f, fGenericOrigin: PType): int =
   var t = a
   assert t.kind == tyObject
@@ -363,12 +452,7 @@ proc isObjectSubtype(c: var TCandidate; a, f, fGenericOrigin: PType): int =
     t = skipTypes(t, skipPtrs)
     inc depth
   if t != nil:
-    if fGenericOrigin != nil and last.kind == tyGenericInst and
-        last.len-1 == fGenericOrigin.len:
-      for i in countup(1, sonsLen(fGenericOrigin) - 1):
-        let x = PType(idTableGet(c.bindings, fGenericOrigin.sons[i]))
-        if x == nil:
-          put(c, fGenericOrigin.sons[i], last.sons[i])
+    genericParamPut(c, last, fGenericOrigin)
     result = depth
   else:
     result = -1
@@ -392,13 +476,13 @@ proc skipToObject(t: PType; skipped: var SkippedPtr): PType =
       inc ptrs
       skipped = skippedPtr
       r = r.lastSon
-    of tyGenericBody, tyGenericInst, tyAlias:
+    of tyGenericBody, tyGenericInst, tyAlias, tySink:
       r = r.lastSon
     else:
       break
   if r.kind == tyObject and ptrs <= 1: result = r
 
-proc isGenericSubtype(a, f: PType, d: var int): bool =
+proc isGenericSubtype(c: var TCandidate; a, f: PType, d: var int, fGenericOrigin: PType = nil): bool =
   assert f.kind in {tyGenericInst, tyGenericInvocation, tyGenericBody}
   var askip = skippedNone
   var fskip = skippedNone
@@ -406,14 +490,17 @@ proc isGenericSubtype(a, f: PType, d: var int): bool =
   let r = f.skipToObject(fskip)
   if r == nil: return false
   var depth = 0
+  var last = a
   # XXX sameObjectType can return false here. Need to investigate
   # why that is but sameObjectType does way too much work here anyway.
   while t != nil and r.sym != t.sym and askip == fskip:
     t = t.sons[0]
-    if t != nil: t = t.skipToObject(askip)
-    else: break
+    if t == nil: break
+    last = t
+    t = t.skipToObject(askip)
     inc depth
   if t != nil and askip == fskip:
+    genericParamPut(c, last, fGenericOrigin)
     d = depth
     result = true
 
@@ -436,8 +523,8 @@ proc recordRel(c: var TCandidate, f, a: PType): TTypeRelation =
     if f.n != nil and a.n != nil:
       for i in countup(0, sonsLen(f.n) - 1):
         # check field names:
-        if f.n.sons[i].kind != nkSym: internalError(f.n.info, "recordRel")
-        elif a.n.sons[i].kind != nkSym: internalError(a.n.info, "recordRel")
+        if f.n.sons[i].kind != nkSym: return isNone
+        elif a.n.sons[i].kind != nkSym: return isNone
         else:
           var x = f.n.sons[i].sym
           var y = a.n.sons[i].sym
@@ -448,8 +535,14 @@ proc recordRel(c: var TCandidate, f, a: PType): TTypeRelation =
 proc allowsNil(f: PType): TTypeRelation {.inline.} =
   result = if tfNotNil notin f.flags: isSubtype else: isNone
 
+proc allowsNilDeprecated(c: TCandidate, f: PType): TTypeRelation =
+  if optNilSeqs in c.c.config.options:
+    result = allowsNil(f)
+  else:
+    result = isNone
+
 proc inconsistentVarTypes(f, a: PType): bool {.inline.} =
-  result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar)
+  result = f.kind != a.kind and (f.kind in {tyVar, tyLent} or a.kind in {tyVar, tyLent})
 
 proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
   ## For example we have:
@@ -482,7 +575,10 @@ proc procParamTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
       # signature. There is a change that the target
       # type is already fully-determined, so we are
       # going to try resolve it
-      f = generateTypeInstance(c.c, c.bindings, c.call.info, f)
+      if c.call != nil:
+        f = generateTypeInstance(c.c, c.bindings, c.call.info, f)
+      else:
+        f = nil
       if f == nil or f.isMetaType:
         # no luck resolving the type, so the inference fails
         return isBothMetaConvertible
@@ -515,7 +611,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
 
     # Note: We have to do unification for the parameters before the
     # return type!
-    for i in 1 .. <f.sonsLen:
+    for i in 1 ..< f.sonsLen:
       checkParam(f.sons[i], a.sons[i])
 
     if f.sons[0] != nil:
@@ -529,7 +625,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
     if tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags:
       return isNone
     elif tfThread in f.flags and a.flags * {tfThread, tfNoSideEffect} == {} and
-        optThreadAnalysis in gGlobalOptions:
+        optThreadAnalysis in c.c.config.globalOptions:
       # noSideEffect implies ``tfThread``!
       return isNone
     elif f.flags * {tfIterator} != a.flags * {tfIterator}:
@@ -550,114 +646,324 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
   else: discard
 
 proc typeRangeRel(f, a: PType): TTypeRelation {.noinline.} =
-  let
-    a0 = firstOrd(a)
-    a1 = lastOrd(a)
-    f0 = firstOrd(f)
-    f1 = lastOrd(f)
-  if a0 == f0 and a1 == f1:
-    result = isEqual
-  elif a0 >= f0 and a1 <= f1:
-    result = isConvertible
-  elif a0 <= f1 and f0 <= a1:
-    # X..Y and C..D overlap iff (X <= D and C <= Y)
-    result = isConvertible
+  template checkRange[T](a0, a1, f0, f1: T): TTypeRelation =
+    if a0 == f0 and a1 == f1:
+      isEqual
+    elif a0 >= f0 and a1 <= f1:
+      isConvertible
+    elif a0 <= f1 and f0 <= a1:
+      # X..Y and C..D overlap iff (X <= D and C <= Y)
+      isConvertible
+    else:
+      isNone
+
+  if f.isOrdinalType:
+    checkRange(firstOrd(nil, a), lastOrd(nil, a), firstOrd(nil, f), lastOrd(nil, f))
   else:
-    result = isNone
+    checkRange(firstFloat(a), lastFloat(a), firstFloat(f), lastFloat(f))
 
-proc matchUserTypeClass*(c: PContext, m: var TCandidate,
-                         ff, a: PType): TTypeRelation =
-  var body = ff.skipTypes({tyUserTypeClassInst})
-  if c.inTypeClass > 4:
-    localError(body.n[3].info, $body.n[3] & " too nested for type matching")
-    return isNone
 
-  openScope(c)
-  inc c.inTypeClass
+proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType =
+  var
+    c = m.c
+    typeClass = ff.skipTypes({tyUserTypeClassInst})
+    body = typeClass.n[3]
+    matchedConceptContext: TMatchedConcept
+    prevMatchedConcept = c.matchedConcept
+    prevCandidateType = typeClass[0][0]
+
+  if prevMatchedConcept != nil:
+    matchedConceptContext.prev = prevMatchedConcept
+    matchedConceptContext.depth = prevMatchedConcept.depth + 1
+    if prevMatchedConcept.depth > 4:
+      localError(m.c.graph.config, body.info, $body & " too nested for type matching")
+      return nil
 
+  openScope(c)
+  matchedConceptContext.candidateType = a
+  typeClass[0].sons[0] = a
+  c.matchedConcept = addr(matchedConceptContext)
   defer:
-    dec c.inTypeClass
+    c.matchedConcept = prevMatchedConcept
+    typeClass[0].sons[0] = prevCandidateType
     closeScope(c)
 
+  var typeParams: seq[(PSym, PType)]
+
   if ff.kind == tyUserTypeClassInst:
-    for i in 1 .. <(ff.len - 1):
+    for i in 1 ..< (ff.len - 1):
       var
         typeParamName = ff.base.sons[i-1].sym.name
         typ = ff.sons[i]
         param: PSym
+        alreadyBound = PType(idTableGet(m.bindings, typ))
+
+      if alreadyBound != nil: typ = alreadyBound
 
       template paramSym(kind): untyped =
-        newSym(kind, typeParamName, body.sym, body.sym.info)
-
-      case typ.kind
-      of tyStatic:
-        param = paramSym skConst
-        param.typ = typ.base
-        param.ast = typ.n
-      of tyUnknown:
-        param = paramSym skVar
-        param.typ = typ
-      else:
-        param = paramSym skType
-        param.typ = makeTypeDesc(c, typ)
+        newSym(kind, typeParamName, typeClass.sym, typeClass.sym.info, {})
+
+      block addTypeParam:
+        for prev in typeParams:
+          if prev[1].id == typ.id:
+            param = paramSym prev[0].kind
+            param.typ = prev[0].typ
+            break addTypeParam
+
+        case typ.kind
+        of tyStatic:
+          param = paramSym skConst
+          param.typ = typ.exactReplica
+          if typ.n == nil:
+            param.typ.flags.incl tfInferrableStatic
+          else:
+            param.ast = typ.n
+        of tyUnknown:
+          param = paramSym skVar
+          param.typ = typ.exactReplica
+        else:
+          param = paramSym skType
+          param.typ = if typ.isMetaType:
+                        c.newTypeWithSons(tyInferred, @[typ])
+                      else:
+                        makeTypeDesc(c, typ)
 
-      addDecl(c, param)
-      #echo "A ", param.name.s, " ", typeToString(param.typ), " ", param.kind
+        typeParams.add((param, typ))
 
-  for param in body.n[0]:
-    var
-      dummyName: PNode
-      dummyType: PType
+      addDecl(c, param)
 
-    if param.kind == nkVarTy:
-      dummyName = param[0]
-      dummyType = if a.kind != tyVar: makeVarType(c, a) else: a
-    else:
-      dummyName = param
-      dummyType = a
+  var
+    oldWriteHook: type(m.c.config.writelnHook)
+    diagnostics: seq[string]
+    errorPrefix: string
+    flags: TExprFlags = {}
+    collectDiagnostics = m.diagnosticsEnabled or
+                         sfExplain in typeClass.sym.flags
+
+  if collectDiagnostics:
+    oldWriteHook = m.c.config.writelnHook
+    # XXX: we can't write to m.diagnostics directly, because
+    # Nim doesn't support capturing var params in closures
+    diagnostics = @[]
+    flags = {efExplain}
+    m.c.config.writelnHook = proc (s: string) =
+      if errorPrefix.len == 0: errorPrefix = typeClass.sym.name.s & ":"
+      let msg = s.replace("Error:", errorPrefix)
+      if oldWriteHook != nil: oldWriteHook msg
+      diagnostics.add msg
+
+  var checkedBody = c.semTryExpr(c, body.copyTree, flags)
+
+  if collectDiagnostics:
+    m.c.config.writelnHook = oldWriteHook
+    for msg in diagnostics:
+      m.diagnostics.add msg
+      m.diagnosticsEnabled = true
+
+  if checkedBody == nil: return nil
+
+  # The inferrable type params have been identified during the semTryExpr above.
+  # We need to put them in the current sigmatch's binding table in order for them
+  # to be resolvable while matching the rest of the parameters
+  for p in typeParams:
+    put(m, p[1], p[0].typ)
 
-    internalAssert dummyName.kind == nkIdent
-    var dummyParam = newSym(skVar, dummyName.ident, body.sym, body.sym.info)
-    dummyParam.typ = dummyType
-    addDecl(c, dummyParam)
-    #echo "B ", dummyName.ident.s, " ", typeToString(dummyType), " ", dummyparam.kind
+  if ff.kind == tyUserTypeClassInst:
+    result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff)
+  else:
+    result = copyType(ff, ff.owner, true)
 
-  var checkedBody = c.semTryExpr(c, body.n[3].copyTree)
-  if checkedBody == nil: return isNone
-  return isGeneric
+  result.n = checkedBody
 
-proc shouldSkipDistinct(rules: PNode, callIdent: PIdent): bool =
+proc shouldSkipDistinct(m: TCandidate; rules: PNode, callIdent: PIdent): bool =
+  # XXX This is bad as 'considerQuotedIdent' can produce an error!
   if rules.kind == nkWith:
     for r in rules:
-      if r.considerQuotedIdent == callIdent: return true
+      if considerQuotedIdent(m.c, r) == callIdent: return true
     return false
   else:
     for r in rules:
-      if r.considerQuotedIdent == callIdent: return false
+      if considerQuotedIdent(m.c, r) == callIdent: return false
     return true
 
-proc maybeSkipDistinct(t: PType, callee: PSym): PType =
+proc maybeSkipDistinct(m: TCandidate; t: PType, callee: PSym): PType =
   if t != nil and t.kind == tyDistinct and t.n != nil and
-     shouldSkipDistinct(t.n, callee.name):
+     shouldSkipDistinct(m, t.n, callee.name):
     result = t.base
   else:
     result = t
 
-proc tryResolvingStaticExpr(c: var TCandidate, n: PNode): PNode =
+proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
+                            allowUnresolved = false): PNode =
   # Consider this example:
   #   type Value[N: static[int]] = object
   #   proc foo[N](a: Value[N], r: range[0..(N-1)])
   # Here, N-1 will be initially nkStaticExpr that can be evaluated only after
   # N is bound to a concrete value during the matching of the first param.
   # This proc is used to evaluate such static expressions.
-  let instantiated = replaceTypesInBody(c.c, c.bindings, n, nil)
+  let instantiated = replaceTypesInBody(c.c, c.bindings, n, nil,
+                                        allowMetaTypes = allowUnresolved)
   result = c.c.semExpr(c.c, instantiated)
 
+proc inferStaticParam*(c: var TCandidate, lhs: PNode, rhs: BiggestInt): bool =
+  # This is a simple integer arithimetic equation solver,
+  # capable of deriving the value of a static parameter in
+  # expressions such as (N + 5) / 2 = rhs
+  #
+  # Preconditions:
+  #
+  #   * The input of this proc must be semantized
+  #     - all templates should be expanded
+  #     - aby constant folding possible should already be performed
+  #
+  #   * There must be exactly one unresolved static parameter
+  #
+  # Result:
+  #
+  #   The proc will return true if the static types was successfully
+  #   inferred. The result will be bound to the original static type
+  #   in the TCandidate.
+  #
+  if lhs.kind in nkCallKinds and lhs[0].kind == nkSym:
+    case lhs[0].sym.magic
+    of mUnaryLt:
+      return inferStaticParam(c, lhs[1], rhs + 1)
+
+    of mAddI, mAddU, mInc, mSucc:
+      if lhs[1].kind == nkIntLit:
+        return inferStaticParam(c, lhs[2], rhs - lhs[1].intVal)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs - lhs[2].intVal)
+
+    of mDec, mSubI, mSubU, mPred:
+      if lhs[1].kind == nkIntLit:
+        return inferStaticParam(c, lhs[2], lhs[1].intVal - rhs)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs + lhs[2].intVal)
+
+    of mMulI, mMulU:
+      if lhs[1].kind == nkIntLit:
+        if rhs mod lhs[1].intVal == 0:
+          return inferStaticParam(c, lhs[2], rhs div lhs[1].intVal)
+      elif lhs[2].kind == nkIntLit:
+        if rhs mod lhs[2].intVal == 0:
+          return inferStaticParam(c, lhs[1], rhs div lhs[2].intVal)
+
+    of mDivI, mDivU:
+      if lhs[1].kind == nkIntLit:
+        if lhs[1].intVal mod rhs == 0:
+          return inferStaticParam(c, lhs[2], lhs[1].intVal div rhs)
+      elif lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], lhs[2].intVal * rhs)
+
+    of mShlI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs shr lhs[2].intVal)
+
+    of mShrI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], rhs shl lhs[2].intVal)
+
+    of mAshrI:
+      if lhs[2].kind == nkIntLit:
+        return inferStaticParam(c, lhs[1], ashr(rhs, lhs[2].intVal))
+
+    of mUnaryMinusI:
+      return inferStaticParam(c, lhs[1], -rhs)
+
+    of mUnaryPlusI, mToInt, mToBiggestInt:
+      return inferStaticParam(c, lhs[1], rhs)
+
+    else: discard
+
+  elif lhs.kind == nkSym and lhs.typ.kind == tyStatic and lhs.typ.n == nil:
+    var inferred = newTypeWithSons(c.c, tyStatic, lhs.typ.sons)
+    inferred.n = newIntNode(nkIntLit, rhs)
+    put(c, lhs.typ, inferred)
+    if c.c.matchedConcept != nil:
+      # inside concepts, binding is currently done with
+      # direct mutation of the involved types:
+      lhs.typ.n = inferred.n
+    return true
+
+  return false
+
+proc failureToInferStaticParam(conf: ConfigRef; n: PNode) =
+  let staticParam = n.findUnresolvedStatic
+  let name = if staticParam != nil: staticParam.sym.name.s
+             else: "unknown"
+  localError(conf, n.info, "cannot infer the value of the static param '" & name & "'")
+
+proc inferStaticsInRange(c: var TCandidate,
+                         inferred, concrete: PType): TTypeRelation =
+  let lowerBound = tryResolvingStaticExpr(c, inferred.n[0],
+                                          allowUnresolved = true)
+  let upperBound = tryResolvingStaticExpr(c, inferred.n[1],
+                                          allowUnresolved = true)
+  template doInferStatic(e: PNode, r: BiggestInt) =
+    var exp = e
+    var rhs = r
+    if inferStaticParam(c, exp, rhs):
+      return isGeneric
+    else:
+      failureToInferStaticParam(c.c.config, exp)
+
+  if lowerBound.kind == nkIntLit:
+    if upperBound.kind == nkIntLit:
+      if lengthOrd(c.c.config, concrete) == upperBound.intVal - lowerBound.intVal + 1:
+        return isGeneric
+      else:
+        return isNone
+    doInferStatic(upperBound, lengthOrd(c.c.config, concrete) + lowerBound.intVal - 1)
+  elif upperBound.kind == nkIntLit:
+    doInferStatic(lowerBound, upperBound.intVal + 1 - lengthOrd(c.c.config, concrete))
+
 template subtypeCheck() =
-  if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar}:
+  if result <= isSubrange and f.lastSon.skipTypes(abstractInst).kind in {tyRef, tyPtr, tyVar, tyLent}:
     result = isNone
 
-proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
+proc isCovariantPtr(c: var TCandidate, f, a: PType): bool =
+  # this proc is always called for a pair of matching types
+  assert f.kind == a.kind
+
+  template baseTypesCheck(lhs, rhs: PType): bool =
+    lhs.kind notin {tyPtr, tyRef, tyVar, tyLent} and
+      typeRel(c, lhs, rhs, {trNoCovariance}) == isSubtype
+
+  case f.kind
+  of tyRef, tyPtr:
+    return baseTypesCheck(f.base, a.base)
+  of tyGenericInst:
+    let body = f.base
+    return body == a.base and
+           a.sonsLen == 3 and
+           tfWeakCovariant notin body.sons[0].flags and
+           baseTypesCheck(f.sons[1], a.sons[1])
+  else:
+    return false
+
+when false:
+  proc maxNumericType(prev, candidate: PType): PType =
+    let c = candidate.skipTypes({tyRange})
+    template greater(s) =
+      if c.kind in s: result = c
+    case prev.kind
+    of tyInt: greater({tyInt64})
+    of tyInt8: greater({tyInt, tyInt16, tyInt32, tyInt64})
+    of tyInt16: greater({tyInt, tyInt32, tyInt64})
+    of tyInt32: greater({tyInt64})
+
+    of tyUInt: greater({tyUInt64})
+    of tyUInt8: greater({tyUInt, tyUInt16, tyUInt32, tyUInt64})
+    of tyUInt16: greater({tyUInt, tyUInt32, tyUInt64})
+    of tyUInt32: greater({tyUInt64})
+
+    of tyFloat32: greater({tyFloat64, tyFloat128})
+    of tyFloat64: greater({tyFloat128})
+    else: discard
+
+proc typeRelImpl(c: var TCandidate, f, aOrig: PType,
+                 flags: TTypeRelFlags = {}): TTypeRelation =
   # typeRel can be used to establish various relationships between types:
   #
   # 1) When used with concrete types, it will check for type equivalence
@@ -683,19 +989,70 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
 
   assert(aOrig != nil)
 
-  # var and static arguments match regular modifier-free types
-  let a = aOrig.skipTypes({tyStatic, tyVar}).maybeSkipDistinct(c.calleeSym)
+  var
+    useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
+                                     not c.isNoCall and
+                                     f.kind != tyTypeDesc and
+                                     tfExplicit notin aOrig.flags and
+                                     tfConceptMatchedTypeSym notin aOrig.flags
+
+    aOrig = if useTypeLoweringRuleInTypeClass:
+          aOrig.skipTypes({tyTypeDesc})
+        else:
+          aOrig
+
+  if aOrig.kind == tyInferred:
+    let prev = aOrig.previouslyInferred
+    if prev != nil:
+      return typeRel(c, f, prev)
+    else:
+      var candidate = f
+
+      case f.kind
+      of tyGenericParam:
+        var prev  = PType(idTableGet(c.bindings, f))
+        if prev != nil: candidate = prev
+      of tyFromExpr:
+        let computedType = tryResolvingStaticExpr(c, f.n).typ
+        case computedType.kind
+        of tyTypeDesc:
+          candidate = computedType.base
+        of tyStatic:
+          candidate = computedType
+        else:
+          # XXX What is this non-sense? Error reporting in signature matching?
+          discard "localError(f.n.info, errTypeExpected)"
+      else:
+        discard
+
+      result = typeRel(c, aOrig.base, candidate)
+      if result != isNone:
+        c.inferredTypes.add aOrig
+        aOrig.sons.add candidate
+        result = isEqual
+      return
+
+  template doBind: bool = trDontBind notin flags
+
+  # var, sink and static arguments match regular modifier-free types
+  var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent, tySink}), c.calleeSym)
   # XXX: Theoretically, maybeSkipDistinct could be called before we even
   # start the param matching process. This could be done in `prepareOperand`
   # for example, but unfortunately `prepareOperand` is not called in certain
   # situation when nkDotExpr are rotated to nkDotCalls
 
-  if a.kind in {tyGenericInst, tyAlias} and
-      skipTypes(f, {tyVar}).kind notin {
+  if aOrig.kind in {tyAlias, tySink}:
+    return typeRel(c, f, lastSon(aOrig))
+
+  if a.kind == tyGenericInst and
+      skipTypes(f, {tyVar, tyLent, tySink}).kind notin {
         tyGenericBody, tyGenericInvocation,
         tyGenericInst, tyGenericParam} + tyTypeClasses:
     return typeRel(c, f, lastSon(a))
 
+  if a.isResolvedUserTypeClass:
+    return typeRel(c, f, a.lastSon)
+
   template bindingRet(res) =
     if doBind:
       let bound = aOrig.skipTypes({tyRange}).skipIntLit
@@ -709,23 +1066,28 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
 
   case a.kind
   of tyOr:
+    # XXX: deal with the current dual meaning of tyGenericParam
+    c.typedescMatched = true
     # seq[int|string] vs seq[number]
     # both int and string must match against number
     # but ensure that '[T: A|A]' matches as good as '[T: A]' (bug #2219):
     result = isGeneric
     for branch in a.sons:
-      let x = typeRel(c, f, branch, false)
+      let x = typeRel(c, f, branch, flags + {trDontBind})
       if x == isNone: return isNone
       if x < result: result = x
+    return
 
   of tyAnd:
+    # XXX: deal with the current dual meaning of tyGenericParam
+    c.typedescMatched = true
     # seq[Sortable and Iterable] vs seq[Sortable]
     # only one match is enough
     for branch in a.sons:
-      let x = typeRel(c, f, branch, false)
+      let x = typeRel(c, f, branch, flags + {trDontBind})
       if x != isNone:
         return if x >= isGeneric: isGeneric else: x
-    result = isNone
+    return isNone
 
   of tyNot:
     case f.kind
@@ -743,15 +1105,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
              else: isNone
 
   of tyAnything:
-    return if f.kind == tyAnything: isGeneric
-           else: isNone
+    if f.kind == tyAnything: return isGeneric
+    else: return isNone
 
   of tyUserTypeClass, tyUserTypeClassInst:
-    # consider this: 'var g: Node' *within* a concept where 'Node'
-    # is a concept too (tgraph)
-    let x = typeRel(c, a, f, false)
-    if x >= isGeneric:
-      return isGeneric
+    if c.c.matchedConcept != nil and c.c.matchedConcept.depth <= 4:
+      # consider this: 'var g: Node' *within* a concept where 'Node'
+      # is a concept too (tgraph)
+      inc c.c.matchedConcept.depth
+      let x = typeRel(c, a, f, flags + {trDontBind})
+      if x >= isGeneric:
+        return isGeneric
   else: discard
 
   case f.kind
@@ -793,14 +1157,15 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
   of tyFloat32:  result = handleFloatRange(f, a)
   of tyFloat64:  result = handleFloatRange(f, a)
   of tyFloat128: result = handleFloatRange(f, a)
-  of tyVar:
-    if aOrig.kind == tyVar: result = typeRel(c, f.base, aOrig.base)
-    else: result = typeRel(c, f.base, aOrig)
+  of tyVar, tyLent:
+    if aOrig.kind == f.kind: result = typeRel(c, f.base, aOrig.base)
+    else: result = typeRel(c, f.base, aOrig, flags + {trNoCovariance})
     subtypeCheck()
   of tyArray:
     case a.kind
     of tyArray:
       var fRange = f.sons[0]
+      var aRange = a.sons[0]
       if fRange.kind == tyGenericParam:
         var prev = PType(idTableGet(c.bindings, fRange))
         if prev == nil:
@@ -808,52 +1173,69 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
           fRange = a
         else:
           fRange = prev
-      result = typeRel(c, f.sons[1], a.sons[1])
-      if result < isGeneric: return isNone
-      if rangeHasStaticIf(fRange):
-        if tfUnresolved in fRange.flags:
-          # This is a range from an array instantiated with a generic
-          # static param. We must extract the static param here and bind
-          # it to the size of the currently supplied array.
-          var
-            rangeStaticT = fRange.getStaticTypeFromRange
-            replacementT = newTypeWithSons(c.c, tyStatic, @[tyInt.getSysType])
-            inputUpperBound = a.sons[0].n[1].intVal
-          # we must correct for the off-by-one discrepancy between
-          # ranges and static params:
-          replacementT.n = newIntNode(nkIntLit, inputUpperBound + 1)
-          put(c, rangeStaticT, replacementT)
-          return isGeneric
-
-        let len = tryResolvingStaticExpr(c, fRange.n[1])
-        if len.kind == nkIntLit and len.intVal+1 == lengthOrd(a):
-          return # if we get this far, the result is already good
+      let ff = f.sons[1].skipTypes({tyTypeDesc})
+      # This typeDesc rule is wrong, see bug #7331
+      let aa = a.sons[1] #.skipTypes({tyTypeDesc})
+
+      if f.sons[0].kind != tyGenericParam and aa.kind == tyEmpty:
+        result = isGeneric
+      else:
+        result = typeRel(c, ff, aa)
+
+      if result < isGeneric:
+        if nimEnableCovariance and
+           trNoCovariance notin flags and
+           ff.kind == aa.kind and
+           isCovariantPtr(c, ff, aa):
+          result = isSubtype
         else:
           return isNone
-      elif lengthOrd(fRange) != lengthOrd(a):
-        result = isNone
+
+      if fRange.rangeHasUnresolvedStatic:
+        return inferStaticsInRange(c, fRange, a)
+      elif c.c.matchedConcept != nil and aRange.rangeHasUnresolvedStatic:
+        return inferStaticsInRange(c, aRange, f)
+      else:
+        if lengthOrd(c.c.config, fRange) != lengthOrd(c.c.config, aRange):
+          result = isNone
+    else: discard
+  of tyUncheckedArray:
+    if a.kind == tyUncheckedArray:
+      result = typeRel(c, base(f), base(a))
+      if result < isGeneric: result = isNone
     else: discard
   of tyOpenArray, tyVarargs:
     # varargs[expr] is special too but handled earlier. So we only need to
     # handle varargs[stmt] which is the same as varargs[typed]:
     if f.kind == tyVarargs:
-      if tfOldSchoolExprStmt in f.sons[0].flags:
-        if f.sons[0].kind == tyExpr: return
-      elif f.sons[0].kind == tyStmt: return
+      if tfVarargs in a.flags:
+        return typeRel(c, f.base, a.lastSon)
+      if f.sons[0].kind == tyStmt: return
+
+    template matchArrayOrSeq(aBase: PType) =
+      let ff = f.base
+      let aa = aBase
+      let baseRel = typeRel(c, ff, aa)
+      if baseRel >= isGeneric:
+        result = isConvertible
+      elif nimEnableCovariance and
+           trNoCovariance notin flags and
+           ff.kind == aa.kind and
+           isCovariantPtr(c, ff, aa):
+        result = isConvertible
+
     case a.kind
     of tyOpenArray, tyVarargs:
       result = typeRel(c, base(f), base(a))
       if result < isGeneric: result = isNone
     of tyArray:
       if (f.sons[0].kind != tyGenericParam) and (a.sons[1].kind == tyEmpty):
-        result = isSubtype
-      elif typeRel(c, base(f), a.sons[1]) >= isGeneric:
-        result = isConvertible
+        return isSubtype
+      matchArrayOrSeq(a.sons[1])
     of tySequence:
       if (f.sons[0].kind != tyGenericParam) and (a.sons[0].kind == tyEmpty):
-        result = isConvertible
-      elif typeRel(c, base(f), a.sons[0]) >= isGeneric:
-        result = isConvertible
+        return isConvertible
+      matchArrayOrSeq(a.sons[0])
     of tyString:
       if f.kind == tyOpenArray:
         if f.sons[0].kind == tyChar:
@@ -868,11 +1250,20 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       if (f.sons[0].kind != tyGenericParam) and (a.sons[0].kind == tyEmpty):
         result = isSubtype
       else:
-        result = typeRel(c, f.sons[0], a.sons[0])
-        if result < isGeneric: result = isNone
+        let ff = f.sons[0]
+        let aa = a.sons[0]
+        result = typeRel(c, ff, aa)
+        if result < isGeneric:
+          if nimEnableCovariance and
+             trNoCovariance notin flags and
+             ff.kind == aa.kind and
+             isCovariantPtr(c, ff, aa):
+            result = isSubtype
+          else:
+            result = isNone
         elif tfNotNil in f.flags and tfNotNil notin a.flags:
           result = isNilConversion
-    of tyNil: result = f.allowsNil
+    of tyNil: result = allowsNilDeprecated(c, f)
     else: discard
   of tyOrdinal:
     if isOrdinalType(a):
@@ -884,7 +1275,9 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
         if result < isGeneric: result = isNone
     elif a.kind == tyGenericParam:
       result = isGeneric
-  of tyForward: internalError("forward type in typeRel()")
+  of tyForward:
+    #internalError("forward type in typeRel()")
+    result = isNone
   of tyNil:
     if a.kind == f.kind: result = isEqual
   of tyTuple:
@@ -902,7 +1295,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
   of tyDistinct:
     if a.kind == tyDistinct:
       if sameDistinctTypes(f, a): result = isEqual
-      elif f.base.kind == tyAnything: result = isGeneric
+      #elif f.base.kind == tyAnything: result = isGeneric  # issue 4435
       elif c.coerceDistincts: result = typeRel(c, f.base, a)
     elif a.kind == tyNil and f.base.kind in NilableTypes:
       result = f.allowsNil
@@ -921,7 +1314,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       if a.len < f.len: return isNone
       for i in 0..f.len-2:
         if typeRel(c, f.sons[i], a.sons[i]) == isNone: return isNone
-      result = typeRel(c, f.lastSon, a.lastSon)
+      result = typeRel(c, f.lastSon, a.lastSon, flags + {trNoCovariance})
       subtypeCheck()
       if result <= isConvertible: result = isNone
       elif tfNotNil in f.flags and tfNotNil notin a.flags:
@@ -955,7 +1348,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
         result = isNilConversion
       else:
         result = isEqual
-    of tyNil: result = f.allowsNil
+    of tyNil: result = allowsNilDeprecated(c, f)
     else: discard
   of tyCString:
     # conversion from string to cstring is automatic:
@@ -969,23 +1362,91 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
     of tyString: result = isConvertible
     of tyPtr:
       # ptr[Tag, char] is not convertible to 'cstring' for now:
-      if a.len == 1 and a.sons[0].kind == tyChar: result = isConvertible
-    of tyArray:
-      if (firstOrd(a.sons[0]) == 0) and
-          (skipTypes(a.sons[0], {tyRange}).kind in {tyInt..tyInt64}) and
-          (a.sons[1].kind == tyChar):
-        result = isConvertible
+      if a.len == 1:
+        let pointsTo = a.sons[0].skipTypes(abstractInst)
+        if pointsTo.kind == tyChar: result = isConvertible
+        elif pointsTo.kind == tyUncheckedArray and pointsTo.sons[0].kind == tyChar:
+          result = isConvertible
+        elif pointsTo.kind == tyArray and firstOrd(nil, pointsTo.sons[0]) == 0 and
+            skipTypes(pointsTo.sons[0], {tyRange}).kind in {tyInt..tyInt64} and
+            pointsTo.sons[1].kind == tyChar:
+          result = isConvertible
     else: discard
 
   of tyEmpty, tyVoid:
     if a.kind == f.kind: result = isEqual
 
-  of tyGenericInst, tyAlias:
+  of tyAlias, tySink:
     result = typeRel(c, lastSon(f), a)
 
+  of tyGenericInst:
+    var prev = PType(idTableGet(c.bindings, f))
+    let origF = f
+    var f = if prev == nil: f else: prev
+
+    let roota = a.skipGenericAlias
+    let rootf = f.skipGenericAlias
+
+    var m = c
+    if a.kind == tyGenericInst:
+      if roota.base == rootf.base:
+        let nextFlags = flags + {trNoCovariance}
+        var hasCovariance = false
+        for i in 1 .. rootf.sonsLen-2:
+          let ff = rootf.sons[i]
+          let aa = roota.sons[i]
+          result = typeRel(c, ff, aa, nextFlags)
+          if result notin {isEqual, isGeneric}:
+            if trNoCovariance notin flags and ff.kind == aa.kind:
+              let paramFlags = rootf.base.sons[i-1].flags
+              hasCovariance =
+                if tfCovariant in paramFlags:
+                  if tfWeakCovariant in paramFlags:
+                    isCovariantPtr(c, ff, aa)
+                  else:
+                    ff.kind notin {tyRef, tyPtr} and result == isSubtype
+                else:
+                  tfContravariant in paramFlags and
+                    typeRel(c, aa, ff) == isSubtype
+              if hasCovariance:
+                continue
+
+            return isNone
+        if prev == nil: put(c, f, a)
+        result = isGeneric
+      else:
+        let fKind = rootf.lastSon.kind
+        if fKind in {tyAnd, tyOr}:
+          result = typeRel(c, lastSon(f), a)
+          if result != isNone: put(c, f, a)
+          return
+
+        var aAsObject = roota.lastSon
+
+        if fKind in {tyRef, tyPtr}:
+          if aAsObject.kind == tyObject:
+            # bug #7600, tyObject cannot be passed
+            # as argument to tyRef/tyPtr
+            return isNone
+          elif aAsObject.kind == fKind:
+            aAsObject = aAsObject.base
+
+        if aAsObject.kind == tyObject:
+          let baseType = aAsObject.base
+          if baseType != nil:
+            c.inheritancePenalty += 1
+            return typeRel(c, f, baseType)
+
+        result = isNone
+    else:
+      assert lastSon(origF) != nil
+      result = typeRel(c, lastSon(origF), a)
+      if result != isNone and a.kind != tyNil:
+        put(c, f, a)
+
   of tyGenericBody:
     considerPreviousT:
-      if a.kind == tyGenericInst and a.sons[0] == f:
+      if a == f or a.kind == tyGenericInst and a.sons[0] == f:
         bindingRet isGeneric
       let ff = lastSon(f)
       if ff != nil:
@@ -993,17 +1454,24 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
 
   of tyGenericInvocation:
     var x = a.skipGenericAlias
+    # XXX: This is very hacky. It should be moved back into liftTypeParam
+    if x.kind in {tyGenericInst, tyArray} and
+       c.calleeSym != nil and
+       c.calleeSym.kind in {skProc, skFunc} and c.call != nil:
+      let inst = prepareMetatypeForSigmatch(c.c, c.bindings, c.call.info, f)
+      return typeRel(c, inst, a)
+
     var depth = 0
     if x.kind == tyGenericInvocation or f.sons[0].kind != tyGenericBody:
       #InternalError("typeRel: tyGenericInvocation -> tyGenericInvocation")
       # simply no match for now:
       discard
     elif x.kind == tyGenericInst and
-          ((f.sons[0] == x.sons[0]) or isGenericSubtype(x, f, depth)) and
+          ((f.sons[0] == x.sons[0]) or isGenericSubType(c, x, f, depth)) and
           (sonsLen(x) - 1 == sonsLen(f)):
       for i in countup(1, sonsLen(f) - 1):
         if x.sons[i].kind == tyGenericParam:
-          internalError("wrong instantiated type!")
+          internalError(c.c.graph.config, "wrong instantiated type!")
         elif typeRel(c, f.sons[i], x.sons[i]) <= isSubtype:
           # Workaround for regression #4589
           if f.sons[i].kind != tyTypeDesc: return
@@ -1035,10 +1503,18 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
           if x == nil:
             discard "maybe fine (for eg. a==tyNil)"
           elif x.kind in {tyGenericInvocation, tyGenericParam}:
-            internalError("wrong instantiated type!")
+            internalError(c.c.graph.config, "wrong instantiated type!")
           else:
             put(c, f.sons[i], x)
 
+      if result == isNone:
+        # Here object inheriting from generic/specialized generic object
+        # crossing path with metatypes/aliases, so we need to separate them
+        # by checking sym.id
+        let genericSubtype = isGenericSubType(c, x, f, depth, f)
+        if not (genericSubtype and aobj.sym.id != fobj.sym.id) and aOrig.kind != tyGenericBody:
+          depth = -1
+
       if depth >= 0:
         c.inheritancePenalty += depth
         # bug #4863: We still need to bind generic alias crap, so
@@ -1058,8 +1534,13 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
   of tyOr:
     considerPreviousT:
       result = isNone
+      let oldInheritancePenalty = c.inheritancePenalty
+      var maxInheritance = 0
       for branch in f.sons:
+        c.inheritancePenalty = 0
         let x = typeRel(c, branch, aOrig)
+        maxInheritance = max(maxInheritance, c.inheritancePenalty)
+
         # 'or' implies maximum matching result:
         if x > result: result = x
       if result >= isSubtype:
@@ -1067,6 +1548,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
         bindingRet result
       else:
         result = isNone
+      c.inheritancePenalty = oldInheritancePenalty + maxInheritance
 
   of tyNot:
     considerPreviousT:
@@ -1087,7 +1569,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
     considerPreviousT:
       let targetKind = f.sons[0].kind
       let effectiveArgType = a.skipTypes({tyRange, tyGenericInst,
-                                          tyBuiltInTypeClass, tyAlias})
+                                          tyBuiltInTypeClass, tyAlias, tySink})
       let typeClassMatches = targetKind == effectiveArgType.kind and
                              not effectiveArgType.isEmptyContainer
       if typeClassMatches or
@@ -1097,11 +1579,19 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       else:
         return isNone
 
-  of tyUserTypeClass, tyUserTypeClassInst:
-    considerPreviousT:
-      result = matchUserTypeClass(c.c, c, f, aOrig)
-      if result == isGeneric:
-        put(c, f, a)
+  of tyUserTypeClassInst, tyUserTypeClass:
+    if f.isResolvedUserTypeClass:
+      result = typeRel(c, f.lastSon, a)
+    else:
+      considerPreviousT:
+        if aOrig == f: return isEqual
+        var matched = matchUserTypeClass(c, f, aOrig)
+        if matched != nil:
+          bindConcreteTypeToUserTypeClass(matched, a)
+          if doBind: put(c, f, matched)
+          result = isGeneric
+        else:
+          result = isNone
 
   of tyCompositeTypeClass:
     considerPreviousT:
@@ -1119,6 +1609,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       if result != isNone:
         put(c, f, a)
         result = isGeneric
+
   of tyGenericParam:
     var x = PType(idTableGet(c.bindings, f))
     if x == nil:
@@ -1135,23 +1626,32 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
           if f.sonsLen == 0:
             result = isGeneric
           else:
-            internalAssert a.sons != nil and a.sons.len > 0
+            internalAssert c.c.graph.config, a.len > 0
             c.typedescMatched = true
             var aa = a
-            while aa.kind in {tyTypeDesc, tyGenericParam} and
-                aa.len > 0:
+            while aa.kind in {tyTypeDesc, tyGenericParam} and aa.len > 0:
               aa = lastSon(aa)
+            if aa.kind == tyGenericParam:
+              return isGeneric
             result = typeRel(c, f.base, aa)
             if result > isGeneric: result = isGeneric
         else:
           result = isNone
       else:
         if f.sonsLen > 0 and f.sons[0].kind != tyNone:
-          result = typeRel(c, f.lastSon, a)
+          let oldInheritancePenalty = c.inheritancePenalty
+          result = typeRel(c, f.lastSon, a, flags + {trDontBind})
           if doBind and result notin {isNone, isGeneric}:
             let concrete = concreteType(c, a)
             if concrete == nil: return isNone
             put(c, f, concrete)
+          # bug #6526
+          if result in {isEqual, isSubtype}:
+            # 'T: Class' is a *better* match than just 'T'
+            # but 'T: Subclass' is even better:
+            c.inheritancePenalty = oldInheritancePenalty - c.inheritancePenalty -
+                                  100 * ord(result == isEqual)
+            result = isGeneric
         else:
           result = isGeneric
 
@@ -1173,18 +1673,37 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
     elif x.kind == tyGenericParam:
       result = isGeneric
     else:
+      # Special type binding rule for numeric types.
+      # See section "Generic type inference for numeric types" of the
+      # manual for further details:
+      when false:
+        let rebinding = maxNumericType(x.skipTypes({tyRange}), a)
+        if rebinding != nil:
+          put(c, f, rebinding)
+          result = isGeneric
+        else:
+          discard
       result = typeRel(c, x, a) # check if it fits
       if result > isGeneric: result = isGeneric
-
   of tyStatic:
     let prev = PType(idTableGet(c.bindings, f))
     if prev == nil:
       if aOrig.kind == tyStatic:
-        result = typeRel(c, f.lastSon, a)
-        if result != isNone and f.n != nil:
-          if not exprStructuralEquivalent(f.n, aOrig.n):
-            result = isNone
+        if f.base.kind != tyNone:
+          result = typeRel(c, f.base, a)
+          if result != isNone and f.n != nil:
+            if not exprStructuralEquivalent(f.n, aOrig.n):
+              result = isNone
+        else:
+          result = isGeneric
         if result != isNone: put(c, f, aOrig)
+      elif aOrig.n != nil and aOrig.n.typ != nil:
+        result = if f.base.kind != tyNone: typeRel(c, f.lastSon, aOrig.n.typ)
+                 else: isGeneric
+        if result != isNone:
+          var boundType = newTypeWithSons(c.c, tyStatic, @[aOrig.n.typ])
+          boundType.n = aOrig.n
+          put(c, f, boundType)
       else:
         result = isNone
     elif prev.kind == tyStatic:
@@ -1198,15 +1717,30 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
       # XXX endless recursion?
       #result = typeRel(c, prev, aOrig)
       result = isNone
+
+  of tyInferred:
+    let prev = f.previouslyInferred
+    if prev != nil:
+      result = typeRel(c, prev, a)
+    else:
+      result = typeRel(c, f.base, a)
+      if result != isNone:
+        c.inferredTypes.add f
+        f.sons.add a
+
   of tyTypeDesc:
     var prev = PType(idTableGet(c.bindings, f))
     if prev == nil:
       # proc foo(T: typedesc, x: T)
       # when `f` is an unresolved typedesc, `a` could be any
       # type, so we should not perform this check earlier
-      if a.kind != tyTypeDesc: return isNone
-
-      if f.base.kind == tyNone:
+      if a.kind != tyTypeDesc:
+        if a.kind == tyGenericParam and tfWildcard in a.flags:
+          # TODO: prevent `a` from matching as a wildcard again
+          result = isGeneric
+        else:
+          result = isNone
+      elif f.base.kind == tyNone:
         result = isGeneric
       else:
         result = typeRel(c, f.base, a.base)
@@ -1222,7 +1756,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
         result = isNone
 
   of tyStmt:
-    if aOrig != nil and tfOldSchoolExprStmt notin f.flags:
+    if aOrig != nil:
       put(c, f, aOrig)
     result = isGeneric
 
@@ -1242,13 +1776,13 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
         if not exprStructuralEquivalent(aOrig.n, reevaluated.typ.n):
           result = isNone
     else:
-      localError(f.n.info, errTypeExpected)
+      localError(c.c.graph.config, f.n.info, "type expected")
       result = isNone
 
   of tyNone:
     if a.kind == tyNone: result = isEqual
   else:
-    internalError " unknown type kind " & $f.kind
+    internalError c.c.graph.config, " unknown type kind " & $f.kind
 
 proc cmpTypes*(c: PContext, f, a: PType): TTypeRelation =
   var m: TCandidate
@@ -1261,7 +1795,7 @@ proc getInstantiatedType(c: PContext, arg: PNode, m: TCandidate,
   if result == nil:
     result = generateTypeInstance(c, m.bindings, arg, f)
   if result == nil:
-    internalError(arg.info, "getInstantiatedType")
+    internalError(c.graph.config, arg.info, "getInstantiatedType")
     result = errorType(c)
 
 proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate,
@@ -1274,8 +1808,8 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate,
       result.typ = errorType(c)
   else:
     result.typ = f
-  if result.typ == nil: internalError(arg.info, "implicitConv")
-  addSon(result, ast.emptyNode)
+  if result.typ == nil: internalError(c.graph.config, arg.info, "implicitConv")
+  addSon(result, c.graph.emptyNode)
   addSon(result, arg)
 
 proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
@@ -1288,22 +1822,47 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
     # 'f <- dest' in order to not break the unification:
     # see tests/tgenericconverter:
     let srca = typeRel(m, src, a)
-    if srca notin {isEqual, isGeneric}: continue
+    if srca notin {isEqual, isGeneric, isSubtype}: continue
+
+    # What's done below matches the logic in ``matchesAux``
+    let constraint = c.converters[i].typ.n[1].sym.constraint
+    if not constraint.isNil and not matchNodeKinds(constraint, arg):
+      continue
+    if src.kind in {tyVar, tyLent} and not arg.isLValue:
+      continue
 
     let destIsGeneric = containsGenericType(dest)
     if destIsGeneric:
       dest = generateTypeInstance(c, m.bindings, arg, dest)
     let fdest = typeRel(m, f, dest)
-    if fdest in {isEqual, isGeneric}:
-      markUsed(arg.info, c.converters[i], c.graph.usageSym)
+    if fdest in {isEqual, isGeneric} and not (dest.kind == tyLent and f.kind == tyVar):
+      markUsed(c.config, arg.info, c.converters[i], c.graph.usageSym)
       var s = newSymNode(c.converters[i])
       s.typ = c.converters[i].typ
       s.info = arg.info
       result = newNodeIT(nkHiddenCallConv, arg.info, dest)
       addSon(result, s)
-      addSon(result, copyTree(arg))
+      # We build the call expression by ourselves in order to avoid passing this
+      # expression trough the semantic check phase once again so let's make sure
+      # it is correct
+      var param: PNode = nil
+      if srca == isSubtype:
+        param = implicitConv(nkHiddenSubConv, src, copyTree(arg), m, c)
+      elif src.kind == tyVar:
+        # Analyse the converter return type
+        param = newNodeIT(nkHiddenAddr, arg.info, s.typ[1])
+        param.addSon(copyTree(arg))
+      else:
+        param = copyTree(arg)
+      addSon(result, param)
+
+      if dest.kind in {tyVar, tyLent}:
+        dest.flags.incl tfVarIsPtr
+        result = newDeref(result)
+
       inc(m.convMatches)
-      m.genericConverter = srca == isGeneric or destIsGeneric
+      if m.genericConverter == false:
+        m.genericConverter = srca == isGeneric or destIsGeneric
       return result
 
 proc localConvMatch(c: PContext, m: var TCandidate, f, a: PType,
@@ -1341,12 +1900,16 @@ proc incMatches(m: var TCandidate; r: TTypeRelation; convMatch = 1) =
   of isEqual: inc(m.exactMatches)
   of isNone: discard
 
-proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
+template matchesVoidProc(t: PType): bool =
+  (t.kind == tyProc and t.len == 1 and t.sons[0] == nil) or
+    (t.kind == tyBuiltInTypeClass and t.sons[0].kind == tyProc)
+
+proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
                         argSemantized, argOrig: PNode): PNode =
   var
     fMaybeStatic = f.skipTypes({tyDistinct})
     arg = argSemantized
-    argType = argType
+    a = a
     c = m.c
 
   if tfHasStatic in fMaybeStatic.flags:
@@ -1356,29 +1919,42 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
 
     # XXX: weaken tyGenericParam and call it tyGenericPlaceholder
     # and finally start using tyTypedesc for generic types properly.
-    if argType.kind == tyGenericParam and tfWildcard in argType.flags:
-      argType.assignType(f)
-      # put(m.bindings, f, argType)
+    if a.kind == tyGenericParam and tfWildcard in a.flags:
+      a.assignType(f)
+      # put(m.bindings, f, a)
       return argSemantized
 
-    if argType.kind == tyStatic:
+    if a.kind == tyStatic:
       if m.callee.kind == tyGenericBody and
-         argType.n == nil and
-         tfGenericTypeParam notin argType.flags:
+         a.n == nil and
+         tfGenericTypeParam notin a.flags:
         return newNodeIT(nkType, argOrig.info, makeTypeFromExpr(c, arg))
     else:
       var evaluated = c.semTryConstExpr(c, arg)
       if evaluated != nil:
-        arg.typ = newTypeS(tyStatic, c)
-        arg.typ.sons = @[evaluated.typ]
-        arg.typ.n = evaluated
-        argType = arg.typ
+        # Don't build the type in-place because `evaluated` and `arg` may point
+        # to the same object and we'd end up creating recursive types (#9255)
+        let typ = newTypeS(tyStatic, c)
+        typ.sons = @[evaluated.typ]
+        typ.n = evaluated
+        arg.typ = typ
+        a = typ
+      else:
+        if m.callee.kind == tyGenericBody:
+          if f.kind == tyStatic and typeRel(m, f.base, a) != isNone:
+            result = makeStaticExpr(m.c, arg)
+            result.typ.flags.incl tfUnresolved
+            result.typ.n = arg
+            return
 
-  var a = argType
+  let oldInheritancePenalty = m.inheritancePenalty
   var r = typeRel(m, f, a)
 
+  # This special typing rule for macros and templates is not documented
+  # anywhere and breaks symmetry. It's hard to get rid of though, my
+  # custom seqs example fails to compile without this:
   if r != isNone and m.calleeSym != nil and
-     m.calleeSym.kind in {skMacro, skTemplate}:
+    m.calleeSym.kind in {skMacro, skTemplate}:
     # XXX: duplicating this is ugly, but we cannot (!) move this
     # directly into typeRel using return-like templates
     incMatches(m, r)
@@ -1386,7 +1962,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
       return arg
     elif f.kind == tyTypeDesc:
       return arg
-    elif f.kind == tyStatic:
+    elif f.kind == tyStatic and arg.typ.n != nil:
       return arg.typ.n
     else:
       return argSemantized # argOrig
@@ -1403,7 +1979,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
       bothMetaCounter < 100:
     lastBindingsLength = m.bindings.counter
     inc(bothMetaCounter)
-    if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds:
+    if arg.kind in {nkProcDef, nkFuncDef, nkIteratorDef} + nkLambdaKinds:
       result = c.semInferredLambda(c, m.bindings, arg)
     elif arg.kind != nkSym:
       return nil
@@ -1436,7 +2012,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
     else:
       result = implicitConv(nkHiddenStdConv, f, arg, m, c)
   of isInferred, isInferredConvertible:
-    if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds:
+    if arg.kind in {nkProcDef, nkFuncDef, nkIteratorDef} + nkLambdaKinds:
       result = c.semInferredLambda(c, m.bindings, arg)
     elif arg.kind != nkSym:
       return nil
@@ -1452,7 +2028,8 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
     inc(m.genericMatches)
     if arg.typ == nil:
       result = arg
-    elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple:
+    elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple or
+         m.inheritancePenalty > oldInheritancePenalty:
       result = implicitConv(nkHiddenSubConv, f, arg, m, c)
     elif arg.typ.isEmptyContainer:
       result = arg.copyTree
@@ -1478,6 +2055,17 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
       inc(m.genericMatches)
       m.fauxMatch = a.kind
       return arg
+    elif a.kind == tyVoid and f.matchesVoidProc and argOrig.kind == nkStmtList:
+      # lift do blocks without params to lambdas
+      let p = c.graph
+      let lifted = c.semExpr(c, newProcNode(nkDo, argOrig.info, body = argOrig,
+          params = p.emptyNode, name = p.emptyNode, pattern = p.emptyNode,
+          genericParams = p.emptyNode, pragmas = p.emptyNode, exceptions = p.emptyNode), {})
+      if f.kind == tyBuiltInTypeClass:
+        inc m.genericMatches
+        put(m, f, lifted.typ)
+      inc m.convMatches
+      return implicitConv(nkHiddenStdConv, f, lifted, m, c)
     result = userConvMatch(c, m, f, a, arg)
     # check for a base type match, which supports varargs[T] without []
     # constructor in a call:
@@ -1492,6 +2080,14 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
           if r == isGeneric:
             result.typ = getInstantiatedType(c, arg, m, base(f))
           m.baseTypeMatch = true
+        # bug #4799, varargs accepting subtype relation object
+        elif r == isSubtype:
+          inc(m.subtypeMatches)
+          if f.kind == tyTypeDesc:
+            result = arg
+          else:
+            result = implicitConv(nkHiddenSubConv, f, arg, m, c)
+          m.baseTypeMatch = true
         else:
           result = userConvMatch(c, m, base(f), a, arg)
           if result != nil: m.baseTypeMatch = true
@@ -1514,8 +2110,9 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
     y.calleeSym = m.calleeSym
     z.calleeSym = m.calleeSym
     var best = -1
-    for i in countup(0, sonsLen(arg) - 1):
-      if arg.sons[i].sym.kind in {skProc, skMethod, skConverter, skIterator}:
+    for i in 0 ..< arg.len:
+      if arg.sons[i].sym.kind in {skProc, skFunc, skMethod, skConverter,
+                                  skIterator, skMacro, skTemplate}:
         copyCandidate(z, m)
         z.callee = arg.sons[i].typ
         if tfUnresolved in z.callee.flags: continue
@@ -1544,23 +2141,27 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
               x = z
             elif cmp == 0:
               y = z           # z is as good as x
+
     if x.state == csEmpty:
       result = nil
     elif y.state == csMatch and cmpCandidates(x, y) == 0:
       if x.state != csMatch:
-        internalError(arg.info, "x.state is not csMatch")
+        internalError(m.c.graph.config, arg.info, "x.state is not csMatch")
       # ambiguous: more than one symbol fits!
       # See tsymchoice_for_expr as an example. 'f.kind == tyExpr' should match
       # anyway:
-      if f.kind == tyExpr: result = arg
+      if f.kind in {tyExpr, tyStmt}: result = arg
       else: result = nil
     else:
       # only one valid interpretation found:
-      markUsed(arg.info, arg.sons[best].sym, m.c.graph.usageSym)
-      styleCheckUse(arg.info, arg.sons[best].sym)
+      markUsed(m.c.config, arg.info, arg.sons[best].sym, m.c.graph.usageSym)
+      onUse(arg.info, arg.sons[best].sym)
       result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best],
                                   argOrig)
-
+  when false:
+    if m.calleeSym != nil and m.calleeSym.name.s == "[]":
+      echo m.c.config $ arg.info, " for ", m.calleeSym.name.s, " ", m.c.config $ m.calleeSym.info
+      writeMatches(m)
 
 proc setSon(father: PNode, at: int, son: PNode) =
   let oldLen = father.len
@@ -1597,15 +2198,16 @@ proc prepareOperand(c: PContext; a: PNode): PNode =
     result = a
     considerGenSyms(c, result)
 
-proc prepareNamedParam(a: PNode) =
+proc prepareNamedParam(a: PNode; c: PContext) =
   if a.sons[0].kind != nkIdent:
     var info = a.sons[0].info
-    a.sons[0] = newIdentNode(considerQuotedIdent(a.sons[0]), info)
+    a.sons[0] = newIdentNode(considerQuotedIdent(c, a.sons[0]), info)
 
 proc arrayConstr(c: PContext, n: PNode): PType =
   result = newTypeS(tyArray, c)
   rawAddSon(result, makeRangeType(c, 0, 0, n.info))
-  addSonSkipIntLit(result, skipTypes(n.typ, {tyGenericInst, tyVar, tyOrdinal}))
+  addSonSkipIntLit(result, skipTypes(n.typ,
+      {tyGenericInst, tyVar, tyLent, tyOrdinal}))
 
 proc arrayConstr(c: PContext, info: TLineInfo): PType =
   result = newTypeS(tyArray, c)
@@ -1617,8 +2219,7 @@ proc incrIndexType(t: PType) =
   inc t.sons[0].n.sons[1].intVal
 
 template isVarargsUntyped(x): untyped =
-  x.kind == tyVarargs and x.sons[0].kind == tyExpr and
-    tfOldSchoolExprStmt notin x.sons[0].flags
+  x.kind == tyVarargs and x.sons[0].kind == tyExpr
 
 proc matchesAux(c: PContext, n, nOrig: PNode,
                 m: var TCandidate, marker: var IntSet) =
@@ -1630,8 +2231,15 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
       else:
         m.state = csNoMatch
         return
+    
     if formal.typ.kind == tyVar:
-      if not n.isLValue:
+      let arg_converter = if arg.kind == nkHiddenDeref: arg[0] else: arg
+      if arg_converter.kind == nkHiddenCallConv:
+        if arg_converter.typ.kind != tyVar:
+          m.state = csNoMatch
+          m.mutabilityProblem = uint8(f-1)
+          return  
+      elif not n.isLValue:
         m.state = csNoMatch
         m.mutabilityProblem = uint8(f-1)
         return
@@ -1642,6 +2250,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
         else: 0
     # iterates over the actual given arguments
     a = 1
+    arg: PNode # current prepared argument
 
   m.state = csMatch # until proven otherwise
   m.call = newNodeI(n.kind, n.info)
@@ -1652,26 +2261,37 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
   var formal: PSym = if formalLen > 1: m.callee.n.sons[1].sym else: nil
 
   while a < n.len:
-    if a >= formalLen-1 and formal != nil and formal.typ.isVarargsUntyped:
+    if a >= formalLen-1 and f < formalLen and m.callee.n[f].typ.isVarargsUntyped:
+      formal = m.callee.n.sons[f].sym
       incl(marker, formal.position)
-      if container.isNil:
-        container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info))
-        setSon(m.call, formal.position + 1, container)
+
+      if n.sons[a].kind == nkHiddenStdConv:
+        doAssert n.sons[a].sons[0].kind == nkEmpty and
+                 n.sons[a].sons[1].kind == nkArgList and
+                 n.sons[a].sons[1].len == 0
+        # Steal the container and pass it along
+        setSon(m.call, formal.position + 1, n.sons[a].sons[1])
       else:
-        incrIndexType(container.typ)
-      addSon(container, n.sons[a])
+        if container.isNil:
+          container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info))
+          setSon(m.call, formal.position + 1, container)
+        else:
+          incrIndexType(container.typ)
+        addSon(container, n.sons[a])
     elif n.sons[a].kind == nkExprEqExpr:
       # named param
       # check if m.callee has such a param:
-      prepareNamedParam(n.sons[a])
+      prepareNamedParam(n.sons[a], c)
       if n.sons[a].sons[0].kind != nkIdent:
-        localError(n.sons[a].info, errNamedParamHasToBeIdent)
+        localError(c.config, n.sons[a].info, "named parameter has to be an identifier")
         m.state = csNoMatch
+        m.firstMismatch = -a
         return
       formal = getSymFromList(m.callee.n, n.sons[a].sons[0].ident, 1)
       if formal == nil:
         # no error message!
         m.state = csNoMatch
+        m.firstMismatch = -a
         return
       if containsOrIncl(marker, formal.position):
         # already in namedParams, so no match
@@ -1682,12 +2302,14 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
         m.state = csNoMatch
         return
       m.baseTypeMatch = false
+      m.typedescMatched = false
       n.sons[a].sons[1] = prepareOperand(c, formal.typ, n.sons[a].sons[1])
       n.sons[a].typ = n.sons[a].sons[1].typ
-      var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ,
+      arg = paramTypesMatch(m, formal.typ, n.sons[a].typ,
                                 n.sons[a].sons[1], n.sons[a].sons[1])
       if arg == nil:
         m.state = csNoMatch
+        m.firstMismatch = a
         return
       checkConstraint(n.sons[a].sons[1])
       if m.baseTypeMatch:
@@ -1708,17 +2330,19 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
           # we have no formal here to snoop at:
           n.sons[a] = prepareOperand(c, n.sons[a])
           if skipTypes(n.sons[a].typ, abstractVar-{tyTypeDesc}).kind==tyString:
-            addSon(m.call, implicitConv(nkHiddenStdConv, getSysType(tyCString),
-                                        copyTree(n.sons[a]), m, c))
+            addSon(m.call, implicitConv(nkHiddenStdConv,
+                  getSysType(c.graph, n.sons[a].info, tyCString),
+                  copyTree(n.sons[a]), m, c))
           else:
             addSon(m.call, copyTree(n.sons[a]))
         elif formal != nil and formal.typ.kind == tyVarargs:
           # beware of the side-effects in 'prepareOperand'! So only do it for
           # varargs matching. See tests/metatype/tstatic_overloading.
           m.baseTypeMatch = false
+          m.typedescMatched = false
           incl(marker, formal.position)
           n.sons[a] = prepareOperand(c, formal.typ, n.sons[a])
-          var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ,
+          arg = paramTypesMatch(m, formal.typ, n.sons[a].typ,
                                     n.sons[a], nOrig.sons[a])
           if arg != nil and m.baseTypeMatch and container != nil:
             addSon(container, arg)
@@ -1732,7 +2356,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
           return
       else:
         if m.callee.n.sons[f].kind != nkSym:
-          internalError(n.sons[a].info, "matches")
+          internalError(c.config, n.sons[a].info, "matches")
           return
         formal = m.callee.n.sons[f].sym
         if containsOrIncl(marker, formal.position) and container.isNil:
@@ -1750,16 +2374,20 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
           addSon(container, n.sons[a])
         else:
           m.baseTypeMatch = false
+          m.typedescMatched = false
           n.sons[a] = prepareOperand(c, formal.typ, n.sons[a])
-          var arg = paramTypesMatch(m, formal.typ, n.sons[a].typ,
+          arg = paramTypesMatch(m, formal.typ, n.sons[a].typ,
                                     n.sons[a], nOrig.sons[a])
           if arg == nil:
             m.state = csNoMatch
+            m.firstMismatch = f
             return
           if m.baseTypeMatch:
+            assert formal.typ.kind == tyVarargs
             #assert(container == nil)
             if container.isNil:
               container = newNodeIT(nkBracket, n.sons[a].info, arrayConstr(c, arg))
+              container.typ.flags.incl tfVarargs
             else:
               incrIndexType(container.typ)
             addSon(container, arg)
@@ -1769,17 +2397,26 @@ proc matchesAux(c: PContext, n, nOrig: PNode,
 
             # pick the formal from the end, so that 'x, y, varargs, z' works:
             f = max(f, formalLen - n.len + a + 1)
-          else:
+          elif formal.typ.kind != tyVarargs or container == nil:
             setSon(m.call, formal.position + 1, arg)
             inc(f)
             container = nil
+          else:
+            # we end up here if the argument can be converted into the varargs
+            # formal (eg. seq[T] -> varargs[T]) but we have already instantiated
+            # a container
+            assert arg.kind == nkHiddenStdConv
+            localError(c.config, n.sons[a].info, "cannot convert $1 to $2" % [
+              typeToString(n.sons[a].typ), typeToString(formal.typ) ])
+            m.state = csNoMatch
+            return
         checkConstraint(n.sons[a])
     inc(a)
 
 proc semFinishOperands*(c: PContext, n: PNode) =
   # this needs to be called to ensure that after overloading resolution every
   # argument has been sem'checked:
-  for i in 1 .. <n.len:
+  for i in 1 ..< n.len:
     n.sons[i] = prepareOperand(c, n.sons[i])
 
 proc partialMatch*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
@@ -1791,6 +2428,11 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
   if m.magic in {mArrGet, mArrPut}:
     m.state = csMatch
     m.call = n
+    # Note the following doesn't work as it would produce ambiguities.
+    # Instead we patch system.nim, see bug #8049.
+    when false:
+      inc m.genericMatches
+      inc m.exactMatches
     return
   var marker = initIntSet()
   matchesAux(c, n, nOrig, m, marker)
@@ -1802,47 +2444,73 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
     if not containsOrIncl(marker, formal.position):
       if formal.ast == nil:
         if formal.typ.kind == tyVarargs:
-          var container = newNodeIT(nkBracket, n.info, arrayConstr(c, n.info))
+          # For consistency with what happens in `matchesAux` select the
+          # container node kind accordingly
+          let cnKind = if formal.typ.isVarargsUntyped: nkArgList else: nkBracket
+          var container = newNodeIT(cnKind, n.info, arrayConstr(c, n.info))
           setSon(m.call, formal.position + 1,
                  implicitConv(nkHiddenStdConv, formal.typ, container, m, c))
         else:
           # no default value
           m.state = csNoMatch
+          m.firstMismatch = f
           break
       else:
-        # use default value:
+        if formal.ast.kind == nkEmpty:
+          # The default param value is set to empty in `instantiateProcType`
+          # when the type of the default expression doesn't match the type
+          # of the instantiated proc param:
+          localError(c.config, m.call.info,
+                     ("The default parameter '$1' has incompatible type " &
+                      "with the explicitly requested proc instantiation") %
+                      formal.name.s)
+        if nfDefaultRefsParam in formal.ast.flags:
+          m.call.flags.incl nfDefaultRefsParam
         var def = copyTree(formal.ast)
         if def.kind == nkNilLit:
           def = implicitConv(nkHiddenStdConv, formal.typ, def, m, c)
+        if {tfImplicitTypeParam, tfGenericTypeParam} * formal.typ.flags != {}:
+          put(m, formal.typ, def.typ)
+        def.flags.incl nfDefaultParam
         setSon(m.call, formal.position + 1, def)
     inc(f)
+  # forget all inferred types if the overload matching failed
+  if m.state == csNoMatch:
+    for t in m.inferredTypes:
+      if t.sonsLen > 1: t.sons.setLen 1
 
-proc argtypeMatches*(c: PContext, f, a: PType): bool =
+proc argtypeMatches*(c: PContext, f, a: PType, fromHlo = false): bool =
   var m: TCandidate
   initCandidate(c, m, f)
-  let res = paramTypesMatch(m, f, a, ast.emptyNode, nil)
+  let res = paramTypesMatch(m, f, a, c.graph.emptyNode, nil)
   #instantiateGenericConverters(c, res, m)
   # XXX this is used by patterns.nim too; I think it's better to not
   # instantiate generic converters for that
-  result = res != nil
+  if not fromHlo:
+    res != nil
+  else:
+    # pattern templates do not allow for conversions except from int literal
+    res != nil and m.convMatches == 0 and m.intConvMatches in [0, 256]
 
 proc instTypeBoundOp*(c: PContext; dc: PSym; t: PType; info: TLineInfo;
                       op: TTypeAttachedOp; col: int): PSym {.procvar.} =
   var m: TCandidate
   initCandidate(c, m, dc.typ)
   if col >= dc.typ.len:
-    localError(info, errGenerated, "cannot instantiate '" & dc.name.s & "'")
+    localError(c.config, info, "cannot instantiate: '" & dc.name.s & "'")
     return nil
   var f = dc.typ.sons[col]
+
   if op == attachedDeepCopy:
     if f.kind in {tyRef, tyPtr}: f = f.lastSon
   else:
     if f.kind == tyVar: f = f.lastSon
   if typeRel(m, f, t) == isNone:
-    localError(info, errGenerated, "cannot instantiate '" & dc.name.s & "'")
+    localError(c.config, info, "cannot instantiate: '" & dc.name.s & "'")
   else:
     result = c.semGenerateInstance(c, dc, m.bindings, info)
-    assert sfFromGeneric in result.flags
+    if op == attachedDeepCopy:
+      assert sfFromGeneric in result.flags
 
 include suggest
 
@@ -1965,4 +2633,3 @@ tests:
 
     yes int, ordinal
     no  string, ordinal
-
diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim
new file mode 100644
index 000000000..a34383d9f
--- /dev/null
+++ b/compiler/sizealignoffsetimpl.nim
@@ -0,0 +1,417 @@
+#
+#
+#           The Nim Compiler
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+## code owner: Arne Döring
+## e-mail: arne.doering@gmx.net
+
+proc align(address, alignment: BiggestInt): BiggestInt =
+  result = (address + (alignment - 1)) and not (alignment - 1)
+
+const
+  ## a size is concidered "unknown" when it is an imported type from C
+  ## or C++.
+  szUnknownSize* = -3
+  szIllegalRecursion* = -2
+  szUncomputedSize* = -1
+
+proc computeSizeAlign(conf: ConfigRef; typ: PType): void
+
+proc computeSubObjectAlign(conf: ConfigRef; n: PNode): BiggestInt =
+  ## returns object alignment
+  case n.kind
+  of nkRecCase:
+    assert(n.sons[0].kind == nkSym)
+    result = computeSubObjectAlign(conf, n.sons[0])
+    for i in 1 ..< sonsLen(n):
+      let child = n.sons[i]
+      case child.kind
+      of nkOfBranch, nkElse:
+        let align = computeSubObjectAlign(conf, child.lastSon)
+        if align < 0:
+          return align
+        result = max(result, align)
+      else:
+        internalError(conf, "computeSubObjectAlign")
+  of nkRecList:
+    result = 1
+    for i, child in n.sons:
+      let align = computeSubObjectAlign(conf, n.sons[i])
+      if align < 0:
+        return align
+      result = max(result, align)
+  of nkSym:
+    computeSizeAlign(conf, n.sym.typ)
+    result = n.sym.typ.align
+  else:
+    result = 1
+
+proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOffset: BiggestInt): tuple[offset, align: BiggestInt] =
+  ## ``offset`` is the offset within the object, after the node has been written, no padding bytes added
+  ## ``align`` maximum alignment from all sub nodes
+  assert n != nil
+  if n.typ != nil and n.typ.size == szIllegalRecursion:
+    result.offset = szIllegalRecursion
+    result.align  = szIllegalRecursion
+    return
+
+  result.align = 1
+  case n.kind
+  of nkRecCase:
+    assert(n.sons[0].kind == nkSym)
+    let (kindOffset, kindAlign) = computeObjectOffsetsFoldFunction(conf, n.sons[0], initialOffset)
+
+    var maxChildAlign: BiggestInt = 0
+    for i in 1 ..< sonsLen(n):
+      let child = n.sons[i]
+      case child.kind
+      of nkOfBranch, nkElse:
+        # offset parameter cannot be known yet, it needs to know the alignment first
+        let align = computeSubObjectAlign(conf, n.sons[i].lastSon)
+
+        if align == szIllegalRecursion:
+          result.offset  = szIllegalRecursion
+          result.align = szIllegalRecursion
+          return
+
+        if align == szUnknownSize or maxChildAlign == szUnknownSize:
+          maxChildAlign = szUnknownSize
+        else:
+          maxChildAlign = max(maxChildAlign, align)
+      else:
+        internalError(conf, "computeObjectOffsetsFoldFunction(record case branch)")
+
+    if maxChildAlign == szUnknownSize:
+      result.align  = szUnknownSize
+      result.offset = szUnknownSize
+    else:
+      # the union neds to be aligned first, before the offsets can be assigned
+      let kindUnionOffset = align(kindOffset, maxChildAlign)
+
+      var maxChildOffset: BiggestInt = 0
+      for i in 1 ..< sonsLen(n):
+        let (offset, align) = computeObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset)
+        maxChildOffset = max(maxChildOffset, offset)
+
+      result.align = max(kindAlign, maxChildAlign)
+      result.offset = maxChildOffset
+
+
+  of nkRecList:
+    result.align = 1 # maximum of all member alignments
+    var offset = initialOffset
+
+    for i, child in n.sons:
+      let (new_offset, align) = computeObjectOffsetsFoldFunction(conf, child, offset)
+
+      if new_offset == szIllegalRecursion:
+        result.offset = szIllegalRecursion
+        result.align = szIllegalRecursion
+        return
+
+      elif new_offset == szUnknownSize or offset == szUnknownSize:
+        # if anything is unknown, the rest becomes unknown as well
+        offset = szUnknownSize
+        result.align = szUnknownSize
+
+      else:
+        offset = new_offset
+        result.align = max(result.align, align)
+
+    # final alignment
+    if offset == szUnknownSize:
+      result.offset = szUnknownSize
+    else:
+      result.offset = align(offset, result.align)
+
+  of nkSym:
+    var size = szUnknownSize
+    var align = szUnknownSize
+
+    if n.sym.bitsize == 0: # 0 represents bitsize not set
+      computeSizeAlign(conf, n.sym.typ)
+      size = n.sym.typ.size.int
+      align = n.sym.typ.align.int
+
+    result.align = align
+    if initialOffset == szUnknownSize or size == szUnknownSize:
+      n.sym.offset = szUnknownSize
+      result.offset = szUnknownSize
+    else:
+      n.sym.offset = align(initialOffset, align).int
+      result.offset = n.sym.offset + n.sym.typ.size
+  else:
+    result.align = szUnknownSize
+    result.offset = szUnknownSize
+
+proc computePackedObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOffset: BiggestInt, debug: bool): BiggestInt =
+  ## ``result`` is the offset within the object, after the node has been written, no padding bytes added
+  case n.kind
+  of nkRecCase:
+    assert(n.sons[0].kind == nkSym)
+    let kindOffset = computePackedObjectOffsetsFoldFunction(conf, n.sons[0], initialOffset, debug)
+    # the union neds to be aligned first, before the offsets can be assigned
+    let kindUnionOffset = kindOffset
+
+    var maxChildOffset: BiggestInt = kindUnionOffset
+    for i in 1 ..< sonsLen(n):
+      let offset = computePackedObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset, debug)
+      maxChildOffset = max(maxChildOffset, offset)
+    result = maxChildOffset
+  of nkRecList:
+    result = initialOffset
+    for i, child in n.sons:
+      result = computePackedObjectOffsetsFoldFunction(conf, child, result, debug)
+      if result == szIllegalRecursion:
+        break
+  of nkSym:
+    computeSizeAlign(conf, n.sym.typ)
+    n.sym.offset = initialOffset.int
+    result = n.sym.offset + n.sym.typ.size
+  else:
+    result = szUnknownSize
+
+# TODO this one needs an alignment map of the individual types
+
+proc computeSizeAlign(conf: ConfigRef; typ: PType) =
+  ## computes and sets ``size`` and ``align`` members of ``typ``
+  assert typ != nil
+  let hasSize = typ.size != szUncomputedSize
+  let hasAlign = typ.align != szUncomputedSize
+
+  if hasSize and hasAlign:
+    # nothing to do, size and align already computed
+    return
+
+  # This function can only calculate both, size and align at the same time.
+  # If one of them is already set this value is stored here and reapplied
+  let revertSize = typ.size
+  let revertAlign = typ.align
+  defer:
+    if hasSize:
+      typ.size = revertSize
+    if hasAlign:
+      typ.align = revertAlign
+
+  if typ.size == szIllegalRecursion or typ.align == szIllegalRecursion:
+    # we are already computing the size of the type
+    # --> illegal recursion in type
+    return
+
+  # mark computation in progress
+  typ.size = szIllegalRecursion
+  typ.align = szIllegalRecursion
+
+  var maxAlign, sizeAccum, length: BiggestInt
+
+  var tk = typ.kind
+  case tk
+  of tyProc:
+    if typ.callConv == ccClosure:
+      typ.size = 2 * conf.target.ptrSize
+    else:
+      typ.size = conf.target.ptrSize
+    typ.align = int16(conf.target.ptrSize)
+
+  of tyNil:
+    typ.size = conf.target.ptrSize
+    typ.align = int16(conf.target.ptrSize)
+
+  of tyString:
+    if conf.selectedGC == gcDestructors:
+      typ.size = conf.target.ptrSize * 2
+    else:
+      typ.size = conf.target.ptrSize
+    typ.align = int16(conf.target.ptrSize)
+
+  of tyCString, tySequence, tyPtr, tyRef, tyVar, tyLent, tyOpenArray:
+    let base = typ.lastSon
+    if base == typ:
+      # this is not the correct location to detect ``type A = ptr A``
+      typ.size = szIllegalRecursion
+      typ.align = szIllegalRecursion
+      return
+
+    # recursive tuplers are not allowed and should be detected in the frontend
+    if base.kind == tyTuple:
+      computeSizeAlign(conf, base)
+      if base.size < 0:
+        typ.size = base.size
+        typ.align = base.align
+        return
+
+    typ.align = int16(conf.target.ptrSize)
+    if typ.kind == tySequence and conf.selectedGC == gcDestructors:
+      typ.size = conf.target.ptrSize * 2
+    else:
+      typ.size = conf.target.ptrSize
+
+  of tyArray:
+    computeSizeAlign(conf, typ.sons[1])
+    let elemSize = typ.sons[1].size
+    if elemSize < 0:
+      typ.size = elemSize
+      typ.align = int16(elemSize)
+    else:
+      typ.size = lengthOrd(conf, typ.sons[0]) * elemSize
+      typ.align = typ.sons[1].align
+
+  of tyUncheckedArray:
+    let base = typ.lastSon
+    computeSizeAlign(conf, base)
+    typ.size = szUnknownSize
+    typ.align = base.align
+  of tyEnum:
+    if firstOrd(conf, typ) < 0:
+      typ.size = 4              # use signed int32
+      typ.align = 4
+    else:
+      length = lastOrd(conf, typ)   # BUGFIX: use lastOrd!
+      if length + 1 < `shl`(1, 8):
+        typ.size = 1
+        typ.align = 1
+      elif length + 1 < `shl`(1, 16):
+        typ.size = 2
+        typ.align = 2
+      elif length + 1 < `shl`(BiggestInt(1), 32):
+        typ.size = 4
+        typ.align = 4
+      else:
+        typ.size = 8
+        typ.align = 8
+  of tySet:
+    if typ.sons[0].kind == tyGenericParam:
+      typ.size = szUncomputedSize
+      typ.align = szUncomputedSize # in original version this was 1
+    else:
+      length = lengthOrd(conf, typ.sons[0])
+      if length <= 8:
+        typ.size = 1
+      elif length <= 16:
+        typ.size = 2
+      elif length <= 32:
+        typ.size = 4
+      elif length <= 64:
+        typ.size = 8
+      elif align(length, 8) mod 8 == 0:
+        typ.size = align(length, 8) div 8
+      else:
+        typ.size = align(length, 8) div 8 + 1
+    typ.align = int16(typ.size)
+  of tyRange:
+    computeSizeAlign(conf, typ.sons[0])
+    typ.size = typ.sons[0].size
+    typ.align = typ.sons[0].align
+  of tyTuple:
+    maxAlign = 1
+    sizeAccum = 0
+    for i in countup(0, sonsLen(typ) - 1):
+      let child = typ.sons[i]
+      computeSizeAlign(conf, child)
+      if child.size < 0:
+        typ.size = child.size
+        typ.align = child.align
+        return
+      maxAlign = max(maxAlign, child.align)
+      sizeAccum = align(sizeAccum, child.align) + child.size
+    typ.size = align(sizeAccum, maxAlign)
+    typ.align = int16(maxAlign)
+  of tyObject:
+    var headerSize: BiggestInt
+    var headerAlign: int16
+    if typ.sons[0] != nil:
+      # compute header size
+
+      if conf.cmd == cmdCompileToCpp:
+        # if the target is C++ the members of this type are written
+        # into the padding byets at the end of the parent type. At the
+        # moment it is not supported to calculate that.
+        headerSize = szUnknownSize
+        headerAlign = szUncomputedSize
+      else:
+        var st = typ.sons[0]
+        while st.kind in skipPtrs:
+          st = st.sons[^1]
+        computeSizeAlign(conf, st)
+        if st.size == szIllegalRecursion:
+          typ.size = st.size
+          typ.align = st.align
+          return
+        headerSize = st.size
+        headerAlign = st.align
+    elif isObjectWithTypeFieldPredicate(typ):
+      # this branch is taken for RootObj
+      headerSize = conf.target.intSize
+      headerAlign = conf.target.intSize.int16
+    else:
+      headerSize = 0
+      headerAlign = 1
+    let (offset, align) =
+      if tfPacked in typ.flags:
+        (computePackedObjectOffsetsFoldFunction(conf, typ.n, headerSize, false), BiggestInt(1))
+      else:
+        computeObjectOffsetsFoldFunction(conf, typ.n, headerSize)
+    if offset == szIllegalRecursion:
+      typ.size = szIllegalRecursion
+      typ.align = szIllegalRecursion
+      return
+    if offset == szUnknownSize or (
+        typ.sym != nil and
+        typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc}):
+      typ.size = szUnknownSize
+      typ.align = szUnknownSize
+      return
+
+    # header size is already in size from computeObjectOffsetsFoldFunction
+    # maxAlign is probably not changed at all from headerAlign
+    if tfPacked in typ.flags:
+      typ.size = offset
+      typ.align = 1
+    else:
+      typ.align = int16(max(align, headerAlign))
+      typ.size = align(offset, typ.align)
+
+  of tyInferred:
+    if typ.len > 1:
+      computeSizeAlign(conf, typ.lastSon)
+      typ.size = typ.lastSon.size
+      typ.align = typ.lastSon.align
+
+  of tyGenericInst, tyDistinct, tyGenericBody, tyAlias, tySink:
+    computeSizeAlign(conf, typ.lastSon)
+    typ.size = typ.lastSon.size
+    typ.align = typ.lastSon.align
+
+  of tyTypeClasses:
+    if typ.isResolvedUserTypeClass:
+      computeSizeAlign(conf, typ.lastSon)
+      typ.size = typ.lastSon.size
+      typ.align = typ.lastSon.align
+    else:
+      typ.size = szUncomputedSize
+      typ.align = szUncomputedSize
+
+  of tyTypeDesc:
+    computeSizeAlign(conf, typ.base)
+    typ.size = typ.base.size
+    typ.align = typ.base.align
+
+  of tyForward:
+    # is this really illegal recursion, or maybe just unknown?
+    typ.size = szIllegalRecursion
+    typ.align = szIllegalRecursion
+
+  of tyStatic:
+    if typ.n != nil:
+      computeSizeAlign(conf, typ.lastSon)
+      typ.size = typ.lastSon.size
+      typ.align = typ.lastSon.align
+    else:
+      typ.size = szUncomputedSize
+      typ.align = szUncomputedSize
+  else:
+    typ.size = szUncomputedSize
+    typ.align = szUncomputedSize
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index f9210cc93..dfa6e5ddb 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -32,7 +32,8 @@
 
 # included from sigmatch.nim
 
-import algorithm, prefixmatches
+import algorithm, prefixmatches, lineinfos, pathutils
+from wordrecg import wDeprecated, wError
 
 when defined(nimsuggest):
   import passes, tables # importer
@@ -40,38 +41,13 @@ when defined(nimsuggest):
 const
   sep = '\t'
 
-type
-  Suggest* = ref object
-    section*: IdeCmd
-    qualifiedPath*: seq[string]
-    name*: PIdent                # not used beyond sorting purposes; name is also
-                                 # part of 'qualifiedPath'
-    filePath*: string
-    line*: int                   # Starts at 1
-    column*: int                 # Starts at 0
-    doc*: string           # Not escaped (yet)
-    symkind*: TSymKind
-    forth*: string               # type
-    quality*: range[0..100]   # matching quality
-    isGlobal*: bool # is a global variable
-    contextFits*: bool # type/non-type context matches
-    prefix*: PrefixMatch
-    scope*, localUsages*, globalUsages*: int # more usages is better
-    tokenLen*: int
-  Suggestions* = seq[Suggest]
-
-var
-  suggestionResultHook*: proc (result: Suggest) {.closure.}
-  suggestVersion*: int
-  suggestMaxResults* = 10_000
-
 #template sectionSuggest(): expr = "##begin\n" & getStackTrace() & "##end\n"
 
 template origModuleName(m: PSym): string = m.name.s
 
 proc findDocComment(n: PNode): PNode =
   if n == nil: return nil
-  if not isNil(n.comment): return n
+  if n.comment.len > 0: return n
   if n.kind in {nkStmtList, nkStmtListExpr, nkObjectTy, nkRecList} and n.len > 0:
     result = findDocComment(n.sons[0])
     if result != nil: return
@@ -103,9 +79,9 @@ proc cmpSuggestions(a, b: Suggest): int =
   cf globalUsages
   # if all is equal, sort alphabetically for deterministic output,
   # independent of hashing order:
-  result = cmp(a.name.s, b.name.s)
+  result = cmp(a.name[], b.name[])
 
-proc symToSuggest(s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo;
+proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo;
                   quality: range[0..100]; prefix: PrefixMatch;
                   inTypeContext: bool; scope: int): Suggest =
   new(result)
@@ -116,15 +92,15 @@ proc symToSuggest(s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo;
   result.prefix = prefix
   result.contextFits = inTypeContext == (s.kind in {skType, skGenericParam})
   result.scope = scope
-  result.name = s.name
+  result.name = addr s.name.s
   when defined(nimsuggest):
     result.globalUsages = s.allUsages.len
     var c = 0
     for u in s.allUsages:
       if u.fileIndex == info.fileIndex: inc c
     result.localUsages = c
-  result.symkind = s.kind
-  if optIdeTerse notin gGlobalOptions:
+  result.symkind = byte s.kind
+  if optIdeTerse notin conf.globalOptions:
     result.qualifiedPath = @[]
     if not isLocal and s.kind != skModule:
       let ow = s.owner
@@ -139,23 +115,24 @@ proc symToSuggest(s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo;
       result.forth = typeToString(s.typ)
     else:
       result.forth = ""
-    when not defined(noDocgen):
+    when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
       result.doc = s.extractDocComment
   let infox = if section in {ideUse, ideHighlight, ideOutline}: info else: s.info
-  result.filePath = toFullPath(infox)
+  result.filePath = toFullPath(conf, infox)
   result.line = toLinenumber(infox)
   result.column = toColumn(infox)
+  result.version = conf.suggestVersion
 
 proc `$`*(suggest: Suggest): string =
   result = $suggest.section
   result.add(sep)
   if suggest.section == ideHighlight:
-    if suggest.symkind == skVar and suggest.isGlobal:
+    if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
       result.add("skGlobalVar")
-    elif suggest.symkind == skLet and suggest.isGlobal:
+    elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
       result.add("skGlobalLet")
     else:
-      result.add($suggest.symkind)
+      result.add($suggest.symkind.TSymKind)
     result.add(sep)
     result.add($suggest.line)
     result.add(sep)
@@ -163,9 +140,9 @@ proc `$`*(suggest: Suggest): string =
     result.add(sep)
     result.add($suggest.tokenLen)
   else:
-    result.add($suggest.symkind)
+    result.add($suggest.symkind.TSymKind)
     result.add(sep)
-    if suggest.qualifiedPath != nil:
+    if suggest.qualifiedPath.len != 0:
       result.add(suggest.qualifiedPath.join("."))
     result.add(sep)
     result.add(suggest.forth)
@@ -176,34 +153,34 @@ proc `$`*(suggest: Suggest): string =
     result.add(sep)
     result.add($suggest.column)
     result.add(sep)
-    when not defined(noDocgen):
+    when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
       result.add(suggest.doc.escape)
-    if suggestVersion == 0:
+    if suggest.version == 0:
       result.add(sep)
       result.add($suggest.quality)
       if suggest.section == ideSug:
         result.add(sep)
         result.add($suggest.prefix)
 
-proc suggestResult(s: Suggest) =
-  if not isNil(suggestionResultHook):
-    suggestionResultHook(s)
+proc suggestResult(conf: ConfigRef; s: Suggest) =
+  if not isNil(conf.suggestionResultHook):
+    conf.suggestionResultHook(s)
   else:
-    suggestWriteln($s)
+    conf.suggestWriteln($s)
 
-proc produceOutput(a: var Suggestions) =
-  if gIdeCmd in {ideSug, ideCon}:
+proc produceOutput(a: var Suggestions; conf: ConfigRef) =
+  if conf.ideCmd in {ideSug, ideCon}:
     a.sort cmpSuggestions
   when defined(debug):
     # debug code
     writeStackTrace()
-  if a.len > suggestMaxResults: a.setLen(suggestMaxResults)
-  if not isNil(suggestionResultHook):
+  if a.len > conf.suggestMaxResults: a.setLen(conf.suggestMaxResults)
+  if not isNil(conf.suggestionResultHook):
     for s in a:
-      suggestionResultHook(s)
+      conf.suggestionResultHook(s)
   else:
     for s in a:
-      suggestWriteln($s)
+      conf.suggestWriteln($s)
 
 proc filterSym(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} =
   proc prefixMatch(s: PSym; n: PNode): PrefixMatch =
@@ -236,11 +213,11 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
 proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var Suggestions) =
   var pm: PrefixMatch
   if filterSym(s, f, pm) and fieldVisible(c, s):
-    outputs.add(symToSuggest(s, isLocal=true, ideSug, info, 100, pm, c.inTypeContext > 0, 0))
+    outputs.add(symToSuggest(c.config, s, isLocal=true, ideSug, info, 100, pm, c.inTypeContext > 0, 0))
 
 proc getQuality(s: PSym): range[0..100] =
   if s.typ != nil and s.typ.len > 1:
-    var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyAlias})
+    var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink})
     if exp.kind == tyVarargs: exp = elemType(exp)
     if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return 50
   return 100
@@ -255,7 +232,7 @@ template wholeSymTab(cond, section: untyped) =
       let it {.inject.} = item
       var pm {.inject.}: PrefixMatch
       if cond:
-        outputs.add(symToSuggest(it, isLocal = isLocal, section, info, getQuality(it),
+        outputs.add(symToSuggest(c.config, it, isLocal = isLocal, section, info, getQuality(it),
                                  pm, c.inTypeContext > 0, scopeN))
 
 proc suggestSymList(c: PContext, list, f: PNode; info: TLineInfo, outputs: var Suggestions) =
@@ -309,7 +286,7 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
     let m = s.getModule()
     if m != nil and sfSystemModule in m.flags:
       if s.kind == skType: return
-      var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyAlias})
+      var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink})
       if exp.kind == tyVarargs: exp = elemType(exp)
       if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return
     result = sigmatch.argtypeMatches(c, s.typ.sons[1], firstArg)
@@ -329,7 +306,7 @@ proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) =
     for it in items(scope.symbols):
       var pm: PrefixMatch
       if filterSym(it, f, pm):
-        outputs.add(symToSuggest(it, isLocal = isLocal, ideSug, n.info, 0, pm,
+        outputs.add(symToSuggest(c.config, it, isLocal = isLocal, ideSug, n.info, 0, pm,
                                  c.inTypeContext > 0, scopeN))
     #if scope == c.topLevelScope and f.isNil: break
 
@@ -339,20 +316,20 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions)
   var typ = n.typ
   var pm: PrefixMatch
   when defined(nimsuggest):
-    if n.kind == nkSym and n.sym.kind == skError and suggestVersion == 0:
+    if n.kind == nkSym and n.sym.kind == skError and c.config.suggestVersion == 0:
       # consider 'foo.|' where 'foo' is some not imported module.
-      let fullPath = findModule(n.sym.name.s, n.info.toFullPath)
-      if fullPath.len == 0:
+      let fullPath = findModule(c.config, n.sym.name.s, toFullPath(c.config, n.info))
+      if fullPath.isEmpty:
         # error: no known module name:
         typ = nil
       else:
-        let m = gImportModule(c.graph, c.module, fullpath.fileInfoIdx, c.cache)
+        let m = c.graph.importModuleCallback(c.graph, c.module, fileInfoIdx(c.config, fullpath))
         if m == nil: typ = nil
         else:
           for it in items(n.sym.tab):
             if filterSym(it, field, pm):
-              outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -100))
-          outputs.add(symToSuggest(m, isLocal=false, ideMod, n.info, 100, PrefixMatch.None,
+              outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -100))
+          outputs.add(symToSuggest(c.config, m, isLocal=false, ideMod, n.info, 100, PrefixMatch.None,
             c.inTypeContext > 0, -99))
 
   if typ == nil:
@@ -362,11 +339,11 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions)
         # all symbols accessible, because we are in the current module:
         for it in items(c.topLevelScope.symbols):
           if filterSym(it, field, pm):
-            outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99))
+            outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99))
       else:
         for it in items(n.sym.tab):
           if filterSym(it, field, pm):
-            outputs.add(symToSuggest(it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99))
+            outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99))
     else:
       # fallback:
       suggestEverything(c, n, field, outputs)
@@ -378,8 +355,8 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions)
       t = t.sons[0]
     suggestOperations(c, n, field, typ, outputs)
   else:
-    let orig = typ # skipTypes(typ, {tyGenericInst, tyAlias})
-    typ = skipTypes(typ, {tyGenericInst, tyVar, tyPtr, tyRef, tyAlias})
+    let orig = typ # skipTypes(typ, {tyGenericInst, tyAlias, tySink})
+    typ = skipTypes(typ, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink})
     if typ.kind == tyObject:
       var t = typ
       while true:
@@ -396,17 +373,17 @@ type
   TCheckPointResult* = enum
     cpNone, cpFuzzy, cpExact
 
-proc inCheckpoint*(current: TLineInfo): TCheckPointResult =
-  if current.fileIndex == gTrackPos.fileIndex:
-    if current.line == gTrackPos.line and
-        abs(current.col-gTrackPos.col) < 4:
+proc inCheckpoint*(current, trackPos: TLineInfo): TCheckPointResult =
+  if current.fileIndex == trackPos.fileIndex:
+    if current.line == trackPos.line and
+        abs(current.col-trackPos.col) < 4:
       return cpExact
-    if current.line >= gTrackPos.line:
+    if current.line >= trackPos.line:
       return cpFuzzy
 
-proc isTracked*(current: TLineInfo, tokenLen: int): bool =
-  if current.fileIndex==gTrackPos.fileIndex and current.line==gTrackPos.line:
-    let col = gTrackPos.col
+proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool =
+  if current.fileIndex==trackPos.fileIndex and current.line==trackPos.line:
+    let col = trackPos.col
     if col >= current.col and col <= current.col+tokenLen-1:
       return true
 
@@ -414,7 +391,7 @@ when defined(nimsuggest):
   # Since TLineInfo defined a == operator that doesn't include the column,
   # we map TLineInfo to a unique int here for this lookup table:
   proc infoToInt(info: TLineInfo): int64 =
-    info.fileIndex + info.line.int64 shl 32 + info.col.int64 shl 48
+    info.fileIndex.int64 + info.line.int64 shl 32 + info.col.int64 shl 48
 
   proc addNoDup(s: PSym; info: TLineInfo) =
     # ensure nothing gets too slow:
@@ -424,30 +401,27 @@ when defined(nimsuggest):
       if infoB.infoToInt == infoAsInt: return
     s.allUsages.add(info)
 
-var
-  lastLineInfo*: TLineInfo
-
-proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) =
-  if suggestVersion == 1:
-    if usageSym == nil and isTracked(info, s.name.s.len):
+proc findUsages(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) =
+  if conf.suggestVersion == 1:
+    if usageSym == nil and isTracked(info, conf.m.trackPos, s.name.s.len):
       usageSym = s
-      suggestResult(symToSuggest(s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0))
+      suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0))
     elif s == usageSym:
-      if lastLineInfo != info:
-        suggestResult(symToSuggest(s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0))
-      lastLineInfo = info
+      if conf.lastLineInfo != info:
+        suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0))
+      conf.lastLineInfo = info
 
 when defined(nimsuggest):
-  proc listUsages*(s: PSym) =
+  proc listUsages*(conf: ConfigRef; s: PSym) =
     #echo "usages ", len(s.allUsages)
     for info in s.allUsages:
       let x = if info == s.info and info.col == s.info.col: ideDef else: ideUse
-      suggestResult(symToSuggest(s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0))
+      suggestResult(conf, symToSuggest(conf, s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0))
 
-proc findDefinition(info: TLineInfo; s: PSym) =
+proc findDefinition(conf: ConfigRef; info: TLineInfo; s: PSym) =
   if s.isNil: return
-  if isTracked(info, s.name.s.len):
-    suggestResult(symToSuggest(s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0))
+  if isTracked(info, conf.m.trackPos, s.name.s.len):
+    suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0))
     suggestQuit()
 
 proc ensureIdx[T](x: var T, y: int) =
@@ -456,49 +430,78 @@ proc ensureIdx[T](x: var T, y: int) =
 proc ensureSeq[T](x: var seq[T]) =
   if x == nil: newSeq(x, 0)
 
-proc suggestSym*(info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} =
+proc suggestSym*(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} =
   ## misnamed: should be 'symDeclared'
   when defined(nimsuggest):
-    if suggestVersion == 0:
-      if s.allUsages.isNil:
+    if conf.suggestVersion == 0:
+      if s.allUsages.len == 0:
         s.allUsages = @[info]
       else:
         s.addNoDup(info)
 
-    if gIdeCmd == ideUse:
-      findUsages(info, s, usageSym)
-    elif gIdeCmd == ideDef:
-      findDefinition(info, s)
-    elif gIdeCmd == ideDus and s != nil:
-      if isTracked(info, s.name.s.len):
-        suggestResult(symToSuggest(s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0))
-      findUsages(info, s, usageSym)
-    elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex:
-      suggestResult(symToSuggest(s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0))
-    elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and
+    if conf.ideCmd == ideUse:
+      findUsages(conf, info, s, usageSym)
+    elif conf.ideCmd == ideDef:
+      findDefinition(conf, info, s)
+    elif conf.ideCmd == ideDus and s != nil:
+      if isTracked(info, conf.m.trackPos, s.name.s.len):
+        suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0))
+      findUsages(conf, info, s, usageSym)
+    elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex:
+      suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0))
+    elif conf.ideCmd == ideOutline and info.fileIndex == conf.m.trackPos.fileIndex and
         isDecl:
-      suggestResult(symToSuggest(s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0))
-
-proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) =
+      suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0))
+
+proc extractPragma(s: PSym): PNode =
+  if s.kind in routineKinds:
+    result = s.ast[pragmasPos]
+  elif s.kind in {skType}:
+    # s.ast = nkTypedef / nkPragmaExpr / [nkSym, nkPragma]
+    result = s.ast[0][1]
+  doAssert result == nil or result.kind == nkPragma
+
+proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) =
+  let pragmaNode = extractPragma(s)
+  if pragmaNode != nil:
+    for it in pragmaNode:
+      if whichPragma(it) == wDeprecated and it.safeLen == 2 and
+          it[1].kind in {nkStrLit..nkTripleStrLit}:
+        message(conf, info, warnDeprecated, it[1].strVal & "; " & s.name.s)
+        return
+  message(conf, info, warnDeprecated, s.name.s)
+
+proc userError(conf: ConfigRef; info: TLineInfo; s: PSym) =
+  let pragmaNode = extractPragma(s)
+
+  if pragmaNode != nil:
+    for it in pragmaNode:
+      if whichPragma(it) == wError and it.safeLen == 2 and
+          it[1].kind in {nkStrLit..nkTripleStrLit}:
+        localError(conf, info, it[1].strVal & "; usage of '$1' is a user-defined error" % s.name.s)
+        return
+  localError(conf, info, "usage of '$1' is a user-defined error" % s.name.s)
+
+proc markUsed(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) =
   incl(s.flags, sfUsed)
   if s.kind == skEnumField and s.owner != nil:
     incl(s.owner.flags, sfUsed)
   if {sfDeprecated, sfError} * s.flags != {}:
-    if sfDeprecated in s.flags: message(info, warnDeprecated, s.name.s)
-    if sfError in s.flags: localError(info, errWrongSymbolX, s.name.s)
+    if sfDeprecated in s.flags: warnAboutDeprecated(conf, info, s)
+    if sfError in s.flags: userError(conf, info, s)
   when defined(nimsuggest):
-    suggestSym(info, s, usageSym, false)
+    suggestSym(conf, info, s, usageSym, false)
 
-proc useSym*(sym: PSym; usageSym: var PSym): PNode =
+proc useSym*(conf: ConfigRef; sym: PSym; usageSym: var PSym): PNode =
   result = newSymNode(sym)
-  markUsed(result.info, sym, usageSym)
+  markUsed(conf, result.info, sym, usageSym)
 
 proc safeSemExpr*(c: PContext, n: PNode): PNode =
   # use only for idetools support!
   try:
     result = c.semExpr(c, n)
   except ERecoverableError:
-    result = ast.emptyNode
+    result = c.graph.emptyNode
 
 proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) =
   if n.kind == nkDotExpr:
@@ -507,14 +510,14 @@ proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) =
     # of the next line, so we check the 'field' is actually on the same
     # line as the object to prevent this from happening:
     let prefix = if n.len == 2 and n[1].info.line == n[0].info.line and
-       not gTrackPosAttached: n[1] else: nil
+       not c.config.m.trackPosAttached: n[1] else: nil
     suggestFieldAccess(c, obj, prefix, outputs)
 
     #if optIdeDebug in gGlobalOptions:
     #  echo "expression ", renderTree(obj), " has type ", typeToString(obj.typ)
     #writeStackTrace()
   else:
-    let prefix = if gTrackPosAttached: nil else: n
+    let prefix = if c.config.m.trackPosAttached: nil else: n
     suggestEverything(c, n, prefix, outputs)
 
 proc suggestExprNoCheck*(c: PContext, n: PNode) =
@@ -522,9 +525,9 @@ proc suggestExprNoCheck*(c: PContext, n: PNode) =
   if c.compilesContextId > 0: return
   inc(c.compilesContextId)
   var outputs: Suggestions = @[]
-  if gIdeCmd == ideSug:
+  if c.config.ideCmd == ideSug:
     sugExpr(c, n, outputs)
-  elif gIdeCmd == ideCon:
+  elif c.config.ideCmd == ideCon:
     if n.kind in nkCallKinds:
       var a = copyNode(n)
       var x = safeSemExpr(c, n.sons[0])
@@ -538,15 +541,15 @@ proc suggestExprNoCheck*(c: PContext, n: PNode) =
       suggestCall(c, a, n, outputs)
 
   dec(c.compilesContextId)
-  if outputs.len > 0 and gIdeCmd in {ideSug, ideCon, ideDef}:
-    produceOutput(outputs)
+  if outputs.len > 0 and c.config.ideCmd in {ideSug, ideCon, ideDef}:
+    produceOutput(outputs, c.config)
     suggestQuit()
 
 proc suggestExpr*(c: PContext, n: PNode) =
-  if exactEquals(gTrackPos, n.info): suggestExprNoCheck(c, n)
+  if exactEquals(c.config.m.trackPos, n.info): suggestExprNoCheck(c, n)
 
 proc suggestDecl*(c: PContext, n: PNode; s: PSym) =
-  let attached = gTrackPosAttached
+  let attached = c.config.m.trackPosAttached
   if attached: inc(c.inTypeContext)
   defer:
     if attached: dec(c.inTypeContext)
@@ -558,11 +561,11 @@ proc suggestStmt*(c: PContext, n: PNode) =
 proc suggestEnum*(c: PContext; n: PNode; t: PType) =
   var outputs: Suggestions = @[]
   suggestSymList(c, t.n, nil, n.info, outputs)
-  produceOutput(outputs)
+  produceOutput(outputs, c.config)
   if outputs.len > 0: suggestQuit()
 
 proc suggestSentinel*(c: PContext) =
-  if gIdeCmd != ideSug or c.module.position != gTrackPos.fileIndex: return
+  if c.config.ideCmd != ideSug or c.module.position != c.config.m.trackPos.fileIndex.int32: return
   if c.compilesContextId > 0: return
   inc(c.compilesContextId)
   var outputs: Suggestions = @[]
@@ -575,7 +578,9 @@ proc suggestSentinel*(c: PContext) =
     for it in items(scope.symbols):
       var pm: PrefixMatch
       if filterSymNoOpr(it, nil, pm):
-        outputs.add(symToSuggest(it, isLocal = isLocal, ideSug, newLineInfo(gTrackPos.fileIndex, -1, -1), 0, PrefixMatch.None, false, scopeN))
+        outputs.add(symToSuggest(c.config, it, isLocal = isLocal, ideSug,
+            newLineInfo(c.config.m.trackPos.fileIndex, -1, -1), 0,
+            PrefixMatch.None, false, scopeN))
 
   dec(c.compilesContextId)
-  produceOutput(outputs)
+  produceOutput(outputs, c.config)
diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim
index 4745b1ac7..60246b9bf 100644
--- a/compiler/syntaxes.nim
+++ b/compiler/syntaxes.nim
@@ -11,17 +11,17 @@
 
 import
   strutils, llstream, ast, astalgo, idents, lexer, options, msgs, parser,
-  pbraces, filters, filter_tmpl, renderer
+  filters, filter_tmpl, renderer, lineinfos, pathutils
 
 type
   TFilterKind* = enum
     filtNone, filtTemplate, filtReplace, filtStrip
   TParserKind* = enum
-    skinStandard, skinStrongSpaces, skinBraces, skinEndX
+    skinStandard, skinEndX
 
 const
-  parserNames*: array[TParserKind, string] = ["standard", "strongspaces",
-                                              "braces", "endx"]
+  parserNames*: array[TParserKind, string] = ["standard",
+                                              "endx"]
   filterNames*: array[TFilterKind, string] = ["none", "stdtmpl", "replace",
                                               "strip"]
 
@@ -30,40 +30,37 @@ type
     skin*: TParserKind
     parser*: TParser
 
+template config(p: TParsers): ConfigRef = p.parser.lex.config
+
 proc parseAll*(p: var TParsers): PNode =
   case p.skin
-  of skinStandard, skinStrongSpaces:
+  of skinStandard:
     result = parser.parseAll(p.parser)
-  of skinBraces:
-    result = pbraces.parseAll(p.parser)
   of skinEndX:
-    internalError("parser to implement")
-    result = ast.emptyNode
+    internalError(p.config, "parser to implement")
 
 proc parseTopLevelStmt*(p: var TParsers): PNode =
   case p.skin
-  of skinStandard, skinStrongSpaces:
+  of skinStandard:
     result = parser.parseTopLevelStmt(p.parser)
-  of skinBraces:
-    result = pbraces.parseTopLevelStmt(p.parser)
   of skinEndX:
-    internalError("parser to implement")
-    result = ast.emptyNode
+    internalError(p.config, "parser to implement")
 
 proc utf8Bom(s: string): int =
-  if s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF':
+  if s.len >= 3 and s[0] == '\xEF' and s[1] == '\xBB' and s[2] == '\xBF':
     result = 3
   else:
     result = 0
 
 proc containsShebang(s: string, i: int): bool =
-  if s[i] == '#' and s[i+1] == '!':
+  if i+1 < s.len and s[i] == '#' and s[i+1] == '!':
     var j = i + 2
-    while s[j] in Whitespace: inc(j)
+    while j < s.len and s[j] in Whitespace: inc(j)
     result = s[j] == '/'
 
-proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache): PNode =
-  result = ast.emptyNode
+proc parsePipe(filename: AbsoluteFile, inputStream: PLLStream; cache: IdentCache;
+               config: ConfigRef): PNode =
+  result = newNode(nkEmpty)
   var s = llStreamOpen(filename, fmRead)
   if s != nil:
     var line = newStringOfCap(80)
@@ -74,13 +71,17 @@ proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache): PNo
       discard llStreamReadLine(s, line)
       i = 0
       inc linenumber
-    if line[i] == '#' and line[i+1] == '?':
-      inc(i, 2)
-      while line[i] in Whitespace: inc(i)
-      var q: TParser
-      parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache)
-      result = parser.parseAll(q)
-      parser.closeParser(q)
+    if i+1 < line.len and line[i] == '#' and line[i+1] == '?':
+      when defined(nimpretty2):
+        # XXX this is a bit hacky, but oh well...
+        quit "can't nimpretty a source code filter"
+      else:
+        inc(i, 2)
+        while i < line.len and line[i] in Whitespace: inc(i)
+        var q: TParser
+        parser.openParser(q, filename, llStreamOpen(substr(line, i)), cache, config)
+        result = parser.parseAll(q)
+        parser.closeParser(q)
     llStreamClose(s)
 
 proc getFilter(ident: PIdent): TFilterKind =
@@ -89,42 +90,44 @@ proc getFilter(ident: PIdent): TFilterKind =
       return i
   result = filtNone
 
-proc getParser(ident: PIdent): TParserKind =
+proc getParser(conf: ConfigRef; n: PNode; ident: PIdent): TParserKind =
   for i in countup(low(TParserKind), high(TParserKind)):
     if cmpIgnoreStyle(ident.s, parserNames[i]) == 0:
       return i
-  rawMessage(errInvalidDirectiveX, ident.s)
+  localError(conf, n.info, "unknown parser: " & ident.s)
 
-proc getCallee(n: PNode): PIdent =
+proc getCallee(conf: ConfigRef; n: PNode): PIdent =
   if n.kind in nkCallKinds and n.sons[0].kind == nkIdent:
     result = n.sons[0].ident
   elif n.kind == nkIdent:
     result = n.ident
   else:
-    rawMessage(errXNotAllowedHere, renderTree(n))
+    localError(conf, n.info, "invalid filter: " & renderTree(n))
 
-proc applyFilter(p: var TParsers, n: PNode, filename: string,
+proc applyFilter(p: var TParsers, n: PNode, filename: AbsoluteFile,
                  stdin: PLLStream): PLLStream =
-  var ident = getCallee(n)
+  var ident = getCallee(p.config, n)
   var f = getFilter(ident)
   case f
   of filtNone:
-    p.skin = getParser(ident)
+    p.skin = getParser(p.config, n, ident)
     result = stdin
   of filtTemplate:
-    result = filterTmpl(stdin, filename, n)
+    result = filterTmpl(stdin, filename, n, p.config)
   of filtStrip:
-    result = filterStrip(stdin, filename, n)
+    result = filterStrip(p.config, stdin, filename, n)
   of filtReplace:
-    result = filterReplace(stdin, filename, n)
+    result = filterReplace(p.config, stdin, filename, n)
   if f != filtNone:
-    if hintCodeBegin in gNotes:
-      rawMessage(hintCodeBegin, [])
-      msgWriteln(result.s)
-      rawMessage(hintCodeEnd, [])
+    assert p.config != nil
+    if hintCodeBegin in p.config.notes:
+      rawMessage(p.config, hintCodeBegin, [])
+      msgWriteln(p.config, result.s)
+      rawMessage(p.config, hintCodeEnd, [])
 
-proc evalPipe(p: var TParsers, n: PNode, filename: string,
+proc evalPipe(p: var TParsers, n: PNode, filename: AbsoluteFile,
               start: PLLStream): PLLStream =
+  assert p.config != nil
   result = start
   if n.kind == nkEmpty: return
   if n.kind == nkInfix and n[0].kind == nkIdent and n[0].ident.s == "|":
@@ -138,31 +141,35 @@ proc evalPipe(p: var TParsers, n: PNode, filename: string,
   else:
     result = applyFilter(p, n, filename, result)
 
-proc openParsers*(p: var TParsers, fileIdx: int32, inputstream: PLLStream;
-                  cache: IdentCache) =
+proc openParsers*(p: var TParsers, fileIdx: FileIndex, inputstream: PLLStream;
+                  cache: IdentCache; config: ConfigRef) =
+  assert config != nil
   var s: PLLStream
   p.skin = skinStandard
-  let filename = fileIdx.toFullPathConsiderDirty
-  var pipe = parsePipe(filename, inputstream, cache)
+  let filename = toFullPathConsiderDirty(config, fileIdx)
+  var pipe = parsePipe(filename, inputstream, cache, config)
+  p.config() = config
   if pipe != nil: s = evalPipe(p, pipe, filename, inputstream)
   else: s = inputstream
   case p.skin
-  of skinStandard, skinBraces, skinEndX:
-    parser.openParser(p.parser, fileIdx, s, cache, false)
-  of skinStrongSpaces:
-    parser.openParser(p.parser, fileIdx, s, cache, true)
+  of skinStandard, skinEndX:
+    parser.openParser(p.parser, fileIdx, s, cache, config)
 
 proc closeParsers*(p: var TParsers) =
   parser.closeParser(p.parser)
 
-proc parseFile*(fileIdx: int32; cache: IdentCache): PNode {.procvar.} =
-  var
-    p: TParsers
-    f: File
-  let filename = fileIdx.toFullPathConsiderDirty
-  if not open(f, filename):
-    rawMessage(errCannotOpenFile, filename)
-    return
-  openParsers(p, fileIdx, llStreamOpen(f), cache)
-  result = parseAll(p)
-  closeParsers(p)
+proc setupParsers*(p: var TParsers; fileIdx: FileIndex; cache: IdentCache;
+                   config: ConfigRef): bool =
+  var f: File
+  let filename = toFullPathConsiderDirty(config, fileIdx)
+  if not open(f, filename.string):
+    rawMessage(config, errGenerated, "cannot open file: " & filename.string)
+    return false
+  openParsers(p, fileIdx, llStreamOpen(f), cache, config)
+  result = true
+
+proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode {.procvar.} =
+  var p: TParsers
+  if setupParsers(p, fileIdx, cache, config):
+    result = parseAll(p)
+    closeParsers(p)
diff --git a/compiler/tccgen.nim b/compiler/tccgen.nim
index ea0fb590f..2301ad404 100644
--- a/compiler/tccgen.nim
+++ b/compiler/tccgen.nim
@@ -39,24 +39,24 @@ proc setupEnvironment =
 
   addIncludePath(gTinyC, libpath)
   when defined(windows):
-    addSysincludePath(gTinyC, nimrodDir / "tinyc/win32/include")
-  addSysincludePath(gTinyC, nimrodDir / "tinyc/include")
+    addSysincludePath(gTinyC, nimDir / "tinyc/win32/include")
+  addSysincludePath(gTinyC, nimDir / "tinyc/include")
   when defined(windows):
     defineSymbol(gTinyC, "_WIN32", nil)
     # we need Mingw's headers too:
     var gccbin = getConfigVar("gcc.path") % ["nim", nimDir]
     addSysincludePath(gTinyC, gccbin /../ "include")
-    #addFile(nimrodDir / r"tinyc\win32\wincrt1.o")
-    addFile(nimrodDir / r"tinyc\win32\alloca86.o")
-    addFile(nimrodDir / r"tinyc\win32\chkstk.o")
-    #addFile(nimrodDir / r"tinyc\win32\crt1.o")
+    #addFile(nimDir / r"tinyc\win32\wincrt1.o")
+    addFile(nimDir / r"tinyc\win32\alloca86.o")
+    addFile(nimDir / r"tinyc\win32\chkstk.o")
+    #addFile(nimDir / r"tinyc\win32\crt1.o")
 
-    #addFile(nimrodDir / r"tinyc\win32\dllcrt1.o")
-    #addFile(nimrodDir / r"tinyc\win32\dllmain.o")
-    addFile(nimrodDir / r"tinyc\win32\libtcc1.o")
+    #addFile(nimDir / r"tinyc\win32\dllcrt1.o")
+    #addFile(nimDir / r"tinyc\win32\dllmain.o")
+    addFile(nimDir / r"tinyc\win32\libtcc1.o")
 
-    #addFile(nimrodDir / r"tinyc\win32\lib\crt1.c")
-    #addFile(nimrodDir / r"tinyc\lib\libtcc1.c")
+    #addFile(nimDir / r"tinyc\win32\lib\crt1.c")
+    #addFile(nimDir / r"tinyc\lib\libtcc1.c")
   else:
     addSysincludePath(gTinyC, "/usr/include")
     when defined(amd64):
diff --git a/compiler/testability.nim b/compiler/testability.nim
deleted file mode 100644
index 4587a5344..000000000
--- a/compiler/testability.nim
+++ /dev/null
@@ -1,5 +0,0 @@
-template tests*(body: stmt) {.immediate.} =
-  when defined(selftest):
-    when not declared(unittest): import unittest
-    body
-
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 771dc58f4..7b2979dea 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -19,11 +19,15 @@
 # * transforms 'defer' into a 'try finally' statement
 
 import
-  intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os,
-  idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread,
-  lambdalifting, sempass2, lowerings, lookups
+  intsets, strutils, options, ast, astalgo, trees, treetab, msgs, lookups,
+  idents, renderer, types, passes, semfold, magicsys, cgmeth,
+  sempass2, lowerings, destroyer, liftlocals,
+  modulegraphs, lineinfos
 
-# implementation
+proc transformBody*(g: ModuleGraph, prc: PSym, cache = true;
+                    noDestructors = false): PNode
+
+import closureiters, lambdalifting
 
 type
   PTransNode* = distinct PNode
@@ -39,13 +43,14 @@ type
                               # if we encounter the 2nd yield statement
     next: PTransCon           # for stacking
 
-  TTransfContext = object of passes.TPassContext
+  TTransfContext = object of TPassContext
     module: PSym
     transCon: PTransCon      # top of a TransCon stack
     inlining: int            # > 0 if we are in inlining context (copy vars)
     nestedProcs: int         # > 0 if we are in a nested proc
     contSyms, breakSyms: seq[PSym]  # to transform 'continue' and 'break'
-    deferDetected, tooEarly: bool
+    deferDetected, tooEarly, needsDestroyPass, noDestructors: bool
+    graph: ModuleGraph
   PTransf = ref TTransfContext
 
 proc newTransNode(a: PNode): PTransNode {.inline.} =
@@ -62,18 +67,25 @@ proc newTransNode(kind: TNodeKind, n: PNode,
   var x = newNodeIT(kind, n.info, n.typ)
   newSeq(x.sons, sons)
   x.typ = n.typ
+#  x.flags = n.flags
   result = x.PTransNode
 
+proc add(a, b: PTransNode) {.inline.} = addSon(PNode(a), PNode(b))
+proc len(a: PTransNode): int {.inline.} = sonsLen(a.PNode)
+
 proc `[]=`(a: PTransNode, i: int, x: PTransNode) {.inline.} =
   var n = PNode(a)
   n.sons[i] = PNode(x)
 
+proc `[]=`(a: PTransNode, i: BackwardsIndex, x: PTransNode) {.inline.} =
+  `[]=`(a, a.len - i.int, x)
+
 proc `[]`(a: PTransNode, i: int): PTransNode {.inline.} =
   var n = PNode(a)
   result = n.sons[i].PTransNode
 
-proc add(a, b: PTransNode) {.inline.} = addSon(PNode(a), PNode(b))
-proc len(a: PTransNode): int {.inline.} = result = sonsLen(a.PNode)
+proc `[]`(a: PTransNode, i: BackwardsIndex): PTransNode {.inline.} =
+  `[]`(a, a.len - i.int)
 
 proc newTransCon(owner: PSym): PTransCon =
   assert owner != nil
@@ -86,7 +98,7 @@ proc pushTransCon(c: PTransf, t: PTransCon) =
   c.transCon = t
 
 proc popTransCon(c: PTransf) =
-  if (c.transCon == nil): internalError("popTransCon")
+  if (c.transCon == nil): internalError(c.graph.config, "popTransCon")
   c.transCon = c.transCon.next
 
 proc getCurrOwner(c: PTransf): PSym =
@@ -94,12 +106,12 @@ proc getCurrOwner(c: PTransf): PSym =
   else: result = c.module
 
 proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode =
-  let r = newSym(skTemp, getIdent(genPrefix), getCurrOwner(c), info)
-  r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias})
+  let r = newSym(skTemp, getIdent(c.graph.cache, genPrefix), getCurrOwner(c), info)
+  r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias, tySink})
   incl(r.flags, sfFromGeneric)
   let owner = getCurrOwner(c)
   if owner.isIterator and not c.tooEarly:
-    result = freshVarForClosureIter(r, owner)
+    result = freshVarForClosureIter(c.graph, r, owner)
   else:
     result = newSymNode(r)
 
@@ -110,20 +122,22 @@ proc transformSons(c: PTransf, n: PNode): PTransNode =
   for i in countup(0, sonsLen(n)-1):
     result[i] = transform(c, n.sons[i])
 
-proc newAsgnStmt(c: PTransf, le: PNode, ri: PTransNode): PTransNode =
-  result = newTransNode(nkFastAsgn, PNode(ri).info, 2)
+proc newAsgnStmt(c: PTransf, kind: TNodeKind, le: PNode, ri: PTransNode): PTransNode =
+  result = newTransNode(kind, PNode(ri).info, 2)
   result[0] = PTransNode(le)
   result[1] = ri
 
 proc transformSymAux(c: PTransf, n: PNode): PNode =
   let s = n.sym
   if s.typ != nil and s.typ.callConv == ccClosure:
+    if s.kind in routineKinds:
+      discard transformBody(c.graph, s, true, c.noDestructors)
     if s.kind == skIterator:
       if c.tooEarly: return n
-      else: return liftIterSym(n, getCurrOwner(c))
-    elif s.kind in {skProc, skConverter, skMethod} and not c.tooEarly:
+      else: return liftIterSym(c.graph, n, getCurrOwner(c))
+    elif s.kind in {skProc, skFunc, skConverter, skMethod} and not c.tooEarly:
       # top level .closure procs are still somewhat supported for 'Nake':
-      return makeClosure(s, nil, n.info)
+      return makeClosure(c.graph, s, nil, n.info)
   #elif n.sym.kind in {skVar, skLet} and n.sym.typ.callConv == ccClosure:
   #  echo n.info, " come heer for ", c.tooEarly
   #  if not c.tooEarly:
@@ -132,7 +146,7 @@ proc transformSymAux(c: PTransf, n: PNode): PNode =
   if sfBorrow in s.flags and s.kind in routineKinds:
     # simply exchange the symbol:
     b = s.getBody
-    if b.kind != nkSym: internalError(n.info, "wrong AST for borrowed symbol")
+    if b.kind != nkSym: internalError(c.graph.config, n.info, "wrong AST for borrowed symbol")
     b = newSymNode(b.sym, n.info)
   else:
     b = n
@@ -153,7 +167,7 @@ proc transformSym(c: PTransf, n: PNode): PTransNode =
 proc freshVar(c: PTransf; v: PSym): PNode =
   let owner = getCurrOwner(c)
   if owner.isIterator and not c.tooEarly:
-    result = freshVarForClosureIter(v, owner)
+    result = freshVarForClosureIter(c.graph, v, owner)
   else:
     var newVar = copySym(v)
     incl(newVar.flags, sfFromGeneric)
@@ -168,11 +182,11 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode =
       result[i] = PTransNode(it)
     elif it.kind == nkIdentDefs:
       if it.sons[0].kind == nkSym:
-        internalAssert(it.len == 3)
+        internalAssert(c.graph.config, it.len == 3)
         let x = freshVar(c, it.sons[0].sym)
         idNodeTablePut(c.transCon.mapping, it.sons[0].sym, x)
         var defs = newTransNode(nkIdentDefs, it.info, 3)
-        if importantComments():
+        if importantComments(c.graph.config):
           # keep documentation information:
           PNode(defs).comment = it.comment
         defs[0] = x.PTransNode
@@ -186,30 +200,36 @@ proc transformVarSection(c: PTransf, v: PNode): PTransNode =
         result[i] = transform(c, it)
     else:
       if it.kind != nkVarTuple:
-        internalError(it.info, "transformVarSection: not nkVarTuple")
+        internalError(c.graph.config, it.info, "transformVarSection: not nkVarTuple")
       var L = sonsLen(it)
       var defs = newTransNode(it.kind, it.info, L)
       for j in countup(0, L-3):
-        let x = freshVar(c, it.sons[j].sym)
-        idNodeTablePut(c.transCon.mapping, it.sons[j].sym, x)
-        defs[j] = x.PTransNode
+        if it[j].kind == nkSym:
+          let x = freshVar(c, it.sons[j].sym)
+          idNodeTablePut(c.transCon.mapping, it.sons[j].sym, x)
+          defs[j] = x.PTransNode
+        else:
+          defs[j] = transform(c, it[j])
       assert(it.sons[L-2].kind == nkEmpty)
-      defs[L-2] = ast.emptyNode.PTransNode
+      defs[L-2] = newNodeI(nkEmpty, it.info).PTransNode
       defs[L-1] = transform(c, it.sons[L-1])
       result[i] = defs
 
 proc transformConstSection(c: PTransf, v: PNode): PTransNode =
-  result = newTransNode(v)
-  for i in countup(0, sonsLen(v)-1):
-    var it = v.sons[i]
-    if it.kind == nkCommentStmt:
-      result[i] = PTransNode(it)
-    else:
-      if it.kind != nkConstDef: internalError(it.info, "transformConstSection")
-      if it.sons[0].kind != nkSym:
-        internalError(it.info, "transformConstSection")
+  result = PTransNode(v)
+  when false:
+    result = newTransNode(v)
+    for i in countup(0, sonsLen(v)-1):
+      var it = v.sons[i]
+      if it.kind == nkCommentStmt:
+        result[i] = PTransNode(it)
+      else:
+        if it.kind != nkConstDef: internalError(c.graph.config, it.info, "transformConstSection")
+        if it.sons[0].kind != nkSym:
+          debug it.sons[0]
+          internalError(c.graph.config, it.info, "transformConstSection")
 
-      result[i] = PTransNode(it)
+        result[i] = PTransNode(it)
 
 proc hasContinue(n: PNode): bool =
   case n.kind
@@ -221,27 +241,19 @@ proc hasContinue(n: PNode): bool =
 
 proc newLabel(c: PTransf, n: PNode): PSym =
   result = newSym(skLabel, nil, getCurrOwner(c), n.info)
-  result.name = getIdent(genPrefix & $result.id)
-
-proc freshLabels(c: PTransf, n: PNode; symMap: var TIdTable) =
-  if n.kind in {nkBlockStmt, nkBlockExpr}:
-    if n.sons[0].kind == nkSym:
-      let x = newLabel(c, n[0])
-      idTablePut(symMap, n[0].sym, x)
-      n.sons[0].sym = x
-  if n.kind == nkSym and n.sym.kind == skLabel:
-    let x = PSym(idTableGet(symMap, n.sym))
-    if x != nil: n.sym = x
-  else:
-    for i in 0 .. <safeLen(n): freshLabels(c, n.sons[i], symMap)
+  result.name = getIdent(c.graph.cache, genPrefix & $result.id)
 
 proc transformBlock(c: PTransf, n: PNode): PTransNode =
   var labl: PSym
-  if n.sons[0].kind != nkEmpty:
-    # already named block? -> Push symbol on the stack:
-    labl = n.sons[0].sym
+  if c.inlining > 0:
+    labl = newLabel(c, n[0])
+    idNodeTablePut(c.transCon.mapping, n[0].sym, newSymNode(labl))
   else:
-    labl = newLabel(c, n)
+    labl =
+      if n.sons[0].kind != nkEmpty:
+        n.sons[0].sym  # already named block? -> Push symbol on the stack
+      else:
+        newLabel(c, n)
   c.breakSyms.add(labl)
   result = transformSons(c, n)
   discard c.breakSyms.pop
@@ -277,33 +289,15 @@ proc transformWhile(c: PTransf; n: PNode): PTransNode =
     var body = newTransNode(n)
     for i in 0..n.len-2:
       body[i] = transform(c, n.sons[i])
-    body[<n.len] = transformLoopBody(c, n.sons[<n.len])
+    body[n.len-1] = transformLoopBody(c, n.sons[n.len-1])
     result[1] = body
     discard c.breakSyms.pop
 
 proc transformBreak(c: PTransf, n: PNode): PTransNode =
-  if n.sons[0].kind != nkEmpty or c.inlining > 0:
-    result = n.PTransNode
-    when false:
-      let lablCopy = idNodeTableGet(c.transCon.mapping, n.sons[0].sym)
-      if lablCopy.isNil:
-        result = n.PTransNode
-      else:
-        result = newTransNode(n.kind, n.info, 1)
-        result[0] = lablCopy.PTransNode
-  elif c.breakSyms.len > 0:
-    # this check can fail for 'nim check'
+  result = transformSons(c, n)
+  if n.sons[0].kind == nkEmpty and c.breakSyms.len > 0:
     let labl = c.breakSyms[c.breakSyms.high]
-    result = transformSons(c, n)
     result[0] = newSymNode(labl).PTransNode
-  else:
-    result = n.PTransNode
-
-proc unpackTuple(c: PTransf, n: PNode, father: PTransNode) =
-  # XXX: BUG: what if `n` is an expression with side-effects?
-  for i in countup(0, sonsLen(c.transCon.forStmt) - 3):
-    add(father, newAsgnStmt(c, c.transCon.forStmt.sons[i],
-        transform(c, newTupleAccess(n, i))))
 
 proc introduceNewLocalVars(c: PTransf, n: PNode): PTransNode =
   case n.kind
@@ -328,25 +322,44 @@ proc introduceNewLocalVars(c: PTransf, n: PNode): PTransNode =
       result[i] = introduceNewLocalVars(c, n.sons[i])
 
 proc transformYield(c: PTransf, n: PNode): PTransNode =
+  proc asgnTo(lhs: PNode, rhs: PTransNode): PTransNode =
+    # Choose the right assignment instruction according to the given ``lhs``
+    # node since it may not be a nkSym (a stack-allocated skForVar) but a
+    # nkDotExpr (a heap-allocated slot into the envP block)
+    case lhs.kind:
+    of nkSym:
+      internalAssert c.graph.config, lhs.sym.kind == skForVar
+      result = newAsgnStmt(c, nkFastAsgn, lhs, rhs)
+    of nkDotExpr:
+      result = newAsgnStmt(c, nkAsgn, lhs, rhs)
+    else:
+      internalAssert c.graph.config, false
   result = newTransNode(nkStmtList, n.info, 0)
   var e = n.sons[0]
   # c.transCon.forStmt.len == 3 means that there is one for loop variable
   # and thus no tuple unpacking:
   if e.typ.isNil: return result # can happen in nimsuggest for unknown reasons
-  if skipTypes(e.typ, {tyGenericInst, tyAlias}).kind == tyTuple and
+  if skipTypes(e.typ, {tyGenericInst, tyAlias, tySink}).kind == tyTuple and
       c.transCon.forStmt.len != 3:
     e = skipConv(e)
-    if e.kind == nkPar:
+    if e.kind in {nkPar, nkTupleConstr}:
       for i in countup(0, sonsLen(e) - 1):
         var v = e.sons[i]
         if v.kind == nkExprColonExpr: v = v.sons[1]
-        add(result, newAsgnStmt(c, c.transCon.forStmt.sons[i],
-                                transform(c, v)))
+        let lhs = c.transCon.forStmt.sons[i]
+        let rhs = transform(c, v)
+        add(result, asgnTo(lhs, rhs))
     else:
-      unpackTuple(c, e, result)
+      # Unpack the tuple into the loop variables
+      # XXX: BUG: what if `n` is an expression with side-effects?
+      for i in countup(0, sonsLen(c.transCon.forStmt) - 3):
+        let lhs = c.transCon.forStmt.sons[i]
+        let rhs = transform(c, newTupleAccess(c.graph, e, i))
+        add(result, asgnTo(lhs, rhs))
   else:
-    var x = transform(c, e)
-    add(result, newAsgnStmt(c, c.transCon.forStmt.sons[0], x))
+    let lhs = c.transCon.forStmt.sons[0]
+    let rhs = transform(c, e)
+    add(result, asgnTo(lhs, rhs))
 
   inc(c.transCon.yieldStmts)
   if c.transCon.yieldStmts <= 1:
@@ -358,7 +371,7 @@ proc transformYield(c: PTransf, n: PNode): PTransNode =
 
 proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
   result = transformSons(c, n)
-  if gCmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags: return
+  if c.graph.config.cmd == cmdCompileToCpp or sfCompileToCpp in c.module.flags: return
   var n = result.PNode
   case n.sons[0].kind
   of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
@@ -367,32 +380,42 @@ proc transformAddrDeref(c: PTransf, n: PNode, a, b: TNodeKind): PTransNode =
       # addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
       n.sons[0].sons[0] = m.sons[0]
       result = PTransNode(n.sons[0])
+      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
+        PNode(result).typ = n.typ
+      elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
+        PNode(result).typ = toVar(PNode(result).typ)
   of nkHiddenStdConv, nkHiddenSubConv, nkConv:
     var m = n.sons[0].sons[1]
     if m.kind == a or m.kind == b:
       # addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
       n.sons[0].sons[1] = m.sons[0]
       result = PTransNode(n.sons[0])
+      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
+        PNode(result).typ = n.typ
+      elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
+        PNode(result).typ = toVar(PNode(result).typ)
   else:
     if n.sons[0].kind == a or n.sons[0].kind == b:
       # addr ( deref ( x )) --> x
       result = PTransNode(n.sons[0].sons[0])
+      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
+        PNode(result).typ = n.typ
 
-proc generateThunk(prc: PNode, dest: PType): PNode =
+proc generateThunk(c: PTransf; prc: PNode, dest: PType): PNode =
   ## Converts 'prc' into '(thunk, nil)' so that it's compatible with
   ## a closure.
 
   # we cannot generate a proper thunk here for GC-safety reasons
   # (see internal documentation):
-  if gCmd == cmdCompileToJS: return prc
+  if c.graph.config.cmd == cmdCompileToJS: return prc
   result = newNodeIT(nkClosure, prc.info, dest)
   var conv = newNodeIT(nkHiddenSubConv, prc.info, dest)
-  conv.add(emptyNode)
+  conv.add(newNodeI(nkEmpty, prc.info))
   conv.add(prc)
   if prc.kind == nkClosure:
-    internalError(prc.info, "closure to closure created")
+    internalError(c.graph.config, prc.info, "closure to closure created")
   result.add(conv)
-  result.add(newNodeIT(nkNilLit, prc.info, getSysType(tyNil)))
+  result.add(newNodeIT(nkNilLit, prc.info, getSysType(c.graph, prc.info, tyNil)))
 
 proc transformConv(c: PTransf, n: PNode): PTransNode =
   # numeric types need range checks:
@@ -404,8 +427,8 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
     if not isOrdinalType(source):
       # float -> int conversions. ugh.
       result = transformSons(c, n)
-    elif firstOrd(n.typ) <= firstOrd(n.sons[1].typ) and
-        lastOrd(n.sons[1].typ) <= lastOrd(n.typ):
+    elif firstOrd(c.graph.config, n.typ) <= firstOrd(c.graph.config, n.sons[1].typ) and
+        lastOrd(c.graph.config, n.sons[1].typ) <= lastOrd(c.graph.config, n.typ):
       # BUGFIX: simply leave n as it is; we need a nkConv node,
       # but no range check:
       result = transformSons(c, n)
@@ -417,8 +440,8 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
         result = newTransNode(nkChckRange, n, 3)
       dest = skipTypes(n.typ, abstractVar)
       result[0] = transform(c, n.sons[1])
-      result[1] = newIntTypeNode(nkIntLit, firstOrd(dest), dest).PTransNode
-      result[2] = newIntTypeNode(nkIntLit, lastOrd(dest), dest).PTransNode
+      result[1] = newIntTypeNode(nkIntLit, firstOrd(c.graph.config, dest), dest).PTransNode
+      result[2] = newIntTypeNode(nkIntLit, lastOrd(c.graph.config, dest), dest).PTransNode
   of tyFloat..tyFloat128:
     # XXX int64 -> float conversion?
     if skipTypes(n.typ, abstractVar).kind == tyRange:
@@ -477,7 +500,7 @@ proc transformConv(c: PTransf, n: PNode): PTransNode =
   of tyProc:
     result = transformSons(c, n)
     if dest.callConv == ccClosure and source.callConv == ccDefault:
-      result = generateThunk(result[1].PNode, dest).PTransNode
+      result = generateThunk(c, result[1].PNode, dest).PTransNode
   else:
     result = transformSons(c, n)
 
@@ -496,28 +519,28 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
   case arg.kind
   of nkEmpty..nkNilLit:
     result = paDirectMapping
-  of nkPar, nkCurly, nkBracket:
+  of nkPar, nkTupleConstr, nkCurly, nkBracket:
     result = paFastAsgn
     for i in countup(0, sonsLen(arg) - 1):
       if putArgInto(arg.sons[i], formal) != paDirectMapping: return
     result = paDirectMapping
   else:
-    if skipTypes(formal, abstractInst).kind == tyVar: result = paVarAsgn
+    if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn
     else: result = paFastAsgn
 
 proc findWrongOwners(c: PTransf, n: PNode) =
   if n.kind == nkVarSection:
     let x = n.sons[0].sons[0]
     if x.kind == nkSym and x.sym.owner != getCurrOwner(c):
-      internalError(x.info, "bah " & x.sym.name.s & " " &
+      internalError(c.graph.config, x.info, "bah " & x.sym.name.s & " " &
         x.sym.owner.name.s & " " & getCurrOwner(c).name.s)
   else:
-    for i in 0 .. <safeLen(n): findWrongOwners(c, n.sons[i])
+    for i in 0 ..< safeLen(n): findWrongOwners(c, n.sons[i])
 
 proc transformFor(c: PTransf, n: PNode): PTransNode =
   # generate access statements for the parameters (unless they are constant)
   # put mapping from formal parameters to actual parameters
-  if n.kind != nkForStmt: internalError(n.info, "transformFor")
+  if n.kind != nkForStmt: internalError(c.graph.config, n.info, "transformFor")
 
   var length = sonsLen(n)
   var call = n.sons[length - 2]
@@ -532,12 +555,10 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
   c.breakSyms.add(labl)
   if call.kind notin nkCallKinds or call.sons[0].kind != nkSym or
       call.sons[0].typ.callConv == ccClosure:
-    n.sons[length-1] = transformLoopBody(c, n.sons[length-1]).PNode
-    if not c.tooEarly:
-      n.sons[length-2] = transform(c, n.sons[length-2]).PNode
-      result[1] = lambdalifting.liftForLoop(n, getCurrOwner(c)).PTransNode
-    else:
-      result[1] = newNode(nkEmpty).PTransNode
+    result[1] = n.PTransNode
+    result[1][^1] = transformLoopBody(c, n[^1])
+    result[1][^2] = transform(c, n[^2])
+    result[1] = lambdalifting.liftForLoop(c.graph, result[1].PNode, getCurrOwner(c)).PTransNode
     discard c.breakSyms.pop
     return result
 
@@ -566,7 +587,10 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
   pushTransCon(c, newC)
   for i in countup(1, sonsLen(call) - 1):
     var arg = transform(c, call.sons[i]).PNode
-    var formal = skipTypes(iter.typ, abstractInst).n.sons[i].sym
+    let ff = skipTypes(iter.typ, abstractInst)
+    # can happen for 'nim check':
+    if i >= ff.n.len: return result
+    var formal = ff.n.sons[i].sym
     case putArgInto(arg, formal.typ)
     of paDirectMapping:
       idNodeTablePut(newC.mapping, formal, arg)
@@ -574,7 +598,7 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
       # generate a temporary and produce an assignment statement:
       var temp = newTemp(c, formal.typ, formal.info)
       addVar(v, temp)
-      add(stmtList, newAsgnStmt(c, temp, arg.PTransNode))
+      add(stmtList, newAsgnStmt(c, nkFastAsgn, temp, arg.PTransNode))
       idNodeTablePut(newC.mapping, formal, temp)
     of paVarAsgn:
       assert(skipTypes(formal.typ, abstractInst).kind == tyVar)
@@ -585,21 +609,16 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
       addSonSkipIntLit(typ, formal.typ.sons[0])
       var temp = newTemp(c, typ, formal.info)
       addVar(v, temp)
-      add(stmtList, newAsgnStmt(c, temp, arg.PTransNode))
+      add(stmtList, newAsgnStmt(c, nkFastAsgn, temp, arg.PTransNode))
       idNodeTablePut(newC.mapping, formal, temp)
 
-  var body = iter.getBody.copyTree
-  pushInfoContext(n.info)
-  # XXX optimize this somehow. But the check "c.inlining" is not correct:
-  var symMap: TIdTable
-  initIdTable symMap
-  freshLabels(c, body, symMap)
-
+  let body = transformBody(c.graph, iter, true, c.noDestructors)
+  pushInfoContext(c.graph.config, n.info)
   inc(c.inlining)
   add(stmtList, transform(c, body))
   #findWrongOwners(c, stmtList.pnode)
   dec(c.inlining)
-  popInfoContext()
+  popInfoContext(c.graph.config)
   popTransCon(c)
   # echo "transformed: ", stmtList.PNode.renderTree
 
@@ -614,7 +633,11 @@ proc transformCase(c: PTransf, n: PNode): PTransNode =
     case it.kind
     of nkElifBranch:
       if ifs.PNode == nil:
-        ifs = newTransNode(nkIfStmt, it.info, 0)
+        # Generate the right node depending on whether `n` is used as a stmt or
+        # as an expr
+        let kind = if n.typ != nil: nkIfExpr else: nkIfStmt
+        ifs = newTransNode(kind, it.info, 0)
+        ifs.PNode.typ = n.typ
       ifs.add(e)
     of nkElse:
       if ifs.PNode == nil: result.add(e)
@@ -639,7 +662,7 @@ proc transformArrayAccess(c: PTransf, n: PNode): PTransNode =
     result = n.PTransNode
   else:
     result = newTransNode(n)
-    for i in 0 .. < n.len:
+    for i in 0 ..< n.len:
       result[i] = transform(c, skipConv(n.sons[i]))
 
 proc getMergeOp(n: PNode): PSym =
@@ -682,11 +705,11 @@ proc transformCall(c: PTransf, n: PNode): PTransNode =
         while (j < sonsLen(n)):
           let b = transform(c, n.sons[j]).PNode
           if not isConstExpr(b): break
-          a = evalOp(op.magic, n, a, b, nil)
+          a = evalOp(op.magic, n, a, b, nil, c.graph)
           inc(j)
       add(result, a.PTransNode)
     if len(result) == 2: result = result[1]
-  elif magic in {mNBindSym, mTypeOf}:
+  elif magic in {mNBindSym, mTypeOf, mRunnableExamples}:
     # for bindSym(myconst) we MUST NOT perform constant folding:
     result = n.PTransNode
   elif magic == mProcCall:
@@ -701,27 +724,27 @@ proc transformCall(c: PTransf, n: PNode): PTransNode =
         let t = lastSon(s.sons[0].sym.ast)
         if t.kind != nkSym or sfDispatcher notin t.sym.flags:
           methodDef(s.sons[0].sym, false)
-      result = methodCall(s).PTransNode
+      result = methodCall(s, c.graph.config).PTransNode
     else:
       result = s.PTransNode
 
 proc transformExceptBranch(c: PTransf, n: PNode): PTransNode =
   result = transformSons(c, n)
-  if n[0].isInfixAs():
+  if n[0].isInfixAs() and not isImportedException(n[0][1].typ, c.graph.config):
     let excTypeNode = n[0][1]
-    let actions = newTransNode(nkStmtList, n[1].info, 2)
+    let actions = newTransNode(nkStmtListExpr, n[1], 2)
     # Generating `let exc = (excType)(getCurrentException())`
     # -> getCurrentException()
-    let excCall = PTransNode(callCodegenProc("getCurrentException", ast.emptyNode))
+    let excCall = PTransNode(callCodegenProc(c.graph, "getCurrentException"))
     # -> (excType)
     let convNode = newTransNode(nkHiddenSubConv, n[1].info, 2)
-    convNode[0] = PTransNode(ast.emptyNode)
+    convNode[0] = PTransNode(newNodeI(nkEmpty, n.info))
     convNode[1] = excCall
     PNode(convNode).typ = excTypeNode.typ.toRef()
     # -> let exc = ...
     let identDefs = newTransNode(nkIdentDefs, n[1].info, 3)
     identDefs[0] = PTransNode(n[0][2])
-    identDefs[1] = PTransNode(ast.emptyNode)
+    identDefs[1] = PTransNode(newNodeI(nkEmpty, n.info))
     identDefs[2] = convNode
 
     let letSection = newTransNode(nkLetSection, n[1].info, 1)
@@ -738,13 +761,13 @@ proc transformExceptBranch(c: PTransf, n: PNode): PTransNode =
 proc dontInlineConstant(orig, cnst: PNode): bool {.inline.} =
   # symbols that expand to a complex constant (array, etc.) should not be
   # inlined, unless it's the empty array:
-  result = orig.kind == nkSym and cnst.kind in {nkCurly, nkPar, nkBracket} and
+  result = orig.kind == nkSym and cnst.kind in {nkCurly, nkPar, nkTupleConstr, nkBracket} and
       cnst.len != 0
 
-proc commonOptimizations*(c: PSym, n: PNode): PNode =
+proc commonOptimizations*(g: ModuleGraph; c: PSym, n: PNode): PNode =
   result = n
-  for i in 0 .. < n.safeLen:
-    result.sons[i] = commonOptimizations(c, n.sons[i])
+  for i in 0 ..< n.safeLen:
+    result.sons[i] = commonOptimizations(g, c, n.sons[i])
   var op = getMergeOp(n)
   if (op != nil) and (op.magic != mNone) and (sonsLen(n) >= 3):
     result = newNodeIT(nkCall, n.info, n.typ)
@@ -759,18 +782,55 @@ proc commonOptimizations*(c: PSym, n: PNode): PNode =
         while j < sonsLen(args):
           let b = args.sons[j]
           if not isConstExpr(b): break
-          a = evalOp(op.magic, result, a, b, nil)
+          a = evalOp(op.magic, result, a, b, nil, g)
           inc(j)
       add(result, a)
     if len(result) == 2: result = result[1]
   else:
-    var cnst = getConstExpr(c, n)
+    var cnst = getConstExpr(c, n, g)
     # we inline constants if they are not complex constants:
     if cnst != nil and not dontInlineConstant(n, cnst):
       result = cnst
     else:
       result = n
 
+proc hoistParamsUsedInDefault(c: PTransf, call, letSection, defExpr: PNode): PNode =
+  # This takes care of complicated signatures such as:
+  # proc foo(a: int, b = a)
+  # proc bar(a: int, b: int, c = a + b)
+  #
+  # The recursion may confuse you. It performs two duties:
+  #
+  # 1) extracting all referenced params from default expressions
+  #    into a let section preceeding the call
+  #
+  # 2) replacing the "references" within the default expression
+  #    with these extracted skLet symbols.
+  #
+  # The first duty is carried out directly in the code here, while the second
+  # duty is activated by returning a non-nil value. The caller is responsible
+  # for replacing the input to the function with the returned non-nil value.
+  # (which is the hoisted symbol)
+  if defExpr.kind == nkSym:
+    if defExpr.sym.kind == skParam and defExpr.sym.owner == call[0].sym:
+      let paramPos = defExpr.sym.position + 1
+
+      if call[paramPos].kind == nkSym and sfHoisted in call[paramPos].sym.flags:
+        # Already hoisted, we still need to return it in order to replace the
+        # placeholder expression in the default value.
+        return call[paramPos]
+
+      let hoistedVarSym = hoistExpr(letSection,
+                                    call[paramPos],
+                                    getIdent(c.graph.cache, genPrefix),
+                                    c.transCon.owner).newSymNode
+      call[paramPos] = hoistedVarSym
+      return hoistedVarSym
+  else:
+    for i in 0..<defExpr.safeLen:
+      let hoisted = hoistParamsUsedInDefault(c, call, letSection, defExpr[i])
+      if hoisted != nil: defExpr[i] = hoisted
+
 proc transform(c: PTransf, n: PNode): PTransNode =
   when false:
     var oldDeferAnchor: PNode
@@ -779,11 +839,12 @@ proc transform(c: PTransf, n: PNode): PTransNode =
                   nkBlockStmt, nkBlockExpr}:
       oldDeferAnchor = c.deferAnchor
       c.deferAnchor = n
-
+  if n.typ != nil and tfHasAsgn in n.typ.flags:
+    c.needsDestroyPass = true
   case n.kind
   of nkSym:
     result = transformSym(c, n)
-  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
+  of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkComesFrom:
     # nothing to be done for leaves:
     result = PTransNode(n)
   of nkBracketExpr: result = transformArrayAccess(c, n)
@@ -839,6 +900,15 @@ proc transform(c: PTransf, n: PNode): PTransNode =
   of nkBreakStmt: result = transformBreak(c, n)
   of nkCallKinds:
     result = transformCall(c, n)
+    var call = result.PNode
+    if nfDefaultRefsParam in call.flags:
+      # We've found a default value that references another param.
+      # See the notes in `hoistParamsUsedInDefault` for more details.
+      var hoistedParams = newNodeI(nkLetSection, call.info, 0)
+      for i in 1 ..< call.len:
+        let hoisted = hoistParamsUsedInDefault(c, call, hoistedParams, call[i])
+        if hoisted != nil: call[i] = hoisted
+      result = newTree(nkStmtListExpr, hoistedParams, call).PTransNode
   of nkAddr, nkHiddenAddr:
     result = transformAddrDeref(c, n, nkDerefExpr, nkHiddenDeref)
   of nkDerefExpr, nkHiddenDeref:
@@ -853,7 +923,8 @@ proc transform(c: PTransf, n: PNode): PTransNode =
         # ensure that e.g. discard "some comment" gets optimized away
         # completely:
         result = PTransNode(newNode(nkCommentStmt))
-  of nkCommentStmt, nkTemplateDef:
+  of nkCommentStmt, nkTemplateDef, nkImportStmt, nkStaticStmt,
+      nkExportStmt, nkExportExceptStmt:
     return n.PTransNode
   of nkConstSection:
     # do not replace ``const c = 3`` with ``const 3 = 3``
@@ -873,14 +944,13 @@ proc transform(c: PTransf, n: PNode): PTransNode =
     else:
       result = transformSons(c, n)
   of nkIdentDefs, nkConstDef:
-    when true:
-      result = transformSons(c, n)
-    else:
-      result = n.PTransNode
-      let L = n.len-1
-      result[L] = transform(c, n.sons[L])
+    result = PTransNode(n)
+    result[0] = transform(c, n[0])
+    # Skip the second son since it only contains an unsemanticized copy of the
+    # variable type used by docgen
+    result[2] = transform(c, n[2])
     # XXX comment handling really sucks:
-    if importantComments():
+    if importantComments(c.graph.config):
       PNode(result).comment = n.comment
   of nkClosure:
     # it can happen that for-loop-inlining produced a fresh
@@ -897,7 +967,7 @@ proc transform(c: PTransf, n: PNode): PTransNode =
   when false:
     if oldDeferAnchor != nil: c.deferAnchor = oldDeferAnchor
 
-  var cnst = getConstExpr(c.module, PNode(result))
+  var cnst = getConstExpr(c.module, PNode(result), c.graph)
   # we inline constants if they are not complex constants:
   if cnst != nil and not dontInlineConstant(n, cnst):
     result = PTransNode(cnst) # do not miss an optimization
@@ -906,17 +976,18 @@ proc processTransf(c: PTransf, n: PNode, owner: PSym): PNode =
   # Note: For interactive mode we cannot call 'passes.skipCodegen' and skip
   # this step! We have to rely that the semantic pass transforms too errornous
   # nodes into an empty node.
-  if c.fromCache or nfTransf in n.flags: return n
+  if nfTransf in n.flags: return n
   pushTransCon(c, newTransCon(owner))
   result = PNode(transform(c, n))
   popTransCon(c)
   incl(result.flags, nfTransf)
 
-proc openTransf(module: PSym, filename: string): PTransf =
+proc openTransf(g: ModuleGraph; module: PSym, filename: string): PTransf =
   new(result)
   result.contSyms = @[]
   result.breakSyms = @[]
   result.module = module
+  result.graph = g
 
 proc flattenStmts(n: PNode) =
   var goOn = true
@@ -959,39 +1030,60 @@ template liftDefer(c, root) =
   if c.deferDetected:
     liftDeferAux(root)
 
-proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
-  if nfTransf in n.flags or prc.kind in {skTemplate}:
-    result = n
+proc transformBody*(g: ModuleGraph, prc: PSym, cache = true;
+                    noDestructors = false): PNode =
+  assert prc.kind in routineKinds
+
+  if prc.transformedBody != nil:
+    result = prc.transformedBody
+  elif nfTransf in prc.ast[bodyPos].flags or prc.kind in {skTemplate}:
+    result = prc.ast[bodyPos]
   else:
-    var c = openTransf(module, "")
-    result = liftLambdas(prc, n, c.tooEarly)
-    #result = n
+    prc.transformedBody = newNode(nkEmpty) # protects from recursion
+    var c = openTransf(g, prc.getModule, "")
+    c.noDestructors = noDestructors
+    result = liftLambdas(g, prc, prc.ast[bodyPos], c.tooEarly)
     result = processTransf(c, result, prc)
     liftDefer(c, result)
-    #result = liftLambdas(prc, result)
+    result = liftLocalsIfRequested(prc, result, g.cache, g.config)
+    if c.needsDestroyPass and not noDestructors:
+      result = injectDestructorCalls(g, prc, result)
+
+    if prc.isIterator:
+      result = g.transformClosureIterator(prc, result)
+
     incl(result.flags, nfTransf)
-    when useEffectSystem: trackProc(prc, result)
-    #if prc.name.s == "testbody":
-    #  echo renderTree(result)
 
-proc transformStmt*(module: PSym, n: PNode): PNode =
+    let cache = cache or prc.typ.callConv == ccInline
+    if cache:
+      # genProc for inline procs will be called multiple times from diffrent modules,
+      # it is important to transform exactly once to get sym ids and locations right
+      prc.transformedBody = result
+    else:
+      prc.transformedBody = nil
+
+proc transformStmt*(g: ModuleGraph; module: PSym, n: PNode): PNode =
   if nfTransf in n.flags:
     result = n
   else:
-    var c = openTransf(module, "")
+    var c = openTransf(g, module, "")
     result = processTransf(c, n, module)
     liftDefer(c, result)
     #result = liftLambdasForTopLevel(module, result)
+    if c.needsDestroyPass:
+      result = injectDestructorCalls(g, module, result)
     incl(result.flags, nfTransf)
-    when useEffectSystem: trackTopLevelStmt(module, result)
-    #if n.info ?? "temp.nim":
-    #  echo renderTree(result, {renderIds})
 
-proc transformExpr*(module: PSym, n: PNode): PNode =
+proc transformExpr*(g: ModuleGraph; module: PSym, n: PNode;
+                    noDestructors = false): PNode =
   if nfTransf in n.flags:
     result = n
   else:
-    var c = openTransf(module, "")
+    var c = openTransf(g, module, "")
     result = processTransf(c, n, module)
     liftDefer(c, result)
+    # expressions are not to be injected with destructor calls as that
+    # the list of top level statements needs to be collected before.
+    if c.needsDestroyPass and not noDestructors:
+      result = injectDestructorCalls(g, module, result)
     incl(result.flags, nfTransf)
diff --git a/compiler/trees.nim b/compiler/trees.nim
index 424fba14c..ca2360e12 100644
--- a/compiler/trees.nim
+++ b/compiler/trees.nim
@@ -41,6 +41,7 @@ proc exprStructuralEquivalent*(a, b: PNode; strictSymEquality=false): bool =
     of nkCharLit..nkUInt64Lit: result = a.intVal == b.intVal
     of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal
     of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal
+    of nkCommentStmt: result = a.comment == b.comment
     of nkEmpty, nkNilLit, nkType: result = true
     else:
       if sonsLen(a) == sonsLen(b):
@@ -91,17 +92,16 @@ proc isCaseObj*(n: PNode): bool =
 
 proc isDeepConstExpr*(n: PNode): bool =
   case n.kind
-  of nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit,
-      nkFloatLit..nkFloat64Lit, nkNilLit:
+  of nkCharLit..nkNilLit:
     result = true
   of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv:
     result = isDeepConstExpr(n.sons[1])
-  of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure:
-    for i in ord(n.kind == nkObjConstr) .. <n.len:
+  of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure, nkRange:
+    for i in ord(n.kind == nkObjConstr) ..< n.len:
       if not isDeepConstExpr(n.sons[i]): return false
     if n.typ.isNil: result = true
     else:
-      let t = n.typ.skipTypes({tyGenericInst, tyDistinct, tyAlias})
+      let t = n.typ.skipTypes({tyGenericInst, tyDistinct, tyAlias, tySink})
       if t.kind in {tyRef, tyPtr}: return false
       if t.kind != tyObject or not isCaseObj(t.n):
         result = true
@@ -109,13 +109,15 @@ proc isDeepConstExpr*(n: PNode): bool =
 
 proc isRange*(n: PNode): bool {.inline.} =
   if n.kind in nkCallKinds:
-    if n[0].kind == nkIdent and n[0].ident.id == ord(wDotDot) or
-        n[0].kind in {nkClosedSymChoice, nkOpenSymChoice} and
-        n[0][1].sym.name.id == ord(wDotDot):
+    let callee = n[0]
+    if (callee.kind == nkIdent and callee.ident.id == ord(wDotDot)) or
+       (callee.kind == nkSym and callee.sym.name.id == ord(wDotDot)) or
+       (callee.kind in {nkClosedSymChoice, nkOpenSymChoice} and
+        callee[1].sym.name.id == ord(wDotDot)):
       result = true
 
 proc whichPragma*(n: PNode): TSpecialWord =
-  let key = if n.kind == nkExprColonExpr: n.sons[0] else: n
+  let key = if n.kind in nkPragmaCallKinds and n.len > 0: n.sons[0] else: n
   if key.kind == nkIdent: result = whichKeyword(key.ident)
 
 proc unnestStmts(n, result: PNode) =
diff --git a/compiler/treetab.nim b/compiler/treetab.nim
index e6eb8c666..f15974f61 100644
--- a/compiler/treetab.nim
+++ b/compiler/treetab.nim
@@ -29,8 +29,7 @@ proc hashTree(n: PNode): Hash =
     if (n.floatVal >= - 1000000.0) and (n.floatVal <= 1000000.0):
       result = result !& toInt(n.floatVal)
   of nkStrLit..nkTripleStrLit:
-    if not n.strVal.isNil:
-      result = result !& hash(n.strVal)
+    result = result !& hash(n.strVal)
   else:
     for i in countup(0, sonsLen(n) - 1):
       result = result !& hashTree(n.sons[i])
diff --git a/compiler/types.nim b/compiler/types.nim
index f4ef75094..b163ca4e9 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -10,14 +10,13 @@
 # this module contains routines for accessing and iterating over types
 
 import
-  intsets, ast, astalgo, trees, msgs, strutils, platform, renderer
+  intsets, ast, astalgo, trees, msgs, strutils, platform, renderer, options,
+  lineinfos
 
-proc firstOrd*(t: PType): BiggestInt
-proc lastOrd*(t: PType): BiggestInt
-proc lengthOrd*(t: PType): BiggestInt
 type
   TPreferedDesc* = enum
-    preferName, preferDesc, preferExported, preferModuleInfo, preferGenericArg
+    preferName, preferDesc, preferExported, preferModuleInfo, preferGenericArg,
+    preferTypeName
 
 proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string
 template `$`*(typ: PType): string = typeToString(typ)
@@ -48,38 +47,26 @@ type
 
 proc equalParams*(a, b: PNode): TParamsEquality
   # returns whether the parameter lists of the procs a, b are exactly the same
-proc isOrdinalType*(t: PType): bool
-proc enumHasHoles*(t: PType): bool
 
 const
   # TODO: Remove tyTypeDesc from each abstractX and (where necessary)
   # replace with typedescX
   abstractPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyDistinct, tyOrdinal,
-                   tyTypeDesc, tyAlias}
+                   tyTypeDesc, tyAlias, tyInferred, tySink, tyLent}
   abstractVar* = {tyVar, tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc,
-                  tyAlias}
+                  tyAlias, tyInferred, tySink, tyLent}
   abstractRange* = {tyGenericInst, tyRange, tyDistinct, tyOrdinal, tyTypeDesc,
-                    tyAlias}
+                    tyAlias, tyInferred, tySink}
   abstractVarRange* = {tyGenericInst, tyRange, tyVar, tyDistinct, tyOrdinal,
-                       tyTypeDesc, tyAlias}
-  abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias}
-
-  skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias}
+                       tyTypeDesc, tyAlias, tyInferred, tySink}
+  abstractInst* = {tyGenericInst, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias,
+                   tyInferred, tySink}
+  skipPtrs* = {tyVar, tyPtr, tyRef, tyGenericInst, tyTypeDesc, tyAlias,
+               tyInferred, tySink, tyLent}
   # typedescX is used if we're sure tyTypeDesc should be included (or skipped)
   typedescPtrs* = abstractPtrs + {tyTypeDesc}
   typedescInst* = abstractInst + {tyTypeDesc}
 
-proc containsObject*(t: PType): bool
-proc containsGarbageCollectedRef*(typ: PType): bool
-proc containsHiddenPointer*(typ: PType): bool
-proc canFormAcycle*(typ: PType): bool
-proc isCompatibleToCString*(a: PType): bool
-proc getOrdValue*(n: PNode): BiggestInt
-proc computeSize*(typ: PType): BiggestInt
-proc getSize*(typ: PType): BiggestInt
-proc isPureObject*(typ: PType): bool
-proc invalidGenericInst*(f: PType): bool
-  # for debugging
 type
   TTypeFieldResult* = enum
     frNone,                   # type has no object type field
@@ -91,23 +78,27 @@ proc analyseObjectWithTypeField*(t: PType): TTypeFieldResult
   # made or intializing of the type field suffices or if there is no type field
   # at all in this type.
 
-proc invalidGenericInst(f: PType): bool =
+proc invalidGenericInst*(f: PType): bool =
   result = f.kind == tyGenericInst and lastSon(f) == nil
 
-proc isPureObject(typ: PType): bool =
+proc isPureObject*(typ: PType): bool =
   var t = typ
   while t.kind == tyObject and t.sons[0] != nil:
     t = t.sons[0].skipTypes(skipPtrs)
   result = t.sym != nil and sfPure in t.sym.flags
 
-proc getOrdValue(n: PNode): BiggestInt =
+proc getOrdValue*(n: PNode): BiggestInt =
   case n.kind
-  of nkCharLit..nkUInt64Lit: result = n.intVal
-  of nkNilLit: result = 0
-  of nkHiddenStdConv: result = getOrdValue(n.sons[1])
-  else:
-    localError(n.info, errOrdinalTypeExpected)
-    result = 0
+  of nkCharLit..nkUInt64Lit: n.intVal
+  of nkNilLit: 0
+  of nkHiddenStdConv: getOrdValue(n.sons[1])
+  else: high(BiggestInt)
+
+proc getFloatValue*(n: PNode): BiggestFloat =
+  case n.kind
+  of nkFloatLiterals: n.floatVal
+  of nkHiddenStdConv: getFloatValue(n.sons[1])
+  else: NaN
 
 proc isIntLit*(t: PType): bool {.inline.} =
   result = t.kind == tyInt and t.n != nil and t.n.kind == nkIntLit
@@ -115,50 +106,45 @@ proc isIntLit*(t: PType): bool {.inline.} =
 proc isFloatLit*(t: PType): bool {.inline.} =
   result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit
 
-proc isCompatibleToCString(a: PType): bool =
-  if a.kind == tyArray:
-    if (firstOrd(a.sons[0]) == 0) and
-        (skipTypes(a.sons[0], {tyRange, tyGenericInst, tyAlias}).kind in
-            {tyInt..tyInt64, tyUInt..tyUInt64}) and
-        (a.sons[1].kind == tyChar):
-      result = true
-
-proc getProcHeader*(sym: PSym; prefer: TPreferedDesc = preferName): string =
+proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferName): string =
   result = sym.owner.name.s & '.' & sym.name.s & '('
   var n = sym.typ.n
   for i in countup(1, sonsLen(n) - 1):
-    var p = n.sons[i]
+    let p = n.sons[i]
     if p.kind == nkSym:
       add(result, p.sym.name.s)
       add(result, ": ")
       add(result, typeToString(p.sym.typ, prefer))
       if i != sonsLen(n)-1: add(result, ", ")
     else:
-      internalError("getProcHeader")
+      result.add renderTree(p)
   add(result, ')')
   if n.sons[0].typ != nil:
     result.add(": " & typeToString(n.sons[0].typ, prefer))
+  result.add "[declared in "
+  result.add(conf$sym.info)
+  result.add "]"
 
 proc elemType*(t: PType): PType =
   assert(t != nil)
   case t.kind
-  of tyGenericInst, tyDistinct, tyAlias: result = elemType(lastSon(t))
+  of tyGenericInst, tyDistinct, tyAlias, tySink: result = elemType(lastSon(t))
   of tyArray: result = t.sons[1]
   else: result = t.lastSon
   assert(result != nil)
 
-proc isOrdinalType(t: PType): bool =
+proc enumHasHoles*(t: PType): bool =
+  var b = t.skipTypes({tyRange, tyGenericInst, tyAlias, tySink})
+  result = b.kind == tyEnum and tfEnumHasHoles in b.flags
+
+proc isOrdinalType*(t: PType, allowEnumWithHoles = false): bool =
   assert(t != nil)
   const
     # caution: uint, uint64 are no ordinal types!
     baseKinds = {tyChar,tyInt..tyInt64,tyUInt8..tyUInt32,tyBool,tyEnum}
-    parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tyDistinct}
-  t.kind in baseKinds or (t.kind in parentKinds and isOrdinalType(t.sons[0]))
-
-proc enumHasHoles(t: PType): bool =
-  var b = t
-  while b.kind in {tyRange, tyGenericInst, tyAlias}: b = b.sons[0]
-  result = b.kind == tyEnum and tfEnumHasHoles in b.flags
+    parentKinds = {tyRange, tyOrdinal, tyGenericInst, tyAlias, tySink, tyDistinct}
+  (t.kind in baseKinds and not (t.enumHasHoles and not allowEnumWithHoles)) or
+    (t.kind in parentKinds and isOrdinalType(t.lastSon))
 
 proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter,
                      closure: RootRef): bool
@@ -182,7 +168,7 @@ proc iterOverTypeAux(marker: var IntSet, t: PType, iter: TTypeIter,
   if result: return
   if not containsOrIncl(marker, t.id):
     case t.kind
-    of tyGenericInst, tyGenericBody, tyAlias:
+    of tyGenericInst, tyGenericBody, tyAlias, tySink, tyInferred:
       result = iterOverTypeAux(marker, lastSon(t), iter, closure)
     else:
       for i in countup(0, sonsLen(t) - 1):
@@ -214,10 +200,10 @@ proc searchTypeNodeForAux(n: PNode, p: TTypePredicate,
       of nkOfBranch, nkElse:
         result = searchTypeNodeForAux(lastSon(n.sons[i]), p, marker)
         if result: return
-      else: internalError("searchTypeNodeForAux(record case branch)")
+      else: discard
   of nkSym:
     result = searchTypeForAux(n.sym.typ, p, marker)
-  else: internalError(n.info, "searchTypeNodeForAux()")
+  else: discard
 
 proc searchTypeForAux(t: PType, predicate: TTypePredicate,
                       marker: var IntSet): bool =
@@ -232,7 +218,7 @@ proc searchTypeForAux(t: PType, predicate: TTypePredicate,
     if t.sons[0] != nil:
       result = searchTypeForAux(t.sons[0].skipTypes(skipPtrs), predicate, marker)
     if not result: result = searchTypeNodeForAux(t.n, predicate, marker)
-  of tyGenericInst, tyDistinct, tyAlias:
+  of tyGenericInst, tyDistinct, tyAlias, tySink:
     result = searchTypeForAux(lastSon(t), predicate, marker)
   of tyArray, tySet, tyTuple:
     for i in countup(0, sonsLen(t) - 1):
@@ -248,10 +234,11 @@ proc searchTypeFor(t: PType, predicate: TTypePredicate): bool =
 proc isObjectPredicate(t: PType): bool =
   result = t.kind == tyObject
 
-proc containsObject(t: PType): bool =
+proc containsObject*(t: PType): bool =
   result = searchTypeFor(t, isObjectPredicate)
 
 proc isObjectWithTypeFieldPredicate(t: PType): bool =
+
   result = t.kind == tyObject and t.sons[0] == nil and
       not (t.sym != nil and {sfPure, sfInfixCall} * t.sym.flags != {}) and
       tfFinal notin t.flags
@@ -263,7 +250,7 @@ proc analyseObjectWithTypeFieldAux(t: PType,
   if t == nil: return
   case t.kind
   of tyObject:
-    if (t.n != nil):
+    if t.n != nil:
       if searchTypeNodeForAux(t.n, isObjectWithTypeFieldPredicate, marker):
         return frEmbedded
     for i in countup(0, sonsLen(t) - 1):
@@ -275,7 +262,7 @@ proc analyseObjectWithTypeFieldAux(t: PType,
       if res == frHeader: result = frHeader
     if result == frNone:
       if isObjectWithTypeFieldPredicate(t): result = frHeader
-  of tyGenericInst, tyDistinct, tyAlias:
+  of tyGenericInst, tyDistinct, tyAlias, tySink:
     result = analyseObjectWithTypeFieldAux(lastSon(t), marker)
   of tyArray, tyTuple:
     for i in countup(0, sonsLen(t) - 1):
@@ -292,8 +279,10 @@ proc analyseObjectWithTypeField(t: PType): TTypeFieldResult =
 proc isGCRef(t: PType): bool =
   result = t.kind in GcTypeKinds or
     (t.kind == tyProc and t.callConv == ccClosure)
+  if result and t.kind in {tyString, tySequence} and tfHasAsgn in t.flags:
+    result = false
 
-proc containsGarbageCollectedRef(typ: PType): bool =
+proc containsGarbageCollectedRef*(typ: PType): bool =
   # returns true if typ contains a reference, sequence or string (all the
   # things that are garbage-collected)
   result = searchTypeFor(typ, isGCRef)
@@ -308,7 +297,7 @@ proc containsTyRef*(typ: PType): bool =
 proc isHiddenPointer(t: PType): bool =
   result = t.kind in {tyString, tySequence}
 
-proc containsHiddenPointer(typ: PType): bool =
+proc containsHiddenPointer*(typ: PType): bool =
   # returns true if typ contains a string, table or sequence (all the things
   # that need to be copied deeply)
   result = searchTypeFor(typ, isHiddenPointer)
@@ -351,7 +340,7 @@ proc canFormAcycleAux(marker: var IntSet, typ: PType, startId: int): bool =
   of tyProc: result = typ.callConv == ccClosure
   else: discard
 
-proc canFormAcycle(typ: PType): bool =
+proc canFormAcycle*(typ: PType): bool =
   var marker = initIntSet()
   result = canFormAcycleAux(marker, typ, typ.id)
 
@@ -407,14 +396,27 @@ const
     "int", "int8", "int16", "int32", "int64",
     "float", "float32", "float64", "float128",
     "uint", "uint8", "uint16", "uint32", "uint64",
-    "unused0", "unused1",
-    "unused2", "varargs[$1]", "unused", "Error Type",
+    "opt", "sink",
+    "lent ", "varargs[$1]", "UncheckedArray[$1]", "Error Type",
     "BuiltInTypeClass", "UserTypeClass",
-    "UserTypeClassInst", "CompositeTypeClass",
+    "UserTypeClassInst", "CompositeTypeClass", "inferred",
     "and", "or", "not", "any", "static", "TypeFromExpr", "FieldAccessor",
     "void"]
 
-const preferToResolveSymbols = {preferName, preferModuleInfo, preferGenericArg}
+const preferToResolveSymbols = {preferName, preferTypeName, preferModuleInfo, preferGenericArg}
+
+template bindConcreteTypeToUserTypeClass*(tc, concrete: PType) =
+  tc.sons.add concrete
+  tc.flags.incl tfResolved
+
+# TODO: It would be a good idea to kill the special state of a resolved
+# concept by switching to tyAlias within the instantiated procs.
+# Currently, tyAlias is always skipped with lastSon, which means that
+# we can store information about the matched concept in another position.
+# Then builtInFieldAccess can be modified to properly read the derived
+# consts and types stored within the concept.
+template isResolvedUserTypeClass*(t: PType): bool =
+  tfResolved in t.flags
 
 proc addTypeFlags(name: var string, typ: PType) {.inline.} =
   if tfNotNil in typ.flags: name.add(" not nil")
@@ -427,8 +429,17 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
        sfAnon notin t.sym.flags:
     if t.kind == tyInt and isIntLit(t):
       result = t.sym.name.s & " literal(" & $t.n.intVal & ")"
-    elif prefer == preferName or t.sym.owner.isNil:
+    elif t.kind == tyAlias:
+      result = typeToString(t.sons[0])
+    elif prefer in {preferName, preferTypeName} or t.sym.owner.isNil:
       result = t.sym.name.s
+      if t.kind == tyGenericParam and t.sonsLen > 0:
+        result.add ": "
+        var first = true
+        for son in t.sons:
+          if not first: result.add " or "
+          result.add son.typeToString
+          first = false
     else:
       result = t.sym.owner.name.s & '.' & t.sym.name.s
     result.addTypeFlags(t)
@@ -450,17 +461,19 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
     add(result, ']')
   of tyTypeDesc:
     if t.sons[0].kind == tyNone: result = "typedesc"
-    else: result = "typedesc[" & typeToString(t.sons[0]) & "]"
+    else: result = "type " & typeToString(t.sons[0])
   of tyStatic:
-    internalAssert t.len > 0
     if prefer == preferGenericArg and t.n != nil:
       result = t.n.renderTree
     else:
-      result = "static[" & typeToString(t.sons[0]) & "]"
+      result = "static[" & (if t.len > 0: typeToString(t.sons[0]) else: "") & "]"
       if t.n != nil: result.add "(" & renderTree(t.n) & ")"
   of tyUserTypeClass:
-    internalAssert t.sym != nil and t.sym.owner != nil
-    return t.sym.owner.name.s
+    if t.sym != nil and t.sym.owner != nil:
+      if t.isResolvedUserTypeClass: return typeToString(t.lastSon)
+      return t.sym.owner.name.s
+    else:
+      result = "<invalid tyUserTypeClass>"
   of tyBuiltInTypeClass:
     result = case t.base.kind:
       of tyVar: "var"
@@ -476,6 +489,10 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
       of tyTuple: "tuple"
       of tyOpenArray: "openarray"
       else: typeToStr[t.base.kind]
+  of tyInferred:
+    let concrete = t.previouslyInferred
+    if concrete != nil: result = typeToString(concrete)
+    else: result = "inferred[" & typeToString(t.base) & "]"
   of tyUserTypeClassInst:
     let body = t.base
     result = body.sym.name.s & "["
@@ -484,16 +501,25 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
       add(result, typeToString(t.sons[i]))
     result.add "]"
   of tyAnd:
-    result = typeToString(t.sons[0]) & " and " & typeToString(t.sons[1])
+    for i, son in t.sons:
+      result.add(typeToString(son))
+      if i < t.sons.high:
+        result.add(" and ")
   of tyOr:
-    result = typeToString(t.sons[0]) & " or " & typeToString(t.sons[1])
+    for i, son in t.sons:
+      result.add(typeToString(son))
+      if i < t.sons.high:
+        result.add(" or ")
   of tyNot:
     result = "not " & typeToString(t.sons[0])
   of tyExpr:
-    internalAssert t.len == 0
+    #internalAssert t.len == 0
     result = "untyped"
-  of tyFromExpr, tyFieldAccessor:
-    result = renderTree(t.n)
+  of tyFromExpr:
+    if t.n == nil:
+      result = "unknown"
+    else:
+      result = "type(" & renderTree(t.n) & ")"
   of tyArray:
     if t.sons[0].kind == tyRange:
       result = "array[" & rangeToStr(t.sons[0].n) & ", " &
@@ -501,8 +527,12 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
     else:
       result = "array[" & typeToString(t.sons[0]) & ", " &
           typeToString(t.sons[1]) & ']'
+  of tyUncheckedArray:
+    result = "UncheckedArray[" & typeToString(t.sons[0]) & ']'
   of tySequence:
     result = "seq[" & typeToString(t.sons[0]) & ']'
+  of tyOpt:
+    result = "opt[" & typeToString(t.sons[0]) & ']'
   of tyOrdinal:
     result = "ordinal[" & typeToString(t.sons[0]) & ']'
   of tySet:
@@ -511,7 +541,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
     result = "openarray[" & typeToString(t.sons[0]) & ']'
   of tyDistinct:
     result = "distinct " & typeToString(t.sons[0],
-      if prefer == preferModuleInfo: preferModuleInfo else: preferName)
+      if prefer == preferModuleInfo: preferModuleInfo else: preferTypeName)
   of tyTuple:
     # we iterate over t.sons here, because t.n may be nil
     if t.n != nil:
@@ -525,12 +555,13 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
     elif sonsLen(t) == 0:
       result = "tuple[]"
     else:
-      result = "("
+      if prefer == preferTypeName: result = "("
+      else: result = "tuple of ("
       for i in countup(0, sonsLen(t) - 1):
         add(result, typeToString(t.sons[i]))
         if i < sonsLen(t) - 1: add(result, ", ")
       add(result, ')')
-  of tyPtr, tyRef, tyVar:
+  of tyPtr, tyRef, tyVar, tyLent:
     result = typeToStr[t.kind]
     if t.len >= 2:
       setLen(result, result.len-1)
@@ -558,7 +589,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
       add(result, typeToString(t.sons[i]))
       if i < sonsLen(t) - 1: add(result, ", ")
     add(result, ')')
-    if t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0]))
+    if t.len > 0 and t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0]))
     var prag = if t.callConv == ccDefault: "" else: CallingConvToStr[t.callConv]
     if tfNoSideEffect in t.flags:
       addSep(prag)
@@ -572,22 +603,25 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
     if len(prag) != 0: add(result, "{." & prag & ".}")
   of tyVarargs:
     result = typeToStr[t.kind] % typeToString(t.sons[0])
+  of tySink:
+    result = "sink " & typeToString(t.sons[0])
   else:
     result = typeToStr[t.kind]
   result.addTypeFlags(t)
 
-proc firstOrd(t: PType): BiggestInt =
+
+proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt =
   case t.kind
   of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy:
     result = 0
-  of tySet, tyVar: result = firstOrd(t.sons[0])
-  of tyArray: result = firstOrd(t.sons[0])
+  of tySet, tyVar: result = firstOrd(conf, t.sons[0])
+  of tyArray: result = firstOrd(conf, t.sons[0])
   of tyRange:
     assert(t.n != nil)        # range directly given:
     assert(t.n.kind == nkRange)
     result = getOrdValue(t.n.sons[0])
   of tyInt:
-    if platform.intSize == 4: result = - (2147483646) - 2
+    if conf != nil and conf.target.intSize == 4: result = - (2147483646) - 2
     else: result = 0x8000000000000000'i64
   of tyInt8: result = - 128
   of tyInt16: result = - 32768
@@ -597,68 +631,110 @@ proc firstOrd(t: PType): BiggestInt =
   of tyEnum:
     # if basetype <> nil then return firstOrd of basetype
     if sonsLen(t) > 0 and t.sons[0] != nil:
-      result = firstOrd(t.sons[0])
+      result = firstOrd(conf, t.sons[0])
     else:
       assert(t.n.sons[0].kind == nkSym)
       result = t.n.sons[0].sym.position
-  of tyGenericInst, tyDistinct, tyTypeDesc, tyFieldAccessor, tyAlias:
-    result = firstOrd(lastSon(t))
+  of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink,
+     tyStatic, tyInferred, tyUserTypeClasses:
+    result = firstOrd(conf, lastSon(t))
   of tyOrdinal:
-    if t.len > 0: result = firstOrd(lastSon(t))
-    else: internalError("invalid kind for first(" & $t.kind & ')')
+    if t.len > 0: result = firstOrd(conf, lastSon(t))
+    else: internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')')
+  of tyUncheckedArray:
+    result = 0
   else:
-    internalError("invalid kind for first(" & $t.kind & ')')
+    internalError(conf, "invalid kind for firstOrd(" & $t.kind & ')')
     result = 0
 
-proc lastOrd(t: PType): BiggestInt =
+
+proc firstFloat*(t: PType): BiggestFloat =
+  case t.kind
+  of tyFloat..tyFloat128: -Inf
+  of tyRange:
+    assert(t.n != nil)        # range directly given:
+    assert(t.n.kind == nkRange)
+    getFloatValue(t.n.sons[0])
+  of tyVar: firstFloat(t.sons[0])
+  of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink,
+     tyStatic, tyInferred, tyUserTypeClasses:
+    firstFloat(lastSon(t))
+  else:
+    internalError(newPartialConfigRef(), "invalid kind for firstFloat(" & $t.kind & ')')
+    NaN
+
+proc lastOrd*(conf: ConfigRef; t: PType; fixedUnsigned = false): BiggestInt =
   case t.kind
   of tyBool: result = 1
   of tyChar: result = 255
-  of tySet, tyVar: result = lastOrd(t.sons[0])
-  of tyArray: result = lastOrd(t.sons[0])
+  of tySet, tyVar: result = lastOrd(conf, t.sons[0])
+  of tyArray: result = lastOrd(conf, t.sons[0])
   of tyRange:
     assert(t.n != nil)        # range directly given:
     assert(t.n.kind == nkRange)
     result = getOrdValue(t.n.sons[1])
   of tyInt:
-    if platform.intSize == 4: result = 0x7FFFFFFF
+    if conf != nil and conf.target.intSize == 4: result = 0x7FFFFFFF
     else: result = 0x7FFFFFFFFFFFFFFF'i64
   of tyInt8: result = 0x0000007F
   of tyInt16: result = 0x00007FFF
   of tyInt32: result = 0x7FFFFFFF
   of tyInt64: result = 0x7FFFFFFFFFFFFFFF'i64
   of tyUInt:
-    if platform.intSize == 4: result = 0xFFFFFFFF
+    if conf != nil and conf.target.intSize == 4: result = 0xFFFFFFFF
+    elif fixedUnsigned: result = 0xFFFFFFFFFFFFFFFF'i64
     else: result = 0x7FFFFFFFFFFFFFFF'i64
   of tyUInt8: result = 0xFF
   of tyUInt16: result = 0xFFFF
   of tyUInt32: result = 0xFFFFFFFF
-  of tyUInt64: result = 0x7FFFFFFFFFFFFFFF'i64
+  of tyUInt64:
+    if fixedUnsigned: result = 0xFFFFFFFFFFFFFFFF'i64
+    else: result = 0x7FFFFFFFFFFFFFFF'i64
   of tyEnum:
     assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym)
     result = t.n.sons[sonsLen(t.n) - 1].sym.position
-  of tyGenericInst, tyDistinct, tyTypeDesc, tyFieldAccessor, tyAlias:
-    result = lastOrd(lastSon(t))
+  of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink,
+     tyStatic, tyInferred, tyUserTypeClasses:
+    result = lastOrd(conf, lastSon(t))
   of tyProxy: result = 0
   of tyOrdinal:
-    if t.len > 0: result = lastOrd(lastSon(t))
-    else: internalError("invalid kind for last(" & $t.kind & ')')
+    if t.len > 0: result = lastOrd(conf, lastSon(t))
+    else: internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')')
+  of tyUncheckedArray:
+    result = high(BiggestInt)
   else:
-    internalError("invalid kind for last(" & $t.kind & ')')
+    internalError(conf, "invalid kind for lastOrd(" & $t.kind & ')')
     result = 0
 
-proc lengthOrd(t: PType): BiggestInt =
+
+proc lastFloat*(t: PType): BiggestFloat =
   case t.kind
-  of tyInt64, tyInt32, tyInt: result = lastOrd(t)
-  of tyDistinct: result = lengthOrd(t.sons[0])
+  of tyFloat..tyFloat128: Inf
+  of tyVar: lastFloat(t.sons[0])
+  of tyRange:
+    assert(t.n != nil)        # range directly given:
+    assert(t.n.kind == nkRange)
+    getFloatValue(t.n.sons[1])
+  of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink,
+     tyStatic, tyInferred, tyUserTypeClasses:
+    lastFloat(lastSon(t))
   else:
-    let last = lastOrd t
-    let first = firstOrd t
+    internalError(newPartialConfigRef(), "invalid kind for lastFloat(" & $t.kind & ')')
+    NaN
+
+
+proc lengthOrd*(conf: ConfigRef; t: PType): BiggestInt =
+  case t.skipTypes(tyUserTypeClasses).kind
+  of tyInt64, tyInt32, tyInt: result = lastOrd(conf, t)
+  of tyDistinct: result = lengthOrd(conf, t.sons[0])
+  else:
+    let last = lastOrd(conf, t)
+    let first = firstOrd(conf, t)
     # XXX use a better overflow check here:
     if last == high(BiggestInt) and first <= 0:
       result = last
     else:
-      result = lastOrd(t) - firstOrd(t) + 1
+      result = last - first + 1
 
 # -------------- type equality -----------------------------------------------
 
@@ -675,6 +751,7 @@ type
     ExactTypeDescValues
     ExactGenericParams
     ExactConstraints
+    ExactGcSafety
     AllowCommonBase
 
   TTypeCmpFlags* = set[TTypeCmpFlag]
@@ -691,9 +768,10 @@ proc initSameTypeClosure: TSameTypeClosure =
   discard
 
 proc containsOrIncl(c: var TSameTypeClosure, a, b: PType): bool =
-  result = not isNil(c.s) and c.s.contains((a.id, b.id))
+  result = c.s.len > 0 and c.s.contains((a.id, b.id))
   if not result:
-    if isNil(c.s): c.s = @[]
+    when not defined(nimNoNilSeqs):
+      if isNil(c.s): c.s = @[]
     c.s.add((a.id, b.id))
 
 proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool
@@ -733,8 +811,8 @@ proc equalParam(a, b: PSym): TParamsEquality =
 
 proc sameConstraints(a, b: PNode): bool =
   if isNil(a) and isNil(b): return true
-  internalAssert a.len == b.len
-  for i in 1 .. <a.len:
+  if a.len != b.len: return false
+  for i in 1 ..< a.len:
     if not exprStructuralEquivalent(a[i].sym.constraint,
                                     b[i].sym.constraint):
       return false
@@ -762,8 +840,8 @@ proc equalParams(a, b: PNode): TParamsEquality =
         return paramsNotEqual # paramsIncompatible;
       # continue traversal! If not equal, we can return immediately; else
       # it stays incompatible
-    if not sameTypeOrNil(a.sons[0].typ, b.sons[0].typ, {ExactTypeDescValues}):
-      if (a.sons[0].typ == nil) or (b.sons[0].typ == nil):
+    if not sameTypeOrNil(a.typ, b.typ, {ExactTypeDescValues}):
+      if (a.typ == nil) or (b.typ == nil):
         result = paramsNotEqual # one proc has a result, the other not is OK
       else:
         result = paramsIncompatible # overloading by different
@@ -792,7 +870,8 @@ proc sameTuple(a, b: PType, c: var TSameTypeClosure): bool =
           var y = b.n.sons[i].sym
           result = x.name.id == y.name.id
           if not result: break
-        else: internalError(a.n.info, "sameTuple")
+        else:
+          return false
     elif a.n != b.n and (a.n == nil or b.n == nil) and IgnoreTupleFields notin c.flags:
       result = false
 
@@ -873,6 +952,9 @@ proc isGenericAlias*(t: PType): bool =
 proc skipGenericAlias*(t: PType): PType =
   return if t.isGenericAlias: t.lastSon else: t
 
+proc sameFlags*(a, b: PType): bool {.inline.} =
+  result = eqTypeFlags*a.flags == eqTypeFlags*b.flags
+
 proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
   template cycleCheck() =
     # believe it or not, the direct check for ``containsOrIncl(c, a, b)``
@@ -885,9 +967,6 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
     else:
       if containsOrIncl(c, a, b): return true
 
-  proc sameFlags(a, b: PType): bool {.inline.} =
-    result = eqTypeFlags*a.flags == eqTypeFlags*b.flags
-
   if x == y: return true
   var a = skipTypes(x, {tyGenericInst, tyAlias})
   var b = skipTypes(y, {tyGenericInst, tyAlias})
@@ -955,14 +1034,21 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
       result = sameFlags(a, b)
   of tyGenericParam:
     result = sameChildrenAux(a, b, c) and sameFlags(a, b)
-    if result and ExactGenericParams in c.flags:
+    if result and {ExactGenericParams, ExactTypeDescValues} * c.flags != {}:
       result = a.sym.position == b.sym.position
   of tyGenericInvocation, tyGenericBody, tySequence,
-     tyOpenArray, tySet, tyRef, tyPtr, tyVar,
-     tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyFieldAccessor:
+     tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyLent, tySink, tyUncheckedArray,
+     tyArray, tyProc, tyVarargs, tyOrdinal, tyTypeClasses, tyOpt:
     cycleCheck()
     if a.kind == tyUserTypeClass and a.n != nil: return a.n == b.n
-    result = sameChildrenAux(a, b, c) and sameFlags(a, b)
+    result = sameChildrenAux(a, b, c)
+    if result:
+      if IgnoreTupleFields in c.flags:
+        result = a.flags * {tfVarIsPtr} == b.flags * {tfVarIsPtr}
+      else:
+        result = sameFlags(a, b)
+    if result and ExactGcSafety in c.flags:
+      result = a.flags * {tfThread} == b.flags * {tfThread}
     if result and a.kind == tyProc:
       result = ((IgnoreCC in c.flags) or a.callConv == b.callConv) and
                ((ExactConstraints notin c.flags) or sameConstraints(a.n, b.n))
@@ -971,13 +1057,16 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
     result = sameTypeOrNilAux(a.sons[0], b.sons[0], c) and
         sameValue(a.n.sons[0], b.n.sons[0]) and
         sameValue(a.n.sons[1], b.n.sons[1])
-  of tyGenericInst, tyAlias: discard
+  of tyGenericInst, tyAlias, tyInferred:
+    cycleCheck()
+    result = sameTypeAux(a.lastSon, b.lastSon, c)
   of tyNone: result = false
-  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("sameFlags")
+  of tyOptAsRef: result = false
 
 proc sameBackendType*(x, y: PType): bool =
   var c = initSameTypeClosure()
   c.flags.incl IgnoreTupleFields
+  c.cmp = dcEqIgnoreDistinct
   result = sameTypeAux(x, y, c)
 
 proc compareTypes*(x, y: PType,
@@ -997,8 +1086,8 @@ proc inheritanceDiff*(a, b: PType): int =
   # | returns: +x iff `a` is the x'th direct subclass of `b`
   # | returns: `maxint` iff `a` and `b` are not compatible at all
   if a == b or a.kind == tyError or b.kind == tyError: return 0
-  assert a.kind == tyObject
-  assert b.kind == tyObject
+  assert a.kind in {tyObject} + skipPtrs
+  assert b.kind in {tyObject} + skipPtrs
   var x = a
   result = 0
   while x != nil:
@@ -1031,16 +1120,21 @@ proc commonSuperclass*(a, b: PType): PType =
     x = x.sons[0]
   var y = b
   while y != nil:
+    var t = y # bug #7818, save type before skip
     y = skipTypes(y, skipPtrs)
-    if ancestors.contains(y.id): return y
+    if ancestors.contains(y.id):
+      # bug #7818, defer the previous skipTypes
+      if t.kind != tyGenericInst: t = y
+      return t
     y = y.sons[0]
 
 type
-  TTypeAllowedFlag = enum
+  TTypeAllowedFlag* = enum
     taField,
-    taHeap
+    taHeap,
+    taConcept
 
-  TTypeAllowedFlags = set[TTypeAllowedFlag]
+  TTypeAllowedFlags* = set[TTypeAllowedFlag]
 
 proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
                     flags: TTypeAllowedFlags = {}): PType
@@ -1049,13 +1143,12 @@ proc typeAllowedNode(marker: var IntSet, n: PNode, kind: TSymKind,
                      flags: TTypeAllowedFlags = {}): PType =
   if n != nil:
     result = typeAllowedAux(marker, n.typ, kind, flags)
-    #if not result: debug(n.typ)
     if result == nil:
       case n.kind
       of nkNone..nkNilLit:
         discard
       else:
-        if n.kind == nkRecCase and kind in {skProc, skConst}:
+        if n.kind == nkRecCase and kind in {skProc, skFunc, skConst}:
           return n[0].typ
         for i in countup(0, sonsLen(n) - 1):
           let it = n.sons[i]
@@ -1073,7 +1166,7 @@ proc matchType*(a: PType, pattern: openArray[tuple[k:TTypeKind, i:int]],
 
 proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
                     flags: TTypeAllowedFlags = {}): PType =
-  assert(kind in {skVar, skLet, skConst, skProc, skParam, skResult})
+  assert(kind in {skVar, skLet, skConst, skProc, skFunc, skParam, skResult})
   # if we have already checked the type, return true, because we stop the
   # evaluation if something is wrong:
   result = nil
@@ -1081,11 +1174,12 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
   if containsOrIncl(marker, typ.id): return
   var t = skipTypes(typ, abstractInst-{tyTypeDesc})
   case t.kind
-  of tyVar:
-    if kind in {skProc, skConst}: return t
+  of tyVar, tyLent:
+    if kind in {skProc, skFunc, skConst}: return t
+    elif t.kind == tyLent and kind != skResult: return t
     var t2 = skipTypes(t.sons[0], abstractInst-{tyTypeDesc})
     case t2.kind
-    of tyVar:
+    of tyVar, tyLent:
       if taHeap notin flags: result = t2 # ``var var`` is illegal on the heap
     of tyOpenArray:
       if kind != skParam: result = t
@@ -1108,30 +1202,39 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
   of tyVoid:
     if taField notin flags: result = t
   of tyTypeClasses:
-    if not (tfGenericTypeParam in t.flags or taField notin flags): result = t
+    if tfGenericTypeParam in t.flags or taConcept in flags: #or taField notin flags:
+      discard
+    elif t.isResolvedUserTypeClass:
+      result = typeAllowedAux(marker, t.lastSon, kind, flags)
+    elif kind notin {skParam, skResult}:
+      result = t
   of tyGenericBody, tyGenericParam, tyGenericInvocation,
-     tyNone, tyForward, tyFromExpr, tyFieldAccessor:
+     tyNone, tyForward, tyFromExpr:
     result = t
   of tyNil:
-    if kind != skConst: result = t
+    if kind != skConst and kind != skParam: result = t
   of tyString, tyBool, tyChar, tyEnum, tyInt..tyUInt64, tyCString, tyPointer:
     result = nil
   of tyOrdinal:
     if kind != skParam: result = t
-  of tyGenericInst, tyDistinct, tyAlias:
+  of tyGenericInst, tyDistinct, tyAlias, tyInferred, tyUncheckedArray:
     result = typeAllowedAux(marker, lastSon(t), kind, flags)
   of tyRange:
     if skipTypes(t.sons[0], abstractInst-{tyTypeDesc}).kind notin
         {tyChar, tyEnum, tyInt..tyFloat128, tyUInt8..tyUInt32}: result = t
-  of tyOpenArray, tyVarargs:
+  of tyOpenArray, tyVarargs, tySink:
     if kind != skParam: result = t
     else: result = typeAllowedAux(marker, t.sons[0], skVar, flags)
-  of tySequence:
+  of tySequence, tyOpt:
     if t.sons[0].kind != tyEmpty:
       result = typeAllowedAux(marker, t.sons[0], skVar, flags+{taHeap})
+    elif kind in {skVar, skLet}:
+      result = t.sons[0]
   of tyArray:
     if t.sons[1].kind != tyEmpty:
       result = typeAllowedAux(marker, t.sons[1], skVar, flags)
+    elif kind in {skVar, skLet}:
+      result = t.sons[1]
   of tyRef:
     if kind == skConst: result = t
     else: result = typeAllowedAux(marker, t.lastSon, skVar, flags+{taHeap})
@@ -1142,7 +1245,7 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
       result = typeAllowedAux(marker, t.sons[i], kind, flags)
       if result != nil: break
   of tyObject, tyTuple:
-    if kind in {skProc, skConst} and
+    if kind in {skProc, skFunc, skConst} and
         t.kind == tyObject and t.sons[0] != nil: return t
     let flags = flags+{taField}
     for i in countup(0, sonsLen(t) - 1):
@@ -1150,201 +1253,51 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind,
       if result != nil: break
     if result.isNil and t.n != nil:
       result = typeAllowedNode(marker, t.n, kind, flags)
-  of tyProxy, tyEmpty:
+  of tyEmpty:
+    if kind in {skVar, skLet}: result = t
+  of tyProxy:
     # for now same as error node; we say it's a valid type as it should
     # prevent cascading errors:
     result = nil
-  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("typeAllowedAux")
+  of tyOptAsRef: result = t
 
-proc typeAllowed*(t: PType, kind: TSymKind): PType =
+proc typeAllowed*(t: PType, kind: TSymKind; flags: TTypeAllowedFlags = {}): PType =
   # returns 'nil' on success and otherwise the part of the type that is
   # wrong!
   var marker = initIntSet()
-  result = typeAllowedAux(marker, t, kind, {})
-
-proc align(address, alignment: BiggestInt): BiggestInt =
-  result = (address + (alignment - 1)) and not (alignment - 1)
-
-const
-  szNonConcreteType* = -3
-  szIllegalRecursion* = -2
-  szUnknownSize* = -1
+  result = typeAllowedAux(marker, t, kind, flags)
 
-proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt
-proc computeRecSizeAux(n: PNode, a, currOffset: var BiggestInt): BiggestInt =
-  var maxAlign, maxSize, b, res: BiggestInt
-  case n.kind
-  of nkRecCase:
-    assert(n.sons[0].kind == nkSym)
-    result = computeRecSizeAux(n.sons[0], a, currOffset)
-    maxSize = 0
-    maxAlign = 1
-    for i in countup(1, sonsLen(n) - 1):
-      case n.sons[i].kind
-      of nkOfBranch, nkElse:
-        res = computeRecSizeAux(lastSon(n.sons[i]), b, currOffset)
-        if res < 0: return res
-        maxSize = max(maxSize, res)
-        maxAlign = max(maxAlign, b)
-      else: internalError("computeRecSizeAux(record case branch)")
-    currOffset = align(currOffset, maxAlign) + maxSize
-    result = align(result, maxAlign) + maxSize
-    a = maxAlign
-  of nkRecList:
-    result = 0
-    maxAlign = 1
-    for i in countup(0, sonsLen(n) - 1):
-      res = computeRecSizeAux(n.sons[i], b, currOffset)
-      if res < 0: return res
-      currOffset = align(currOffset, b) + res
-      result = align(result, b) + res
-      if b > maxAlign: maxAlign = b
-    a = maxAlign
-  of nkSym:
-    result = computeSizeAux(n.sym.typ, a)
-    n.sym.offset = int(currOffset)
-  else:
-    a = 1
-    result = szNonConcreteType
-
-proc computeSizeAux(typ: PType, a: var BiggestInt): BiggestInt =
-  var res, maxAlign, length, currOffset: BiggestInt
-  if typ.size == szIllegalRecursion:
-    # we are already computing the size of the type
-    # --> illegal recursion in type
-    return szIllegalRecursion
-  if typ.size >= 0:
-    # size already computed
-    result = typ.size
-    a = typ.align
-    return
-  typ.size = szIllegalRecursion # mark as being computed
-  case typ.kind
-  of tyInt, tyUInt:
-    result = intSize
-    a = result
-  of tyInt8, tyUInt8, tyBool, tyChar:
-    result = 1
-    a = result
-  of tyInt16, tyUInt16:
-    result = 2
-    a = result
-  of tyInt32, tyUInt32, tyFloat32:
-    result = 4
-    a = result
-  of tyInt64, tyUInt64, tyFloat64:
-    result = 8
-    a = result
-  of tyFloat128:
-    result = 16
-    a = result
-  of tyFloat:
-    result = floatSize
-    a = result
-  of tyProc:
-    if typ.callConv == ccClosure: result = 2 * ptrSize
-    else: result = ptrSize
-    a = ptrSize
-  of tyNil, tyCString, tyString, tySequence, tyPtr, tyRef, tyVar, tyOpenArray:
-    let base = typ.lastSon
-    if base == typ or (base.kind == tyTuple and base.size==szIllegalRecursion):
-      result = szIllegalRecursion
-    else: result = ptrSize
-    a = result
-  of tyArray:
-    let elemSize = computeSizeAux(typ.sons[1], a)
-    if elemSize < 0: return elemSize
-    result = lengthOrd(typ.sons[0]) * elemSize
-  of tyEnum:
-    if firstOrd(typ) < 0:
-      result = 4              # use signed int32
-    else:
-      length = lastOrd(typ)   # BUGFIX: use lastOrd!
-      if length + 1 < `shl`(1, 8): result = 1
-      elif length + 1 < `shl`(1, 16): result = 2
-      elif length + 1 < `shl`(BiggestInt(1), 32): result = 4
-      else: result = 8
-    a = result
-  of tySet:
-    if typ.sons[0].kind == tyGenericParam:
-      result = szUnknownSize
-    else:
-      length = lengthOrd(typ.sons[0])
-      if length <= 8: result = 1
-      elif length <= 16: result = 2
-      elif length <= 32: result = 4
-      elif length <= 64: result = 8
-      elif align(length, 8) mod 8 == 0: result = align(length, 8) div 8
-      else: result = align(length, 8) div 8 + 1
-    a = result
-  of tyRange:
-    result = computeSizeAux(typ.sons[0], a)
-  of tyTuple:
-    result = 0
-    maxAlign = 1
-    for i in countup(0, sonsLen(typ) - 1):
-      res = computeSizeAux(typ.sons[i], a)
-      if res < 0: return res
-      maxAlign = max(maxAlign, a)
-      result = align(result, a) + res
-    result = align(result, maxAlign)
-    a = maxAlign
-  of tyObject:
-    if typ.sons[0] != nil:
-      result = computeSizeAux(typ.sons[0].skipTypes(skipPtrs), a)
-      if result < 0: return
-      maxAlign = a
-    elif isObjectWithTypeFieldPredicate(typ):
-      result = intSize
-      maxAlign = result
-    else:
-      result = 0
-      maxAlign = 1
-    currOffset = result
-    result = computeRecSizeAux(typ.n, a, currOffset)
-    if result < 0: return
-    if a < maxAlign: a = maxAlign
-    result = align(result, a)
-  of tyGenericInst, tyDistinct, tyGenericBody, tyAlias:
-    result = computeSizeAux(lastSon(typ), a)
-  of tyTypeDesc:
-    result = computeSizeAux(typ.base, a)
-  of tyForward: return szIllegalRecursion
-  of tyStatic:
-    if typ.n != nil: result = computeSizeAux(lastSon(typ), a)
-    else: result = szUnknownSize
-  else:
-    #internalError("computeSizeAux()")
-    result = szUnknownSize
-  typ.size = result
-  typ.align = int16(a)
+include sizealignoffsetimpl
 
-proc computeSize(typ: PType): BiggestInt =
-  var a: BiggestInt = 1
-  result = computeSizeAux(typ, a)
+proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt =
+  computeSizeAlign(conf, typ)
+  result = typ.size
 
 proc getReturnType*(s: PSym): PType =
   # Obtains the return type of a iterator/proc/macro/template
   assert s.kind in skProcKinds
   result = s.typ.sons[0]
 
-proc getSize(typ: PType): BiggestInt =
-  result = computeSize(typ)
-  if result < 0: internalError("getSize: " & $typ.kind)
+proc getAlign*(conf: ConfigRef; typ: PType): BiggestInt =
+  computeSizeAlign(conf, typ)
+  result = typ.align
+
+proc getSize*(conf: ConfigRef; typ: PType): BiggestInt =
+  computeSizeAlign(conf, typ)
+  result = typ.size
 
 proc containsGenericTypeIter(t: PType, closure: RootRef): bool =
-  if t.kind == tyStatic:
+  case t.kind
+  of tyStatic:
     return t.n == nil
-
-  if t.kind == tyTypeDesc:
+  of tyTypeDesc:
     if t.base.kind == tyNone: return true
     if containsGenericTypeIter(t.base, closure): return true
     return false
-
-  if t.kind in GenericTypes + tyTypeClasses + {tyFromExpr}:
+  of GenericTypes + tyTypeClasses + {tyFromExpr}:
     return true
-
-  return false
+  else:
+    return false
 
 proc containsGenericType*(t: PType): bool =
   result = iterOverType(t, containsGenericTypeIter, nil)
@@ -1359,8 +1312,7 @@ proc baseOfDistinct*(t: PType): PType =
     while it.kind in {tyPtr, tyRef}:
       parent = it
       it = it.lastSon
-    if it.kind == tyDistinct:
-      internalAssert parent != nil
+    if it.kind == tyDistinct and parent != nil:
       parent.sons[0] = it.sons[0]
 
 proc safeInheritanceDiff*(a, b: PType): int =
@@ -1392,8 +1344,9 @@ type
 proc compatibleEffects*(formal, actual: PType): EffectsCompat =
   # for proc type compatibility checking:
   assert formal.kind == tyProc and actual.kind == tyProc
-  internalAssert formal.n.sons[0].kind == nkEffectList
-  internalAssert actual.n.sons[0].kind == nkEffectList
+  if formal.n.sons[0].kind != nkEffectList or
+     actual.n.sons[0].kind != nkEffectList:
+    return efTagsUnknown
 
   var spec = formal.n.sons[0]
   if spec.len != 0:
@@ -1426,10 +1379,9 @@ proc isCompileTimeOnly*(t: PType): bool {.inline.} =
 
 proc containsCompileTimeOnly*(t: PType): bool =
   if isCompileTimeOnly(t): return true
-  if t.sons != nil:
-    for i in 0 .. <t.sonsLen:
-      if t.sons[i] != nil and isCompileTimeOnly(t.sons[i]):
-        return true
+  for i in 0 ..< t.sonsLen:
+    if t.sons[i] != nil and isCompileTimeOnly(t.sons[i]):
+      return true
   return false
 
 type
@@ -1482,7 +1434,7 @@ proc isEmptyContainer*(t: PType): bool =
   of tyArray: result = t.sons[1].kind == tyEmpty
   of tySet, tySequence, tyOpenArray, tyVarargs:
     result = t.sons[0].kind == tyEmpty
-  of tyGenericInst, tyAlias: result = isEmptyContainer(t.lastSon)
+  of tyGenericInst, tyAlias, tySink: result = isEmptyContainer(t.lastSon)
   else: result = false
 
 proc takeType*(formal, arg: PType): PType =
@@ -1518,14 +1470,14 @@ proc skipHiddenSubConv*(n: PNode): PNode =
   else:
     result = n
 
-proc typeMismatch*(info: TLineInfo, formal, actual: PType) =
+proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType) =
   if formal.kind != tyError and actual.kind != tyError:
     let named = typeToString(formal)
     let desc = typeToString(formal, preferDesc)
     let x = if named == desc: named else: named & " = " & desc
-    var msg = msgKindToString(errTypeMismatch) &
-              typeToString(actual) & ") " &
-              msgKindToString(errButExpectedX) % [x]
+    var msg = "type mismatch: got <" &
+              typeToString(actual) & "> " &
+              "but expected '" & x & "'"
 
     if formal.kind == tyProc and actual.kind == tyProc:
       case compatibleEffects(formal, actual)
@@ -1540,4 +1492,4 @@ proc typeMismatch*(info: TLineInfo, formal, actual: PType) =
         msg.add "\n.tag effect is 'any tag allowed'"
       of efLockLevelsDiffer:
         msg.add "\nlock levels differ"
-    localError(info, errGenerated, msg)
+    localError(conf, info, msg)
diff --git a/compiler/typesrenderer.nim b/compiler/typesrenderer.nim
index e9c27ac9d..0c4fe01e1 100644
--- a/compiler/typesrenderer.nim
+++ b/compiler/typesrenderer.nim
@@ -17,10 +17,9 @@ proc renderPlainSymbolName*(n: PNode): string =
   ## Use this on documentation name nodes to extract the *raw* symbol name,
   ## without decorations, parameters, or anything. That can be used as the base
   ## for the HTML hyperlinks.
-  result = ""
   case n.kind
   of nkPostfix, nkAccQuoted:
-    result = renderPlainSymbolName(n[<n.len])
+    result = renderPlainSymbolName(n[n.len-1])
   of nkIdent:
     result = n.ident.s
   of nkSym:
@@ -28,8 +27,8 @@ proc renderPlainSymbolName*(n: PNode): string =
   of nkPragmaExpr:
     result = renderPlainSymbolName(n[0])
   else:
-    internalError(n.info, "renderPlainSymbolName() with " & $n.kind)
-  assert(not result.isNil)
+    result = ""
+    #internalError(n.info, "renderPlainSymbolName() with " & $n.kind)
 
 proc renderType(n: PNode): string =
   ## Returns a string with the node type or the empty string.
@@ -52,32 +51,34 @@ proc renderType(n: PNode): string =
     else:
       result = "ptr"
   of nkProcTy:
-    assert len(n) > 1
-    let params = n[0]
-    assert params.kind == nkFormalParams
-    assert len(params) > 0
-    result = "proc("
-    for i in 1 .. <len(params): result.add(renderType(params[i]) & ',')
-    result[<len(result)] = ')'
+    assert len(n) != 1
+    if len(n) > 1:
+      let params = n[0]
+      assert params.kind == nkFormalParams
+      assert len(params) > 0
+      result = "proc("
+      for i in 1 ..< len(params): result.add(renderType(params[i]) & ',')
+      result[len(result)-1] = ')'
+    else:
+      result = "proc"
   of nkIdentDefs:
     assert len(n) >= 3
     let typePos = len(n) - 2
     let typeStr = renderType(n[typePos])
     result = typeStr
-    for i in 1 .. <typePos:
+    for i in 1 ..< typePos:
       assert n[i].kind == nkIdent
       result.add(',' & typeStr)
   of nkTupleTy:
     result = "tuple["
-    for i in 0 .. <len(n): result.add(renderType(n[i]) & ',')
-    result[<len(result)] = ']'
+    for i in 0 ..< len(n): result.add(renderType(n[i]) & ',')
+    result[len(result)-1] = ']'
   of nkBracketExpr:
     assert len(n) >= 2
     result = renderType(n[0]) & '['
-    for i in 1 .. <len(n): result.add(renderType(n[i]) & ',')
-    result[<len(result)] = ']'
+    for i in 1 ..< len(n): result.add(renderType(n[i]) & ',')
+    result[len(result)-1] = ']'
   else: result = ""
-  assert(not result.isNil)
 
 
 proc renderParamTypes(found: var seq[string], n: PNode) =
@@ -88,7 +89,7 @@ proc renderParamTypes(found: var seq[string], n: PNode) =
   ## generator does include the information.
   case n.kind
   of nkFormalParams:
-    for i in 1 .. <len(n): renderParamTypes(found, n[i])
+    for i in 1 ..< len(n): renderParamTypes(found, n[i])
   of nkIdentDefs:
     # These are parameter names + type + default value node.
     let typePos = len(n) - 2
@@ -99,10 +100,11 @@ proc renderParamTypes(found: var seq[string], n: PNode) =
       let typ = n[typePos+1].typ
       if not typ.isNil: typeStr = typeToString(typ, preferExported)
       if typeStr.len < 1: return
-    for i in 0 .. <typePos:
+    for i in 0 ..< typePos:
       found.add(typeStr)
   else:
-    internalError(n.info, "renderParamTypes(found,n) with " & $n.kind)
+    found.add($n)
+    #internalError(n.info, "renderParamTypes(found,n) with " & $n.kind)
 
 proc renderParamTypes*(n: PNode, sep = defaultParamSeparator): string =
   ## Returns the types contained in `n` joined by `sep`.
diff --git a/compiler/vm.nim b/compiler/vm.nim
index 6a9545193..c8784c3e7 100644
--- a/compiler/vm.nim
+++ b/compiler/vm.nim
@@ -19,12 +19,12 @@ import ast except getstr
 import
   strutils, astalgo, msgs, vmdef, vmgen, nimsets, types, passes,
   parser, vmdeps, idents, trees, renderer, options, transf, parseutils,
-  vmmarshal
+  vmmarshal, gorgeimpl, lineinfos, tables, btrees, macrocacheimpl
 
 from semfold import leValueConv, ordinalValToString
 from evaltempl import evalTemplate
 
-from modulegraphs import ModuleGraph
+from modulegraphs import ModuleGraph, PPassContext
 
 when hasFFI:
   import evalffi
@@ -61,12 +61,15 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) =
       while x != nil:
         inc calls
         x = x.next
-      msgWriteln($calls & " calls omitted\n")
+      msgWriteln(c.config, $calls & " calls omitted\n")
       return
     stackTraceAux(c, x.next, x.comesFrom, recursionLimit-1)
     var info = c.debug[pc]
     # we now use the same format as in system/except.nim
-    var s = toFilename(info)
+    var s = substr(toFilename(c.config, info), 0)
+    # this 'substr' prevents a strange corruption. XXX This needs to be
+    # investigated eventually but first attempts to fix it broke everything
+    # see the araq-wip-fixed-writebarrier branch.
     var line = toLinenumber(info)
     if line > 0:
       add(s, '(')
@@ -75,19 +78,21 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) =
     if x.prc != nil:
       for k in 1..max(1, 25-s.len): add(s, ' ')
       add(s, x.prc.name.s)
-    msgWriteln(s)
+    msgWriteln(c.config, s)
 
 proc stackTrace(c: PCtx, tos: PStackFrame, pc: int,
-                msg: TMsgKind, arg = "", n: PNode = nil) =
-  msgWriteln("stack trace: (most recent call last)")
+                msg: string, lineInfo: TLineInfo) =
+  msgWriteln(c.config, "stack trace: (most recent call last)")
   stackTraceAux(c, tos, pc)
   # XXX test if we want 'globalError' for every mode
-  let lineInfo = if n == nil: c.debug[pc] else: n.info
-  if c.mode == emRepl: globalError(lineInfo, msg, arg)
-  else: localError(lineInfo, msg, arg)
+  if c.mode == emRepl: globalError(c.config, lineInfo, msg)
+  else: localError(c.config, lineInfo, msg)
+
+proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string) =
+  stackTrace(c, tos, pc, msg, c.debug[pc])
 
 proc bailOut(c: PCtx; tos: PStackFrame) =
-  stackTrace(c, tos, c.exceptionInstr, errUnhandledExceptionX,
+  stackTrace(c, tos, c.exceptionInstr, "unhandled exception: " &
              c.currentExceptionA.sons[3].skipColon.strVal)
 
 when not defined(nimComputedGoto):
@@ -201,16 +206,14 @@ proc asgnComplex(x: var TFullReg, y: TFullReg) =
   of rkRegisterAddr: x.regAddr = y.regAddr
   of rkNodeAddr: x.nodeAddr = y.nodeAddr
 
-proc putIntoNode(n: var PNode; x: TFullReg) =
+proc writeField(n: var PNode, x: TFullReg) =
   case x.kind
   of rkNone: discard
   of rkInt: n.intVal = x.intVal
   of rkFloat: n.floatVal = x.floatVal
-  of rkNode:
-    if nfIsRef in x.node.flags: n = x.node
-    else: n[] = x.node[]
-  of rkRegisterAddr: putIntoNode(n, x.regAddr[])
-  of rkNodeAddr: n[] = x.nodeAddr[][]
+  of rkNode: n = copyValue(x.node)
+  of rkRegisterAddr: writeField(n, x.regAddr[])
+  of rkNodeAddr: n = x.nodeAddr[]
 
 proc putIntoReg(dest: var TFullReg; n: PNode) =
   case n.kind
@@ -241,7 +244,8 @@ template getstr(a: untyped): untyped =
   (if a.kind == rkNode: a.node.strVal else: $chr(int(a.intVal)))
 
 proc pushSafePoint(f: PStackFrame; pc: int) =
-  if f.safePoints.isNil: f.safePoints = @[]
+  when not defined(nimNoNilSeqs):
+    if f.safePoints.isNil: f.safePoints = @[]
   f.safePoints.add(pc)
 
 proc popSafePoint(f: PStackFrame) =
@@ -254,7 +258,7 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame):
   let raisedType = c.currentExceptionA.typ.skipTypes(abstractPtrs)
   var f = tos
   while true:
-    while f.safePoints.isNil or f.safePoints.len == 0:
+    while f.safePoints.len == 0:
       f = f.next
       if f.isNil: return (-1, nil)
     var pc2 = f.safePoints[f.safePoints.high]
@@ -269,7 +273,7 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame):
                           abstractPtrs)
                        else: nil
       #echo typeToString(exceptType), " ", typeToString(raisedType)
-      if exceptType.isNil or inheritanceDiff(exceptType, raisedType) <= 0:
+      if exceptType.isNil or inheritanceDiff(raisedType, exceptType) <= 0:
         # mark exception as handled but keep it in B for
         # the getCurrentException() builtin:
         c.currentExceptionB = c.currentExceptionA
@@ -296,7 +300,6 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame):
     discard f.safePoints.pop
 
 proc cleanUpOnReturn(c: PCtx; f: PStackFrame): int =
-  if f.safePoints.isNil: return -1
   for s in f.safePoints:
     var pc = s
     while c.code[pc].opcode == opcExcept:
@@ -305,7 +308,7 @@ proc cleanUpOnReturn(c: PCtx; f: PStackFrame): int =
       return pc
   return -1
 
-proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool =
+proc opConv(c: PCtx; dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool =
   if desttyp.kind == tyString:
     if dest.kind != rkNode:
       myreset(dest)
@@ -319,8 +322,8 @@ proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool =
       if x <% n.len and (let f = n.sons[x].sym; f.position == x):
         dest.node.strVal = if f.ast.isNil: f.name.s else: f.ast.strVal
       else:
-        for i in 0.. <n.len:
-          if n.sons[i].kind != nkSym: internalError("opConv for enum")
+        for i in 0..<n.len:
+          if n.sons[i].kind != nkSym: internalError(c.config, "opConv for enum")
           let f = n.sons[i].sym
           if f.position == x:
             dest.node.strVal = if f.ast.isNil: f.name.s else: f.ast.strVal
@@ -350,7 +353,7 @@ proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool =
     of tyChar:
       dest.node.strVal = $chr(src.intVal)
     else:
-      internalError("cannot convert to string " & desttyp.typeToString)
+      internalError(c.config, "cannot convert to string " & desttyp.typeToString)
   else:
     case skipTypes(desttyp, abstractRange).kind
     of tyInt..tyInt64:
@@ -361,7 +364,7 @@ proc opConv*(dest: var TFullReg, src: TFullReg, desttyp, srctyp: PType): bool =
         dest.intVal = int(src.floatVal)
       else:
         dest.intVal = src.intVal
-      if dest.intVal < firstOrd(desttyp) or dest.intVal > lastOrd(desttyp):
+      if dest.intVal < firstOrd(c.config, desttyp) or dest.intVal > lastOrd(c.config, desttyp):
         return true
     of tyUInt..tyUInt64:
       if dest.kind != rkInt:
@@ -399,9 +402,9 @@ template handleJmpBack() {.dirty.} =
     if allowInfiniteLoops in c.features:
       c.loopIterations = MaxLoopIterations
     else:
-      msgWriteln("stack trace: (most recent call last)")
+      msgWriteln(c.config, "stack trace: (most recent call last)")
       stackTraceAux(c, tos, pc)
-      globalError(c.debug[pc], errTooManyIterations)
+      globalError(c.config, c.debug[pc], errTooManyIterations)
   dec(c.loopIterations)
 
 proc recSetFlagIsRef(arg: PNode) =
@@ -409,6 +412,25 @@ proc recSetFlagIsRef(arg: PNode) =
   for i in 0 ..< arg.safeLen:
     arg.sons[i].recSetFlagIsRef
 
+proc setLenSeq(c: PCtx; node: PNode; newLen: int; info: TLineInfo) =
+  let typ = node.typ.skipTypes(abstractInst+{tyRange}-{tyTypeDesc})
+  let oldLen = node.len
+  setLen(node.sons, newLen)
+  if oldLen < newLen:
+    for i in oldLen ..< newLen:
+      node.sons[i] = getNullValue(typ.sons[0], info, c.config)
+
+const
+  errIndexOutOfBounds = "index out of bounds"
+  errNilAccess = "attempt to access a nil address"
+  errOverOrUnderflow = "over- or underflow"
+  errConstantDivisionByZero = "division by zero"
+  errIllegalConvFromXtoY = "illegal conversion from '$1' to '$2'"
+  errTooManyIterations = "interpretation requires too many iterations; " &
+    "if you are sure this is not a bug in your code edit " &
+    "compiler/vmdef.MaxLoopIterations and rebuild the compiler"
+  errFieldXNotFound = "node lacks field: "
+
 proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
   var pc = start
   var tos = tos
@@ -422,7 +444,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     #if c.traceActive:
     when traceCode:
       echo "PC ", pc, " ", c.code[pc].opcode, " ra ", ra, " rb ", instr.regB, " rc ", instr.regC
-    #  message(c.debug[pc], warnUser, "Trace")
+      # message(c.config, c.debug[pc], warnUser, "Trace")
 
     case instr.opcode
     of opcEof: return regs[ra]
@@ -452,13 +474,26 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcAsgnFloat:
       decodeB(rkFloat)
       regs[ra].floatVal = regs[rb].floatVal
+    of opcAsgnIntFromFloat32:
+      let rb = instr.regB
+      ensureKind(rkInt)
+      regs[ra].intVal = cast[int32](float32(regs[rb].floatVal))
+    of opcAsgnIntFromFloat64:
+      let rb = instr.regB
+      ensureKind(rkInt)
+      regs[ra].intVal = cast[int64](regs[rb].floatVal)
+    of opcAsgnFloat32FromInt:
+      let rb = instr.regB
+      ensureKind(rkFloat)
+      regs[ra].floatVal = cast[float32](int32(regs[rb].intVal))
+    of opcAsgnFloat64FromInt:
+      let rb = instr.regB
+      ensureKind(rkFloat)
+      regs[ra].floatVal = cast[float64](int64(regs[rb].intVal))
     of opcAsgnComplex:
       asgnComplex(regs[ra], regs[instr.regB])
     of opcAsgnRef:
       asgnRef(regs[ra], regs[instr.regB])
-    of opcRegToNode:
-      decodeB(rkNode)
-      putIntoNode(regs[ra].node, regs[rb])
     of opcNodeToReg:
       let ra = instr.regA
       let rb = instr.regB
@@ -486,7 +521,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
         stackTrace(c, tos, pc, errIndexOutOfBounds)
       let idx = regs[rc].intVal.int
       let src = regs[rb].node
-      if src.kind notin {nkEmpty..nkNilLit} and idx <% src.len:
+      if src.kind in {nkStrLit..nkTripleStrLit}:
+        if idx <% src.strVal.len:
+          regs[ra].node = newNodeI(nkCharLit, c.debug[pc])
+          regs[ra].node.intVal = src.strVal[idx].ord
+        else:
+          stackTrace(c, tos, pc, errIndexOutOfBounds)
+      elif src.kind notin {nkEmpty..nkFloat128Lit} and idx <% src.len:
         regs[ra].node = src.sons[idx]
       else:
         stackTrace(c, tos, pc, errIndexOutOfBounds)
@@ -494,29 +535,39 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       decodeBC(rkInt)
       let idx = regs[rc].intVal.int
       let s = regs[rb].node.strVal
-      if s.isNil:
-        stackTrace(c, tos, pc, errNilAccess)
-      elif idx <=% s.len:
+      if idx <% s.len:
         regs[ra].intVal = s[idx].ord
+      elif idx == s.len and optLaxStrings in c.config.options:
+        regs[ra].intVal = 0
       else:
         stackTrace(c, tos, pc, errIndexOutOfBounds)
     of opcWrArr:
       # a[b] = c
       decodeBC(rkNode)
       let idx = regs[rb].intVal.int
-      if idx <% regs[ra].node.len:
-        putIntoNode(regs[ra].node.sons[idx], regs[rc])
+      let arr = regs[ra].node
+      if arr.kind in {nkStrLit..nkTripleStrLit}:
+        if idx <% arr.strVal.len:
+          arr.strVal[idx] = chr(regs[rc].intVal)
+        else:
+          stackTrace(c, tos, pc, errIndexOutOfBounds)
+      elif idx <% arr.len:
+        writeField(arr.sons[idx], regs[rc])
       else:
         stackTrace(c, tos, pc, errIndexOutOfBounds)
     of opcLdObj:
       # a = b.c
       decodeBC(rkNode)
       let src = regs[rb].node
-      if src.kind notin {nkEmpty..nkNilLit}:
-        let n = src.sons[rc + ord(src.kind == nkObjConstr)].skipColon
+      case src.kind
+      of nkEmpty..nkNilLit:
+        stackTrace(c, tos, pc, errNilAccess)
+      of nkObjConstr:
+        let n = src.sons[rc + 1].skipColon
         regs[ra].node = n
       else:
-        stackTrace(c, tos, pc, errNilAccess)
+        let n = src.sons[rc]
+        regs[ra].node = n
     of opcWrObj:
       # a.b = c
       decodeBC(rkNode)
@@ -525,9 +576,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       if dest.kind == nkNilLit:
         stackTrace(c, tos, pc, errNilAccess)
       elif dest.sons[shiftedRb].kind == nkExprColonExpr:
-        putIntoNode(dest.sons[shiftedRb].sons[1], regs[rc])
+        writeField(dest.sons[shiftedRb].sons[1], regs[rc])
       else:
-        putIntoNode(dest.sons[shiftedRb], regs[rc])
+        writeField(dest.sons[shiftedRb], regs[rc])
     of opcWrStrIdx:
       decodeBC(rkNode)
       let idx = regs[rb].intVal.int
@@ -543,7 +594,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       if regs[rb].kind == rkNode:
         regs[ra].nodeAddr = addr(regs[rb].node)
       else:
-        stackTrace(c, tos, pc, errGenerated, "limited VM support for 'addr'")
+        stackTrace(c, tos, pc, "limited VM support for 'addr'")
     of opcLdDeref:
       # a = b[]
       let ra = instr.regA
@@ -570,9 +621,24 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       let ra = instr.regA
       let rc = instr.regC
       case regs[ra].kind
-      of rkNodeAddr: putIntoNode(regs[ra].nodeAddr[], regs[rc])
+      of rkNodeAddr:
+        let n = regs[rc].regToNode
+        # `var object` parameters are sent as rkNodeAddr. When they are mutated
+        # vmgen generates opcWrDeref, which means that we must dereference
+        # twice.
+        # TODO: This should likely be handled differently in vmgen.
+        if (nfIsRef notin regs[ra].nodeAddr[].flags and
+            nfIsRef notin n.flags):
+          regs[ra].nodeAddr[][] = n[]
+        else:
+          regs[ra].nodeAddr[] = n
       of rkRegisterAddr: regs[ra].regAddr[] = regs[rc]
-      of rkNode: putIntoNode(regs[ra].node, regs[rc])
+      of rkNode:
+        if regs[ra].node.kind == nkNilLit:
+          stackTrace(c, tos, pc, errNilAccess)
+        assert nfIsRef in regs[ra].node.flags
+        regs[ra].node[] = regs[rc].regToNode[]
+        regs[ra].node.flags.incl nfIsRef
       else: stackTrace(c, tos, pc, errNilAccess)
     of opcAddInt:
       decodeBC(rkInt)
@@ -586,7 +652,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
         stackTrace(c, tos, pc, errOverOrUnderflow)
     of opcAddImmInt:
       decodeBImm(rkInt)
-      #message(c.debug[pc], warnUser, "came here")
+      #message(c.config, c.debug[pc], warnUser, "came here")
       #debug regs[rb].node
       let
         bVal = regs[rb].intVal
@@ -619,8 +685,14 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcLenSeq:
       decodeBImm(rkInt)
       #assert regs[rb].kind == nkBracket
-      # also used by mNLen:
-      regs[ra].intVal = regs[rb].node.safeLen - imm
+      let high = (imm and 1) # discard flags
+      if (imm and nimNodeFlag) != 0:
+        # used by mNLen (NimNode.len)
+        regs[ra].intVal = regs[rb].node.safeLen - high
+      else:
+        # safeArrLen also return string node len
+        # used when string is passed as openArray in VM
+        regs[ra].intVal = regs[rb].node.safeArrLen - high
     of opcLenStr:
       decodeBImm(rkInt)
       assert regs[rb].kind == rkNode
@@ -640,12 +712,12 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       decodeB(rkNode)
       var b = newNodeIT(nkCurly, regs[ra].node.info, regs[ra].node.typ)
       addSon(b, regs[rb].regToNode)
-      var r = diffSets(regs[ra].node, b)
+      var r = diffSets(c.config, regs[ra].node, b)
       discardSons(regs[ra].node)
       for i in countup(0, sonsLen(r) - 1): addSon(regs[ra].node, r.sons[i])
     of opcCard:
       decodeB(rkInt)
-      regs[ra].intVal = nimsets.cardSet(regs[rb].node)
+      regs[ra].intVal = nimsets.cardSet(c.config, regs[rb].node)
     of opcMulInt:
       decodeBC(rkInt)
       let
@@ -686,6 +758,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcShlInt:
       decodeBC(rkInt)
       regs[ra].intVal = regs[rb].intVal shl regs[rc].intVal
+    of opcAshrInt:
+      decodeBC(rkInt)
+      regs[ra].intVal = ashr(regs[rb].intVal, regs[rc].intVal)
     of opcBitandInt:
       decodeBC(rkInt)
       regs[ra].intVal = regs[rb].intVal and regs[rc].intVal
@@ -736,10 +811,22 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       regs[ra].intVal = ord(regs[rb].intVal <% regs[rc].intVal)
     of opcEqRef:
       decodeBC(rkInt)
-      regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and
-                             regs[rc].node.kind == nkNilLit) or
-                             regs[rb].node == regs[rc].node)
-    of opcEqNimrodNode:
+      if regs[rb].kind == rkNodeAddr:
+        if regs[rc].kind == rkNodeAddr:
+          regs[ra].intVal = ord(regs[rb].nodeAddr == regs[rc].nodeAddr)
+        else:
+          assert regs[rc].kind == rkNode
+          # we know these cannot be equal
+          regs[ra].intVal = ord(false)
+      elif regs[rc].kind == rkNodeAddr:
+        assert regs[rb].kind == rkNode
+        # we know these cannot be equal
+        regs[ra].intVal = ord(false)
+      else:
+        regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and
+                              regs[rc].node.kind == nkNilLit) or
+                              regs[rb].node == regs[rc].node)
+    of opcEqNimNode:
       decodeBC(rkInt)
       regs[ra].intVal =
         ord(exprStructuralEquivalent(regs[rb].node, regs[rc].node,
@@ -781,35 +868,35 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       regs[ra].intVal = ord(regs[rb].node.strVal < regs[rc].node.strVal)
     of opcLeSet:
       decodeBC(rkInt)
-      regs[ra].intVal = ord(containsSets(regs[rb].node, regs[rc].node))
+      regs[ra].intVal = ord(containsSets(c.config, regs[rb].node, regs[rc].node))
     of opcEqSet:
       decodeBC(rkInt)
-      regs[ra].intVal = ord(equalSets(regs[rb].node, regs[rc].node))
+      regs[ra].intVal = ord(equalSets(c.config, regs[rb].node, regs[rc].node))
     of opcLtSet:
       decodeBC(rkInt)
       let a = regs[rb].node
       let b = regs[rc].node
-      regs[ra].intVal = ord(containsSets(a, b) and not equalSets(a, b))
+      regs[ra].intVal = ord(containsSets(c.config, a, b) and not equalSets(c.config, a, b))
     of opcMulSet:
       decodeBC(rkNode)
       createSet(regs[ra])
       move(regs[ra].node.sons,
-            nimsets.intersectSets(regs[rb].node, regs[rc].node).sons)
+            nimsets.intersectSets(c.config, regs[rb].node, regs[rc].node).sons)
     of opcPlusSet:
       decodeBC(rkNode)
       createSet(regs[ra])
       move(regs[ra].node.sons,
-           nimsets.unionSets(regs[rb].node, regs[rc].node).sons)
+           nimsets.unionSets(c.config, regs[rb].node, regs[rc].node).sons)
     of opcMinusSet:
       decodeBC(rkNode)
       createSet(regs[ra])
       move(regs[ra].node.sons,
-           nimsets.diffSets(regs[rb].node, regs[rc].node).sons)
+           nimsets.diffSets(c.config, regs[rb].node, regs[rc].node).sons)
     of opcSymdiffSet:
       decodeBC(rkNode)
       createSet(regs[ra])
       move(regs[ra].node.sons,
-           nimsets.symdiffSets(regs[rb].node, regs[rc].node).sons)
+           nimsets.symdiffSets(c.config, regs[rb].node, regs[rc].node).sons)
     of opcConcatStr:
       decodeBC(rkNode)
       createStr regs[ra]
@@ -836,18 +923,50 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       if a.kind == nkSym:
         regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit)
                         else: copyTree(a.sym.ast)
+        regs[ra].node.flags.incl nfIsRef
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "symbol")
+        stackTrace(c, tos, pc, "node is not a symbol")
+    of opcGetImplTransf:
+      decodeB(rkNode)
+      let a = regs[rb].node
+      if a.kind == nkSym:
+        regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit)
+                        else:
+                          let ast = a.sym.ast.shallowCopy
+                          for i in 0..<a.sym.ast.len:
+                            ast[i] = a.sym.ast[i]
+                          ast[bodyPos] = transformBody(c.graph, a.sym)
+                          ast.copyTree()
+    of opcSymOwner:
+      decodeB(rkNode)
+      let a = regs[rb].node
+      if a.kind == nkSym:
+        regs[ra].node = if a.sym.owner.isNil: newNode(nkNilLit)
+                        else: newSymNode(a.sym.skipGenericOwner)
+        regs[ra].node.flags.incl nfIsRef
+      else:
+        stackTrace(c, tos, pc, "node is not a symbol")
+    of opcSymIsInstantiationOf:
+      decodeBC(rkInt)
+      let a = regs[rb].node
+      let b = regs[rc].node
+      if a.kind == nkSym and a.sym.kind in skProcKinds and 
+         b.kind == nkSym and b.sym.kind in skProcKinds:
+        regs[ra].intVal =
+          if sfFromGeneric in a.sym.flags and a.sym.owner == b.sym: 1
+          else: 0
+      else:    
+        stackTrace(c, tos, pc, "node is not a proc symbol") 
     of opcEcho:
       let rb = instr.regB
       if rb == 1:
-        msgWriteln(regs[ra].node.strVal, {msgStdout})
+        msgWriteln(c.config, regs[ra].node.strVal, {msgStdout})
       else:
         var outp = ""
         for i in ra..ra+rb-1:
           #if regs[i].kind != rkNode: debug regs[i]
           outp.add(regs[i].node.strVal)
-        msgWriteln(outp, {msgStdout})
+        msgWriteln(c.config, outp, {msgStdout})
     of opcContainsSet:
       decodeBC(rkInt)
       regs[ra].intVal = ord(inSet(regs[rb].node, regs[rc].regToNode))
@@ -876,15 +995,15 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       let rc = instr.regC
       if not (leValueConv(regs[rb].regToNode, regs[ra].regToNode) and
               leValueConv(regs[ra].regToNode, regs[rc].regToNode)):
-        stackTrace(c, tos, pc, errGenerated,
-          msgKindToString(errIllegalConvFromXtoY) % [
-          $regs[ra].regToNode, "[" & $regs[rb].regToNode & ".." & $regs[rc].regToNode & "]"])
+        stackTrace(c, tos, pc,
+          errIllegalConvFromXtoY % [
+             $regs[ra].regToNode, "[" & $regs[rb].regToNode & ".." & $regs[rc].regToNode & "]"])
     of opcIndCall, opcIndCallAsgn:
       # dest = call regStart, n; where regStart = fn, arg1, ...
       let rb = instr.regB
       let rc = instr.regC
       let bb = regs[rb].node
-      let isClosure = bb.kind == nkPar
+      let isClosure = bb.kind == nkTupleConstr
       let prc = if not isClosure: bb.sym else: bb.sons[0].sym
       if prc.offset < -1:
         # it's a callback:
@@ -894,20 +1013,20 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
                  currentLineInfo: c.debug[pc]))
       elif sfImportc in prc.flags:
         if allowFFI notin c.features:
-          globalError(c.debug[pc], errGenerated, "VM not allowed to do FFI")
+          globalError(c.config, c.debug[pc], "VM not allowed to do FFI")
         # we pass 'tos.slots' instead of 'regs' so that the compiler can keep
         # 'regs' in a register:
         when hasFFI:
           let prcValue = c.globals.sons[prc.position-1]
           if prcValue.kind == nkEmpty:
-            globalError(c.debug[pc], errGenerated, "canot run " & prc.name.s)
+            globalError(c.config, c.debug[pc], "cannot run " & prc.name.s)
           let newValue = callForeignFunction(prcValue, prc.typ, tos.slots,
                                              rb+1, rc-1, c.debug[pc])
           if newValue.kind != nkEmpty:
             assert instr.opcode == opcIndCallAsgn
             putIntoReg(regs[ra], newValue)
         else:
-          globalError(c.debug[pc], errGenerated, "VM not built with FFI support")
+          globalError(c.config, c.debug[pc], "VM not built with FFI support")
       elif prc.kind != skTemplate:
         let newPc = compile(c, prc)
         # tricky: a recursion is also a jump back, so we use the same
@@ -917,7 +1036,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
         var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos)
         newSeq(newFrame.slots, prc.offset+ord(isClosure))
         if not isEmptyType(prc.typ.sons[0]) or prc.kind == skMacro:
-          putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info))
+          putIntoReg(newFrame.slots[0], getNullValue(prc.typ.sons[0], prc.info, c.config))
         for i in 1 .. rc-1:
           newFrame.slots[i] = regs[rb+i]
         if isClosure:
@@ -939,7 +1058,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
           let node = regs[rb+i].regToNode
           node.info = c.debug[pc]
           macroCall.add(node)
-        let a = evalTemplate(macroCall, prc, genSymOwner)
+        var a = evalTemplate(macroCall, prc, genSymOwner, c.config)
+        if a.kind == nkStmtList and a.len == 1: a = a[0]
         a.recSetFlagIsRef
         ensureKind(rkNode)
         regs[ra].node = a
@@ -1023,7 +1143,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcNew:
       ensureKind(rkNode)
       let typ = c.types[instr.regBx - wordExcess]
-      regs[ra].node = getNullValue(typ, c.debug[pc])
+      regs[ra].node = getNullValue(typ, c.debug[pc], c.config)
       regs[ra].node.flags.incl nfIsRef
     of opcNewSeq:
       let typ = c.types[instr.regBx - wordExcess]
@@ -1034,8 +1154,8 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       regs[ra].node = newNodeI(nkBracket, c.debug[pc])
       regs[ra].node.typ = typ
       newSeq(regs[ra].node.sons, count)
-      for i in 0 .. <count:
-        regs[ra].node.sons[i] = getNullValue(typ.sons[0], c.debug[pc])
+      for i in 0 ..< count:
+        regs[ra].node.sons[i] = getNullValue(typ.sons[0], c.debug[pc], c.config)
     of opcNewStr:
       decodeB(rkNode)
       regs[ra].node = newNodeI(nkStrLit, c.debug[pc])
@@ -1047,7 +1167,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcLdNull:
       ensureKind(rkNode)
       let typ = c.types[instr.regBx - wordExcess]
-      regs[ra].node = getNullValue(typ, c.debug[pc])
+      regs[ra].node = getNullValue(typ, c.debug[pc], c.config)
       # opcLdNull really is the gist of the VM's problems: should it load
       # a fresh null to  regs[ra].node  or to regs[ra].node[]? This really
       # depends on whether regs[ra] represents the variable itself or wether
@@ -1094,7 +1214,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       regs[ra].node.strVal = renderTree(regs[rb].regToNode, {renderNoComments, renderDocComments})
     of opcQuit:
       if c.mode in {emRepl, emStaticExpr, emStaticStmt}:
-        message(c.debug[pc], hintQuitCalled)
+        message(c.config, c.debug[pc], hintQuitCalled)
         msgQuit(int8(getOrdValue(regs[ra].regToNode)))
       else:
         return TFullReg(kind: rkNone)
@@ -1105,7 +1225,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcOf:
       decodeBC(rkInt)
       let typ = c.types[regs[rc].intVal.int]
-      regs[ra].intVal = ord(inheritanceDiff(regs[rb].node.typ, typ) >= 0)
+      regs[ra].intVal = ord(inheritanceDiff(regs[rb].node.typ, typ) <= 0)
     of opcIs:
       decodeBC(rkInt)
       let t1 = regs[rb].node.typ.skipTypes({tyTypeDesc})
@@ -1118,34 +1238,48 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       decodeB(rkNode)
       let newLen = regs[rb].intVal.int
       if regs[ra].node.isNil: stackTrace(c, tos, pc, errNilAccess)
-      else:
-        let oldLen = regs[ra].node.len
-        setLen(regs[ra].node.sons, newLen)
-        if oldLen < newLen:
-          # XXX This is still not entirely correct
-          # set to default value:
-          for i in oldLen .. <newLen:
-            regs[ra].node.sons[i] = newNodeI(nkEmpty, c.debug[pc])
+      else: c.setLenSeq(regs[ra].node, newLen, c.debug[pc])
     of opcReset:
-      internalError(c.debug[pc], "too implement")
+      internalError(c.config, c.debug[pc], "too implement")
     of opcNarrowS:
       decodeB(rkInt)
       let min = -(1.BiggestInt shl (rb-1))
       let max = (1.BiggestInt shl (rb-1))-1
       if regs[ra].intVal < min or regs[ra].intVal > max:
-        stackTrace(c, tos, pc, errGenerated,
-          msgKindToString(errUnhandledExceptionX) % "value out of range")
+        stackTrace(c, tos, pc, "unhandled exception: value out of range")
     of opcNarrowU:
       decodeB(rkInt)
       regs[ra].intVal = regs[ra].intVal and ((1'i64 shl rb)-1)
     of opcIsNil:
       decodeB(rkInt)
       let node = regs[rb].node
-      regs[ra].intVal = ord(node.kind == nkNilLit or
-        (node.kind in {nkStrLit..nkTripleStrLit} and node.strVal.isNil))
+      regs[ra].intVal = ord(
+        # Note that `nfIsRef` + `nkNilLit` represents an allocated
+        # reference with the value `nil`, so `isNil` should be false!
+        (node.kind == nkNilLit and nfIsRef notin node.flags) or
+        (not node.typ.isNil and node.typ.kind == tyProc and
+          node.typ.callConv == ccClosure and node.sons[0].kind == nkNilLit and
+          node.sons[1].kind == nkNilLit))
     of opcNBindSym:
+      # cannot use this simple check
+      # if dynamicBindSym notin c.config.features:
+
+      # bindSym with static input
       decodeBx(rkNode)
       regs[ra].node = copyTree(c.constants.sons[rbx])
+      regs[ra].node.flags.incl nfIsRef
+    of opcNDynBindSym:
+      # experimental bindSym
+      let
+        rb = instr.regB
+        rc = instr.regC
+        idx = int(regs[rb+rc-1].intVal)
+        callback = c.callbacks[idx].value
+        args = VmArgs(ra: ra, rb: rb, rc: rc, slots: cast[pointer](regs),
+                currentException: c.currentExceptionB,
+                currentLineInfo: c.debug[pc])
+      callback(args)
+      regs[ra].node.flags.incl nfIsRef
     of opcNChild:
       decodeBC(rkNode)
       let idx = regs[rc].intVal.int
@@ -1168,7 +1302,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       if u.kind notin {nkEmpty..nkNilLit}:
         u.add(regs[rc].node)
       else:
-        stackTrace(c, tos, pc, errGenerated, "cannot add to node kind: " & $u.kind)
+        stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind)
       regs[ra].node = u
     of opcNAddMultiple:
       decodeBC(rkNode)
@@ -1176,40 +1310,48 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       var u = regs[rb].node
       if u.kind notin {nkEmpty..nkNilLit}:
         # XXX can be optimized:
-        for i in 0.. <x.len: u.add(x.sons[i])
+        for i in 0..<x.len: u.add(x.sons[i])
       else:
-        stackTrace(c, tos, pc, errGenerated, "cannot add to node kind: " & $u.kind)
+        stackTrace(c, tos, pc, "cannot add to node kind: " & $u.kind)
       regs[ra].node = u
     of opcNKind:
       decodeB(rkInt)
       regs[ra].intVal = ord(regs[rb].node.kind)
       c.comesFromHeuristic = regs[rb].node.info
+    of opcNSymKind:
+      decodeB(rkInt)
+      let a = regs[rb].node
+      if a.kind == nkSym:
+        regs[ra].intVal = ord(a.sym.kind)
+      else:
+        stackTrace(c, tos, pc, "node is not a symbol")
+      c.comesFromHeuristic = regs[rb].node.info
     of opcNIntVal:
       decodeB(rkInt)
       let a = regs[rb].node
       case a.kind
       of nkCharLit..nkUInt64Lit: regs[ra].intVal = a.intVal
-      else: stackTrace(c, tos, pc, errFieldXNotFound, "intVal")
+      else: stackTrace(c, tos, pc, errFieldXNotFound & "intVal")
     of opcNFloatVal:
       decodeB(rkFloat)
       let a = regs[rb].node
       case a.kind
       of nkFloatLit..nkFloat64Lit: regs[ra].floatVal = a.floatVal
-      else: stackTrace(c, tos, pc, errFieldXNotFound, "floatVal")
+      else: stackTrace(c, tos, pc, errFieldXNotFound & "floatVal")
     of opcNSymbol:
       decodeB(rkNode)
       let a = regs[rb].node
       if a.kind == nkSym:
         regs[ra].node = copyNode(a)
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "symbol")
+        stackTrace(c, tos, pc, errFieldXNotFound & "symbol")
     of opcNIdent:
       decodeB(rkNode)
       let a = regs[rb].node
       if a.kind == nkIdent:
         regs[ra].node = copyNode(a)
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "ident")
+        stackTrace(c, tos, pc, errFieldXNotFound & "ident")
     of opcNGetType:
       let rb = instr.regB
       let rc = instr.regC
@@ -1218,84 +1360,106 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
         # getType opcode:
         ensureKind(rkNode)
         if regs[rb].kind == rkNode and regs[rb].node.typ != nil:
-          regs[ra].node = opMapTypeToAst(regs[rb].node.typ, c.debug[pc])
+          regs[ra].node = opMapTypeToAst(c.cache, regs[rb].node.typ, c.debug[pc])
+        elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil:
+          regs[ra].node = opMapTypeToAst(c.cache, regs[rb].node.sym.typ, c.debug[pc])
         else:
-          stackTrace(c, tos, pc, errGenerated, "node has no type")
+          stackTrace(c, tos, pc, "node has no type")
       of 1:
         # typeKind opcode:
         ensureKind(rkInt)
         if regs[rb].kind == rkNode and regs[rb].node.typ != nil:
           regs[ra].intVal = ord(regs[rb].node.typ.kind)
+        elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil:
+          regs[ra].intVal = ord(regs[rb].node.sym.typ.kind)
         #else:
-        #  stackTrace(c, tos, pc, errGenerated, "node has no type")
+        #  stackTrace(c, tos, pc, "node has no type")
       of 2:
         # getTypeInst opcode:
         ensureKind(rkNode)
         if regs[rb].kind == rkNode and regs[rb].node.typ != nil:
-          regs[ra].node = opMapTypeInstToAst(regs[rb].node.typ, c.debug[pc])
+          regs[ra].node = opMapTypeInstToAst(c.cache, regs[rb].node.typ, c.debug[pc])
+        elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil:
+          regs[ra].node = opMapTypeInstToAst(c.cache, regs[rb].node.sym.typ, c.debug[pc])
         else:
-          stackTrace(c, tos, pc, errGenerated, "node has no type")
+          stackTrace(c, tos, pc, "node has no type")
       else:
         # getTypeImpl opcode:
         ensureKind(rkNode)
         if regs[rb].kind == rkNode and regs[rb].node.typ != nil:
-          regs[ra].node = opMapTypeImplToAst(regs[rb].node.typ, c.debug[pc])
+          regs[ra].node = opMapTypeImplToAst(c.cache, regs[rb].node.typ, c.debug[pc])
+        elif regs[rb].kind == rkNode and regs[rb].node.kind == nkSym and regs[rb].node.sym.typ != nil:
+          regs[ra].node = opMapTypeImplToAst(c.cache, regs[rb].node.sym.typ, c.debug[pc])
         else:
-          stackTrace(c, tos, pc, errGenerated, "node has no type")
+          stackTrace(c, tos, pc, "node has no type")
     of opcNStrVal:
       decodeB(rkNode)
       createStr regs[ra]
       let a = regs[rb].node
-      if a.kind in {nkStrLit..nkTripleStrLit}: regs[ra].node.strVal = a.strVal
-      elif a.kind == nkCommentStmt: regs[ra].node.strVal = a.comment
-      else: stackTrace(c, tos, pc, errFieldXNotFound, "strVal")
+      case a.kind
+      of {nkStrLit..nkTripleStrLit}:
+        regs[ra].node.strVal = a.strVal
+      of nkCommentStmt:
+        regs[ra].node.strVal = a.comment
+      of nkIdent:
+        regs[ra].node.strVal = a.ident.s
+      of nkSym:
+        regs[ra].node.strVal = a.sym.name.s
+      else:
+        stackTrace(c, tos, pc, errFieldXNotFound & "strVal")
     of opcSlurp:
       decodeB(rkNode)
       createStr regs[ra]
       regs[ra].node.strVal = opSlurp(regs[rb].node.strVal, c.debug[pc],
-                                     c.module)
+                                     c.module, c.config)
     of opcGorge:
-      decodeBC(rkNode)
-      inc pc
-      let rd = c.code[pc].regA
-
-      createStr regs[ra]
-      regs[ra].node.strVal = opGorge(regs[rb].node.strVal,
-                                     regs[rc].node.strVal, regs[rd].node.strVal,
-                                     c.debug[pc])[0]
-    of opcNError:
+      when defined(nimcore):
+        decodeBC(rkNode)
+        inc pc
+        let rd = c.code[pc].regA
+
+        createStr regs[ra]
+        regs[ra].node.strVal = opGorge(regs[rb].node.strVal,
+                                      regs[rc].node.strVal, regs[rd].node.strVal,
+                                      c.debug[pc], c.config)[0]
+      else:
+        globalError(c.config, c.debug[pc], "VM is not built with 'gorge' support")
+    of opcNError, opcNWarning, opcNHint:
       decodeB(rkNode)
       let a = regs[ra].node
       let b = regs[rb].node
-      stackTrace(c, tos, pc, errUser, a.strVal, if b.kind == nkNilLit: nil else: b)
-    of opcNWarning:
-      message(c.debug[pc], warnUser, regs[ra].node.strVal)
-    of opcNHint:
-      message(c.debug[pc], hintUser, regs[ra].node.strVal)
+      let info = if b.kind == nkNilLit: c.debug[pc] else: b.info
+      if instr.opcode == opcNError:
+        stackTrace(c, tos, pc, a.strVal, info)
+      elif instr.opcode == opcNWarning:
+        message(c.config, info, warnUser, a.strVal)
+      elif instr.opcode == opcNHint:
+        message(c.config, info, hintUser, a.strVal)
     of opcParseExprToAst:
       decodeB(rkNode)
       # c.debug[pc].line.int - countLines(regs[rb].strVal) ?
       var error: string
-      let ast = parseString(regs[rb].node.strVal, c.cache, c.debug[pc].toFullPath,
-                            c.debug[pc].line.int,
-                            proc (info: TLineInfo; msg: TMsgKind; arg: string) =
-                              if error.isNil and msg <= msgs.errMax:
-                                error = formatMsg(info, msg, arg))
-      if not error.isNil:
+      let ast = parseString(regs[rb].node.strVal, c.cache, c.config,
+                            toFullPath(c.config, c.debug[pc]), c.debug[pc].line.int,
+                            proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string) =
+                              if error.len == 0 and msg <= errMax:
+                                error = formatMsg(conf, info, msg, arg))
+      if error.len > 0:
         c.errorFlag = error
       elif sonsLen(ast) != 1:
-        c.errorFlag = formatMsg(c.debug[pc], errExprExpected, "multiple statements")
+        c.errorFlag = formatMsg(c.config, c.debug[pc], errGenerated,
+          "expected expression, but got multiple statements")
       else:
         regs[ra].node = ast.sons[0]
     of opcParseStmtToAst:
       decodeB(rkNode)
       var error: string
-      let ast = parseString(regs[rb].node.strVal, c.cache, c.debug[pc].toFullPath,
-                            c.debug[pc].line.int,
-                            proc (info: TLineInfo; msg: TMsgKind; arg: string) =
-                              if error.isNil and msg <= msgs.errMax:
-                                error = formatMsg(info, msg, arg))
-      if not error.isNil:
+      let ast = parseString(regs[rb].node.strVal, c.cache, c.config,
+                            toFullPath(c.config, c.debug[pc]), c.debug[pc].line.int,
+                            proc (conf: ConfigRef; info: TLineInfo; msg: TMsgKind; arg: string) =
+                              if error.len == 0 and msg <= errMax:
+                                error = formatMsg(conf, info, msg, arg))
+      if error.len > 0:
         c.errorFlag = error
       else:
         regs[ra].node = ast
@@ -1306,40 +1470,75 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
     of opcCallSite:
       ensureKind(rkNode)
       if c.callsite != nil: regs[ra].node = c.callsite
-      else: stackTrace(c, tos, pc, errFieldXNotFound, "callsite")
-    of opcNLineInfo:
-      decodeB(rkNode)
+      else: stackTrace(c, tos, pc, errFieldXNotFound & "callsite")
+    of opcNGetLineInfo:
+      decodeBImm(rkNode)
       let n = regs[rb].node
-      createStr regs[ra]
-      regs[ra].node.strVal = n.info.toFileLineCol
-      regs[ra].node.info = c.debug[pc]
+      case imm
+      of 0: # getFile
+        regs[ra].node = newStrNode(nkStrLit, toFullPath(c.config, n.info))
+      of 1: # getLine
+        regs[ra].node = newIntNode(nkIntLit, n.info.line.int)
+      of 2: # getColumn
+        regs[ra].node = newIntNode(nkIntLit, n.info.col)
+      else:
+        internalAssert c.config, false
+      regs[ra].node.info = n.info
+      regs[ra].node.typ = n.typ
+    of opcNSetLineInfo:
+      decodeB(rkNode)
+      regs[ra].node.info = regs[rb].node.info
     of opcEqIdent:
       decodeBC(rkInt)
-      if regs[rb].node.kind == nkIdent and regs[rc].node.kind == nkIdent:
-        regs[ra].intVal = ord(regs[rb].node.ident.id == regs[rc].node.ident.id)
+      # aliases for shorter and easier to understand code below
+      let aNode = regs[rb].node
+      let bNode = regs[rc].node
+      # these are cstring to prevent string copy, and cmpIgnoreStyle from
+      # takes cstring arguments
+      var aStrVal: cstring = nil
+      var bStrVal: cstring = nil
+      # extract strVal from argument ``a``
+      case aNode.kind
+      of {nkStrLit..nkTripleStrLit}:
+        aStrVal = aNode.strVal.cstring
+      of nkIdent:
+        aStrVal = aNode.ident.s.cstring
+      of nkSym:
+        aStrVal = aNode.sym.name.s.cstring
+      of nkOpenSymChoice, nkClosedSymChoice:
+        aStrVal = aNode[0].sym.name.s.cstring
       else:
-        regs[ra].intVal = 0
+        discard
+      # extract strVal from argument ``b``
+      case bNode.kind
+      of {nkStrLit..nkTripleStrLit}:
+        bStrVal = bNode.strVal.cstring
+      of nkIdent:
+        bStrVal = bNode.ident.s.cstring
+      of nkSym:
+        bStrVal = bNode.sym.name.s.cstring
+      of nkOpenSymChoice, nkClosedSymChoice:
+        bStrVal = bNode[0].sym.name.s.cstring
+      else:
+        discard
+      # set result
+      regs[ra].intVal =
+        if aStrVal != nil and bStrVal != nil:
+          ord(idents.cmpIgnoreStyle(aStrVal,bStrVal,high(int)) == 0)
+        else:
+          0
+
     of opcStrToIdent:
       decodeB(rkNode)
       if regs[rb].node.kind notin {nkStrLit..nkTripleStrLit}:
-        stackTrace(c, tos, pc, errFieldXNotFound, "strVal")
+        stackTrace(c, tos, pc, errFieldXNotFound & "strVal")
       else:
         regs[ra].node = newNodeI(nkIdent, c.debug[pc])
-        regs[ra].node.ident = getIdent(regs[rb].node.strVal)
-    of opcIdentToStr:
-      decodeB(rkNode)
-      let a = regs[rb].node
-      createStr regs[ra]
-      regs[ra].node.info = c.debug[pc]
-      if a.kind == nkSym:
-        regs[ra].node.strVal = a.sym.name.s
-      elif a.kind == nkIdent:
-        regs[ra].node.strVal = a.ident.s
-      else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "ident")
+        regs[ra].node.ident = getIdent(c.cache, regs[rb].node.strVal)
+        regs[ra].node.flags.incl nfIsRef
     of opcSetType:
       if regs[ra].kind != rkNode:
-        internalError(c.debug[pc], "cannot set type")
+        internalError(c.config, c.debug[pc], "cannot set type")
       regs[ra].node.typ = c.types[instr.regBx - wordExcess]
     of opcConv:
       let rb = instr.regB
@@ -1348,9 +1547,9 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       inc pc
       let srctyp = c.types[c.code[pc].regBx - wordExcess]
 
-      if opConv(regs[ra], regs[rb], desttyp, srctyp):
-        stackTrace(c, tos, pc, errGenerated,
-          msgKindToString(errIllegalConvFromXtoY) % [
+      if opConv(c, regs[ra], regs[rb], desttyp, srctyp):
+        stackTrace(c, tos, pc,
+          errIllegalConvFromXtoY % [
           typeToString(srctyp), typeToString(desttyp)])
     of opcCast:
       let rb = instr.regB
@@ -1363,7 +1562,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
         let dest = fficast(regs[rb], desttyp)
         asgnRef(regs[ra], dest)
       else:
-        globalError(c.debug[pc], "cannot evaluate cast")
+        globalError(c.config, c.debug[pc], "cannot evaluate cast")
     of opcNSetIntVal:
       decodeB(rkNode)
       var dest = regs[ra].node
@@ -1371,7 +1570,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
          regs[rb].kind in {rkInt}:
         dest.intVal = regs[rb].intVal
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "intVal")
+        stackTrace(c, tos, pc, errFieldXNotFound & "intVal")
     of opcNSetFloatVal:
       decodeB(rkNode)
       var dest = regs[ra].node
@@ -1379,26 +1578,26 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
          regs[rb].kind in {rkFloat}:
         dest.floatVal = regs[rb].floatVal
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "floatVal")
+        stackTrace(c, tos, pc, errFieldXNotFound & "floatVal")
     of opcNSetSymbol:
       decodeB(rkNode)
       var dest = regs[ra].node
       if dest.kind == nkSym and regs[rb].node.kind == nkSym:
         dest.sym = regs[rb].node.sym
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "symbol")
+        stackTrace(c, tos, pc, errFieldXNotFound & "symbol")
     of opcNSetIdent:
       decodeB(rkNode)
       var dest = regs[ra].node
       if dest.kind == nkIdent and regs[rb].node.kind == nkIdent:
         dest.ident = regs[rb].node.ident
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "ident")
+        stackTrace(c, tos, pc, errFieldXNotFound & "ident")
     of opcNSetType:
       decodeB(rkNode)
       let b = regs[rb].node
-      internalAssert b.kind == nkSym and b.sym.kind == skType
-      internalAssert regs[ra].node != nil
+      internalAssert c.config, b.kind == nkSym and b.sym.kind == skType
+      internalAssert c.config, regs[ra].node != nil
       regs[ra].node.typ = b.sym.typ
     of opcNSetStrVal:
       decodeB(rkNode)
@@ -1406,26 +1605,31 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       if dest.kind in {nkStrLit..nkTripleStrLit} and
          regs[rb].kind in {rkNode}:
         dest.strVal = regs[rb].node.strVal
+      elif dest.kind == nkCommentStmt and regs[rb].kind in {rkNode}:
+        dest.comment = regs[rb].node.strVal
       else:
-        stackTrace(c, tos, pc, errFieldXNotFound, "strVal")
+        stackTrace(c, tos, pc, errFieldXNotFound & "strVal")
     of opcNNewNimNode:
       decodeBC(rkNode)
       var k = regs[rb].intVal
       if k < 0 or k > ord(high(TNodeKind)):
-        internalError(c.debug[pc],
+        internalError(c.config, c.debug[pc],
           "request to create a NimNode of invalid kind")
       let cc = regs[rc].node
 
-      regs[ra].node = newNodeI(TNodeKind(int(k)),
+      let x = newNodeI(TNodeKind(int(k)),
         if cc.kind != nkNilLit:
           cc.info
-        elif c.comesFromHeuristic.line > -1:
+        elif c.comesFromHeuristic.line != 0'u16:
           c.comesFromHeuristic
         elif c.callsite != nil and c.callsite.safeLen > 1:
           c.callsite[1].info
         else:
           c.debug[pc])
-      regs[ra].node.flags.incl nfIsRef
+      x.flags.incl nfIsRef
+      # prevent crashes in the compiler resulting from wrong macros:
+      if x.kind == nkIdent: x.ident = c.cache.emptyIdent
+      regs[ra].node = x
     of opcNCopyNimNode:
       decodeB(rkNode)
       regs[ra].node = copyNode(regs[rb].node)
@@ -1443,16 +1647,117 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       let name = if regs[rc].node.strVal.len == 0: ":tmp"
                  else: regs[rc].node.strVal
       if k < 0 or k > ord(high(TSymKind)):
-        internalError(c.debug[pc], "request to create symbol of invalid kind")
-      var sym = newSym(k.TSymKind, name.getIdent, c.module.owner, c.debug[pc])
+        internalError(c.config, c.debug[pc], "request to create symbol of invalid kind")
+      var sym = newSym(k.TSymKind, getIdent(c.cache, name), c.module.owner, c.debug[pc])
       incl(sym.flags, sfGenSym)
       regs[ra].node = newSymNode(sym)
+      regs[ra].node.flags.incl nfIsRef
+    of opcNccValue:
+      decodeB(rkInt)
+      let destKey = regs[rb].node.strVal
+      regs[ra].intVal = getOrDefault(c.graph.cacheCounters, destKey)
+    of opcNccInc:
+      let g = c.graph
+      let destKey = regs[ra].node.strVal
+      let by = regs[instr.regB].intVal
+      let v = getOrDefault(g.cacheCounters, destKey)
+      g.cacheCounters[destKey] = v+by
+      recordInc(c, c.debug[pc], destKey, by)
+    of opcNcsAdd:
+      let g = c.graph
+      let destKey = regs[ra].node.strVal
+      let val = regs[instr.regB].node
+      if not contains(g.cacheSeqs, destKey):
+        g.cacheSeqs[destKey] = newTree(nkStmtList, val)
+        # newNodeI(nkStmtList, c.debug[pc])
+      else:
+        g.cacheSeqs[destKey].add val
+      recordAdd(c, c.debug[pc], destKey, val)
+    of opcNcsIncl:
+      let g = c.graph
+      let destKey = regs[ra].node.strVal
+      let val = regs[instr.regB].node
+      if not contains(g.cacheSeqs, destKey):
+        g.cacheSeqs[destKey] = newTree(nkStmtList, val)
+      else:
+        block search:
+          for existing in g.cacheSeqs[destKey]:
+            if exprStructuralEquivalent(existing, val, strictSymEquality=true):
+              break search
+          g.cacheSeqs[destKey].add val
+      recordIncl(c, c.debug[pc], destKey, val)
+    of opcNcsLen:
+      let g = c.graph
+      decodeB(rkInt)
+      let destKey = regs[rb].node.strVal
+      regs[ra].intVal =
+        if contains(g.cacheSeqs, destKey): g.cacheSeqs[destKey].len else: 0
+    of opcNcsAt:
+      let g = c.graph
+      decodeBC(rkNode)
+      let idx = regs[rc].intVal
+      let destKey = regs[rb].node.strVal
+      if contains(g.cacheSeqs, destKey) and idx <% g.cacheSeqs[destKey].len:
+        regs[ra].node = g.cacheSeqs[destKey][idx.int]
+      else:
+        stackTrace(c, tos, pc, errIndexOutOfBounds)
+    of opcNctPut:
+      let g = c.graph
+      let destKey = regs[ra].node.strVal
+      let key = regs[instr.regB].node.strVal
+      let val = regs[instr.regC].node
+      if not contains(g.cacheTables, destKey):
+        g.cacheTables[destKey] = initBTree[string, PNode]()
+      if not contains(g.cacheTables[destKey], key):
+        g.cacheTables[destKey].add(key, val)
+        recordPut(c, c.debug[pc], destKey, key, val)
+      else:
+        stackTrace(c, tos, pc, "key already exists: " & key)
+    of opcNctLen:
+      let g = c.graph
+      decodeB(rkInt)
+      let destKey = regs[rb].node.strVal
+      regs[ra].intVal =
+        if contains(g.cacheTables, destKey): g.cacheTables[destKey].len else: 0
+    of opcNctGet:
+      let g = c.graph
+      decodeBC(rkNode)
+      let destKey = regs[rb].node.strVal
+      let key = regs[rc].node.strVal
+      if contains(g.cacheTables, destKey):
+        if contains(g.cacheTables[destKey], key):
+          regs[ra].node = getOrDefault(g.cacheTables[destKey], key)
+        else:
+          stackTrace(c, tos, pc, "key does not exist: " & key)
+      else:
+        stackTrace(c, tos, pc, "key does not exist: " & destKey)
+    of opcNctHasNext:
+      let g = c.graph
+      decodeBC(rkInt)
+      let destKey = regs[rb].node.strVal
+      regs[ra].intVal =
+        if g.cacheTables.contains(destKey):
+          ord(btrees.hasNext(g.cacheTables[destKey], regs[rc].intVal.int))
+        else:
+          0
+    of opcNctNext:
+      let g = c.graph
+      decodeBC(rkNode)
+      let destKey = regs[rb].node.strVal
+      let index = regs[rc].intVal
+      if contains(g.cacheTables, destKey):
+        let (k, v, nextIndex) = btrees.next(g.cacheTables[destKey], index.int)
+        regs[ra].node = newTree(nkTupleConstr, newStrNode(k, c.debug[pc]), v,
+                                newIntNode(nkIntLit, nextIndex))
+      else:
+        stackTrace(c, tos, pc, "key does not exist: " & destKey)
+
     of opcTypeTrait:
       # XXX only supports 'name' for now; we can use regC to encode the
       # type trait operation
       decodeB(rkNode)
       var typ = regs[rb].node.typ
-      internalAssert typ != nil
+      internalAssert c.config, typ != nil
       while typ.kind == tyTypeDesc and typ.len > 0: typ = typ.sons[0]
       createStr regs[ra]
       regs[ra].node.strVal = typ.typeToString(preferExported)
@@ -1461,14 +1766,26 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
       let rb = instr.regB
       inc pc
       let typ = c.types[c.code[pc].regBx - wordExcess]
-      putIntoReg(regs[ra], loadAny(regs[rb].node.strVal, typ))
+      putIntoReg(regs[ra], loadAny(regs[rb].node.strVal, typ, c.cache, c.config))
     of opcMarshalStore:
       decodeB(rkNode)
       inc pc
       let typ = c.types[c.code[pc].regBx - wordExcess]
       createStrKeepNode(regs[ra])
-      if regs[ra].node.strVal.isNil: regs[ra].node.strVal = newStringOfCap(1000)
-      storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode)
+      when not defined(nimNoNilSeqs):
+        if regs[ra].node.strVal.isNil: regs[ra].node.strVal = newStringOfCap(1000)
+      storeAny(regs[ra].node.strVal, typ, regs[rb].regToNode, c.config)
+    of opcToNarrowInt:
+      decodeBC(rkInt)
+      let mask = (1'i64 shl rc) - 1 # 0xFF
+      let signbit = 1'i64 shl (rc - 1) # 0x80
+      let toggle = mask - signbit # 0x7F
+      # algorithm: -((i8 and 0xFF) xor 0x7F) + 0x7F
+      # mask off higher bits.
+      # uses two's complement to sign-extend integer.
+      # reajust integer into desired range.
+      regs[ra].intVal = -((regs[rb].intVal and mask) xor toggle) + toggle
+
     inc pc
 
 proc execute(c: PCtx, start: int): PNode =
@@ -1479,7 +1796,7 @@ proc execute(c: PCtx, start: int): PNode =
 proc execProc*(c: PCtx; sym: PSym; args: openArray[PNode]): PNode =
   if sym.kind in routineKinds:
     if sym.typ.len-1 != args.len:
-      localError(sym.info,
+      localError(c.config, sym.info,
         "NimScript: expected $# arguments, but got $#" % [
         $(sym.typ.len-1), $args.len])
     else:
@@ -1491,18 +1808,18 @@ proc execProc*(c: PCtx; sym: PSym; args: openArray[PNode]): PNode =
 
       # setup parameters:
       if not isEmptyType(sym.typ.sons[0]) or sym.kind == skMacro:
-        putIntoReg(tos.slots[0], getNullValue(sym.typ.sons[0], sym.info))
+        putIntoReg(tos.slots[0], getNullValue(sym.typ.sons[0], sym.info, c.config))
       # XXX We could perform some type checking here.
-      for i in 1.. <sym.typ.len:
+      for i in 1..<sym.typ.len:
         putIntoReg(tos.slots[i], args[i-1])
 
       result = rawExecute(c, start, tos).regToNode
   else:
-    localError(sym.info,
+    localError(c.config, sym.info,
       "NimScript: attempt to call non-routine: " & sym.name.s)
 
 proc evalStmt*(c: PCtx, n: PNode) =
-  let n = transformExpr(c.module, n)
+  let n = transformExpr(c.graph, c.module, n, noDestructors = true)
   let start = genStmt(c, n)
   # execute new instructions; this redundant opcEof check saves us lots
   # of allocations in 'execute':
@@ -1510,86 +1827,80 @@ proc evalStmt*(c: PCtx, n: PNode) =
     discard execute(c, start)
 
 proc evalExpr*(c: PCtx, n: PNode): PNode =
-  let n = transformExpr(c.module, n)
+  let n = transformExpr(c.graph, c.module, n, noDestructors = true)
   let start = genExpr(c, n)
   assert c.code[start].opcode != opcEof
   result = execute(c, start)
 
 proc getGlobalValue*(c: PCtx; s: PSym): PNode =
-  internalAssert s.kind in {skLet, skVar} and sfGlobal in s.flags
+  internalAssert c.config, s.kind in {skLet, skVar} and sfGlobal in s.flags
   result = c.globals.sons[s.position-1]
 
 include vmops
 
-# for now we share the 'globals' environment. XXX Coming soon: An API for
-# storing&loading the 'globals' environment to get what a component system
-# requires.
-var
-  globalCtx*: PCtx
-
-proc setupGlobalCtx(module: PSym; cache: IdentCache) =
-  if globalCtx.isNil:
-    globalCtx = newCtx(module, cache)
-    registerAdditionalOps(globalCtx)
+proc setupGlobalCtx*(module: PSym; graph: ModuleGraph) =
+  if graph.vm.isNil:
+    graph.vm = newCtx(module, graph.cache, graph)
+    registerAdditionalOps(PCtx graph.vm)
   else:
-    refresh(globalCtx, module)
+    refresh(PCtx graph.vm, module)
 
-proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
+proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
   #var c = newEvalContext(module, emRepl)
   #c.features = {allowCast, allowFFI, allowInfiniteLoops}
   #pushStackFrame(c, newStackFrame())
 
   # XXX produce a new 'globals' environment here:
-  setupGlobalCtx(module, cache)
-  result = globalCtx
+  setupGlobalCtx(module, graph)
+  result = PCtx graph.vm
   when hasFFI:
-    globalCtx.features = {allowFFI, allowCast}
-
-var oldErrorCount: int
+    PCtx(graph.vm).features = {allowFFI, allowCast}
 
 proc myProcess(c: PPassContext, n: PNode): PNode =
+  let c = PCtx(c)
   # don't eval errornous code:
-  if oldErrorCount == msgs.gErrorCounter:
-    evalStmt(PCtx(c), n)
-    result = emptyNode
+  if c.oldErrorCount == c.config.errorCounter:
+    evalStmt(c, n)
+    result = newNodeI(nkEmpty, n.info)
   else:
     result = n
-  oldErrorCount = msgs.gErrorCounter
+  c.oldErrorCount = c.config.errorCounter
 
 proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode =
   myProcess(c, n)
 
-const evalPass* = makePass(myOpen, nil, myProcess, myClose)
+const evalPass* = makePass(myOpen, myProcess, myClose)
 
-proc evalConstExprAux(module: PSym; cache: IdentCache; prc: PSym, n: PNode,
+proc evalConstExprAux(module: PSym;
+                      g: ModuleGraph; prc: PSym, n: PNode,
                       mode: TEvalMode): PNode =
-  let n = transformExpr(module, n)
-  setupGlobalCtx(module, cache)
-  var c = globalCtx
+  let n = transformExpr(g, module, n, noDestructors = true)
+  setupGlobalCtx(module, g)
+  var c = PCtx g.vm
   let oldMode = c.mode
   defer: c.mode = oldMode
   c.mode = mode
   let start = genExpr(c, n, requiresValue = mode!=emStaticStmt)
-  if c.code[start].opcode == opcEof: return emptyNode
+  if c.code[start].opcode == opcEof: return newNodeI(nkEmpty, n.info)
   assert c.code[start].opcode != opcEof
   when debugEchoCode: c.echoCode start
   var tos = PStackFrame(prc: prc, comesFrom: 0, next: nil)
   newSeq(tos.slots, c.prc.maxSlots)
-  #for i in 0 .. <c.prc.maxSlots: tos.slots[i] = newNode(nkEmpty)
+  #for i in 0 ..< c.prc.maxSlots: tos.slots[i] = newNode(nkEmpty)
   result = rawExecute(c, start, tos).regToNode
-  if result.info.line < 0: result.info = n.info
+  if result.info.col < 0: result.info = n.info
 
-proc evalConstExpr*(module: PSym; cache: IdentCache, e: PNode): PNode =
-  result = evalConstExprAux(module, cache, nil, e, emConst)
+proc evalConstExpr*(module: PSym; g: ModuleGraph; e: PNode): PNode =
+  result = evalConstExprAux(module, g, nil, e, emConst)
 
-proc evalStaticExpr*(module: PSym; cache: IdentCache, e: PNode, prc: PSym): PNode =
-  result = evalConstExprAux(module, cache, prc, e, emStaticExpr)
+proc evalStaticExpr*(module: PSym; g: ModuleGraph; e: PNode, prc: PSym): PNode =
+  result = evalConstExprAux(module, g, prc, e, emStaticExpr)
 
-proc evalStaticStmt*(module: PSym; cache: IdentCache, e: PNode, prc: PSym) =
-  discard evalConstExprAux(module, cache, prc, e, emStaticStmt)
+proc evalStaticStmt*(module: PSym; g: ModuleGraph; e: PNode, prc: PSym) =
+  discard evalConstExprAux(module, g, prc, e, emStaticStmt)
 
-proc setupCompileTimeVar*(module: PSym; cache: IdentCache, n: PNode) =
-  discard evalConstExprAux(module, cache, nil, n, emStaticStmt)
+proc setupCompileTimeVar*(module: PSym; g: ModuleGraph; n: PNode) =
+  discard evalConstExprAux(module, g, nil, n, emStaticStmt)
 
 proc setupMacroParam(x: PNode, typ: PType): TFullReg =
   case typ.kind
@@ -1606,25 +1917,32 @@ proc setupMacroParam(x: PNode, typ: PType): TFullReg =
     n.typ = x.typ
     result.node = n
 
-var evalMacroCounter: int
+iterator genericParamsInMacroCall*(macroSym: PSym, call: PNode): (PSym, PNode) =
+  let gp = macroSym.ast[genericParamsPos]
+  for i in 0 ..< gp.len:
+    let genericParam = gp[i].sym
+    let posInCall = macroSym.typ.len + i
+    yield (genericParam, call[posInCall])
+
+# to prevent endless recursion in macro instantiation
+const evalMacroLimit = 1000
 
-proc evalMacroCall*(module: PSym; cache: IdentCache, n, nOrig: PNode,
-                    sym: PSym): PNode =
+proc evalMacroCall*(module: PSym; g: ModuleGraph;
+                    n, nOrig: PNode, sym: PSym): PNode =
   # XXX globalError() is ugly here, but I don't know a better solution for now
-  inc(evalMacroCounter)
-  if evalMacroCounter > 100:
-    globalError(n.info, errTemplateInstantiationTooNested)
+  inc(g.config.evalMacroCounter)
+  if g.config.evalMacroCounter > evalMacroLimit:
+    globalError(g.config, n.info, "macro instantiation too nested")
 
   # immediate macros can bypass any type and arity checking so we check the
   # arity here too:
   if sym.typ.len > n.safeLen and sym.typ.len > 1:
-    globalError(n.info, "in call '$#' got $#, but expected $# argument(s)" % [
-        n.renderTree,
-        $ <n.safeLen, $ <sym.typ.len])
+    globalError(g.config, n.info, "in call '$#' got $#, but expected $# argument(s)" % [
+        n.renderTree, $(n.safeLen-1), $(sym.typ.len-1)])
 
-  setupGlobalCtx(module, cache)
-  var c = globalCtx
-  c.comesFromHeuristic.line = -1
+  setupGlobalCtx(module, g)
+  var c = PCtx g.vm
+  c.comesFromHeuristic.line = 0'u16
 
   c.callsite = nOrig
   let start = genProc(c, sym)
@@ -1644,20 +1962,28 @@ proc evalMacroCall*(module: PSym; cache: IdentCache, n, nOrig: PNode,
   tos.slots[0].node = newNodeI(nkEmpty, n.info)
 
   # setup parameters:
-  for i in 1.. <sym.typ.len:
+  for i in 1..<sym.typ.len:
     tos.slots[i] = setupMacroParam(n.sons[i], sym.typ.sons[i])
 
   let gp = sym.ast[genericParamsPos]
-  for i in 0 .. <gp.len:
+  for i in 0 ..< gp.len:
     if sfImmediate notin sym.flags:
       let idx = sym.typ.len + i
-      tos.slots[idx] = setupMacroParam(n.sons[idx], gp[i].sym.typ)
+      if idx < n.len:
+        tos.slots[idx] = setupMacroParam(n.sons[idx], gp[i].sym.typ)
+      else:
+        dec(g.config.evalMacroCounter)
+        c.callsite = nil
+        localError(c.config, n.info, "expected " & $gp.len &
+                   " generic parameter(s)")
     elif gp[i].sym.typ.kind in {tyStatic, tyTypeDesc}:
-      globalError(n.info, "static[T] or typedesc nor supported for .immediate macros")
+      dec(g.config.evalMacroCounter)
+      c.callsite = nil
+      globalError(c.config, n.info, "static[T] or typedesc nor supported for .immediate macros")
   # temporary storage:
-  #for i in L .. <maxSlots: tos.slots[i] = newNode(nkEmpty)
+  #for i in L ..< maxSlots: tos.slots[i] = newNode(nkEmpty)
   result = rawExecute(c, start, tos).regToNode
   if result.info.line < 0: result.info = n.info
-  if cyclicTree(result): globalError(n.info, errCyclicTree)
-  dec(evalMacroCounter)
+  if cyclicTree(result): globalError(c.config, n.info, "macro produced a cyclic tree")
+  dec(g.config.evalMacroCounter)
   c.callsite = nil
diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim
index 263ec8378..493078f74 100644
--- a/compiler/vmdef.nim
+++ b/compiler/vmdef.nim
@@ -10,13 +10,14 @@
 ## This module contains the type definitions for the new evaluation engine.
 ## An instruction is 1-3 int32s in memory, it is a register based VM.
 
-import ast, passes, msgs, idents, intsets
+import ast, passes, msgs, idents, intsets, options, modulegraphs, lineinfos,
+  tables, btrees
 
 const
   byteExcess* = 128 # we use excess-K for immediates
   wordExcess* = 32768
 
-  MaxLoopIterations* = 1500_000 # max iterations of all loops
+  MaxLoopIterations* = 3_000_000 # max iterations of all loops
 
 
 type
@@ -34,8 +35,11 @@ type
     opcAsgnStr,
     opcAsgnFloat,
     opcAsgnRef,
+    opcAsgnIntFromFloat32,    # int and float must be of the same byte size
+    opcAsgnIntFromFloat64,    # int and float must be of the same byte size
+    opcAsgnFloat32FromInt,    # int and float must be of the same byte size
+    opcAsgnFloat64FromInt,    # int and float must be of the same byte size
     opcAsgnComplex,
-    opcRegToNode,
     opcNodeToReg,
 
     opcLdArr,  # a = b[c]
@@ -57,11 +61,12 @@ type
     opcLenStr,
 
     opcIncl, opcInclRange, opcExcl, opcCard, opcMulInt, opcDivInt, opcModInt,
-    opcAddFloat, opcSubFloat, opcMulFloat, opcDivFloat, opcShrInt, opcShlInt,
+    opcAddFloat, opcSubFloat, opcMulFloat, opcDivFloat,
+    opcShrInt, opcShlInt, opcAshrInt,
     opcBitandInt, opcBitorInt, opcBitxorInt, opcAddu, opcSubu, opcMulu,
     opcDivu, opcModu, opcEqInt, opcLeInt, opcLtInt, opcEqFloat,
     opcLeFloat, opcLtFloat, opcLeu, opcLtu,
-    opcEqRef, opcEqNimrodNode, opcSameNodeType,
+    opcEqRef, opcEqNimNode, opcSameNodeType,
     opcXor, opcNot, opcUnaryMinusInt, opcUnaryMinusFloat, opcBitnotInt,
     opcEqStr, opcLeStr, opcLtStr, opcEqSet, opcLeSet, opcLtSet,
     opcMulSet, opcPlusSet, opcMinusSet, opcSymdiffSet, opcConcatStr,
@@ -79,6 +84,7 @@ type
     opcNAdd,
     opcNAddMultiple,
     opcNKind,
+    opcNSymKind,
     opcNIntVal,
     opcNFloatVal,
     opcNSymbol,
@@ -90,6 +96,9 @@ type
     opcNSetFloatVal, opcNSetSymbol, opcNSetIdent, opcNSetType, opcNSetStrVal,
     opcNNewNimNode, opcNCopyNimNode, opcNCopyNimTree, opcNDel, opcGenSym,
 
+    opcNccValue, opcNccInc, opcNcsAdd, opcNcsIncl, opcNcsLen, opcNcsAt,
+    opcNctPut, opcNctLen, opcNctGet, opcNctHasNext, opcNctNext,
+
     opcSlurp,
     opcGorge,
     opcParseExprToAst,
@@ -98,11 +107,11 @@ type
     opcNError,
     opcNWarning,
     opcNHint,
-    opcNLineInfo,
+    opcNGetLineInfo, opcNSetLineInfo,
     opcEqIdent,
     opcStrToIdent,
-    opcIdentToStr,
     opcGetImpl,
+    opcGetImplTransf
 
     opcEcho,
     opcIndCall, # dest = call regStart, n; where regStart = fn, arg1, ...
@@ -133,10 +142,13 @@ type
     opcLdGlobalAddr, # dest = addr(globals[Bx])
 
     opcLdImmInt,  # dest = immediate value
-    opcNBindSym,
+    opcNBindSym, opcNDynBindSym,
     opcSetType,   # dest.typ = types[Bx]
     opcTypeTrait,
-    opcMarshalLoad, opcMarshalStore
+    opcMarshalLoad, opcMarshalStore,
+    opcToNarrowInt,
+    opcSymOwner,
+    opcSymIsInstantiationOf
 
   TBlock* = object
     label*: PSym
@@ -185,7 +197,7 @@ type
   VmCallback* = proc (args: VmArgs) {.closure.}
 
   PCtx* = ref TCtx
-  TCtx* = object of passes.TPassContext # code gen context
+  TCtx* = object of TPassContext # code gen context
     code*: seq[TInstr]
     debug*: seq[TLineInfo]  # line info for every instruction; kept separate
                             # to not slow down interpretation
@@ -205,24 +217,28 @@ type
     callbacks*: seq[tuple[key: string, value: VmCallback]]
     errorFlag*: string
     cache*: IdentCache
+    config*: ConfigRef
+    graph*: ModuleGraph
+    oldErrorCount*: int
 
   TPosition* = distinct int
 
   PEvalContext* = PCtx
 
-proc newCtx*(module: PSym; cache: IdentCache): PCtx =
+proc newCtx*(module: PSym; cache: IdentCache; g: ModuleGraph): PCtx =
   PCtx(code: @[], debug: @[],
     globals: newNode(nkStmtListExpr), constants: newNode(nkStmtList), types: @[],
     prc: PProc(blocks: @[]), module: module, loopIterations: MaxLoopIterations,
     comesFromHeuristic: unknownLineInfo(), callbacks: @[], errorFlag: "",
-    cache: cache)
+    cache: cache, config: g.config, graph: g)
 
 proc refresh*(c: PCtx, module: PSym) =
   c.module = module
   c.prc = PProc(blocks: @[])
   c.loopIterations = MaxLoopIterations
 
-proc registerCallback*(c: PCtx; name: string; callback: VmCallback) =
+proc registerCallback*(c: PCtx; name: string; callback: VmCallback): int {.discardable.} =
+  result = c.callbacks.len
   c.callbacks.add((name, callback))
 
 const
@@ -233,6 +249,9 @@ const
   slotSomeTemp* = slotTempUnknown
   relativeJumps* = {opcTJmp, opcFJmp, opcJmp, opcJmpBack}
 
+# flag is used to signal opcSeqLen if node is NimNode.
+const nimNodeFlag* = 16
+
 template opcode*(x: TInstr): TOpcode = TOpcode(x.uint32 and 0xff'u32)
 template regA*(x: TInstr): TRegister = TRegister(x.uint32 shr 8'u32 and 0xff'u32)
 template regB*(x: TInstr): TRegister = TRegister(x.uint32 shr 16'u32 and 0xff'u32)
diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim
index d684c4c32..f160a3096 100644
--- a/compiler/vmdeps.nim
+++ b/compiler/vmdeps.nim
@@ -7,67 +7,24 @@
 #    distribution, for details about the copyright.
 #
 
-import ast, types, msgs, os, osproc, streams, options, idents, securehash
+import ast, types, msgs, os, streams, options, idents, lineinfos
 
-proc readOutput(p: Process): (string, int) =
-  result[0] = ""
-  var output = p.outputStream
-  while not output.atEnd:
-    result[0].add(output.readLine)
-    result[0].add("\n")
-  if result[0].len > 0:
-    result[0].setLen(result[0].len - "\n".len)
-  result[1] = p.waitForExit
-
-proc opGorge*(cmd, input, cache: string, info: TLineInfo): (string, int) =
-  let workingDir = parentDir(info.toFullPath)
-  if cache.len > 0:# and optForceFullMake notin gGlobalOptions:
-    let h = secureHash(cmd & "\t" & input & "\t" & cache)
-    let filename = options.toGeneratedFile("gorge_" & $h, "txt")
-    var f: File
-    if open(f, filename):
-      result = (f.readAll, 0)
-      f.close
-      return
-    var readSuccessful = false
-    try:
-      var p = startProcess(cmd, workingDir,
-                           options={poEvalCommand, poStderrToStdout})
-      if input.len != 0:
-        p.inputStream.write(input)
-        p.inputStream.close()
-      result = p.readOutput
-      readSuccessful = true
-      writeFile(filename, result[0])
-    except IOError, OSError:
-      if not readSuccessful: result = ("", -1)
-  else:
-    try:
-      var p = startProcess(cmd, workingDir,
-                           options={poEvalCommand, poStderrToStdout})
-      if input.len != 0:
-        p.inputStream.write(input)
-        p.inputStream.close()
-      result = p.readOutput
-    except IOError, OSError:
-      result = ("", -1)
-
-proc opSlurp*(file: string, info: TLineInfo, module: PSym): string =
+proc opSlurp*(file: string, info: TLineInfo, module: PSym; conf: ConfigRef): string =
   try:
-    var filename = parentDir(info.toFullPath) / file
+    var filename = parentDir(toFullPath(conf, info)) / file
     if not fileExists(filename):
-      filename = file.findFile
+      filename = findFile(conf, file).string
     result = readFile(filename)
     # we produce a fake include statement for every slurped filename, so that
     # the module dependencies are accurate:
     appendToModule(module, newNode(nkIncludeStmt, info, @[
       newStrNode(nkStrLit, filename)]))
   except IOError:
-    localError(info, errCannotOpenFile, file)
+    localError(conf, info, "cannot open file: " & file)
     result = ""
 
-proc atomicTypeX(name: string; m: TMagic; t: PType; info: TLineInfo): PNode =
-  let sym = newSym(skType, getIdent(name), t.owner, info)
+proc atomicTypeX(cache: IdentCache; name: string; m: TMagic; t: PType; info: TLineInfo): PNode =
+  let sym = newSym(skType, getIdent(cache, name), t.owner, info)
   sym.magic = m
   sym.typ = t
   result = newSymNode(sym)
@@ -77,60 +34,57 @@ proc atomicTypeX(s: PSym; info: TLineInfo): PNode =
   result = newSymNode(s)
   result.info = info
 
-proc mapTypeToAstX(t: PType; info: TLineInfo;
+proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo;
                    inst=false; allowRecursionX=false): PNode
 
-proc mapTypeToBracketX(name: string; m: TMagic; t: PType; info: TLineInfo;
+proc mapTypeToBracketX(cache: IdentCache; name: string; m: TMagic; t: PType; info: TLineInfo;
                        inst=false): PNode =
   result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t)
-  result.add atomicTypeX(name, m, t, info)
-  for i in 0 .. < t.len:
+  result.add atomicTypeX(cache, name, m, t, info)
+  for i in 0 ..< t.len:
     if t.sons[i] == nil:
-      let void = atomicTypeX("void", mVoid, t, info)
+      let void = atomicTypeX(cache, "void", mVoid, t, info)
       void.typ = newType(tyVoid, t.owner)
       result.add void
     else:
-      result.add mapTypeToAstX(t.sons[i], info, inst)
+      result.add mapTypeToAstX(cache, t.sons[i], info, inst)
 
-proc objectNode(n: PNode): PNode =
+proc objectNode(cache: IdentCache; n: PNode): PNode =
   if n.kind == nkSym:
     result = newNodeI(nkIdentDefs, n.info)
     result.add n  # name
-    result.add mapTypeToAstX(n.sym.typ, n.info, true, false)  # type
-    result.add ast.emptyNode  # no assigned value
+    result.add mapTypeToAstX(cache, n.sym.typ, n.info, true, false)  # type
+    result.add newNodeI(nkEmpty, n.info)  # no assigned value
   else:
     result = copyNode(n)
     for i in 0 ..< n.safeLen:
-      result.add objectNode(n[i])
+      result.add objectNode(cache, n[i])
 
-proc mapTypeToAstX(t: PType; info: TLineInfo;
+proc mapTypeToAstX(cache: IdentCache; t: PType; info: TLineInfo;
                    inst=false; allowRecursionX=false): PNode =
   var allowRecursion = allowRecursionX
-  template atomicType(name, m): untyped = atomicTypeX(name, m, t, info)
+  template atomicType(name, m): untyped = atomicTypeX(cache, name, m, t, info)
   template atomicType(s): untyped = atomicTypeX(s, info)
-  template mapTypeToAst(t,info): untyped = mapTypeToAstX(t, info, inst)
-  template mapTypeToAstR(t,info): untyped = mapTypeToAstX(t, info, inst, true)
+  template mapTypeToAst(t,info): untyped = mapTypeToAstX(cache, t, info, inst)
+  template mapTypeToAstR(t,info): untyped = mapTypeToAstX(cache, t, info, inst, true)
   template mapTypeToAst(t,i,info): untyped =
-    if i<t.len and t.sons[i]!=nil: mapTypeToAstX(t.sons[i], info, inst)
-    else: ast.emptyNode
+    if i<t.len and t.sons[i]!=nil: mapTypeToAstX(cache, t.sons[i], info, inst)
+    else: newNodeI(nkEmpty, info)
   template mapTypeToBracket(name, m, t, info): untyped =
-    mapTypeToBracketX(name, m, t, info, inst)
+    mapTypeToBracketX(cache, name, m, t, info, inst)
   template newNodeX(kind): untyped =
     newNodeIT(kind, if t.n.isNil: info else: t.n.info, t)
   template newIdentDefs(n,t): untyped =
     var id = newNodeX(nkIdentDefs)
     id.add n  # name
     id.add mapTypeToAst(t, info)  # type
-    id.add ast.emptyNode  # no assigned value
+    id.add newNodeI(nkEmpty, info)  # no assigned value
     id
   template newIdentDefs(s): untyped = newIdentDefs(s, s.typ)
 
-  if inst:
-    if t.sym != nil:  # if this node has a symbol
-      if allowRecursion:  # getTypeImpl behavior: turn off recursion
-        allowRecursion = false
-      else:  # getTypeInst behavior: return symbol
-        return atomicType(t.sym)
+  if inst and not allowRecursion and t.sym != nil:
+    # getTypeInst behavior: return symbol
+    return atomicType(t.sym)
 
   case t.kind
   of tyNone: result = atomicType("none", mNone)
@@ -141,12 +95,16 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
   of tyStmt: result = atomicType("stmt", mStmt)
   of tyVoid: result = atomicType("void", mVoid)
   of tyEmpty: result = atomicType("empty", mNone)
+  of tyUncheckedArray:
+    result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t)
+    result.add atomicType("UncheckedArray", mUncheckedArray)
+    result.add mapTypeToAst(t.sons[0], info)
   of tyArray:
     result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t)
     result.add atomicType("array", mArray)
     if inst and t.sons[0].kind == tyRange:
       var rng = newNodeX(nkInfix)
-      rng.add newIdentNode(getIdent(".."), info)
+      rng.add newIdentNode(getIdent(cache, ".."), info)
       rng.add t.sons[0].n.sons[0].copyTree
       rng.add t.sons[0].n.sons[1].copyTree
       result.add rng
@@ -162,20 +120,28 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
       result = atomicType("typeDesc", mTypeDesc)
   of tyGenericInvocation:
     result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t)
-    for i in 0 .. < t.len:
+    for i in 0 ..< t.len:
       result.add mapTypeToAst(t.sons[i], info)
-  of tyGenericInst, tyAlias:
+  of tyGenericInst:
     if inst:
       if allowRecursion:
         result = mapTypeToAstR(t.lastSon, info)
       else:
         result = newNodeX(nkBracketExpr)
-        result.add mapTypeToAst(t.lastSon, info)
-        for i in 1 .. < t.len-1:
+        #result.add mapTypeToAst(t.lastSon, info)
+        result.add mapTypeToAst(t[0], info)
+        for i in 1 ..< t.len-1:
           result.add mapTypeToAst(t.sons[i], info)
     else:
-      result = mapTypeToAstX(t.lastSon, info, inst, allowRecursion)
-  of tyGenericBody, tyOrdinal, tyUserTypeClassInst:
+      result = mapTypeToAstX(cache, t.lastSon, info, inst, allowRecursion)
+  of tyGenericBody:
+    if inst:
+      result = mapTypeToAstR(t.lastSon, info)
+    else:
+      result = mapTypeToAst(t.lastSon, info)
+  of tyAlias:
+    result = mapTypeToAstX(cache, t.lastSon, info, inst, allowRecursion)
+  of tyOrdinal:
     result = mapTypeToAst(t.lastSon, info)
   of tyDistinct:
     if inst:
@@ -191,22 +157,26 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
   of tyObject:
     if inst:
       result = newNodeX(nkObjectTy)
-      result.add ast.emptyNode  # pragmas not reconstructed yet
-      if t.sons[0] == nil: result.add ast.emptyNode  # handle parent object
+      if t.sym.ast != nil:
+        result.add t.sym.ast[2][0].copyTree  # copy object pragmas
       else:
+        result.add newNodeI(nkEmpty, info)
+      if t.sons[0] == nil:
+        result.add newNodeI(nkEmpty, info)
+      else:  # handle parent object
         var nn = newNodeX(nkOfInherit)
         nn.add mapTypeToAst(t.sons[0], info)
         result.add nn
       if t.n.len > 0:
-        result.add objectNode(t.n)
+        result.add objectNode(cache, t.n)
       else:
-        result.add ast.emptyNode
+        result.add newNodeI(nkEmpty, info)
     else:
       if allowRecursion or t.sym == nil:
         result = newNodeIT(nkObjectTy, if t.n.isNil: info else: t.n.info, t)
-        result.add ast.emptyNode
+        result.add newNodeI(nkEmpty, info)
         if t.sons[0] == nil:
-          result.add ast.emptyNode
+          result.add newNodeI(nkEmpty, info)
         else:
           result.add mapTypeToAst(t.sons[0], info)
         result.add copyTree(t.n)
@@ -214,17 +184,18 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
         result = atomicType(t.sym)
   of tyEnum:
     result = newNodeIT(nkEnumTy, if t.n.isNil: info else: t.n.info, t)
-    result.add ast.emptyNode  # pragma node, currently always empty for enum
+    result.add newNodeI(nkEmpty, info)  # pragma node, currently always empty for enum
     for c in t.n.sons:
       result.add copyTree(c)
   of tyTuple:
     if inst:
-      result = newNodeX(nkTupleTy)
       # only named tuples have a node, unnamed tuples don't
       if t.n.isNil:
+        result = newNodeX(nkTupleConstr)
         for subType in t.sons:
           result.add mapTypeToAst(subType, info)
       else:
+        result = newNodeX(nkTupleTy)
         for s in t.n.sons:
           result.add newIdentDefs(s)
     else:
@@ -242,28 +213,44 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
       result.add mapTypeToAst(t.sons[0], info)
     else:
       result = mapTypeToBracket("ref", mRef, t, info)
-  of tyVar: result = mapTypeToBracket("var", mVar, t, info)
+  of tyVar:
+    if inst:
+      result = newNodeX(nkVarTy)
+      result.add mapTypeToAst(t.sons[0], info)
+    else:
+      result = mapTypeToBracket("var", mVar, t, info)
+  of tyLent: result = mapTypeToBracket("lent", mBuiltinType, t, info)
+  of tySink: result = mapTypeToBracket("sink", mBuiltinType, t, info)
   of tySequence: result = mapTypeToBracket("seq", mSeq, t, info)
+  of tyOpt: result = mapTypeToBracket("opt", mOpt, t, info)
   of tyProc:
     if inst:
       result = newNodeX(nkProcTy)
       var fp = newNodeX(nkFormalParams)
       if t.sons[0] == nil:
-        fp.add ast.emptyNode
+        fp.add newNodeI(nkEmpty, info)
       else:
         fp.add mapTypeToAst(t.sons[0], t.n[0].info)
       for i in 1..<t.sons.len:
         fp.add newIdentDefs(t.n[i], t.sons[i])
       result.add fp
-      result.add ast.emptyNode  # pragmas aren't reconstructed yet
+      result.add if t.n[0].len > 0: t.n[0][pragmasEffects].copyTree
+                 else: newNodeI(nkEmpty, info)
     else:
       result = mapTypeToBracket("proc", mNone, t, info)
   of tyOpenArray: result = mapTypeToBracket("openArray", mOpenArray, t, info)
   of tyRange:
     result = newNodeIT(nkBracketExpr, if t.n.isNil: info else: t.n.info, t)
     result.add atomicType("range", mRange)
-    result.add t.n.sons[0].copyTree
-    result.add t.n.sons[1].copyTree
+    if inst:
+      let rng = newNodeX(nkInfix)
+      rng.add newIdentNode(getIdent(cache, ".."), info)
+      rng.add t.n.sons[0].copyTree
+      rng.add t.n.sons[1].copyTree
+      result.add rng
+    else:
+      result.add t.n.sons[0].copyTree
+      result.add t.n.sons[1].copyTree
   of tyPointer: result = atomicType("pointer", mPointer)
   of tyString: result = atomicType("string", mString)
   of tyCString: result = atomicType("cstring", mCString)
@@ -285,16 +272,20 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
   of tyProxy: result = atomicType("error", mNone)
   of tyBuiltInTypeClass:
     result = mapTypeToBracket("builtinTypeClass", mNone, t, info)
-  of tyUserTypeClass:
-    result = mapTypeToBracket("concept", mNone, t, info)
-    result.add t.n.copyTree
+  of tyUserTypeClass, tyUserTypeClassInst:
+    if t.isResolvedUserTypeClass:
+      result = mapTypeToAst(t.lastSon, info)
+    else:
+      result = mapTypeToBracket("concept", mNone, t, info)
+      result.add t.n.copyTree
   of tyCompositeTypeClass:
     result = mapTypeToBracket("compositeTypeClass", mNone, t, info)
   of tyAnd: result = mapTypeToBracket("and", mAnd, t, info)
   of tyOr: result = mapTypeToBracket("or", mOr, t, info)
   of tyNot: result = mapTypeToBracket("not", mNot, t, info)
   of tyAnything: result = atomicType("anything", mNone)
-  of tyStatic, tyFromExpr, tyFieldAccessor:
+  of tyInferred: assert false
+  of tyStatic, tyFromExpr:
     if inst:
       if t.n != nil: result = t.n.copyTree
       else: result = atomicType("void", mVoid)
@@ -303,17 +294,17 @@ proc mapTypeToAstX(t: PType; info: TLineInfo;
       result.add atomicType("static", mNone)
       if t.n != nil:
         result.add t.n.copyTree
-  of tyUnused, tyUnused0, tyUnused1, tyUnused2: internalError("mapTypeToAstX")
+  of tyOptAsRef: assert(false, "mapTypeToAstX")
 
-proc opMapTypeToAst*(t: PType; info: TLineInfo): PNode =
-  result = mapTypeToAstX(t, info, false, true)
+proc opMapTypeToAst*(cache: IdentCache; t: PType; info: TLineInfo): PNode =
+  result = mapTypeToAstX(cache, t, info, false, true)
 
 # the "Inst" version includes generic parameters in the resulting type tree
 # and also tries to look like the corresponding Nim type declaration
-proc opMapTypeInstToAst*(t: PType; info: TLineInfo): PNode =
-  result = mapTypeToAstX(t, info, true, false)
+proc opMapTypeInstToAst*(cache: IdentCache; t: PType; info: TLineInfo): PNode =
+  result = mapTypeToAstX(cache, t, info, true, false)
 
 # the "Impl" version includes generic parameters in the resulting type tree
 # and also tries to look like the corresponding Nim type implementation
-proc opMapTypeImplToAst*(t: PType; info: TLineInfo): PNode =
-  result = mapTypeToAstX(t, info, true, true)
+proc opMapTypeImplToAst*(cache: IdentCache; t: PType; info: TLineInfo): PNode =
+  result = mapTypeToAstX(cache, t, info, true, true)
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 125fe8ae0..1f2a3e6d1 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -29,19 +29,21 @@
 
 import
   strutils, ast, astalgo, types, msgs, renderer, vmdef,
-  trees, intsets, rodread, magicsys, options, lowerings
-
+  trees, intsets, magicsys, options, lowerings, lineinfos, transf
+import platform
 from os import splitFile
 
 when hasFFI:
   import evalffi
 
 type
-  TGenFlag = enum gfAddrOf, gfFieldAccess
+  TGenFlag = enum
+    gfNode # Affects how variables are loaded - always loads as rkNode
+    gfNodeAddr # Affects how variables are loaded - always loads as rkNodeAddr
   TGenFlags = set[TGenFlag]
 
-proc debugInfo(info: TLineInfo): string =
-  result = info.toFilename.splitFile.name & ":" & $info.line
+proc debugInfo(c: PCtx; info: TLineInfo): string =
+  result = toFilename(c.config, info).splitFile.name & ":" & $info.line
 
 proc codeListing(c: PCtx, result: var string, start=0; last = -1) =
   # first iteration: compute all necessary labels:
@@ -85,7 +87,7 @@ proc codeListing(c: PCtx, result: var string, start=0; last = -1) =
     else:
       result.addf("\t$#\tr$#, $#", ($opc).substr(3), x.regA, x.regBx-wordExcess)
     result.add("\t#")
-    result.add(debugInfo(c.debug[i]))
+    result.add(debugInfo(c, c.debug[i]))
     result.add("\n")
     inc i
 
@@ -120,7 +122,7 @@ proc gABI(c: PCtx; n: PNode; opc: TOpcode; a, b: TRegister; imm: BiggestInt) =
     c.code.add(ins)
     c.debug.add(n.info)
   else:
-    localError(n.info, errGenerated,
+    localError(c.config, n.info,
       "VM: immediate value does not fit into an int8")
 
 proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) =
@@ -137,7 +139,7 @@ proc gABx(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0; bx: int) =
     c.code.add(ins)
     c.debug.add(n.info)
   else:
-    localError(n.info, errGenerated,
+    localError(c.config, n.info,
       "VM: immediate value does not fit into an int16")
 
 proc xjmp(c: PCtx; n: PNode; opc: TOpcode; a: TRegister = 0): TPosition =
@@ -151,7 +153,7 @@ proc genLabel(c: PCtx): TPosition =
 
 proc jmpBack(c: PCtx, n: PNode, p = TPosition(0)) =
   let dist = p.int - c.code.len
-  internalAssert(-0x7fff < dist and dist < 0x7fff)
+  internalAssert(c.config, -0x7fff < dist and dist < 0x7fff)
   gABx(c, n, opcJmpBack, 0, dist)
 
 proc patch(c: PCtx, p: TPosition) =
@@ -159,7 +161,7 @@ proc patch(c: PCtx, p: TPosition) =
   let p = p.int
   let diff = c.code.len - p
   #c.jumpTargets.incl(c.code.len)
-  internalAssert(-0x7fff < diff and diff < 0x7fff)
+  internalAssert(c.config, -0x7fff < diff and diff < 0x7fff)
   let oldInstr = c.code[p]
   # opcode and regA stay the same:
   c.code[p] = ((oldInstr.uint32 and 0xffff'u32).uint32 or
@@ -180,7 +182,10 @@ const
   HighRegisterPressure = 40
 
 proc bestEffort(c: PCtx): TLineInfo =
-  (if c.prc == nil: c.module.info else: c.prc.sym.info)
+  if c.prc != nil and c.prc.sym != nil:
+    c.prc.sym.info
+  else:
+    c.module.info
 
 proc getTemp(cc: PCtx; tt: PType): TRegister =
   let typ = tt.skipTypesOrNil({tyStatic})
@@ -201,7 +206,7 @@ proc getTemp(cc: PCtx; tt: PType): TRegister =
         c.slots[i] = (inUse: true, kind: k)
         return TRegister(i)
   if c.maxSlots >= high(TRegister):
-    globalError(cc.bestEffort, "VM problem: too many registers required")
+    globalError(cc.config, cc.bestEffort, "VM problem: too many registers required")
   result = TRegister(c.maxSlots)
   c.slots[c.maxSlots] = (inUse: true, kind: k)
   inc c.maxSlots
@@ -223,7 +228,7 @@ proc getTempRange(cc: PCtx; n: int; kind: TSlotKind): TRegister =
           for k in result .. result+n-1: c.slots[k] = (inUse: true, kind: kind)
           return
   if c.maxSlots+n >= high(TRegister):
-    globalError(cc.bestEffort, "VM problem: too many registers required")
+    globalError(cc.config, cc.bestEffort, "VM problem: too many registers required")
   result = TRegister(c.maxSlots)
   inc c.maxSlots, n
   for k in result .. result+n-1: c.slots[k] = (inUse: true, kind: kind)
@@ -251,7 +256,7 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {})
 proc gen(c: PCtx; n: PNode; dest: TRegister; flags: TGenFlags = {}) =
   var d: TDest = dest
   gen(c, n, d, flags)
-  internalAssert d == dest
+  #internalAssert c.config, d == dest # issue #7407
 
 proc gen(c: PCtx; n: PNode; flags: TGenFlags = {}) =
   var tmp: TDest = -1
@@ -261,7 +266,7 @@ proc gen(c: PCtx; n: PNode; flags: TGenFlags = {}) =
 proc genx(c: PCtx; n: PNode; flags: TGenFlags = {}): TRegister =
   var tmp: TDest = -1
   gen(c, n, tmp, flags)
-  #internalAssert tmp >= 0 # 'nim check' does not like this internalAssert.
+  #internalAssert c.config, tmp >= 0 # 'nim check' does not like this internalAssert.
   if tmp >= 0:
     result = TRegister(tmp)
 
@@ -320,7 +325,7 @@ proc genBreak(c: PCtx; n: PNode) =
       if c.prc.blocks[i].label == n.sons[0].sym:
         c.prc.blocks[i].fixups.add L1
         return
-    globalError(n.info, errGenerated, "VM problem: cannot find 'break' target")
+    globalError(c.config, n.info, "VM problem: cannot find 'break' target")
   else:
     c.prc.blocks[c.prc.blocks.high].fixups.add L1
 
@@ -378,7 +383,7 @@ proc rawGenLiteral(c: PCtx; n: PNode): int =
   #assert(n.kind != nkCall)
   n.flags.incl nfAllConst
   c.constants.add n.canonValue
-  internalAssert result < 0x7fff
+  internalAssert c.config, result < 0x7fff
 
 proc sameConstant*(a, b: PNode): bool =
   result = false
@@ -401,14 +406,14 @@ proc sameConstant*(a, b: PNode): bool =
 
 proc genLiteral(c: PCtx; n: PNode): int =
   # types do not matter here:
-  for i in 0 .. <c.constants.len:
+  for i in 0 ..< c.constants.len:
     if sameConstant(c.constants[i], n): return i
   result = rawGenLiteral(c, n)
 
-proc unused(n: PNode; x: TDest) {.inline.} =
+proc unused(c: PCtx; n: PNode; x: TDest) {.inline.} =
   if x >= 0:
     #debug(n)
-    globalError(n.info, "not unused")
+    globalError(c.config, n.info, "not unused")
 
 proc genCase(c: PCtx; n: PNode; dest: var TDest) =
   #  if (!expr1) goto L1;
@@ -424,13 +429,13 @@ proc genCase(c: PCtx; n: PNode; dest: var TDest) =
   if not isEmptyType(n.typ):
     if dest < 0: dest = getTemp(c, n.typ)
   else:
-    unused(n, dest)
+    unused(c, n, dest)
   var endings: seq[TPosition] = @[]
   withTemp(tmp, n.sons[0].typ):
     c.gen(n.sons[0], tmp)
     # branch tmp, codeIdx
     # fjmp   elseLabel
-    for i in 1 .. <n.len:
+    for i in 1 ..< n.len:
       let it = n.sons[i]
       if it.len == 1:
         # else stmt:
@@ -451,7 +456,7 @@ proc genType(c: PCtx; typ: PType): int =
     if sameType(t, typ): return i
   result = c.types.len
   c.types.add(typ)
-  internalAssert(result <= 0x7fff)
+  internalAssert(c.config, result <= 0x7fff)
 
 proc genTry(c: PCtx; n: PNode; dest: var TDest) =
   if dest < 0 and not isEmptyType(n.typ): dest = getTemp(c, n.typ)
@@ -460,7 +465,7 @@ proc genTry(c: PCtx; n: PNode; dest: var TDest) =
   c.gen(n.sons[0], dest)
   c.clearDest(n, dest)
   c.patch(elsePos)
-  for i in 1 .. <n.len:
+  for i in 1 ..< n.len:
     let it = n.sons[i]
     if it.kind != nkFinally:
       var blen = len(it)
@@ -518,14 +523,14 @@ proc genCall(c: PCtx; n: PNode; dest: var TDest) =
   let x = c.getTempRange(n.len, slotTempUnknown)
   # varargs need 'opcSetType' for the FFI support:
   let fntyp = skipTypes(n.sons[0].typ, abstractInst)
-  for i in 0.. <n.len:
+  for i in 0..<n.len:
     #if i > 0 and i < sonsLen(fntyp):
     #  let paramType = fntyp.n.sons[i]
     #  if paramType.typ.isCompileTimeOnly: continue
     var r: TRegister = x+i
     c.gen(n.sons[i], r)
     if i >= fntyp.len:
-      internalAssert tfVarargs in fntyp.flags
+      internalAssert c.config, tfVarargs in fntyp.flags
       c.gABx(n, opcSetType, r, c.genType(n.sons[i].typ))
   if dest < 0:
     c.gABC(n, opcIndCall, 0, x, n.len)
@@ -540,17 +545,17 @@ proc needsAsgnPatch(n: PNode): bool =
   n.kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr,
              nkDerefExpr, nkHiddenDeref} or (n.kind == nkSym and n.sym.isGlobal)
 
-proc genField(n: PNode): TRegister =
+proc genField(c: PCtx; n: PNode): TRegister =
   if n.kind != nkSym or n.sym.kind != skField:
-    globalError(n.info, "no field symbol")
+    globalError(c.config, n.info, "no field symbol")
   let s = n.sym
   if s.position > high(result):
-    globalError(n.info,
+    globalError(c.config, n.info,
         "too large offset! cannot generate code for: " & s.name.s)
   result = s.position
 
 proc genIndex(c: PCtx; n: PNode; arr: PType): TRegister =
-  if arr.skipTypes(abstractInst).kind == tyArray and (let x = firstOrd(arr);
+  if arr.skipTypes(abstractInst).kind == tyArray and (let x = firstOrd(c.config, arr);
       x != 0):
     let tmp = c.genx(n)
     # freeing the temporary here means we can produce:  regA = regA - Imm
@@ -560,28 +565,34 @@ proc genIndex(c: PCtx; n: PNode; arr: PType): TRegister =
   else:
     result = c.genx(n)
 
+proc genCheckedObjAccessAux(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags)
+
 proc genAsgnPatch(c: PCtx; le: PNode, value: TRegister) =
   case le.kind
   of nkBracketExpr:
-    let dest = c.genx(le.sons[0], {gfAddrOf, gfFieldAccess})
+    let dest = c.genx(le.sons[0], {gfNode})
     let idx = c.genIndex(le.sons[1], le.sons[0].typ)
     c.gABC(le, opcWrArr, dest, idx, value)
     c.freeTemp(dest)
     c.freeTemp(idx)
-  of nkDotExpr, nkCheckedFieldExpr:
-    # XXX field checks here
-    let left = if le.kind == nkDotExpr: le else: le.sons[0]
-    let dest = c.genx(left.sons[0], {gfAddrOf, gfFieldAccess})
-    let idx = genField(left.sons[1])
-    c.gABC(left, opcWrObj, dest, idx, value)
+  of nkCheckedFieldExpr:
+    var objR: TDest = -1
+    genCheckedObjAccessAux(c, le, objR, {gfNode})
+    let idx = genField(c, le[0].sons[1])
+    c.gABC(le[0], opcWrObj, objR, idx, value)
+    c.freeTemp(objR)
+  of nkDotExpr:
+    let dest = c.genx(le.sons[0], {gfNode})
+    let idx = genField(c, le.sons[1])
+    c.gABC(le, opcWrObj, dest, idx, value)
     c.freeTemp(dest)
   of nkDerefExpr, nkHiddenDeref:
-    let dest = c.genx(le.sons[0], {gfAddrOf})
+    let dest = c.genx(le.sons[0], {gfNode})
     c.gABC(le, opcWrDeref, dest, 0, value)
     c.freeTemp(dest)
   of nkSym:
     if le.sym.isGlobal:
-      let dest = c.genx(le, {gfAddrOf})
+      let dest = c.genx(le, {gfNodeAddr})
       c.gABC(le, opcWrDeref, dest, 0, value)
       c.freeTemp(dest)
   else:
@@ -625,10 +636,10 @@ proc genUnaryABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
   c.gABC(n, opc, dest, tmp)
   c.freeTemp(tmp)
 
-proc genUnaryABI(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
+proc genUnaryABI(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode; imm: BiggestInt=0) =
   let tmp = c.genx(n.sons[1])
   if dest < 0: dest = c.getTemp(n.typ)
-  c.gABI(n, opc, dest, tmp, 0)
+  c.gABI(n, opc, dest, tmp, imm)
   c.freeTemp(tmp)
 
 proc genBinaryABC(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
@@ -656,16 +667,17 @@ proc genNarrow(c: PCtx; n: PNode; dest: TDest) =
   let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
   # uint is uint64 in the VM, we we only need to mask the result for
   # other unsigned types:
-  if t.kind in {tyUInt8..tyUInt32}:
+  if t.kind in {tyUInt8..tyUInt32} or (t.kind == tyUInt and t.size < 8):
     c.gABC(n, opcNarrowU, dest, TRegister(t.size*8))
-  elif t.kind in {tyInt8..tyInt32}:
+  elif t.kind in {tyInt8..tyInt32} or (t.kind == tyInt and t.size < 8):
     c.gABC(n, opcNarrowS, dest, TRegister(t.size*8))
 
 proc genNarrowU(c: PCtx; n: PNode; dest: TDest) =
   let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
   # uint is uint64 in the VM, we we only need to mask the result for
   # other unsigned types:
-  if t.kind in {tyUInt8..tyUInt32, tyInt8..tyInt32}:
+  if t.kind in {tyUInt8..tyUInt32, tyInt8..tyInt32} or
+    (t.kind in {tyUInt, tyInt} and t.size < 8):
     c.gABC(n, opcNarrowU, dest, TRegister(t.size*8))
 
 proc genBinaryABCnarrow(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
@@ -760,6 +772,117 @@ proc genCard(c: PCtx; n: PNode; dest: var TDest) =
   c.gABC(n, opcCard, dest, tmp)
   c.freeTemp(tmp)
 
+proc genCastIntFloat(c: PCtx; n: PNode; dest: var TDest) =
+  const allowedIntegers = {tyInt..tyInt64, tyUInt..tyUInt64, tyChar}
+  var signedIntegers = {tyInt8..tyInt32}
+  var unsignedIntegers = {tyUInt8..tyUInt32, tyChar}
+  let src = n.sons[1].typ.skipTypes(abstractRange)#.kind
+  let dst = n.sons[0].typ.skipTypes(abstractRange)#.kind
+  let src_size = getSize(c.config, src)
+  let dst_size = getSize(c.config, dst)
+  if c.config.target.intSize < 8:
+    signedIntegers.incl(tyInt)
+    unsignedIntegers.incl(tyUInt)
+  if src_size == dst_size and src.kind in allowedIntegers and
+                                 dst.kind in allowedIntegers:
+    let tmp = c.genx(n.sons[1])
+    var tmp2 = c.getTemp(n.sons[1].typ)
+    let tmp3 = c.getTemp(n.sons[1].typ)
+    if dest < 0: dest = c.getTemp(n[0].typ)
+    proc mkIntLit(ival: int): int =
+      result = genLiteral(c, newIntTypeNode(nkIntLit, ival, getSysType(c.graph, n.info, tyInt)))
+    if src.kind in unsignedIntegers and dst.kind in signedIntegers:
+      # cast unsigned to signed integer of same size
+      # signedVal = (unsignedVal xor offset) -% offset
+      let offset = 1 shl (src_size * 8 - 1)
+      c.gABx(n, opcLdConst, tmp2, mkIntLit(offset))
+      c.gABC(n, opcBitxorInt, tmp3, tmp, tmp2)
+      c.gABC(n, opcSubInt, dest, tmp3, tmp2)
+    elif src.kind in signedIntegers and dst.kind in unsignedIntegers:
+      # cast signed to unsigned integer of same size
+      # unsignedVal = (offset +% signedVal +% 1) and offset
+      let offset = (1 shl (src_size * 8)) - 1
+      c.gABx(n, opcLdConst, tmp2, mkIntLit(offset))
+      c.gABx(n, opcLdConst, dest, mkIntLit(offset+1))
+      c.gABC(n, opcAddu, tmp3, tmp, dest)
+      c.gABC(n, opcNarrowU, tmp3, TRegister(src_size*8))
+      c.gABC(n, opcBitandInt, dest, tmp3, tmp2)
+    else:
+      c.gABC(n, opcAsgnInt, dest, tmp)
+    c.freeTemp(tmp)
+    c.freeTemp(tmp2)
+    c.freeTemp(tmp3)
+  elif src_size == dst_size and src.kind in allowedIntegers and
+                           dst.kind in {tyFloat, tyFloat32, tyFloat64}:
+    let tmp = c.genx(n[1])
+    if dest < 0: dest = c.getTemp(n[0].typ)
+    if dst.kind == tyFloat32:
+      c.gABC(n, opcAsgnFloat32FromInt, dest, tmp)
+    else:
+      c.gABC(n, opcAsgnFloat64FromInt, dest, tmp)
+    c.freeTemp(tmp)
+
+  elif src_size == dst_size and src.kind in {tyFloat, tyFloat32, tyFloat64} and
+                           dst.kind in allowedIntegers:
+    let tmp = c.genx(n[1])
+    if dest < 0: dest = c.getTemp(n[0].typ)
+    if src.kind == tyFloat32:
+      c.gABC(n, opcAsgnIntFromFloat32, dest, tmp)
+    else:
+      c.gABC(n, opcAsgnIntFromFloat64, dest, tmp)
+    c.freeTemp(tmp)
+
+  else:
+    globalError(c.config, n.info, "VM is only allowed to 'cast' between integers and/or floats of same size")
+
+proc genVoidABC(c: PCtx, n: PNode, dest: TDest, opcode: TOpcode) =
+  unused(c, n, dest)
+  var
+    tmp1 = c.genx(n[1])
+    tmp2 = c.genx(n[2])
+    tmp3 = c.genx(n[3])
+  c.gABC(n, opcode, tmp1, tmp2, tmp3)
+  c.freeTemp(tmp1)
+  c.freeTemp(tmp2)
+  c.freeTemp(tmp3)
+
+proc genBindSym(c: PCtx; n: PNode; dest: var TDest) =
+  # nah, cannot use c.config.features because sempass context
+  # can have local experimental switch
+  # if dynamicBindSym notin c.config.features:
+  if n.len == 2: # hmm, reliable?
+    # bindSym with static input
+    if n[1].kind in {nkClosedSymChoice, nkOpenSymChoice, nkSym}:
+      let idx = c.genLiteral(n[1])
+      if dest < 0: dest = c.getTemp(n.typ)
+      c.gABx(n, opcNBindSym, dest, idx)
+    else:
+      localError(c.config, n.info, "invalid bindSym usage")
+  else:
+    # experimental bindSym
+    if dest < 0: dest = c.getTemp(n.typ)
+    let x = c.getTempRange(n.len, slotTempUnknown)
+
+    # callee symbol
+    var tmp0 = TDest(x)
+    c.genLit(n.sons[0], tmp0)
+
+    # original parameters
+    for i in 1..<n.len-2:
+      var r = TRegister(x+i)
+      c.gen(n.sons[i], r)
+
+    # info node
+    var tmp1 = TDest(x+n.len-2)
+    c.genLit(n.sons[^2], tmp1)
+
+    # payload idx
+    var tmp2 = TDest(x+n.len-1)
+    c.genLit(n.sons[^1], tmp2)
+
+    c.gABC(n, opcNDynBindSym, dest, x, n.len)
+    c.freeTempRange(x, n.len)
+
 proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
   case m
   of mAnd: c.genAndOr(n, opcFJmp, dest)
@@ -774,7 +897,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
   of mSucc, mAddI:
     c.genAddSubInt(n, dest, opcAddInt)
   of mInc, mDec:
-    unused(n, dest)
+    unused(c, n, dest)
     let opc = if m == mInc: opcAddInt else: opcSubInt
     let d = c.genx(n.sons[1])
     if n.sons[2].isInt8Lit:
@@ -788,10 +911,10 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     c.freeTemp(d)
   of mOrd, mChr, mArrToSeq: c.gen(n.sons[1], dest)
   of mNew, mNewFinalize:
-    unused(n, dest)
+    unused(c, n, dest)
     c.genNew(n)
   of mNewSeq:
-    unused(n, dest)
+    unused(c, n, dest)
     c.genNewSeq(n)
   of mNewSeqOfCap: c.genNewSeqOfCap(n, dest)
   of mNewString:
@@ -800,7 +923,8 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
   of mNewStringOfCap:
     # we ignore the 'cap' argument and translate it as 'newString(0)'.
     # eval n.sons[1] for possible side effects:
-    var tmp = c.genx(n.sons[1])
+    c.freeTemp(c.genx(n.sons[1]))
+    var tmp = c.getTemp(n.sons[1].typ)
     c.gABx(n, opcLdImmInt, tmp, 0)
     if dest < 0: dest = c.getTemp(n.typ)
     c.gABC(n, opcNewStr, dest, tmp)
@@ -811,7 +935,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
   of mLengthStr, mXLenStr:
     genUnaryABI(c, n, dest, opcLenStr)
   of mIncl, mExcl:
-    unused(n, dest)
+    unused(c, n, dest)
     var d = c.genx(n.sons[1])
     var tmp = c.genx(n.sons[2])
     c.genSetType(n.sons[1], d)
@@ -826,8 +950,25 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
   of mSubF64: genBinaryABC(c, n, dest, opcSubFloat)
   of mMulF64: genBinaryABC(c, n, dest, opcMulFloat)
   of mDivF64: genBinaryABC(c, n, dest, opcDivFloat)
-  of mShrI: genBinaryABCnarrowU(c, n, dest, opcShrInt)
+  of mShrI:
+    # the idea here is to narrow type if needed before executing right shift
+    # inlined modified: genNarrowU(c, n, dest)
+    let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
+    # uint is uint64 in the VM, we we only need to mask the result for
+    # other unsigned types:
+    let tmp = c.genx(n.sons[1])
+    if t.kind in {tyUInt8..tyUInt32, tyInt8..tyInt32}:
+      c.gABC(n, opcNarrowU, tmp, TRegister(t.size*8))
+
+    # inlined modified: genBinaryABC(c, n, dest, opcShrInt)
+    let tmp2 = c.genx(n.sons[2])
+    if dest < 0: dest = c.getTemp(n.typ)
+    c.gABC(n, opcShrInt, dest, tmp, tmp2)
+    c.freeTemp(tmp)
+    c.freeTemp(tmp2)
+
   of mShlI: genBinaryABCnarrowU(c, n, dest, opcShlInt)
+  of mAshrI: genBinaryABCnarrow(c, n, dest, opcAshrInt)
   of mBitandI: genBinaryABCnarrowU(c, n, dest, opcBitandInt)
   of mBitorI: genBinaryABCnarrowU(c, n, dest, opcBitorInt)
   of mBitxorI: genBinaryABCnarrowU(c, n, dest, opcBitxorInt)
@@ -859,11 +1000,25 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
   of mBitnotI:
     genUnaryABC(c, n, dest, opcBitnotInt)
     genNarrowU(c, n, dest)
-  of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64,
-     mToU8, mToU16, mToU32, mToFloat, mToBiggestFloat, mToInt,
+  of mToFloat, mToBiggestFloat, mToInt,
      mToBiggestInt, mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr,
      mFloatToStr, mCStrToStr, mStrToStr, mEnumToStr:
     genConv(c, n, n.sons[1], dest)
+  of mZe8ToI, mZe8ToI64, mZe16ToI, mZe16ToI64, mZe32ToI64, mZeIToI64:
+    #genNarrowU modified
+    let t = skipTypes(n.sons[1].typ, abstractVar-{tyTypeDesc})
+    let tmp = c.genx(n.sons[1])
+    c.gABC(n, opcNarrowU, tmp, TRegister(t.size*8))
+    # assign result to dest register
+    if dest < 0: dest = c.getTemp(n.typ)
+    c.gABC(n, opcAsgnInt, dest, tmp)
+    c.freeTemp(tmp)
+  of mToU8, mToU16, mToU32:
+    let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
+    var tmp = c.genx(n.sons[1])
+    if dest < 0: dest = c.getTemp(n.typ)
+    c.gABC(n, opcToNarrowInt, dest, tmp, TRegister(t.size*8))
+    c.freeTemp(tmp)
   of mEqStr, mEqCString: genBinaryABC(c, n, dest, opcEqStr)
   of mLeStr: genBinaryABC(c, n, dest, opcLeStr)
   of mLtStr: genBinaryABC(c, n, dest, opcLtStr)
@@ -878,20 +1033,20 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
   of mInSet: genBinarySet(c, n, dest, opcContainsSet)
   of mRepr: genUnaryABC(c, n, dest, opcRepr)
   of mExit:
-    unused(n, dest)
+    unused(c, n, dest)
     var tmp = c.genx(n.sons[1])
     c.gABC(n, opcQuit, tmp)
     c.freeTemp(tmp)
   of mSetLengthStr, mSetLengthSeq:
-    unused(n, dest)
+    unused(c, n, dest)
     var d = c.genx(n.sons[1])
     var tmp = c.genx(n.sons[2])
     c.gABC(n, if m == mSetLengthStr: opcSetLenStr else: opcSetLenSeq, d, tmp)
     c.genAsgnPatch(n.sons[1], d)
     c.freeTemp(tmp)
   of mSwap:
-    unused(n, dest)
-    c.gen(lowerSwap(n, if c.prc == nil: c.module else: c.prc.sym))
+    unused(c, n, dest)
+    c.gen(lowerSwap(c.graph, n, if c.prc == nil: c.module else: c.prc.sym))
   of mIsNil: genUnaryABC(c, n, dest, opcIsNil)
   of mCopyStr:
     if dest < 0: dest = c.getTemp(n.typ)
@@ -922,7 +1077,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     # skip 'nkHiddenAddr':
     let d2AsNode = n.sons[2].sons[0]
     if needsAsgnPatch(d2AsNode):
-      d2 = c.getTemp(getSysType(tyFloat))
+      d2 = c.getTemp(getSysType(c.graph, n.info, tyFloat))
     else:
       d2 = c.genx(d2AsNode)
     var
@@ -935,21 +1090,19 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     c.genAsgnPatch(d2AsNode, d2)
     c.freeTemp(d2)
   of mReset:
-    unused(n, dest)
+    unused(c, n, dest)
     var d = c.genx(n.sons[1])
     c.gABC(n, opcReset, d)
   of mOf, mIs:
     if dest < 0: dest = c.getTemp(n.typ)
     var tmp = c.genx(n.sons[1])
-    var idx = c.getTemp(getSysType(tyInt))
+    var idx = c.getTemp(getSysType(c.graph, n.info, tyInt))
     var typ = n.sons[2].typ
-    if m == mOf: typ = typ.skipTypes(abstractPtrs-{tyTypeDesc})
+    if m == mOf: typ = typ.skipTypes(abstractPtrs)
     c.gABx(n, opcLdImmInt, idx, c.genType(typ))
     c.gABC(n, if m == mOf: opcOf else: opcIs, dest, tmp, idx)
     c.freeTemp(tmp)
     c.freeTemp(idx)
-  of mSizeOf:
-    globalError(n.info, errCannotInterpretNodeX, renderTree(n))
   of mHigh:
     if dest < 0: dest = c.getTemp(n.typ)
     let tmp = c.genx(n.sons[1])
@@ -960,23 +1113,24 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
       c.gABI(n, opcLenSeq, dest, tmp, 1)
     c.freeTemp(tmp)
   of mEcho:
-    unused(n, dest)
+    unused(c, n, dest)
     let n = n[1].skipConv
-    let x = c.getTempRange(n.len, slotTempUnknown)
-    internalAssert n.kind == nkBracket
-    for i in 0.. <n.len:
-      var r: TRegister = x+i
-      c.gen(n.sons[i], r)
-    c.gABC(n, opcEcho, x, n.len)
-    c.freeTempRange(x, n.len)
+    if n.kind == nkBracket:
+      # can happen for nim check, see bug #9609
+      let x = c.getTempRange(n.len, slotTempUnknown)
+      for i in 0..<n.len:
+        var r: TRegister = x+i
+        c.gen(n.sons[i], r)
+      c.gABC(n, opcEcho, x, n.len)
+      c.freeTempRange(x, n.len)
   of mAppendStrCh:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmtVar(c, n, opcAddStrCh)
   of mAppendStrStr:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmtVar(c, n, opcAddStrStr)
   of mAppendSeqElem:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmtVar(c, n, opcAddSeqElem)
   of mParseExprToAst:
     genUnaryABC(c, n, dest, opcParseExprToAst)
@@ -990,22 +1144,31 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     c.freeTemp(tmp)
   of mSlurp: genUnaryABC(c, n, dest, opcSlurp)
   of mStaticExec: genBinaryABCD(c, n, dest, opcGorge)
-  of mNLen: genUnaryABI(c, n, dest, opcLenSeq)
+  of mNLen: genUnaryABI(c, n, dest, opcLenSeq, nimNodeFlag)
   of mGetImpl: genUnaryABC(c, n, dest, opcGetImpl)
+  of mGetImplTransf: genUnaryABC(c, n, dest, opcGetImplTransf)
+  of mSymOwner: genUnaryABC(c, n, dest, opcSymOwner)
+  of mSymIsInstantiationOf: genBinaryABC(c, n, dest, opcSymIsInstantiationOf)
   of mNChild: genBinaryABC(c, n, dest, opcNChild)
-  of mNSetChild, mNDel:
-    unused(n, dest)
-    var
-      tmp1 = c.genx(n.sons[1])
-      tmp2 = c.genx(n.sons[2])
-      tmp3 = c.genx(n.sons[3])
-    c.gABC(n, if m == mNSetChild: opcNSetChild else: opcNDel, tmp1, tmp2, tmp3)
-    c.freeTemp(tmp1)
-    c.freeTemp(tmp2)
-    c.freeTemp(tmp3)
+  of mNSetChild: genVoidABC(c, n, dest, opcNSetChild)
+  of mNDel: genVoidABC(c, n, dest, opcNDel)
   of mNAdd: genBinaryABC(c, n, dest, opcNAdd)
   of mNAddMultiple: genBinaryABC(c, n, dest, opcNAddMultiple)
   of mNKind: genUnaryABC(c, n, dest, opcNKind)
+  of mNSymKind: genUnaryABC(c, n, dest, opcNSymKind)
+
+  of mNccValue: genUnaryABC(c, n, dest, opcNccValue)
+  of mNccInc: genBinaryABC(c, n, dest, opcNccInc)
+  of mNcsAdd: genBinaryABC(c, n, dest, opcNcsAdd)
+  of mNcsIncl: genBinaryABC(c, n, dest, opcNcsIncl)
+  of mNcsLen: genUnaryABC(c, n, dest, opcNcsLen)
+  of mNcsAt: genBinaryABC(c, n, dest, opcNcsAt)
+  of mNctPut: genVoidABC(c, n, dest, opcNctPut)
+  of mNctLen: genUnaryABC(c, n, dest, opcNctLen)
+  of mNctGet: genBinaryABC(c, n, dest, opcNctGet)
+  of mNctHasNext: genBinaryABC(c, n, dest, opcNctHasNext)
+  of mNctNext: genBinaryABC(c, n, dest, opcNctNext)
+
   of mNIntVal: genUnaryABC(c, n, dest, opcNIntVal)
   of mNFloatVal: genUnaryABC(c, n, dest, opcNFloatVal)
   of mNSymbol: genUnaryABC(c, n, dest, opcNSymbol)
@@ -1023,63 +1186,64 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     #genUnaryABC(c, n, dest, opcNGetType)
   of mNStrVal: genUnaryABC(c, n, dest, opcNStrVal)
   of mNSetIntVal:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmt(c, n, opcNSetIntVal)
   of mNSetFloatVal:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmt(c, n, opcNSetFloatVal)
   of mNSetSymbol:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmt(c, n, opcNSetSymbol)
   of mNSetIdent:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmt(c, n, opcNSetIdent)
   of mNSetType:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmt(c, n, opcNSetType)
   of mNSetStrVal:
-    unused(n, dest)
+    unused(c, n, dest)
     genBinaryStmt(c, n, opcNSetStrVal)
   of mNNewNimNode: genBinaryABC(c, n, dest, opcNNewNimNode)
   of mNCopyNimNode: genUnaryABC(c, n, dest, opcNCopyNimNode)
   of mNCopyNimTree: genUnaryABC(c, n, dest, opcNCopyNimTree)
-  of mNBindSym:
-    if n[1].kind in {nkClosedSymChoice, nkOpenSymChoice, nkSym}:
-      let idx = c.genLiteral(n[1])
-      if dest < 0: dest = c.getTemp(n.typ)
-      c.gABx(n, opcNBindSym, dest, idx)
-    else:
-      localError(n.info, "invalid bindSym usage")
+  of mNBindSym: genBindSym(c, n, dest)
   of mStrToIdent: genUnaryABC(c, n, dest, opcStrToIdent)
-  of mIdentToStr: genUnaryABC(c, n, dest, opcIdentToStr)
   of mEqIdent: genBinaryABC(c, n, dest, opcEqIdent)
-  of mEqNimrodNode: genBinaryABC(c, n, dest, opcEqNimrodNode)
+  of mEqNimrodNode: genBinaryABC(c, n, dest, opcEqNimNode)
   of mSameNodeType: genBinaryABC(c, n, dest, opcSameNodeType)
-  of mNLineInfo: genUnaryABC(c, n, dest, opcNLineInfo)
+  of mNLineInfo:
+    case n[0].sym.name.s
+    of "getFile": genUnaryABI(c, n, dest, opcNGetLineInfo, 0)
+    of "getLine": genUnaryABI(c, n, dest, opcNGetLineInfo, 1)
+    of "getColumn": genUnaryABI(c, n, dest, opcNGetLineInfo, 2)
+    of "copyLineInfo":
+      internalAssert c.config, n.len == 3
+      unused(c, n, dest)
+      genBinaryStmt(c, n, opcNSetLineInfo)
+    else: internalAssert c.config, false
   of mNHint:
-    unused(n, dest)
-    genUnaryStmt(c, n, opcNHint)
+    unused(c, n, dest)
+    genBinaryStmt(c, n, opcNHint)
   of mNWarning:
-    unused(n, dest)
-    genUnaryStmt(c, n, opcNWarning)
+    unused(c, n, dest)
+    genBinaryStmt(c, n, opcNWarning)
   of mNError:
     if n.len <= 1:
       # query error condition:
       c.gABC(n, opcQueryErrorFlag, dest)
     else:
       # setter
-      unused(n, dest)
+      unused(c, n, dest)
       genBinaryStmt(c, n, opcNError)
   of mNCallSite:
     if dest < 0: dest = c.getTemp(n.typ)
     c.gABC(n, opcCallSite, dest)
   of mNGenSym: genBinaryABC(c, n, dest, opcGenSym)
-  of mMinI, mMaxI, mAbsF64, mMinF64, mMaxF64, mAbsI,
-     mDotDot:
+  of mMinI, mMaxI, mAbsF64, mMinF64, mMaxF64, mAbsI, mDotDot:
     c.genCall(n, dest)
   of mExpandToAst:
     if n.len != 2:
-      globalError(n.info, errGenerated, "expandToAst requires 1 argument")
+      globalError(c.config, n.info, "expandToAst requires 1 argument")
     let arg = n.sons[1]
     if arg.kind in nkCallKinds:
       #if arg[0].kind != nkSym or arg[0].sym.kind notin {skTemplate, skMacro}:
@@ -1089,10 +1253,15 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
       # do not call clearDest(n, dest) here as getAst has a meta-type as such
       # produces a value
     else:
-      globalError(n.info, "expandToAst requires a call expression")
+      globalError(c.config, n.info, "expandToAst requires a call expression")
+  of mSizeOf, mAlignOf:
+    globalError(c.config, n.info, "cannot evaluate 'sizeof/alignof' because its type is not defined completely")
+  of mRunnableExamples:
+    discard "just ignore any call to runnableExamples"
+  of mDestroy: discard "ignore calls to the default destructor"
   else:
     # mGCref, mGCunref,
-    globalError(n.info, "cannot generate code for: " & $m)
+    globalError(c.config, n.info, "cannot generate code for: " & $m)
 
 proc genMarshalLoad(c: PCtx, n: PNode, dest: var TDest) =
   ## Signature: proc to*[T](data: string): T
@@ -1150,41 +1319,21 @@ proc canElimAddr(n: PNode): PNode =
       # addr ( deref ( x )) --> x
       result = n.sons[0].sons[0]
 
-proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
-                  flags: TGenFlags) =
-  # a nop for certain types
-  let isAddr = opc in {opcAddrNode, opcAddrReg}
-  if isAddr and (let m = canElimAddr(n); m != nil):
+proc genAddr(c: PCtx, n: PNode, dest: var TDest, flags: TGenFlags) =
+  if (let m = canElimAddr(n); m != nil):
     gen(c, m, dest, flags)
     return
 
-  let af = if n[0].kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr}: {gfAddrOf, gfFieldAccess}
-           else: {gfAddrOf}
-  let newflags = if isAddr: flags+af else: flags
-  # consider:
-  # proc foo(f: var ref int) =
-  #   f = new(int)
-  # proc blah() =
-  #   var x: ref int
-  #   foo x
-  #
-  # The type of 'f' is 'var ref int' and of 'x' is 'ref int'. Hence for
-  # nkAddr we must not use 'unneededIndirection', but for deref we use it.
-  if not isAddr and unneededIndirection(n.sons[0]):
-    gen(c, n.sons[0], dest, newflags)
-    if gfAddrOf notin flags and fitsRegister(n.typ):
-      c.gABC(n, opcNodeToReg, dest, dest)
-  elif isAddr and isGlobal(n.sons[0]):
+  let af = if n[0].kind in {nkBracketExpr, nkDotExpr, nkCheckedFieldExpr}: {gfNode}
+           else: {gfNodeAddr}
+  let newflags = flags-{gfNode, gfNodeAddr}+af
+
+  if isGlobal(n.sons[0]):
     gen(c, n.sons[0], dest, flags+af)
   else:
     let tmp = c.genx(n.sons[0], newflags)
     if dest < 0: dest = c.getTemp(n.typ)
-    if not isAddr:
-      gABC(c, n, opc, dest, tmp)
-      assert n.typ != nil
-      if gfAddrOf notin flags and fitsRegister(n.typ):
-        c.gABC(n, opcNodeToReg, dest, dest)
-    elif c.prc.slots[tmp].kind >= slotTempUnknown:
+    if c.prc.slots[tmp].kind >= slotTempUnknown:
       gABC(c, n, opcAddrNode, dest, tmp)
       # hack ahead; in order to fix bug #1781 we mark the temporary as
       # permanent, so that it's not used for anything else:
@@ -1195,6 +1344,19 @@ proc genAddrDeref(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
       gABC(c, n, opcAddrReg, dest, tmp)
     c.freeTemp(tmp)
 
+proc genDeref(c: PCtx, n: PNode, dest: var TDest, flags: TGenFlags) =
+  if unneededIndirection(n.sons[0]):
+    gen(c, n.sons[0], dest, flags)
+    if {gfNodeAddr, gfNode} * flags == {} and fitsRegister(n.typ):
+      c.gABC(n, opcNodeToReg, dest, dest)
+  else:
+    let tmp = c.genx(n.sons[0], flags)
+    if dest < 0: dest = c.getTemp(n.typ)
+    gABC(c, n, opcLdDeref, dest, tmp)
+    assert n.typ != nil
+    if {gfNodeAddr, gfNode} * flags == {} and fitsRegister(n.typ):
+      c.gABC(n, opcNodeToReg, dest, dest)
+
 proc whichAsgnOpc(n: PNode): TOpcode =
   case n.typ.skipTypes(abstractRange-{tyTypeDesc}).kind
   of tyBool, tyChar, tyEnum, tyOrdinal, tyInt..tyInt64, tyUInt..tyUInt64:
@@ -1203,7 +1365,7 @@ proc whichAsgnOpc(n: PNode): TOpcode =
     opcAsgnStr
   of tyFloat..tyFloat128:
     opcAsgnFloat
-  of tyRef, tyNil, tyVar:
+  of tyRef, tyNil, tyVar, tyLent, tyPtr:
     opcAsgnRef
   else:
     opcAsgnComplex
@@ -1221,14 +1383,14 @@ proc setSlot(c: PCtx; v: PSym) =
   if v.position == 0:
     if c.prc.maxSlots == 0: c.prc.maxSlots = 1
     if c.prc.maxSlots >= high(TRegister):
-      globalError(v.info, "cannot generate code; too many registers required")
+      globalError(c.config, v.info, "cannot generate code; too many registers required")
     v.position = c.prc.maxSlots
     c.prc.slots[v.position] = (inUse: true,
         kind: if v.kind == skLet: slotFixedLet else: slotFixedVar)
     inc c.prc.maxSlots
 
-proc cannotEval(n: PNode) {.noinline.} =
-  globalError(n.info, errGenerated, "cannot evaluate at compile time: " &
+proc cannotEval(c: PCtx; n: PNode) {.noinline.} =
+  globalError(c.config, n.info, "cannot evaluate at compile time: " &
     n.renderTree)
 
 proc isOwnedBy(a, b: PSym): bool =
@@ -1248,10 +1410,10 @@ proc checkCanEval(c: PCtx; n: PNode) =
   if {sfCompileTime, sfGlobal} <= s.flags: return
   if s.kind in {skVar, skTemp, skLet, skParam, skResult} and
       not s.isOwnedBy(c.prc.sym) and s.owner != c.module and c.mode != emRepl:
-    cannotEval(n)
-  elif s.kind in {skProc, skConverter, skMethod,
+    cannotEval(c, n)
+  elif s.kind in {skProc, skFunc, skConverter, skMethod,
                   skIterator} and sfForward in s.flags:
-    cannotEval(n)
+    cannotEval(c, n)
 
 proc isTemp(c: PCtx; dest: TDest): bool =
   result = dest >= 0 and c.prc.slots[dest].kind >= slotTempUnknown
@@ -1280,7 +1442,7 @@ proc preventFalseAlias(c: PCtx; n: PNode; opc: TOpcode;
 proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) =
   case le.kind
   of nkBracketExpr:
-    let dest = c.genx(le.sons[0], {gfAddrOf, gfFieldAccess})
+    let dest = c.genx(le.sons[0], {gfNode})
     let idx = c.genIndex(le.sons[1], le.sons[0].typ)
     let tmp = c.genx(ri)
     if le.sons[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind in {
@@ -1289,16 +1451,22 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) =
     else:
       c.preventFalseAlias(le, opcWrArr, dest, idx, tmp)
     c.freeTemp(tmp)
-  of nkDotExpr, nkCheckedFieldExpr:
-    # XXX field checks here
-    let left = if le.kind == nkDotExpr: le else: le.sons[0]
-    let dest = c.genx(left.sons[0], {gfAddrOf, gfFieldAccess})
-    let idx = genField(left.sons[1])
+  of nkCheckedFieldExpr:
+    var objR: TDest = -1
+    genCheckedObjAccessAux(c, le, objR, {gfNode})
+    let idx = genField(c, le[0].sons[1])
     let tmp = c.genx(ri)
-    c.preventFalseAlias(left, opcWrObj, dest, idx, tmp)
+    c.preventFalseAlias(le[0], opcWrObj, objR, idx, tmp)
+    c.freeTemp(tmp)
+    c.freeTemp(objR)
+  of nkDotExpr:
+    let dest = c.genx(le.sons[0], {gfNode})
+    let idx = genField(c, le.sons[1])
+    let tmp = c.genx(ri)
+    c.preventFalseAlias(le, opcWrObj, dest, idx, tmp)
     c.freeTemp(tmp)
   of nkDerefExpr, nkHiddenDeref:
-    let dest = c.genx(le.sons[0], {gfAddrOf})
+    let dest = c.genx(le.sons[0], {gfNode})
     let tmp = c.genx(ri)
     c.preventFalseAlias(le, opcWrDeref, dest, 0, tmp)
     c.freeTemp(tmp)
@@ -1307,13 +1475,13 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) =
     checkCanEval(c, le)
     if s.isGlobal:
       withTemp(tmp, le.typ):
-        c.gen(le, tmp, {gfAddrOf})
+        c.gen(le, tmp, {gfNodeAddr})
         let val = c.genx(ri)
         c.preventFalseAlias(le, opcWrDeref, tmp, 0, val)
         c.freeTemp(val)
     else:
       if s.kind == skForVar: c.setSlot s
-      internalAssert s.position > 0 or (s.position == 0 and
+      internalAssert c.config, s.position > 0 or (s.position == 0 and
                                         s.kind in {skParam,skResult})
       var dest: TRegister = s.position + ord(s.kind == skParam)
       assert le.typ != nil
@@ -1325,7 +1493,7 @@ proc genAsgn(c: PCtx; le, ri: PNode; requiresCopy: bool) =
       else:
         gen(c, ri, dest)
   else:
-    let dest = c.genx(le, {gfAddrOf})
+    let dest = c.genx(le, {gfNodeAddr})
     genAsgn(c, dest, ri, requiresCopy)
 
 proc genTypeLit(c: PCtx; t: PType; dest: var TDest) =
@@ -1339,15 +1507,15 @@ proc importcSym(c: PCtx; info: TLineInfo; s: PSym) =
       c.globals.add(importcSymbol(s))
       s.position = c.globals.len
     else:
-      localError(info, errGenerated, "VM is not allowed to 'importc'")
+      localError(c.config, info, "VM is not allowed to 'importc'")
   else:
-    localError(info, errGenerated,
+    localError(c.config, info,
                "cannot 'importc' variable at compile time")
 
-proc getNullValue*(typ: PType, info: TLineInfo): PNode
+proc getNullValue*(typ: PType, info: TLineInfo; conf: ConfigRef): PNode
 
 proc genGlobalInit(c: PCtx; n: PNode; s: PSym) =
-  c.globals.add(getNullValue(s.typ, n.info))
+  c.globals.add(getNullValue(s.typ, n.info, c.config))
   s.position = c.globals.len
   # This is rather hard to support, due to the laziness of the VM code
   # generator. See tests/compile/tmacro2 for why this is necessary:
@@ -1361,24 +1529,26 @@ proc genGlobalInit(c: PCtx; n: PNode; s: PSym) =
     c.freeTemp(tmp)
 
 proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
+  # gfNodeAddr and gfNode are mutually exclusive
+  assert card(flags * {gfNodeAddr, gfNode}) < 2
   let s = n.sym
   if s.isGlobal:
     if sfCompileTime in s.flags or c.mode == emRepl:
       discard
     elif s.position == 0:
-      cannotEval(n)
+      cannotEval(c, n)
     if s.position == 0:
       if sfImportc in s.flags: c.importcSym(n.info, s)
       else: genGlobalInit(c, n, s)
     if dest < 0: dest = c.getTemp(n.typ)
     assert s.typ != nil
-    if gfAddrOf notin flags and fitsRegister(s.typ):
+    if gfNodeAddr in flags:
+      c.gABx(n, opcLdGlobalAddr, dest, s.position)
+    elif fitsRegister(s.typ) and gfNode notin flags:
       var cc = c.getTemp(n.typ)
       c.gABx(n, opcLdGlobal, cc, s.position)
       c.gABC(n, opcNodeToReg, dest, cc)
       c.freeTemp(cc)
-    elif {gfAddrOf, gfFieldAccess} * flags == {gfAddrOf}:
-      c.gABx(n, opcLdGlobalAddr, dest, s.position)
     else:
       c.gABx(n, opcLdGlobal, dest, s.position)
   else:
@@ -1387,16 +1557,17 @@ proc genRdVar(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
                           s.kind in {skParam,skResult}):
       if dest < 0:
         dest = s.position + ord(s.kind == skParam)
-        internalAssert(c.prc.slots[dest].kind < slotSomeTemp)
+        internalAssert(c.config, c.prc.slots[dest].kind < slotSomeTemp)
       else:
         # we need to generate an assignment:
         genAsgn(c, dest, n, c.prc.slots[dest].kind >= slotSomeTemp)
     else:
       # see tests/t99bott for an example that triggers it:
-      cannotEval(n)
+      cannotEval(c, n)
 
 template needsRegLoad(): untyped =
-  gfAddrOf notin flags and fitsRegister(n.typ.skipTypes({tyVar}))
+  {gfNode, gfNodeAddr} * flags == {} and
+    fitsRegister(n.typ.skipTypes({tyVar, tyLent, tyStatic}))
 
 proc genArrAccess2(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
                    flags: TGenFlags) =
@@ -1417,7 +1588,7 @@ proc genArrAccess2(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
 
 proc genObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
   let a = c.genx(n.sons[0], flags)
-  let b = genField(n.sons[1])
+  let b = genField(c, n.sons[1])
   if dest < 0: dest = c.getTemp(n.typ)
   if needsRegLoad():
     var cc = c.getTemp(n.typ)
@@ -1428,9 +1599,59 @@ proc genObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
     c.gABC(n, opcLdObj, dest, a, b)
   c.freeTemp(a)
 
+proc genCheckedObjAccessAux(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
+  internalAssert c.config, n.kind == nkCheckedFieldExpr
+  # nkDotExpr to access the requested field
+  let accessExpr = n[0]
+  # nkCall to check if the discriminant is valid
+  var checkExpr = n[1]
+
+  let negCheck = checkExpr[0].sym.magic == mNot
+  if negCheck:
+    checkExpr = checkExpr[^1]
+
+  # Discriminant symbol
+  let disc = checkExpr[2]
+  internalAssert c.config, disc.sym.kind == skField
+
+  # Load the object in `dest`
+  c.gen(accessExpr[0], dest, flags)
+  # Load the discriminant
+  var discVal = c.getTemp(disc.typ)
+  c.gABC(n, opcLdObj, discVal, dest, genField(c, disc))
+  # Check if its value is contained in the supplied set
+  let setLit = c.genx(checkExpr[1])
+  var rs = c.getTemp(getSysType(c.graph, n.info, tyBool))
+  c.gABC(n, opcContainsSet, rs, setLit, discVal)
+  c.freeTemp(setLit)
+  # If the check fails let the user know
+  let L1 = c.xjmp(n, if negCheck: opcFJmp else: opcTJmp, rs)
+  c.freeTemp(rs)
+  # Not ideal but will do for the moment
+  c.gABC(n, opcQuit)
+  c.patch(L1)
+
 proc genCheckedObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
-  # XXX implement field checks!
-  genObjAccess(c, n.sons[0], dest, flags)
+  var objR: TDest = -1
+  genCheckedObjAccessAux(c, n, objR, flags)
+
+  let accessExpr = n[0]
+  # Field symbol
+  var field = accessExpr[1]
+  internalAssert c.config, field.sym.kind == skField
+
+  # Load the content now
+  if dest < 0: dest = c.getTemp(n.typ)
+  let fieldPos = genField(c, field)
+  if needsRegLoad():
+    var cc = c.getTemp(accessExpr.typ)
+    c.gABC(n, opcLdObj, cc, objR, fieldPos)
+    c.gABC(n, opcNodeToReg, dest, cc)
+    c.freeTemp(cc)
+  else:
+    c.gABC(n, opcLdObj, dest, objR, fieldPos)
+
+  c.freeTemp(objR)
 
 proc genArrAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
   let arrayType = n.sons[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind
@@ -1441,24 +1662,23 @@ proc genArrAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) =
   else:
     genArrAccess2(c, n, dest, opcLdArr, flags)
 
-proc getNullValueAux(obj: PNode, result: PNode) =
+proc getNullValueAux(obj: PNode, result: PNode; conf: ConfigRef) =
   case obj.kind
   of nkRecList:
-    for i in countup(0, sonsLen(obj) - 1): getNullValueAux(obj.sons[i], result)
+    for i in countup(0, sonsLen(obj) - 1): getNullValueAux(obj.sons[i], result, conf)
   of nkRecCase:
-    getNullValueAux(obj.sons[0], result)
+    getNullValueAux(obj.sons[0], result, conf)
     for i in countup(1, sonsLen(obj) - 1):
-      getNullValueAux(lastSon(obj.sons[i]), result)
+      getNullValueAux(lastSon(obj.sons[i]), result, conf)
   of nkSym:
     let field = newNodeI(nkExprColonExpr, result.info)
     field.add(obj)
-    field.add(getNullValue(obj.sym.typ, result.info))
+    field.add(getNullValue(obj.sym.typ, result.info, conf))
     addSon(result, field)
-  else: globalError(result.info, "cannot create null element for: " & $obj)
+  else: globalError(conf, result.info, "cannot create null element for: " & $obj)
 
-proc getNullValue(typ: PType, info: TLineInfo): PNode =
-  var t = skipTypes(typ, abstractRange-{tyTypeDesc})
-  result = emptyNode
+proc getNullValue(typ: PType, info: TLineInfo; conf: ConfigRef): PNode =
+  var t = skipTypes(typ, abstractRange+{tyStatic}-{tyTypeDesc})
   case t.kind
   of tyBool, tyEnum, tyChar, tyInt..tyInt64:
     result = newNodeIT(nkIntLit, info, t)
@@ -1468,14 +1688,15 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode =
     result = newNodeIT(nkFloatLit, info, t)
   of tyCString, tyString:
     result = newNodeIT(nkStrLit, info, t)
-  of tyVar, tyPointer, tyPtr, tySequence, tyExpr,
-     tyStmt, tyTypeDesc, tyStatic, tyRef, tyNil:
+    result.strVal = ""
+  of tyVar, tyLent, tyPointer, tyPtr, tyExpr,
+     tyStmt, tyTypeDesc, tyRef, tyNil:
     result = newNodeIT(nkNilLit, info, t)
   of tyProc:
     if t.callConv != ccClosure:
       result = newNodeIT(nkNilLit, info, t)
     else:
-      result = newNodeIT(nkPar, info, t)
+      result = newNodeIT(nkTupleConstr, info, t)
       result.add(newNodeIT(nkNilLit, info, t))
       result.add(newNodeIT(nkNilLit, info, t))
   of tyObject:
@@ -1484,21 +1705,26 @@ proc getNullValue(typ: PType, info: TLineInfo): PNode =
     # initialize inherited fields:
     var base = t.sons[0]
     while base != nil:
-      getNullValueAux(skipTypes(base, skipPtrs).n, result)
+      getNullValueAux(skipTypes(base, skipPtrs).n, result, conf)
       base = base.sons[0]
-    getNullValueAux(t.n, result)
+    getNullValueAux(t.n, result, conf)
   of tyArray:
     result = newNodeIT(nkBracket, info, t)
-    for i in countup(0, int(lengthOrd(t)) - 1):
-      addSon(result, getNullValue(elemType(t), info))
+    for i in countup(0, int(lengthOrd(conf, t)) - 1):
+      addSon(result, getNullValue(elemType(t), info, conf))
   of tyTuple:
-    result = newNodeIT(nkPar, info, t)
+    result = newNodeIT(nkTupleConstr, info, t)
     for i in countup(0, sonsLen(t) - 1):
-      addSon(result, getNullValue(t.sons[i], info))
+      addSon(result, getNullValue(t.sons[i], info, conf))
   of tySet:
     result = newNodeIT(nkCurly, info, t)
+  of tyOpt:
+    result = newNodeIT(nkNilLit, info, t)
+  of tySequence:
+    result = newNodeIT(nkBracket, info, t)
   else:
-    globalError(info, "cannot create null element for: " & $t.kind)
+    globalError(conf, info, "cannot create null element for: " & $t.kind)
+    result = newNodeI(nkEmpty, info)
 
 proc ldNullOpcode(t: PType): TOpcode =
   assert t != nil
@@ -1512,7 +1738,7 @@ proc genVarSection(c: PCtx; n: PNode) =
       for i in 0 .. a.len-3:
         if not a[i].sym.isGlobal: setSlot(c, a[i].sym)
         checkCanEval(c, a[i])
-      c.gen(lowerTupleUnpacking(a, c.getOwner))
+      c.gen(lowerTupleUnpacking(c.graph, a, c.getOwner))
     elif a.sons[0].kind == nkSym:
       let s = a.sons[0].sym
       checkCanEval(c, a.sons[0])
@@ -1520,14 +1746,14 @@ proc genVarSection(c: PCtx; n: PNode) =
         if s.position == 0:
           if sfImportc in s.flags: c.importcSym(a.info, s)
           else:
-            let sa = getNullValue(s.typ, a.info)
+            let sa = getNullValue(s.typ, a.info, c.config)
             #if s.ast.isNil: getNullValue(s.typ, a.info)
             #else: canonValue(s.ast)
             assert sa.kind != nkCall
             c.globals.add(sa)
             s.position = c.globals.len
         if a.sons[2].kind != nkEmpty:
-          let tmp = c.genx(a.sons[0], {gfAddrOf})
+          let tmp = c.genx(a.sons[0], {gfNodeAddr})
           let val = c.genx(a.sons[2])
           c.genAdditionalCopy(a.sons[2], opcWrDeref, tmp, 0, val)
           c.freeTemp(val)
@@ -1562,7 +1788,7 @@ proc genArrayConstr(c: PCtx, n: PNode, dest: var TDest) =
   if dest < 0: dest = c.getTemp(n.typ)
   c.gABx(n, opcLdNull, dest, c.genType(n.typ))
 
-  let intType = getSysType(tyInt)
+  let intType = getSysType(c.graph, n.info, tyInt)
   let seqType = n.typ.skipTypes(abstractVar-{tyTypeDesc})
   if seqType.kind == tySequence:
     var tmp = c.getTemp(intType)
@@ -1603,25 +1829,25 @@ proc genObjConstr(c: PCtx, n: PNode, dest: var TDest) =
     c.gABx(n, opcNew, dest, c.genType(t.sons[0]))
   else:
     c.gABx(n, opcLdNull, dest, c.genType(n.typ))
-  for i in 1.. <n.len:
+  for i in 1..<n.len:
     let it = n.sons[i]
     if it.kind == nkExprColonExpr and it.sons[0].kind == nkSym:
-      let idx = genField(it.sons[0])
+      let idx = genField(c, it.sons[0])
       let tmp = c.genx(it.sons[1])
       c.preventFalseAlias(it.sons[1], whichAsgnOpc(it.sons[1], opcWrObj),
                           dest, idx, tmp)
       c.freeTemp(tmp)
     else:
-      globalError(n.info, "invalid object constructor")
+      globalError(c.config, n.info, "invalid object constructor")
 
 proc genTupleConstr(c: PCtx, n: PNode, dest: var TDest) =
   if dest < 0: dest = c.getTemp(n.typ)
   c.gABx(n, opcLdNull, dest, c.genType(n.typ))
   # XXX x = (x.old, 22)  produces wrong code ... stupid self assignments
-  for i in 0.. <n.len:
+  for i in 0..<n.len:
     let it = n.sons[i]
     if it.kind == nkExprColonExpr:
-      let idx = genField(it.sons[0])
+      let idx = genField(c, it.sons[0])
       let tmp = c.genx(it.sons[1])
       c.preventFalseAlias(it.sons[1], whichAsgnOpc(it.sons[1], opcWrObj),
                           dest, idx, tmp)
@@ -1672,14 +1898,18 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
     case s.kind
     of skVar, skForVar, skTemp, skLet, skParam, skResult:
       genRdVar(c, n, dest, flags)
-    of skProc, skConverter, skMacro, skTemplate, skMethod, skIterator:
+    of skProc, skFunc, skConverter, skMacro, skTemplate, skMethod, skIterator:
       # 'skTemplate' is only allowed for 'getAst' support:
       if procIsCallback(c, s): discard
       elif sfImportc in s.flags: c.importcSym(n.info, s)
       genLit(c, n, dest)
     of skConst:
-      gen(c, s.ast, dest)
+      let constVal = if s.ast != nil: s.ast else: s.typ.n
+      gen(c, constVal, dest)
     of skEnumField:
+      # we never reach this case - as of the time of this comment,
+      # skEnumField is folded to an int in semfold.nim, but this code
+      # remains for robustness
       if dest < 0: dest = c.getTemp(n.typ)
       if s.position >= low(int16) and s.position <= high(int16):
         c.gABx(n, opcLdImmInt, dest, s.position)
@@ -1692,15 +1922,20 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
       if c.prc.sym != nil and c.prc.sym.kind == skMacro:
         genRdVar(c, n, dest, flags)
       else:
-        globalError(n.info, errGenerated, "cannot generate code for: " & s.name.s)
+        globalError(c.config, n.info, "cannot generate code for: " & s.name.s)
     else:
-      globalError(n.info, errGenerated, "cannot generate code for: " & s.name.s)
+      globalError(c.config, n.info, "cannot generate code for: " & s.name.s)
   of nkCallKinds:
     if n.sons[0].kind == nkSym:
       let s = n.sons[0].sym
       if s.magic != mNone:
         genMagic(c, n, dest, s.magic)
+      elif s.kind == skMethod:
+        localError(c.config, n.info, "cannot call method " & s.name.s &
+          " at compile time")
       elif matches(s, "stdlib", "marshal", "to"):
+        # XXX marshal load&store should not be opcodes, but use the
+        # general callback mechanisms.
         genMarshalLoad(c, n, dest)
       elif matches(s, "stdlib", "marshal", "$$"):
         genMarshalStore(c, n, dest)
@@ -1718,57 +1953,56 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
       genLit(c, n, dest)
   of nkUIntLit..pred(nkNilLit): genLit(c, n, dest)
   of nkNilLit:
-    if not n.typ.isEmptyType: genLit(c, getNullValue(n.typ, n.info), dest)
-    else: unused(n, dest)
+    if not n.typ.isEmptyType: genLit(c, getNullValue(n.typ, n.info, c.config), dest)
+    else: unused(c, n, dest)
   of nkAsgn, nkFastAsgn:
-    unused(n, dest)
+    unused(c, n, dest)
     genAsgn(c, n.sons[0], n.sons[1], n.kind == nkAsgn)
   of nkDotExpr: genObjAccess(c, n, dest, flags)
   of nkCheckedFieldExpr: genCheckedObjAccess(c, n, dest, flags)
   of nkBracketExpr: genArrAccess(c, n, dest, flags)
-  of nkDerefExpr, nkHiddenDeref: genAddrDeref(c, n, dest, opcLdDeref, flags)
-  of nkAddr, nkHiddenAddr: genAddrDeref(c, n, dest, opcAddrNode, flags)
+  of nkDerefExpr, nkHiddenDeref: genDeref(c, n, dest, flags)
+  of nkAddr, nkHiddenAddr: genAddr(c, n, dest, flags)
   of nkIfStmt, nkIfExpr: genIf(c, n, dest)
   of nkWhenStmt:
-      # This is "when nimvm" node. Chose the first branch.
-      gen(c, n.sons[0].sons[1], dest)
+    # This is "when nimvm" node. Chose the first branch.
+    gen(c, n.sons[0].sons[1], dest)
   of nkCaseStmt: genCase(c, n, dest)
   of nkWhileStmt:
-    unused(n, dest)
+    unused(c, n, dest)
     genWhile(c, n)
   of nkBlockExpr, nkBlockStmt: genBlock(c, n, dest)
   of nkReturnStmt:
-    unused(n, dest)
+    unused(c, n, dest)
     genReturn(c, n)
   of nkRaiseStmt:
-    unused(n, dest)
     genRaise(c, n)
   of nkBreakStmt:
-    unused(n, dest)
+    unused(c, n, dest)
     genBreak(c, n)
   of nkTryStmt: genTry(c, n, dest)
   of nkStmtList:
-    #unused(n, dest)
+    #unused(c, n, dest)
     # XXX Fix this bug properly, lexim triggers it
     for x in n: gen(c, x)
   of nkStmtListExpr:
     let L = n.len-1
-    for i in 0 .. <L: gen(c, n.sons[i])
+    for i in 0 ..< L: gen(c, n.sons[i])
     gen(c, n.sons[L], dest, flags)
   of nkPragmaBlock:
     gen(c, n.lastSon, dest, flags)
   of nkDiscardStmt:
-    unused(n, dest)
+    unused(c, n, dest)
     gen(c, n.sons[0])
   of nkHiddenStdConv, nkHiddenSubConv, nkConv:
     genConv(c, n, n.sons[1], dest)
   of nkObjDownConv:
     genConv(c, n, n.sons[0], dest)
   of nkVarSection, nkLetSection:
-    unused(n, dest)
+    unused(c, n, dest)
     genVarSection(c, n)
-  of declarativeDefs:
-    unused(n, dest)
+  of declarativeDefs, nkMacroDef:
+    unused(c, n, dest)
   of nkLambdaKinds:
     #let s = n.sons[namePos].sym
     #discard genProc(c, s)
@@ -1787,23 +2021,25 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) =
     else:
       dest = tmp0
   of nkEmpty, nkCommentStmt, nkTypeSection, nkConstSection, nkPragma,
-     nkTemplateDef, nkIncludeStmt, nkImportStmt, nkFromStmt:
-    unused(n, dest)
+     nkTemplateDef, nkIncludeStmt, nkImportStmt, nkFromStmt, nkExportStmt:
+    unused(c, n, dest)
   of nkStringToCString, nkCStringToString:
     gen(c, n.sons[0], dest)
   of nkBracket: genArrayConstr(c, n, dest)
   of nkCurly: genSetConstr(c, n, dest)
   of nkObjConstr: genObjConstr(c, n, dest)
-  of nkPar, nkClosure: genTupleConstr(c, n, dest)
+  of nkPar, nkClosure, nkTupleConstr: genTupleConstr(c, n, dest)
   of nkCast:
     if allowCast in c.features:
       genConv(c, n, n.sons[1], dest, opcCast)
     else:
-      globalError(n.info, errGenerated, "VM is not allowed to 'cast'")
+      genCastIntFloat(c, n, dest)
   of nkTypeOfExpr:
     genTypeLit(c, n.typ, dest)
+  of nkComesFrom:
+    discard "XXX to implement for better stack traces"
   else:
-    globalError(n.info, errGenerated, "cannot generate VM code for " & $n)
+    globalError(c.config, n.info, "cannot generate VM code for " & $n)
 
 proc removeLastEof(c: PCtx) =
   let last = c.code.len-1
@@ -1820,7 +2056,7 @@ proc genStmt*(c: PCtx; n: PNode): int =
   c.gen(n, d)
   c.gABC(n, opcEof)
   if d >= 0:
-    globalError(n.info, errGenerated, "VM problem: dest register is set")
+    globalError(c.config, n.info, "VM problem: dest register is set")
 
 proc genExpr*(c: PCtx; n: PNode, requiresValue = true): int =
   c.removeLastEof
@@ -1829,7 +2065,7 @@ proc genExpr*(c: PCtx; n: PNode, requiresValue = true): int =
   c.gen(n, d)
   if d < 0:
     if requiresValue:
-      globalError(n.info, errGenerated, "VM problem: dest register is not set")
+      globalError(c.config, n.info, "VM problem: dest register is not set")
     d = 0
   c.gABC(n, opcEof, d)
 
@@ -1839,12 +2075,12 @@ proc genExpr*(c: PCtx; n: PNode, requiresValue = true): int =
 proc genParams(c: PCtx; params: PNode) =
   # res.sym.position is already 0
   c.prc.slots[0] = (inUse: true, kind: slotFixedVar)
-  for i in 1.. <params.len:
+  for i in 1..<params.len:
     c.prc.slots[i] = (inUse: true, kind: slotFixedLet)
   c.prc.maxSlots = max(params.len, 1)
 
 proc finalJumpTarget(c: PCtx; pc, diff: int) =
-  internalAssert(-0x7fff < diff and diff < 0x7fff)
+  internalAssert(c.config, -0x7fff < diff and diff < 0x7fff)
   let oldInstr = c.code[pc]
   # opcode and regA stay the same:
   c.code[pc] = ((oldInstr.uint32 and 0xffff'u32).uint32 or
@@ -1852,7 +2088,7 @@ proc finalJumpTarget(c: PCtx; pc, diff: int) =
 
 proc genGenericParams(c: PCtx; gp: PNode) =
   var base = c.prc.maxSlots
-  for i in 0.. <gp.len:
+  for i in 0..<gp.len:
     var param = gp.sons[i].sym
     param.position = base + i # XXX: fix this earlier; make it consistent with templates
     c.prc.slots[base + i] = (inUse: true, kind: slotFixedLet)
@@ -1860,7 +2096,7 @@ proc genGenericParams(c: PCtx; gp: PNode) =
 
 proc optimizeJumps(c: PCtx; start: int) =
   const maxIterations = 10
-  for i in start .. <c.code.len:
+  for i in start ..< c.code.len:
     let opc = c.code[i].opcode
     case opc
     of opcTJmp, opcFJmp:
@@ -1913,13 +2149,14 @@ proc genProc(c: PCtx; s: PSym): int =
     #c.removeLastEof
     result = c.code.len+1 # skip the jump instruction
     if x.kind == nkEmpty:
-      x = newTree(nkBracket, newIntNode(nkIntLit, result), ast.emptyNode)
+      x = newTree(nkBracket, newIntNode(nkIntLit, result), x)
     else:
       x.sons[0] = newIntNode(nkIntLit, result)
     s.ast.sons[miscPos] = x
     # thanks to the jmp we can add top level statements easily and also nest
     # procs easily:
-    let body = s.getBody
+    let body = transformBody(c.graph, s, cache = not isCompileTimeProc(s),
+                             noDestructors = true)
     let procStart = c.xjmp(body, opcJmp, 0)
     var p = PProc(blocks: @[], sym: s)
     let oldPrc = c.prc
diff --git a/compiler/vmhooks.nim b/compiler/vmhooks.nim
index 548a3af97..39e435e4b 100644
--- a/compiler/vmhooks.nim
+++ b/compiler/vmhooks.nim
@@ -7,6 +7,8 @@
 #    distribution, for details about the copyright.
 #
 
+import pathutils
+
 template setX(k, field) {.dirty.} =
   var s: seq[TFullReg]
   move(s, cast[seq[TFullReg]](a.slots))
@@ -38,6 +40,8 @@ proc setResult*(a: VmArgs; n: PNode) =
     s[a.ra].kind = rkNode
   s[a.ra].node = n
 
+proc setResult*(a: VmArgs; v: AbsoluteDir) = setResult(a, v.string)
+
 proc setResult*(a: VmArgs; v: seq[string]) =
   var s: seq[TFullReg]
   move(s, cast[seq[TFullReg]](a.slots))
diff --git a/compiler/vmmarshal.nim b/compiler/vmmarshal.nim
index 51301b931..149d2e08f 100644
--- a/compiler/vmmarshal.nim
+++ b/compiler/vmmarshal.nim
@@ -9,7 +9,8 @@
 
 ## Implements marshaling for the VM.
 
-import streams, json, intsets, tables, ast, astalgo, idents, types, msgs
+import streams, json, intsets, tables, ast, astalgo, idents, types, msgs,
+  options, lineinfos
 
 proc ptrToInt(x: PNode): int {.inline.} =
   result = cast[int](x) # don't skip alignment
@@ -28,37 +29,38 @@ proc getField(n: PNode; position: int): PSym =
       of nkOfBranch, nkElse:
         result = getField(lastSon(n.sons[i]), position)
         if result != nil: return
-      else: internalError(n.info, "getField(record case branch)")
+      else: discard
   of nkSym:
     if n.sym.position == position: result = n.sym
   else: discard
 
-proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet)
+proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet; conf: ConfigRef)
 
-proc storeObj(s: var string; typ: PType; x: PNode; stored: var IntSet) =
-  internalAssert x.kind == nkObjConstr
+proc storeObj(s: var string; typ: PType; x: PNode; stored: var IntSet; conf: ConfigRef) =
+  assert x.kind == nkObjConstr
   let start = 1
   for i in countup(start, sonsLen(x) - 1):
     if i > start: s.add(", ")
     var it = x.sons[i]
     if it.kind == nkExprColonExpr:
-      internalAssert it.sons[0].kind == nkSym
-      let field = it.sons[0].sym
-      s.add(escapeJson(field.name.s))
-      s.add(": ")
-      storeAny(s, field.typ, it.sons[1], stored)
+      if it.sons[0].kind == nkSym:
+        let field = it.sons[0].sym
+        s.add(escapeJson(field.name.s))
+        s.add(": ")
+        storeAny(s, field.typ, it.sons[1], stored, conf)
     elif typ.n != nil:
       let field = getField(typ.n, i)
       s.add(escapeJson(field.name.s))
       s.add(": ")
-      storeAny(s, field.typ, it, stored)
+      storeAny(s, field.typ, it, stored, conf)
 
 proc skipColon*(n: PNode): PNode =
   result = n
   if n.kind == nkExprColonExpr:
     result = n.sons[1]
 
-proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) =
+proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet;
+              conf: ConfigRef) =
   case t.kind
   of tyNone: assert false
   of tyBool: s.add($(a.intVal != 0))
@@ -74,35 +76,36 @@ proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) =
       s.add("[")
       for i in 0 .. a.len-1:
         if i > 0: s.add(", ")
-        storeAny(s, t.elemType, a[i], stored)
+        storeAny(s, t.elemType, a[i], stored, conf)
       s.add("]")
   of tyTuple:
     s.add("{")
-    for i in 0.. <t.len:
+    for i in 0..<t.len:
       if i > 0: s.add(", ")
       s.add("\"Field" & $i)
       s.add("\": ")
-      storeAny(s, t.sons[i], a[i].skipColon, stored)
+      storeAny(s, t.sons[i], a[i].skipColon, stored, conf)
     s.add("}")
   of tyObject:
     s.add("{")
-    storeObj(s, t, a, stored)
+    storeObj(s, t, a, stored, conf)
     s.add("}")
   of tySet:
     s.add("[")
-    for i in 0.. <a.len:
+    for i in 0..<a.len:
       if i > 0: s.add(", ")
       if a[i].kind == nkRange:
         var x = copyNode(a[i][0])
-        storeAny(s, t.lastSon, x, stored)
+        storeAny(s, t.lastSon, x, stored, conf)
         while x.intVal+1 <= a[i][1].intVal:
           s.add(", ")
-          storeAny(s, t.lastSon, x, stored)
+          storeAny(s, t.lastSon, x, stored, conf)
           inc x.intVal
       else:
-        storeAny(s, t.lastSon, a[i], stored)
+        storeAny(s, t.lastSon, a[i], stored, conf)
     s.add("]")
-  of tyRange, tyGenericInst, tyAlias: storeAny(s, t.lastSon, a, stored)
+  of tyRange, tyGenericInst, tyAlias, tySink:
+    storeAny(s, t.lastSon, a, stored, conf)
   of tyEnum:
     # we need a slow linear search because of enums with holes:
     for e in items(t.n):
@@ -121,22 +124,24 @@ proc storeAny(s: var string; t: PType; a: PNode; stored: var IntSet) =
       s.add("[")
       s.add($x.ptrToInt)
       s.add(", ")
-      storeAny(s, t.lastSon, a, stored)
+      storeAny(s, t.lastSon, a, stored, conf)
       s.add("]")
   of tyString, tyCString:
-    if a.kind == nkNilLit or a.strVal.isNil: s.add("null")
+    if a.kind == nkNilLit: s.add("null")
     else: s.add(escapeJson(a.strVal))
   of tyInt..tyInt64, tyUInt..tyUInt64: s.add($a.intVal)
   of tyFloat..tyFloat128: s.add($a.floatVal)
   else:
-    internalError a.info, "cannot marshal at compile-time " & t.typeToString
+    internalError conf, a.info, "cannot marshal at compile-time " & t.typeToString
 
-proc storeAny*(s: var string; t: PType; a: PNode) =
+proc storeAny*(s: var string; t: PType; a: PNode; conf: ConfigRef) =
   var stored = initIntSet()
-  storeAny(s, t, a, stored)
+  storeAny(s, t, a, stored, conf)
 
 proc loadAny(p: var JsonParser, t: PType,
-             tab: var Table[BiggestInt, PNode]): PNode =
+             tab: var Table[BiggestInt, PNode];
+             cache: IdentCache;
+             conf: ConfigRef): PNode =
   case t.kind
   of tyNone: assert false
   of tyBool:
@@ -170,7 +175,7 @@ proc loadAny(p: var JsonParser, t: PType,
     next(p)
     result = newNode(nkBracket)
     while p.kind != jsonArrayEnd and p.kind != jsonEof:
-      result.add loadAny(p, t.elemType, tab)
+      result.add loadAny(p, t.elemType, tab, cache, conf)
     if p.kind == jsonArrayEnd: next(p)
     else: raiseParseErr(p, "']' end of array expected")
   of tySequence:
@@ -182,7 +187,7 @@ proc loadAny(p: var JsonParser, t: PType,
       next(p)
       result = newNode(nkBracket)
       while p.kind != jsonArrayEnd and p.kind != jsonEof:
-        result.add loadAny(p, t.elemType, tab)
+        result.add loadAny(p, t.elemType, tab, cache, conf)
       if p.kind == jsonArrayEnd: next(p)
       else: raiseParseErr(p, "")
     else:
@@ -190,7 +195,7 @@ proc loadAny(p: var JsonParser, t: PType,
   of tyTuple:
     if p.kind != jsonObjectStart: raiseParseErr(p, "'{' expected for an object")
     next(p)
-    result = newNode(nkPar)
+    result = newNode(nkTupleConstr)
     var i = 0
     while p.kind != jsonObjectEnd and p.kind != jsonEof:
       if p.kind != jsonString:
@@ -198,7 +203,7 @@ proc loadAny(p: var JsonParser, t: PType,
       next(p)
       if i >= t.len:
         raiseParseErr(p, "too many fields to tuple type " & typeToString(t))
-      result.add loadAny(p, t.sons[i], tab)
+      result.add loadAny(p, t.sons[i], tab, cache, conf)
       inc i
     if p.kind == jsonObjectEnd: next(p)
     else: raiseParseErr(p, "'}' end of object expected")
@@ -210,7 +215,7 @@ proc loadAny(p: var JsonParser, t: PType,
     while p.kind != jsonObjectEnd and p.kind != jsonEof:
       if p.kind != jsonString:
         raiseParseErr(p, "string expected for a field name")
-      let ident = getIdent(p.str)
+      let ident = getIdent(cache, p.str)
       let field = lookupInRecord(t.n, ident)
       if field.isNil:
         raiseParseErr(p, "unknown field for object of type " & typeToString(t))
@@ -220,7 +225,7 @@ proc loadAny(p: var JsonParser, t: PType,
         setLen(result.sons, pos + 1)
       let fieldNode = newNode(nkExprColonExpr)
       fieldNode.addSon(newSymNode(newSym(skField, ident, nil, unknownLineInfo())))
-      fieldNode.addSon(loadAny(p, field.typ, tab))
+      fieldNode.addSon(loadAny(p, field.typ, tab, cache, conf))
       result.sons[pos] = fieldNode
     if p.kind == jsonObjectEnd: next(p)
     else: raiseParseErr(p, "'}' end of object expected")
@@ -229,7 +234,7 @@ proc loadAny(p: var JsonParser, t: PType,
     next(p)
     result = newNode(nkCurly)
     while p.kind != jsonArrayEnd and p.kind != jsonEof:
-      result.add loadAny(p, t.lastSon, tab)
+      result.add loadAny(p, t.lastSon, tab, cache, conf)
       next(p)
     if p.kind == jsonArrayEnd: next(p)
     else: raiseParseErr(p, "']' end of array expected")
@@ -248,7 +253,7 @@ proc loadAny(p: var JsonParser, t: PType,
       if p.kind == jsonInt:
         let idx = p.getInt
         next(p)
-        result = loadAny(p, t.lastSon, tab)
+        result = loadAny(p, t.lastSon, tab, cache, conf)
         tab[idx] = result
       else: raiseParseErr(p, "index for ref type expected")
       if p.kind == jsonArrayEnd: next(p)
@@ -275,14 +280,15 @@ proc loadAny(p: var JsonParser, t: PType,
       next(p)
       return
     raiseParseErr(p, "float expected")
-  of tyRange, tyGenericInst, tyAlias: result = loadAny(p, t.lastSon, tab)
+  of tyRange, tyGenericInst, tyAlias, tySink:
+    result = loadAny(p, t.lastSon, tab, cache, conf)
   else:
-    internalError "cannot marshal at compile-time " & t.typeToString
+    internalError conf, "cannot marshal at compile-time " & t.typeToString
 
-proc loadAny*(s: string; t: PType): PNode =
+proc loadAny*(s: string; t: PType; cache: IdentCache; conf: ConfigRef): PNode =
   var tab = initTable[BiggestInt, PNode]()
   var p: JsonParser
   open(p, newStringStream(s), "unknown file")
   next(p)
-  result = loadAny(p, t, tab)
+  result = loadAny(p, t, tab, cache, conf)
   close(p)
diff --git a/compiler/vmops.nim b/compiler/vmops.nim
index b0911579e..75873bfe8 100644
--- a/compiler/vmops.nim
+++ b/compiler/vmops.nim
@@ -11,9 +11,9 @@
 #import vmdeps, vm
 from math import sqrt, ln, log10, log2, exp, round, arccos, arcsin,
   arctan, arctan2, cos, cosh, hypot, sinh, sin, tan, tanh, pow, trunc,
-  floor, ceil, fmod
+  floor, ceil, `mod`
 
-from os import getEnv, existsEnv, dirExists, fileExists, walkDir
+from os import getEnv, existsEnv, dirExists, fileExists, putEnv, walkDir
 
 template mathop(op) {.dirty.} =
   registerCallback(c, "stdlib.math." & astToStr(op), `op Wrapper`)
@@ -24,6 +24,9 @@ template osop(op) {.dirty.} =
 template systemop(op) {.dirty.} =
   registerCallback(c, "stdlib.system." & astToStr(op), `op Wrapper`)
 
+template macrosop(op) {.dirty.} =
+  registerCallback(c, "stdlib.macros." & astToStr(op), `op Wrapper`)
+
 template wrap1f_math(op) {.dirty.} =
   proc `op Wrapper`(a: VmArgs) {.nimcall.} =
     setResult(a, op(getFloat(a, 0)))
@@ -34,20 +37,30 @@ template wrap2f_math(op) {.dirty.} =
     setResult(a, op(getFloat(a, 0), getFloat(a, 1)))
   mathop op
 
-template wrap1s_os(op) {.dirty.} =
+template wrap0(op, modop) {.dirty.} =
   proc `op Wrapper`(a: VmArgs) {.nimcall.} =
-    setResult(a, op(getString(a, 0)))
-  osop op
+    setResult(a, op())
+  modop op
 
-template wrap1s_system(op) {.dirty.} =
+template wrap1s(op, modop) {.dirty.} =
   proc `op Wrapper`(a: VmArgs) {.nimcall.} =
     setResult(a, op(getString(a, 0)))
-  systemop op
+  modop op
+
+template wrap2s(op, modop) {.dirty.} =
+  proc `op Wrapper`(a: VmArgs) {.nimcall.} =
+    setResult(a, op(getString(a, 0), getString(a, 1)))
+  modop op
+
+template wrap1svoid(op, modop) {.dirty.} =
+  proc `op Wrapper`(a: VmArgs) {.nimcall.} =
+    op(getString(a, 0))
+  modop op
 
-template wrap2svoid_system(op) {.dirty.} =
+template wrap2svoid(op, modop) {.dirty.} =
   proc `op Wrapper`(a: VmArgs) {.nimcall.} =
     op(getString(a, 0), getString(a, 1))
-  systemop op
+  modop op
 
 proc getCurrentExceptionMsgWrapper(a: VmArgs) {.nimcall.} =
   setResult(a, if a.currentException.isNil: ""
@@ -56,15 +69,18 @@ proc getCurrentExceptionMsgWrapper(a: VmArgs) {.nimcall.} =
 proc staticWalkDirImpl(path: string, relative: bool): PNode =
   result = newNode(nkBracket)
   for k, f in walkDir(path, relative):
-    result.add newTree(nkPar, newIntNode(nkIntLit, k.ord),
+    result.add newTree(nkTupleConstr, newIntNode(nkIntLit, k.ord),
                               newStrNode(nkStrLit, f))
 
-proc gorgeExWrapper(a: VmArgs) {.nimcall.} =
-  let (s, e) = opGorge(getString(a, 0), getString(a, 1), getString(a, 2),
-                       a.currentLineInfo)
-  setResult a, newTree(nkPar, newStrNode(nkStrLit, s), newIntNode(nkIntLit, e))
-
 proc registerAdditionalOps*(c: PCtx) =
+  proc gorgeExWrapper(a: VmArgs) =
+    let (s, e) = opGorge(getString(a, 0), getString(a, 1), getString(a, 2),
+                         a.currentLineInfo, c.config)
+    setResult a, newTree(nkTupleConstr, newStrNode(nkStrLit, s), newIntNode(nkIntLit, e))
+
+  proc getProjectPathWrapper(a: VmArgs) =
+    setResult a, c.config.projectPath.string
+
   wrap1f_math(sqrt)
   wrap1f_math(ln)
   wrap1f_math(log10)
@@ -86,15 +102,21 @@ proc registerAdditionalOps*(c: PCtx) =
   wrap1f_math(trunc)
   wrap1f_math(floor)
   wrap1f_math(ceil)
-  wrap2f_math(fmod)
-
-  wrap1s_os(getEnv)
-  wrap1s_os(existsEnv)
-  wrap1s_os(dirExists)
-  wrap1s_os(fileExists)
-  wrap2svoid_system(writeFile)
-  wrap1s_system(readFile)
-  systemop getCurrentExceptionMsg
-  registerCallback c, "stdlib.*.staticWalkDir", proc (a: VmArgs) {.nimcall.} =
-    setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1)))
-  systemop gorgeEx
+
+  proc `mod Wrapper`(a: VmArgs) {.nimcall.} =
+    setResult(a, `mod`(getFloat(a, 0), getFloat(a, 1)))
+  registerCallback(c, "stdlib.math.mod", `mod Wrapper`)
+
+  when defined(nimcore):
+    wrap2s(getEnv, osop)
+    wrap1s(existsEnv, osop)
+    wrap2svoid(putEnv, osop)
+    wrap1s(dirExists, osop)
+    wrap1s(fileExists, osop)
+    wrap2svoid(writeFile, systemop)
+    wrap1s(readFile, systemop)
+    systemop getCurrentExceptionMsg
+    registerCallback c, "stdlib.*.staticWalkDir", proc (a: VmArgs) {.nimcall.} =
+      setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1)))
+    systemop gorgeEx
+  macrosop getProjectPath
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 6072bd64c..41bdc9fcb 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -21,16 +21,16 @@ type
   TSpecialWord* = enum
     wInvalid,
 
-    wAddr, wAnd, wAs, wAsm, wAtomic,
+    wAddr, wAnd, wAs, wAsm,
     wBind, wBlock, wBreak, wCase, wCast, wConcept, wConst,
     wContinue, wConverter, wDefer, wDiscard, wDistinct, wDiv, wDo,
     wElif, wElse, wEnd, wEnum, wExcept, wExport,
-    wFinally, wFor, wFrom, wFunc, wGeneric, wIf, wImport, wIn,
+    wFinally, wFor, wFrom, wFunc, wIf, wImport, wIn,
     wInclude, wInterface, wIs, wIsnot, wIterator, wLet,
     wMacro, wMethod, wMixin, wMod, wNil,
     wNot, wNotin, wObject, wOf, wOr, wOut, wProc, wPtr, wRaise, wRef, wReturn,
     wShl, wShr, wStatic, wTemplate, wTry, wTuple, wType, wUsing, wVar,
-    wWhen, wWhile, wWith, wWithout, wXor, wYield,
+    wWhen, wWhile, wXor, wYield,
 
     wColon, wColonColon, wEquals, wDot, wDotDot,
     wStar, wMinus,
@@ -45,17 +45,18 @@ type
     wImportc, wExportc, wExportNims, wIncompleteStruct, wRequiresInit,
     wAlign, wNodecl, wPure, wSideeffect, wHeader,
     wNosideeffect, wGcSafe, wNoreturn, wMerge, wLib, wDynlib,
-    wCompilerproc, wProcVar, wBase, wUsed,
+    wCompilerproc, wCore, wProcVar, wBase, wUsed,
     wFatal, wError, wWarning, wHint, wLine, wPush, wPop, wDefine, wUndef,
     wLinedir, wStacktrace, wLinetrace, wLink, wCompile,
     wLinksys, wDeprecated, wVarargs, wCallconv, wBreakpoint, wDebugger,
     wNimcall, wStdcall, wCdecl, wSafecall, wSyscall, wInline, wNoInline,
     wFastcall, wClosure, wNoconv, wOn, wOff, wChecks, wRangechecks,
     wBoundchecks, wOverflowchecks, wNilchecks,
-    wFloatchecks, wNanChecks, wInfChecks,
+    wFloatchecks, wNanChecks, wInfChecks, wMoveChecks,
     wAssertions, wPatterns, wWarnings,
     wHints, wOptimization, wRaises, wWrites, wReads, wSize, wEffects, wTags,
-    wDeadCodeElim, wSafecode, wNoForward, wNoRewrite,
+    wDeadCodeElimUnused,  # deprecated, dead code elim always happens
+    wSafecode, wPackage, wNoForward, wReorder, wNoRewrite,
     wPragma,
     wCompileTime, wNoInit,
     wPassc, wPassl, wBorrow, wDiscardable,
@@ -66,15 +67,15 @@ type
     wWrite, wGensym, wInject, wDirty, wInheritable, wThreadVar, wEmit,
     wAsmNoStackFrame,
     wImplicitStatic, wGlobal, wCodegenDecl, wUnchecked, wGuard, wLocks,
-    wPartial,
+    wPartial, wExplain, wLiftLocals,
 
-    wAuto, wBool, wCatch, wChar, wClass,
+    wAuto, wBool, wCatch, wChar, wClass, wCompl
     wConst_cast, wDefault, wDelete, wDouble, wDynamic_cast,
     wExplicit, wExtern, wFalse, wFloat, wFriend,
     wGoto, wInt, wLong, wMutable, wNamespace, wNew, wOperator,
-    wPrivate, wProtected, wPublic, wRegister, wReinterpret_cast,
+    wPrivate, wProtected, wPublic, wRegister, wReinterpret_cast, wRestrict,
     wShort, wSigned, wSizeof, wStatic_cast, wStruct, wSwitch,
-    wThis, wThrow, wTrue, wTypedef, wTypeid, wTypename,
+    wThis, wThrow, wTrue, wTypedef, wTypeid, wTypeof, wTypename,
     wUnion, wPacked, wUnsigned, wVirtual, wVoid, wVolatile, wWchar_t,
 
     wAlignas, wAlignof, wConstexpr, wDecltype, wNullptr, wNoexcept,
@@ -83,7 +84,7 @@ type
     wStdIn, wStdOut, wStdErr,
 
     wInOut, wByCopy, wByRef, wOneWay,
-    wBitsize,
+    wBitsize
 
   TSpecialWords* = set[TSpecialWord]
 
@@ -103,12 +104,12 @@ const
 
   specialWords*: array[low(TSpecialWord)..high(TSpecialWord), string] = ["",
 
-    "addr", "and", "as", "asm", "atomic",
+    "addr", "and", "as", "asm",
     "bind", "block", "break", "case", "cast",
     "concept", "const", "continue", "converter",
     "defer", "discard", "distinct", "div", "do",
     "elif", "else", "end", "enum", "except", "export",
-    "finally", "for", "from", "func", "generic", "if",
+    "finally", "for", "from", "func", "if",
     "import", "in", "include", "interface", "is", "isnot", "iterator",
     "let",
     "macro", "method", "mixin", "mod", "nil", "not", "notin",
@@ -116,7 +117,7 @@ const
     "out", "proc", "ptr", "raise", "ref", "return",
     "shl", "shr", "static",
     "template", "try", "tuple", "type", "using", "var",
-    "when", "while", "with", "without", "xor",
+    "when", "while", "xor",
     "yield",
 
     ":", "::", "=", ".", "..",
@@ -131,7 +132,7 @@ const
     "incompletestruct",
     "requiresinit", "align", "nodecl", "pure", "sideeffect",
     "header", "nosideeffect", "gcsafe", "noreturn", "merge", "lib", "dynlib",
-    "compilerproc", "procvar", "base", "used",
+    "compilerproc", "core", "procvar", "base", "used",
     "fatal", "error", "warning", "hint", "line",
     "push", "pop", "define", "undef", "linedir", "stacktrace", "linetrace",
     "link", "compile", "linksys", "deprecated", "varargs",
@@ -139,11 +140,12 @@ const
     "cdecl", "safecall", "syscall", "inline", "noinline", "fastcall", "closure",
     "noconv", "on", "off", "checks", "rangechecks", "boundchecks",
     "overflowchecks", "nilchecks",
-    "floatchecks", "nanchecks", "infchecks",
+    "floatchecks", "nanchecks", "infchecks", "movechecks",
 
     "assertions", "patterns", "warnings", "hints",
     "optimization", "raises", "writes", "reads", "size", "effects", "tags",
-    "deadcodeelim", "safecode", "noforward", "norewrite",
+    "deadcodeelim",  # deprecated, dead code elim always happens
+    "safecode", "package", "noforward", "reorder", "norewrite",
     "pragma",
     "compiletime", "noinit",
     "passc", "passl", "borrow", "discardable", "fieldchecks",
@@ -152,16 +154,16 @@ const
     "computedgoto", "injectstmt", "experimental",
     "write", "gensym", "inject", "dirty", "inheritable", "threadvar", "emit",
     "asmnostackframe", "implicitstatic", "global", "codegendecl", "unchecked",
-    "guard", "locks", "partial",
+    "guard", "locks", "partial", "explain", "liftlocals",
 
-    "auto", "bool", "catch", "char", "class",
+    "auto", "bool", "catch", "char", "class", "compl",
     "const_cast", "default", "delete", "double",
     "dynamic_cast", "explicit", "extern", "false",
     "float", "friend", "goto", "int", "long", "mutable",
     "namespace", "new", "operator",
-    "private", "protected", "public", "register", "reinterpret_cast",
+    "private", "protected", "public", "register", "reinterpret_cast", "restrict",
     "short", "signed", "sizeof", "static_cast", "struct", "switch",
-    "this", "throw", "true", "typedef", "typeid",
+    "this", "throw", "true", "typedef", "typeid", "typeof",
     "typename", "union", "packed", "unsigned", "virtual", "void", "volatile",
     "wchar_t",
 
@@ -171,7 +173,7 @@ const
     "stdin", "stdout", "stderr",
 
     "inout", "bycopy", "byref", "oneway",
-    "bitsize",
+    "bitsize"
     ]
 
 proc findStr*(a: openArray[string], s: string): int =
diff --git a/compiler/writetracking.nim b/compiler/writetracking.nim
index 443e8ddf1..1ea1deb2d 100644
--- a/compiler/writetracking.nim
+++ b/compiler/writetracking.nim
@@ -15,7 +15,8 @@
 ##   * Computing an aliasing relation based on the assignments. This relation
 ##     is then used to compute the 'writes' and 'escapes' effects.
 
-import intsets, idents, ast, astalgo, trees, renderer, msgs, types
+import intsets, idents, ast, astalgo, trees, renderer, msgs, types, options,
+  lineinfos
 
 const
   debug = false
@@ -120,10 +121,10 @@ proc returnsNewExpr*(n: PNode): NewLocation =
       nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkOfBranch,
       nkElifBranch, nkElse, nkExceptBranch, nkFinally, nkCast:
     result = returnsNewExpr(n.lastSon)
-  of nkCurly, nkBracket, nkPar, nkObjConstr, nkClosure,
+  of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure,
       nkIfExpr, nkIfStmt, nkWhenStmt, nkCaseStmt, nkTryStmt:
     result = newLit
-    for i in ord(n.kind == nkObjConstr) .. <n.len:
+    for i in ord(n.kind == nkObjConstr) ..< n.len:
       let x = returnsNewExpr(n.sons[i])
       case x
       of newNone: return newNone
@@ -179,8 +180,8 @@ proc deps(w: var W; n: PNode) =
     for child in n:
       let last = lastSon(child)
       if last.kind == nkEmpty: continue
-      if child.kind == nkVarTuple and last.kind == nkPar:
-        internalAssert child.len-2 == last.len
+      if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
+        if child.len-2 != last.len: return
         for i in 0 .. child.len-3:
           deps(w, child.sons[i], last.sons[i], {})
       else:
@@ -220,7 +221,7 @@ proc possibleAliases(w: var W; result: var seq[ptr TSym]) =
         # x = f(..., y, ....)
         for i in 0 ..< a.srcNoTc: addNoDup a.src[i]
 
-proc markWriteOrEscape(w: var W) =
+proc markWriteOrEscape(w: var W; conf: ConfigRef) =
   ## Both 'writes' and 'escapes' effects ultimately only care
   ## about *parameters*.
   ## However, due to aliasing, even locals that might not look as parameters
@@ -248,6 +249,8 @@ proc markWriteOrEscape(w: var W) =
       for p in a.dest:
         if p.kind == skParam and p.owner == w.owner:
           incl(p.flags, sfWrittenTo)
+          if w.owner.kind == skFunc and p.typ.kind != tyVar:
+            localError(conf, a.info, "write access to non-var parameter: " & p.name.s)
 
     if {rootIsResultOrParam, rootIsHeapAccess, markAsEscaping}*a.destInfo != {}:
       var destIsParam = false
@@ -261,14 +264,14 @@ proc markWriteOrEscape(w: var W) =
           if p.kind == skParam and p.owner == w.owner:
             incl(p.flags, sfEscapes)
 
-proc trackWrites*(owner: PSym; body: PNode) =
+proc trackWrites*(owner: PSym; body: PNode; conf: ConfigRef) =
   var w: W
   w.owner = owner
   w.assignments = @[]
   # Phase 1: Collect and preprocess any assignments in the proc body:
   deps(w, body)
   # Phase 2: Compute the 'writes' and 'escapes' effects:
-  markWriteOrEscape(w)
+  markWriteOrEscape(w, conf)
   if w.returnsNew != asgnOther and not isEmptyType(owner.typ.sons[0]) and
       containsGarbageCollectedRef(owner.typ.sons[0]):
     incl(owner.typ.flags, tfReturnsNew)