summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/ccgmerge.nim10
-rw-r--r--compiler/cgen.nim35
-rw-r--r--compiler/cgendata.nim6
-rw-r--r--compiler/closureiters.nim19
-rw-r--r--compiler/cmdlinehelper.nim39
-rw-r--r--compiler/commands.nim56
-rw-r--r--compiler/depends.nim7
-rw-r--r--compiler/docgen.nim232
-rw-r--r--compiler/docgen2.nim12
-rw-r--r--compiler/extccomp.nim113
-rw-r--r--compiler/filter_tmpl.nim5
-rw-r--r--compiler/filters.nim6
-rw-r--r--compiler/gorgeimpl.nim4
-rw-r--r--compiler/idgen.nim10
-rw-r--r--compiler/importer.nim14
-rw-r--r--compiler/jsgen.nim81
-rw-r--r--compiler/layouter.nim7
-rw-r--r--compiler/lexer.nim51
-rw-r--r--compiler/lineinfos.nim8
-rw-r--r--compiler/linter.nim6
-rw-r--r--compiler/llstream.nim12
-rw-r--r--compiler/main.nim21
-rw-r--r--compiler/modulepaths.nim7
-rw-r--r--compiler/modules.nim19
-rw-r--r--compiler/msgs.nim61
-rw-r--r--compiler/ndi.nim8
-rw-r--r--compiler/nim.nim15
-rw-r--r--compiler/nimblecmd.nim14
-rw-r--r--compiler/nimconf.nim32
-rw-r--r--compiler/nimeval.nim6
-rw-r--r--compiler/nversion.nim2
-rw-r--r--compiler/options.nim185
-rw-r--r--compiler/packagehandling.nim6
-rw-r--r--compiler/parser.nim81
-rw-r--r--compiler/passes.nim6
-rw-r--r--compiler/pathutils.nim260
-rw-r--r--compiler/pragmas.nim56
-rw-r--r--compiler/renderer.nim91
-rw-r--r--compiler/rodimpl.nim4
-rw-r--r--compiler/ropes.nim12
-rw-r--r--compiler/scriptconfig.nim14
-rw-r--r--compiler/sem.nim8
-rw-r--r--compiler/semexprs.nim27
-rw-r--r--compiler/semfields.nim4
-rw-r--r--compiler/semfold.nim75
-rw-r--r--compiler/semstmts.nim73
-rw-r--r--compiler/semtypes.nim17
-rw-r--r--compiler/semtypinst.nim7
-rw-r--r--compiler/sighashes.nim22
-rw-r--r--compiler/suggest.nim4
-rw-r--r--compiler/syntaxes.nim12
-rw-r--r--compiler/transf.nim6
-rw-r--r--compiler/vmdeps.nim2
-rw-r--r--compiler/vmhooks.nim4
-rw-r--r--compiler/vmops.nim2
55 files changed, 1191 insertions, 705 deletions
diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim
index 067a60c57..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.
@@ -226,7 +226,7 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) =
 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
@@ -238,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)
@@ -251,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)
@@ -285,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/cgen.nim b/compiler/cgen.nim
index dea8b1e8a..7a74d8a9b 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -14,7 +14,7 @@ import
   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, lineinfos
+  lowerings, semparallel, tables, sets, ndi, lineinfos, pathutils
 
 import strutils except `%` # collides with ropes.`%`
 
@@ -1064,7 +1064,8 @@ proc genFilenames(m: BModule): Rope =
   discard cgsym(m, "dbgRegisterFilename")
   result = nil
   for i in 0..<m.config.m.fileInfos.len:
-    result.addf("dbgRegisterFilename($1);$N", [m.config.m.fileInfos[i].projPath.makeCString])
+    result.addf("dbgRegisterFilename($1);$N",
+      [m.config.m.fileInfos[i].projPath.string.makeCString])
 
 proc genMainProc(m: BModule) =
   const
@@ -1348,7 +1349,7 @@ proc initProcOptions(m: BModule): TOptions =
   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) & "_")
@@ -1376,7 +1377,7 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule =
     incl result.flags, preventStackTrace
     excl(result.preInitProc.options, optStackTrace)
   let ndiName = if optCDebug in g.config.globalOptions: changeFileExt(completeCFilePath(g.config, filename), "ndi")
-                else: ""
+                else: AbsoluteFile""
   open(result.ndi, ndiName, g.config)
 
 proc nullify[T](arr: var T) =
@@ -1427,7 +1428,7 @@ proc resetCgenModules*(g: BModuleList) =
   for m in cgenModules(g): resetModule(m)
 
 proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
-  result = rawNewModule(g, module, toFullPath(conf, module.position.FileIndex))
+  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
@@ -1446,7 +1447,7 @@ 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: graph.config.headerFile
+    let f = if graph.config.headerFile.len > 0: AbsoluteFile graph.config.headerFile
             else: graph.config.projectFull
     g.generatedHeader = rawNewModule(g, module,
       changeFileExt(completeCFilePath(graph.config, f), hExt))
@@ -1477,9 +1478,9 @@ proc writeHeader(m: BModule) =
   if optUseNimNamespace in m.config.globalOptions: result.add closeNamespaceNim()
   result.addf("#endif /* $1 */$n", [guard])
   if not writeRope(result, m.filename):
-    rawMessage(m.config, errCannotOpenFile, 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 m.config.cmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m"
@@ -1523,18 +1524,18 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
     if not equalsFile(code, cfile.cname):
       if isDefined(m.config, "nimdiff"):
         if fileExists(cfile.cname):
-          copyFile(cfile.cname, cfile.cname & ".backup")
-          echo "diff ", cfile.cname, ".backup ", 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
+          echo "new file ", cfile.cname.string
       if not writeRope(code, cfile.cname):
-        rawMessage(m.config, errCannotOpenFile, 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:
     if not writeRope(code, cfile.cname):
-      rawMessage(m.config, errCannotOpenFile, 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
@@ -1570,14 +1571,14 @@ proc writeModule(m: BModule, pending: bool) =
     finishTypeDescriptions(m)
     var code = genModule(m, cf)
     if not writeRope(code, cfile):
-      rawMessage(m.config, errCannotOpenFile, 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(m.config, toObjFile(m.config, cfile)), flags: {})
-    if not existsFile(cf.obj): cf.flags = {CfileFlag.Cached}
+    if not fileExists(cf.obj): cf.flags = {CfileFlag.Cached}
     addFileToCompile(m.config, cf)
   close(m.ndi)
 
@@ -1592,7 +1593,7 @@ proc updateCachedModule(m: BModule) =
 
     var code = genModule(m, cf)
     if not writeRope(code, cfile):
-      rawMessage(m.config, errCannotOpenFile, cfile)
+      rawMessage(m.config, errCannotOpenFile, cfile.string)
   else:
     cf.flags = {CfileFlag.Cached}
   addFileToCompile(m.config, cf)
diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim
index 56dbd65a2..406ee050f 100644
--- a/compiler/cgendata.nim
+++ b/compiler/cgendata.nim
@@ -11,7 +11,7 @@
 
 import
   ast, astalgo, ropes, passes, options, intsets, platform, sighashes,
-  tables, ndi, lineinfos
+  tables, ndi, lineinfos, pathutils
 
 from modulegraphs import ModuleGraph
 
@@ -136,8 +136,8 @@ type
     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
diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim
index e3e9c2236..b0857e6c7 100644
--- a/compiler/closureiters.nim
+++ b/compiler/closureiters.nim
@@ -712,6 +712,25 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode =
 
       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
 
diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim
index 9d2334af5..8bd073314 100644
--- a/compiler/cmdlinehelper.nim
+++ b/compiler/cmdlinehelper.nim
@@ -1,10 +1,17 @@
-## Helpers for binaries that use compiler passes, eg: nim, nimsuggest, nimfix
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Nim contributors
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
 
-# TODO: nimfix should use this; currently out of sync
+## Helpers for binaries that use compiler passes, eg: nim, nimsuggest, nimfix
 
 import
-  compiler/[options, idents, nimconf, scriptconfig, extccomp, commands, msgs, lineinfos, modulegraphs, condsyms],
-  std/os
+  options, idents, nimconf, scriptconfig, extccomp, commands, msgs,
+  lineinfos, modulegraphs, condsyms, os, pathutils
 
 type
   NimProg* = ref object
@@ -21,27 +28,27 @@ proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) =
   self.processCmdLine(passCmd1, "", conf)
   if self.supportsStdinFile and conf.projectName == "-":
     conf.projectName = "stdinfile"
-    conf.projectFull = "stdinfile"
-    conf.projectPath = canonicalizePath(conf, getCurrentDir())
+    conf.projectFull = AbsoluteFile "stdinfile"
+    conf.projectPath = AbsoluteDir getCurrentDir()
     conf.projectIsStdin = true
   elif conf.projectName != "":
     try:
-      conf.projectFull = canonicalizePath(conf, conf.projectName)
+      conf.projectFull = canonicalizePath(conf, AbsoluteFile conf.projectName)
     except OSError:
-      conf.projectFull = conf.projectName
+      conf.projectFull = AbsoluteFile conf.projectName
     let p = splitFile(conf.projectFull)
-    let dir = if p.dir.len > 0: p.dir else: getCurrentDir()
-    conf.projectPath = canonicalizePath(conf, dir)
+    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 = canonicalizePath(conf, getCurrentDir())
+    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: string)=
+  proc runNimScriptIfExists(path: AbsoluteFile)=
     if fileExists(path):
       runNimScript(cache, path, freshDefines = false, conf)
 
@@ -53,8 +60,8 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi
     runNimScriptIfExists(getUserConfigPath(DefaultConfigNims))
 
   if optSkipParentConfigFiles notin conf.globalOptions:
-    for dir in parentDirs(conf.projectPath, fromRoot = true, inclusive = false):
-      runNimScriptIfExists(dir / DefaultConfigNims)
+    for dir in parentDirs(conf.projectPath.string, fromRoot = true, inclusive = false):
+      runNimScriptIfExists(AbsoluteDir(dir) / DefaultConfigNims)
 
   if optSkipProjConfigFile notin conf.globalOptions:
     runNimScriptIfExists(conf.projectPath / DefaultConfigNims)
@@ -63,10 +70,10 @@ proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: Confi
     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.cmpPaths(conf.projectFull) == 0:
+      if fileExists(scriptFile) and scriptFile == conf.projectFull:
         return false
     else:
-      if scriptFile.cmpPaths(conf.projectFull) != 0:
+      if scriptFile != conf.projectFull:
         runNimScriptIfExists(scriptFile)
       else:
         # 'nimsuggest foo.nims' means to just auto-complete the NimScript file
diff --git a/compiler/commands.nim b/compiler/commands.nim
index b47ccf610..fe820a589 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -26,7 +26,8 @@ bootSwitch(usedNoGC, defined(nogc), "--gc:none")
 
 import
   os, msgs, options, nversion, condsyms, strutils, extccomp, platform,
-  wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos
+  wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos,
+  pathutils
 
 # but some have deps to imported modules. Yay.
 bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc")
@@ -208,7 +209,7 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass,
 
 proc processCompile(conf: ConfigRef; filename: string) =
   var found = findFile(conf, filename)
-  if found == "": found = filename
+  if found.isEmpty: found = AbsoluteFile filename
   extccomp.addExternalFileToCompile(conf, found)
 
 const
@@ -292,31 +293,32 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
   else: invalidCmdLineOption(conf, passCmd1, switch, info)
 
 proc processPath(conf: ConfigRef; path: string, info: TLineInfo,
-                 notRelativeToProj = false): string =
+                 notRelativeToProj = false): AbsoluteDir =
   let p = if os.isAbsolute(path) or '$' in path:
             path
           elif notRelativeToProj:
             getCurrentDir() / path
           else:
-            conf.projectPath / path
+            conf.projectPath.string / path
   try:
-    result = pathSubs(conf, p, toFullPath(conf, info).splitFile().dir)
+    result = AbsoluteDir pathSubs(conf, p, toFullPath(conf, info).splitFile().dir)
   except ValueError:
     localError(conf, info, "invalid path: " & p)
-    result = p
+    result = AbsoluteDir p
 
-proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): string =
-  let path = if path[0] == '"': strutils.unescape(path) else: path
+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(conf, p, basedir)
+    result = AbsoluteDir pathSubs(conf, p, basedir)
   except ValueError:
     localError(conf, info, "invalid path: " & p)
-    result = p
+    result = AbsoluteDir p
 
 const
   errInvalidNumber = "$1 is not a valid number"
@@ -331,9 +333,9 @@ proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) =
   if parseUtils.parseInt(a[3], column) <= 0:
     localError(conf, info, errInvalidNumber % a[2])
 
-  let dirtyOriginalIdx = fileInfoIdx(conf, a[1])
+  let dirtyOriginalIdx = fileInfoIdx(conf, AbsoluteFile a[1])
   if dirtyOriginalIdx.int32 >= 0:
-    msgs.setDirtyFile(conf, dirtyOriginalIdx, a[0])
+    msgs.setDirtyFile(conf, dirtyOriginalIdx, AbsoluteFile a[0])
 
   conf.m.trackPos = newLineInfo(dirtyOriginalIdx, line, column)
 
@@ -345,7 +347,7 @@ proc track(conf: ConfigRef; arg: string, info: TLineInfo) =
     localError(conf, info, errInvalidNumber % a[1])
   if parseUtils.parseInt(a[2], column) <= 0:
     localError(conf, info, errInvalidNumber % a[2])
-  conf.m.trackPos = newLineInfo(conf, a[0], line, column)
+  conf.m.trackPos = newLineInfo(conf, AbsoluteFile a[0], line, column)
 
 proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
   if pass in {passCmd2, passPP}:
@@ -359,14 +361,16 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
   case switch.normalize
   of "path", "p":
     expectArg(conf, switch, arg, pass, info)
-    addPath(conf, if pass == passPP: processCfgPath(conf, arg, info) else: processPath(conf, arg, info), 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 optNoNimblePath notin conf.globalOptions:
       expectArg(conf, switch, arg, pass, info)
       var path = processPath(conf, arg, info, notRelativeToProj=true)
-      let nimbleDir = getEnv("NIMBLE_DIR")
-      if nimbleDir.len > 0 and pass == passPP: path = nimbleDir / "pkgs"
+      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(conf, switch, arg, pass, info)
@@ -374,20 +378,14 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
   of "excludepath":
     expectArg(conf, switch, arg, pass, info)
     let path = processPath(conf, arg, info)
-
-    conf.searchPaths.keepItIf(cmpPaths(it, path) != 0)
-    conf.lazyPaths.keepItIf(cmpPaths(it, path) != 0)
-
-    if (len(path) > 0) and (path[len(path) - 1] == DirSep):
-      let strippedPath = path[0 .. (len(path) - 2)]
-      conf.searchPaths.keepItIf(cmpPaths(it, strippedPath) != 0)
-      conf.lazyPaths.keepItIf(cmpPaths(it, strippedPath) != 0)
+    conf.searchPaths.keepItIf(it != path)
+    conf.lazyPaths.keepItIf(it != path)
   of "nimcache":
     expectArg(conf, switch, arg, pass, info)
     conf.nimcacheDir = processPath(conf, arg, info, true)
   of "out", "o":
     expectArg(conf, switch, arg, pass, info)
-    conf.outFile = arg
+    conf.outFile = AbsoluteFile arg
   of "docseesrcurl":
     expectArg(conf, switch, arg, pass, info)
     conf.docSeeSrcUrl = arg
@@ -411,7 +409,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
     if pass in {passCmd2, passPP}: processCompile(conf, arg)
   of "link":
     expectArg(conf, switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: addExternalFileToLink(conf, arg)
+    if pass in {passCmd2, passPP}:
+      addExternalFileToLink(conf, AbsoluteFile arg)
   of "debuginfo":
     expectNoArg(conf, switch, arg, pass, info)
     incl(conf.globalOptions, optCDebug)
@@ -581,7 +580,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
     if pass in {passCmd2, passPP}: conf.cLibs.add processPath(conf, arg, info)
   of "clib":
     expectArg(conf, switch, arg, pass, info)
-    if pass in {passCmd2, passPP}: conf.cLinkedLibs.add processPath(conf, arg, info)
+    if pass in {passCmd2, passPP}:
+      conf.cLinkedLibs.add processPath(conf, arg, info).string
   of "header":
     if conf != nil: conf.headerFile = arg
     incl(conf.globalOptions, optGenIndex)
@@ -742,7 +742,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
     if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg)
     else: invalidCmdLineOption(conf, pass, switch, info)
 
-template gCmdLineInfo*(): untyped = newLineInfo(config, "command line", 1, 1)
+template gCmdLineInfo*(): untyped = newLineInfo(config, AbsoluteFile"command line", 1, 1)
 
 proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) =
   var cmd, arg: string
diff --git a/compiler/depends.nim b/compiler/depends.nim
index d0a1139ef..c26593ea5 100644
--- a/compiler/depends.nim
+++ b/compiler/depends.nim
@@ -10,7 +10,8 @@
 # This module implements a dependency file generator.
 
 import
-  os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths
+  os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths,
+  pathutils
 
 from modulegraphs import ModuleGraph
 
@@ -45,10 +46,10 @@ proc addDotDependency(c: PPassContext, n: PNode): PNode =
   else:
     discard
 
-proc generateDot*(graph: ModuleGraph; project: string) =
+proc generateDot*(graph: ModuleGraph; project: AbsoluteFile) =
   let b = Backend(graph.backend)
   discard writeRope("digraph $1 {$n$2}$n" % [
-      rope(changeFileExt(extractFilename(project), "")), b.dotGraph],
+      rope(project.splitFile.name), b.dotGraph],
             changeFileExt(project, "dot"))
 
 proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index 23d156e05..ca3b1ac2d 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -16,7 +16,8 @@ import
   wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast,
   packages/docutils/rst, packages/docutils/rstgen,
   packages/docutils/highlite, sempass2, json, xmltree, cgi,
-  typesrenderer, astalgo, modulepaths, lineinfos, sequtils
+  typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets,
+  pathutils
 
 type
   TSections = array[TSymKind, Rope]
@@ -32,6 +33,10 @@ type
     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.
 
@@ -46,12 +51,12 @@ 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)
@@ -72,10 +77,10 @@ template declareClosures =
     of mwUnknownSubstitution: k = warnUnknownSubstitutionX
     of mwUnsupportedLanguage: k = warnLanguageXNotSupported
     of mwUnsupportedField: k = warnFieldXNotSupported
-    globalError(conf, newLineInfo(conf, filename, line, col), k, arg)
+    globalError(conf, newLineInfo(conf, AbsoluteFile filename, line, col), k, arg)
 
   proc docgenFindFile(s: string): string {.procvar.} =
-    result = options.findFile(conf, s)
+    result = options.findFile(conf, s).string
     if result.len == 0:
       result = getCurrentDir() / s
       if not existsFile(result): result = ""
@@ -88,13 +93,29 @@ proc parseRst(text, filename: string,
   result = rstParse(text, filename, line, column, hasToc, rstOptions,
                     docgenFindFile, compilerMsgHandler)
 
-proc newDocumentor*(filename: string; cache: IdentCache; conf: ConfigRef): 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): PDoc =
   declareClosures()
   new(result)
   result.conf = conf
   result.cache = cache
   initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex),
-                   conf.configVars, filename, {roSupportRawDirective},
+                   conf.configVars, filename.string, {roSupportRawDirective},
                    docgenFindFile, compilerMsgHandler)
 
   if conf.configVars.hasKey("doc.googleAnalytics"):
@@ -118,7 +139,12 @@ proc newDocumentor*(filename: string; cache: IdentCache; conf: ConfigRef): PDoc
   result.jArray = newJArray()
   initStrTable result.types
   result.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) =
-    localError(conf, newLineInfo(conf, d.filename, -1, -1), warnUser, "only 'rst2html' supports the ':test:' attribute")
+    localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1),
+               warnUser, "only 'rst2html' supports the ':test:' attribute")
+  result.emitted = initIntSet()
+  result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath),
+                                HtmlExt, 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)
@@ -224,6 +250,10 @@ proc getPlainDocstring(n: PNode): string =
       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 nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) =
   var r: TSrcGen
   var literal = ""
@@ -256,8 +286,22 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
       dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>",
             "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))])
     of tkSymbol:
-      dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
-            "\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
+      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 full = AbsoluteFile toFullPath(d.conf, FileIndex s.owner.position)
+        let tmp = getOutFile2(d.conf, full.relativeTo(d.conf.projectPath),
+            HtmlExt, RelativeDir"htmldocs", sfMainModule notin s.owner.flags)
+
+        let external = tmp.relativeTo(d.thisDir, '/')
+        result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>",
+          [rope changeFileExt(external, "html").string, rope literal,
+           rope(esc(d.target, literal))]
+      else:
+        dispA(d.conf, result, "<span class=\"Identifier\">$1</span>",
+              "\\spanIdentifier{$1}", [rope(esc(d.target, literal))])
     of tkSpaces, tkInvalid:
       add(result, literal)
     of tkCurlyDotLe:
@@ -287,23 +331,25 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
 
 proc testExample(d: PDoc; ex: PNode) =
   if d.conf.errorCounter > 0: return
-  let outputDir = d.conf.getNimcacheDir / "runnableExamples"
+  let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples"
   createDir(outputDir)
   inc d.exampleCounter
-  let outp = outputDir / extractFilename(d.filename.changeFileExt"" &
-      "_examples" & $d.exampleCounter & ".nim")
+  let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" &
+      "_examples" & $d.exampleCounter & ".nim"))
   #let nimcache = outp.changeFileExt"" & "_nimcache"
-  renderModule(ex, d.filename, outp, conf = d.conf)
+  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 &
-                    " --nimcache:" & outputDir & " -r " & outp) != 0:
-    quit "[Examples] failed: see " & outp
+                    " --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])
+    rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string])
     removeFile(outp.changeFileExt(ExeExt))
 
 proc extractImports(n: PNode; result: PNode) =
@@ -329,7 +375,8 @@ proc isRunnableExample(n: PNode): bool =
   result = n.kind == nkSym and n.sym.magic == mRunnableExamples or
     n.kind == nkIdent and n.ident.s == "runnableExamples"
 
-proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) =
+proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) =
+  if n.info.fileIndex != orig.info.fileIndex: return
   case n.kind
   of nkCallKinds:
     if isRunnableExample(n[0]) and
@@ -354,7 +401,10 @@ proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) =
       dest.add(d.config.getOrDefault"doc.listing_end" % id)
   else: discard
   for i in 0 ..< n.safeLen:
-    getAllRunnableExamples(d, n[i], dest)
+    getAllRunnableExamplesRec(d, n[i], orig, dest)
+
+proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) =
+  getAllRunnableExamplesRec(d, n, n, dest)
 
 when false:
   proc findDocComment(n: PNode): PNode =
@@ -377,7 +427,7 @@ when false:
       else:
         result = n.comment.substr(2).replace("\n##", "\n").strip
 
-proc isVisible(n: PNode): bool =
+proc isVisible(d: PDoc; n: PNode): bool =
   result = false
   if n.kind == nkPostfix:
     if n.len == 2 and n.sons[0].kind == nkIdent:
@@ -388,8 +438,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
@@ -444,10 +496,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):
@@ -455,7 +505,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.
   ##
@@ -477,11 +526,9 @@ proc complexName(k: TSymKind, n: PNode, baseName: string): string =
   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
@@ -490,7 +537,6 @@ proc isCallable(n: PNode): bool =
   else:
     result = false
 
-
 proc docstringSummary(rstText: string): string =
   ## Returns just the first line or a brief chunk of text from a rst string.
   ##
@@ -518,9 +564,8 @@ 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
@@ -540,8 +585,8 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
       break
     plainName.add(literal)
 
-  # Render the HTML hyperlink.
-  nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, renderDocComments})
+  nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments,
+    renderDocComments, renderSyms})
 
   inc(d.id)
   let
@@ -558,16 +603,18 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
   var seeSrcRope: Rope = nil
   let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc")
   if docItemSeeSrc.len > 0:
-    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 path = relativeTo(AbsoluteFile toFullPath(d.conf, n.info), d.conf.projectPath, '/')
+    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,
+          ["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"),
@@ -576,11 +623,22 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
     [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)
+  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"],
@@ -591,24 +649,22 @@ 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.int,
                  "col": %n.info.col}
   if comm.len > 0:
@@ -621,7 +677,6 @@ proc checkForFalse(n: PNode): bool =
 
 proc traceDeps(d: PDoc, it: PNode) =
   const k = skModule
-
   if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket:
     let sep = it[0]
     let dir = it[1]
@@ -632,13 +687,19 @@ proc traceDeps(d: PDoc, it: PNode) =
     for x in it[2]:
       a.sons[2] = x
       traceDeps(d, a)
-  else:
+  elif it.kind == nkSym and belongsToPackage(d.conf, it.sym):
+    let full = AbsoluteFile toFullPath(d.conf, FileIndex it.sym.position)
+    let tmp = getOutFile2(d.conf, full.relativeTo(d.conf.projectPath), HtmlExt,
+        RelativeDir"htmldocs", sfMainModule notin it.sym.flags)
+    let external = relativeTo(tmp, d.thisDir, '/').string
     if d.section[k] != nil: add(d.section[k], ", ")
     dispA(d.conf, d.section[k],
-          "<a class=\"reference external\" href=\"$1.html\">$1</a>",
-          "$1", [rope(splitFile(getModuleName(d.conf, it)).name)])
+          "<a class=\"reference external\" href=\"$2\">$1</a>",
+          "$1", [rope esc(d.target, changeFileExt(external, "")),
+          rope changeFileExt(external, "html")])
 
-proc generateDoc*(d: PDoc, n: PNode) =
+proc generateDoc*(d: PDoc, n, orig: PNode) =
+  if orig.info.fileIndex != n.info.fileIndex: return
   case n.kind
   of nkCommentStmt: add(d.modDesc, genComment(d, n))
   of nkProcDef:
@@ -665,11 +726,11 @@ 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])
   of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0])
@@ -798,7 +859,8 @@ proc genOutFile(d: PDoc): Rope =
   # 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, ""))
@@ -824,29 +886,27 @@ proc genOutFile(d: PDoc): Rope =
 
 proc generateIndex*(d: PDoc) =
   if optGenIndex in d.conf.globalOptions:
-    writeIndexFile(d[], splitFile(d.conf.outFile).dir /
-                        splitFile(d.filename).name & IndexExt)
-
-proc getOutFile2(conf: ConfigRef; filename, ext, dir: string): string =
-  if optWholeProject in conf.globalOptions:
-    let d = if conf.outFile != "": conf.outFile else: dir
-    createDir(d)
-    result = d / changeFileExt(filename, ext)
-  else:
-    result = getOutFile(conf, filename, ext)
-
-proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) =
+    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 d.conf.globalOptions:
     writeRope(stdout, content)
   else:
-    let outfile = getOutFile2(d.conf, filename, outExt, "htmldocs")
-    createDir(outfile.parentDir)
+    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)
+      rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile,
+        outfile.string)
 
-proc writeOutputJson*(d: PDoc, filename, outExt: string,
-                      useWarning = false) =
+proc writeOutputJson*(d: PDoc, useWarning = false) =
   let content = %*{"orig": d.filename,
     "nimble": getPackageName(d.conf, d.filename),
     "entries": d.jArray}
@@ -854,8 +914,7 @@ proc writeOutputJson*(d: PDoc, filename, outExt: string,
     write(stdout, $content)
   else:
     var f: File
-    if open(f, getOutFile2(d.conf, splitFile(filename).name,
-            outExt, "jsondocs"), fmWrite):
+    if open(f, d.destFile.string, fmWrite):
       write(f, $content)
       close(f)
     else:
@@ -866,27 +925,28 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) =
   if ast == nil: return
   var d = newDocumentor(conf.projectFull, cache, conf)
   d.hasToc = true
-  generateDoc(d, ast)
-  writeOutput(d, conf.projectFull, HtmlExt)
+  generateDoc(d, ast, ast)
+  writeOutput(d)
   generateIndex(d)
 
-proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename, outExt: string) =
+proc commandRstAux(cache: IdentCache, conf: ConfigRef;
+                   filename: AbsoluteFile, outExt: string) =
   var filen = addFileExt(filename, "txt")
   var d = newDocumentor(filen, cache, conf)
   d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string;
                           status: int; content: string) =
-    var outp: string
+    var outp: AbsoluteFile
     if filename.len == 0:
       inc(d.id)
       let nameOnly = splitFile(d.filename).name
-      let subdir = getNimcacheDir(conf) / nameOnly
+      let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly)
       createDir(subdir)
-      outp = subdir / (nameOnly & "_snippet_" & $d.id & ".nim")
+      outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim")
     elif isAbsolute(filename):
-      outp = filename
+      outp = AbsoluteFile filename
     else:
       # Nim's convention: every path is relative to the file it was written in:
-      outp = splitFile(d.filename).dir / filename
+      outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename)
     writeFile(outp, content)
     let cmd = cmd % quoteShell(outp)
     rawMessage(conf, hintExecuting, cmd)
@@ -894,14 +954,12 @@ proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename, outExt: string)
       rawMessage(conf, errGenerated, "executing of external program failed: " & cmd)
 
   d.isPureRst = true
-  var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc,
+  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*(cache: IdentCache, conf: ConfigRef) =
@@ -923,9 +981,9 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) =
     writeRope(stdout, content)
   else:
     #echo getOutFile(gProjectFull, JsonExt)
-    let filename = getOutFile(conf, conf.projectFull, JsonExt)
+    let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt)
     if not writeRope(content, filename):
-      rawMessage(conf, errCannotOpenFile, filename)
+      rawMessage(conf, errCannotOpenFile, filename.string)
 
 proc commandTags*(cache: IdentCache, conf: ConfigRef) =
   var ast = parseFile(conf.projectMainIdx, cache, conf)
@@ -940,12 +998,12 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) =
     writeRope(stdout, content)
   else:
     #echo getOutFile(gProjectFull, TagsExt)
-    let filename = getOutFile(conf, conf.projectFull, TagsExt)
+    let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt)
     if not writeRope(content, filename):
-      rawMessage(conf, errCannotOpenFile, filename)
+      rawMessage(conf, errCannotOpenFile, filename.string)
 
 proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) =
-  var content = mergeIndexes(conf.projectFull).rope
+  var content = mergeIndexes(conf.projectFull.string).rope
 
   let code = ropeFormatNamedVars(conf, getConfigVar(conf, "doc.file"), ["title",
       "tableofcontents", "moduledesc", "date", "time",
@@ -953,6 +1011,6 @@ proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) =
       ["Index".rope, nil, nil, rope(getDateStr()),
                    rope(getClockStr()), content, nil, nil, nil])
   # no analytics because context is not available
-  let filename = getOutFile(conf, "theindex", HtmlExt)
+  let filename = getOutFile(conf, RelativeFile"theindex", HtmlExt)
   if not writeRope(code, filename):
-    rawMessage(conf, errCannotOpenFile, filename)
+    rawMessage(conf, errCannotOpenFile, filename.string)
diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim
index 22fef0d47..01acdd4ca 100644
--- a/compiler/docgen2.nim
+++ b/compiler/docgen2.nim
@@ -11,7 +11,8 @@
 # semantic checking.
 
 import
-  os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos
+  os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos,
+  pathutils
 
 from modulegraphs import ModuleGraph
 
@@ -38,17 +39,17 @@ template closeImpl(body: untyped) {.dirty.} =
 
 proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
   closeImpl:
-    writeOutput(g.doc, toFullPath(graph.config, FileIndex g.module.position), HtmlExt, useWarning)
+    writeOutput(g.doc, useWarning)
 
 proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode =
   closeImpl:
-    writeOutputJson(g.doc, toFullPath(graph.config, FileIndex g.module.position), ".json", useWarning)
+    writeOutputJson(g.doc, useWarning)
 
 proc processNode(c: PPassContext, n: PNode): PNode =
   result = n
   var g = PGen(c)
   if shouldProcess(g):
-    generateDoc(g.doc, n)
+    generateDoc(g.doc, n, n)
 
 proc processNodeJson(c: PPassContext, n: PNode): PNode =
   result = n
@@ -60,7 +61,8 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext =
   var g: PGen
   new(g)
   g.module = module
-  var d = newDocumentor(toFullPath(graph.config, FileIndex module.position), graph.cache, graph.config)
+  var d = newDocumentor(AbsoluteFile toFullPath(graph.config, FileIndex module.position),
+      graph.cache, graph.config)
   d.hasToc = true
   g.doc = d
   result = g
diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim
index 16b0d614d..69698ae09 100644
--- a/compiler/extccomp.nim
+++ b/compiler/extccomp.nim
@@ -14,7 +14,7 @@
 
 import
   ropes, os, strutils, osproc, platform, condsyms, options, msgs,
-  lineinfos, std / sha1, streams
+  lineinfos, std / sha1, streams, pathutils
 
 type
   TInfoCCProp* = enum         # properties of the C compiler:
@@ -429,12 +429,13 @@ proc initVars*(conf: ConfigRef) =
   if len(conf.ccompilerpath) == 0:
     conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path")
 
-proc completeCFilePath*(conf: ConfigRef; cfile: string, createSubDir: bool = true): string =
+proc completeCFilePath*(conf: ConfigRef; cfile: AbsoluteFile,
+                        createSubDir: bool = true): AbsoluteFile =
   result = completeGeneratedFilePath(conf, cfile, createSubDir)
 
-proc toObjFile*(conf: ConfigRef; filename: string): string =
+proc toObjFile*(conf: ConfigRef; filename: AbsoluteFile): AbsoluteFile =
   # Object file for compilation
-  result = filename & "." & CC[conf.cCompiler].objExt
+  result = AbsoluteFile(filename.string & "." & CC[conf.cCompiler].objExt)
 
 proc addFileToCompile*(conf: ConfigRef; cf: Cfile) =
   conf.toCompile.add(cf)
@@ -447,8 +448,8 @@ proc resetCompilationLists*(conf: ConfigRef) =
   # Maybe we can do that in checkDep on the other hand?
   conf.externalToLink.setLen 0
 
-proc addExternalFileToLink*(conf: ConfigRef; filename: string) =
-  conf.externalToLink.insert(filename, 0)
+proc addExternalFileToLink*(conf: ConfigRef; filename: AbsoluteFile) =
+  conf.externalToLink.insert(filename.string, 0)
 
 proc execWithEcho(conf: ConfigRef; cmd: string, msg = hintExecuting): int =
   rawMessage(conf, msg, cmd)
@@ -459,14 +460,15 @@ proc execExternalProgram*(conf: ConfigRef; cmd: string, msg = hintExecuting) =
     rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
       cmd)
 
-proc generateScript(conf: ConfigRef; projectFile: string, script: Rope) =
-  let (dir, name, ext) = splitFile(projectFile)
-  let filename = getNimcacheDir(conf) / addFileExt("compile_" & name,
-                                     platform.OS[conf.target.targetOS].scriptExt)
+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 / "nimbase.h", getNimcacheDir(conf) / "nimbase.h")
+    copyFile(conf.libpath / RelativeFile"nimbase.h",
+             getNimcacheDir(conf) / RelativeFile"nimbase.h")
   else:
-    rawMessage(conf, errGenerated, "could not write to file: " & filename)
+    rawMessage(conf, errGenerated, "could not write to file: " & filename.string)
 
 proc getOptSpeed(conf: ConfigRef; c: TSystemCC): string =
   result = getConfigVar(conf, c, ".options.speed")
@@ -490,7 +492,7 @@ proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} =
   # `optGenMapping` is included here for niminst.
   result = conf.globalOptions * {optGenScript, optGenMapping} != {}
 
-proc cFileSpecificOptions(conf: ConfigRef; cfilename: string): string =
+proc cFileSpecificOptions(conf: ConfigRef; cfilename: AbsoluteFile): string =
   result = conf.compileOptions
   for option in conf.compileOptionsCmd:
     if strutils.find(result, option, 0) < 0:
@@ -513,7 +515,7 @@ proc cFileSpecificOptions(conf: ConfigRef; cfilename: string): string =
   if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key))
 
 proc getCompileOptions(conf: ConfigRef): string =
-  result = cFileSpecificOptions(conf, "__dummy__")
+  result = cFileSpecificOptions(conf, AbsoluteFile"__dummy__")
 
 proc getLinkOptions(conf: ConfigRef): string =
   result = conf.linkOptions & " " & conf.linkOptionsCmd & " "
@@ -526,8 +528,8 @@ 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: string): string =
-  result = if conf.cmd == cmdCompileToCpp and not cfile.endsWith(".c"):
+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
@@ -539,7 +541,7 @@ proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: string): string
 proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string =
   result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe
            elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler
-           else: getCompilerExe(conf, compiler, "")
+           else: getCompilerExe(conf, compiler, AbsoluteFile"")
 
 proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string =
   var c = conf.cCompiler
@@ -565,43 +567,42 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string =
     includeCmd = ""
     compilePattern = getCompilerExe(conf, c, cfile.cname)
 
-  var cf = if noAbsolutePaths(conf): extractFilename(cfile.cname)
+  var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string)
            else: cfile.cname
 
   var objfile =
-    if cfile.obj.len == 0:
+    if cfile.obj.isEmpty:
       if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths(conf):
-        toObjFile(conf, cf)
+        toObjFile(conf, cf).string
       else:
-        completeCFilePath(conf, toObjFile(conf, cf))
+        completeCFilePath(conf, toObjFile(conf, cf)).string
     elif noAbsolutePaths(conf):
-      extractFilename(cfile.obj)
+      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 % [
     "dfile", dfile,
-    "file", cf, "objfile", objfile, "options", options,
-    "include", includeCmd, "nim", getPrefixDir(conf),
-    "nim", getPrefixDir(conf), "lib", conf.libpath])
+    "file", cfsh, "objfile", objfile, "options", options,
+    "include", includeCmd, "nim", getPrefixDir(conf).string,
+    "lib", conf.libpath.string])
   add(result, ' ')
   addf(result, CC[c].compileTmpl, [
     "dfile", dfile,
-    "file", cf, "objfile", objfile,
+    "file", cfsh, "objfile", objfile,
     "options", options, "include", includeCmd,
     "nim", quoteShell(getPrefixDir(conf)),
-    "nim", quoteShell(getPrefixDir(conf)),
     "lib", quoteShell(conf.libpath)])
 
 proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash =
   result = secureHash(
-    $secureHashFile(cfile.cname) &
+    $secureHashFile(cfile.cname.string) &
     platform.OS[conf.target.targetOS].name &
     platform.CPU[conf.target.targetCPU].name &
     extccomp.CC[conf.cCompiler].name &
@@ -614,14 +615,14 @@ proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool =
   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)
 
@@ -630,7 +631,7 @@ proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) =
     c.flags.incl CfileFlag.Cached
   conf.toCompile.add(c)
 
-proc addExternalFileToCompile*(conf: ConfigRef; filename: string) =
+proc addExternalFileToCompile*(conf: ConfigRef; filename: AbsoluteFile) =
   var c = Cfile(cname: filename,
     obj: toObjFile(conf, completeCFilePath(conf, filename, false)),
     flags: {CfileFlag.External})
@@ -650,11 +651,11 @@ proc compileCFile(conf: ConfigRef; list: CFileList, script: var Rope, cmds: var
       add(script, compileCmd)
       add(script, "\n")
 
-proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string =
+proc getLinkCmd(conf: ConfigRef; projectfile: AbsoluteFile, objfiles: string): string =
   if optGenStaticLib in conf.globalOptions:
     var libname: string
-    if conf.outFile.len > 0:
-      libname = conf.outFile.expandTilde
+    if not conf.outFile.isEmpty:
+      libname = conf.outFile.string.expandTilde
       if not libname.isAbsolute():
         libname = getCurrentDir() / libname
     else:
@@ -679,13 +680,13 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string =
     else:
       exefile = splitFile(projectfile).name & platform.OS[conf.target.targetOS].exeExt
       builddll = ""
-    if conf.outFile.len > 0:
-      exefile = conf.outFile.expandTilde
+    if not conf.outFile.isEmpty:
+      exefile = conf.outFile.string.expandTilde
       if not exefile.isAbsolute():
         exefile = getCurrentDir() / exefile
     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 conf.globalOptions:
         writeDebugInfo(exefile.changeFileExt("ndb"))
@@ -693,7 +694,7 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string =
 
     # 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) / splitFile(projectFile).name & ".map")
+    let mapfile = quoteShell(getNimcacheDir(conf) / RelativeFile(splitFile(projectFile).name & ".map"))
 
     let linkOptions = getLinkOptions(conf) & " " &
                       getConfigVar(conf, conf.cCompiler, ".options.linker")
@@ -703,7 +704,7 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string =
     result = quoteShell(result % ["builddll", builddll,
         "mapfile", mapfile,
         "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles,
-        "exefile", exefile, "nim", getPrefixDir(conf), "lib", conf.libpath])
+        "exefile", exefile, "nim", getPrefixDir(conf).string, "lib", conf.libpath.string])
     result.add ' '
     addf(result, linkTmpl, ["builddll", builddll,
         "mapfile", mapfile,
@@ -761,7 +762,7 @@ proc execCmdsInParallel(conf: ConfigRef; cmds: seq[string]; prettyCb: proc (idx:
       rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" %
         cmds.join())
 
-proc callCCompiler*(conf: ConfigRef; projectfile: string) =
+proc callCCompiler*(conf: ConfigRef; projectfile: AbsoluteFile) =
   var
     linkCmd: string
   if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}:
@@ -787,7 +788,7 @@ proc callCCompiler*(conf: ConfigRef; projectfile: string) =
       add(objfiles, quoteShell(
           addFileExt(objFile, CC[conf.cCompiler].objExt)))
     for x in conf.toCompile:
-      let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj
+      let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj.string
       add(objfiles, ' ')
       add(objfiles, quoteShell(objFile))
 
@@ -804,7 +805,7 @@ proc callCCompiler*(conf: ConfigRef; projectfile: string) =
 #from json import escapeJson
 import json
 
-proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) =
+proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
   template lit(x: untyped) = f.write x
   template str(x: untyped) =
     when compiles(escapeJson(x, buf)):
@@ -821,7 +822,7 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) =
       let compileCmd = getCompileCFileCmd(conf, it)
       if pastStart: lit "],\L"
       lit "["
-      str it.cname
+      str it.cname.string
       lit ", "
       str compileCmd
       pastStart = true
@@ -851,11 +852,10 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) =
 
   var buf = newStringOfCap(50)
 
-  let file = projectfile.splitFile.name
-  let jsonFile = toGeneratedFile(conf, 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(conf, f, buf, conf.toCompile, false)
     lit "],\L\"link\":[\L"
@@ -868,11 +868,10 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) =
     lit "\L}\L"
     close(f)
 
-proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: string) =
-  let file = projectfile.splitFile.name
-  let jsonFile = toGeneratedFile(conf, file, "json")
+proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) =
+  let jsonFile = toGeneratedFile(conf, projectfile, "json")
   try:
-    let data = json.parseFile(jsonFile)
+    let data = json.parseFile(jsonFile.string)
     let toCompile = data["compile"]
     doAssert toCompile.kind == JArray
     var cmds: TStringSeq = @[]
@@ -896,11 +895,11 @@ proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: string) =
   except:
     when declared(echo):
       echo getCurrentException().getStackTrace()
-    quit "error evaluating JSON file: " & jsonFile
+    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*(conf: ConfigRef; symbolMapping: Rope) =
   if optGenMapping notin conf.globalOptions: return
@@ -914,9 +913,9 @@ proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) =
                             getConfigVar(conf, conf.cCompiler, ".options.linker")))
 
   add(code, "\n[Environment]\nlibpath=")
-  add(code, strutils.escape(conf.libpath))
+  add(code, strutils.escape(conf.libpath.string))
 
   addf(code, "\n[Symbols]$n$1", [symbolMapping])
-  let filename = joinPath(conf.projectPath, "mapping.txt")
+  let filename = conf.projectPath / RelativeFile"mapping.txt"
   if not writeRope(code, filename):
-    rawMessage(conf, errGenerated, "could not write to file: " & filename)
+    rawMessage(conf, errGenerated, "could not write to file: " & filename.string)
diff --git a/compiler/filter_tmpl.nim b/compiler/filter_tmpl.nim
index 09455ced7..b884b1ec3 100644
--- a/compiler/filter_tmpl.nim
+++ b/compiler/filter_tmpl.nim
@@ -11,7 +11,7 @@
 
 import
   llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options,
-  renderer, filters, lineinfos
+  renderer, filters, lineinfos, pathutils
 
 type
   TParseState = enum
@@ -199,7 +199,8 @@ proc parseLine(p: var TTmplParser) =
           inc(j)
     llStreamWrite(p.outp, "\\n\"")
 
-proc filterTmpl*(stdin: PLLStream, filename: string, call: PNode; conf: ConfigRef): PLLStream =
+proc filterTmpl*(stdin: PLLStream, filename: AbsoluteFile,
+                 call: PNode; conf: ConfigRef): PLLStream =
   var p: TTmplParser
   p.config = conf
   p.info = newLineInfo(conf, filename, 0, 0)
diff --git a/compiler/filters.nim b/compiler/filters.nim
index 3ebbad678..d9e8e41da 100644
--- a/compiler/filters.nim
+++ b/compiler/filters.nim
@@ -11,7 +11,7 @@
 
 import
   llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options,
-  renderer
+  renderer, pathutils
 
 proc invalidPragma(conf: ConfigRef; n: PNode) =
   localError(conf, n.info,
@@ -47,7 +47,7 @@ proc boolArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: bool):
   elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "false") == 0: result = false
   else: invalidPragma(conf, n)
 
-proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream =
+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)
@@ -61,7 +61,7 @@ proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNo
       llStreamWriteln(result, line)
   llStreamClose(stdin)
 
-proc filterReplace*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream =
+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, "")
diff --git a/compiler/gorgeimpl.nim b/compiler/gorgeimpl.nim
index 44ad46136..44636f382 100644
--- a/compiler/gorgeimpl.nim
+++ b/compiler/gorgeimpl.nim
@@ -10,7 +10,7 @@
 ## Module that implements ``gorge`` for the compiler.
 
 import msgs, std / sha1, os, osproc, streams, strutils, options,
-  lineinfos
+  lineinfos, pathutils
 
 proc readOutput(p: Process): (string, int) =
   result[0] = ""
@@ -26,7 +26,7 @@ proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (str
   let workingDir = parentDir(toFullPath(conf, info))
   if cache.len > 0:# and optForceFullMake notin gGlobalOptions:
     let h = secureHash(cmd & "\t" & input & "\t" & cache)
-    let filename = options.toGeneratedFile(conf, "gorge_" & $h, "txt")
+    let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string
     var f: File
     if open(f, filename):
       result = (f.readAll, 0)
diff --git a/compiler/idgen.nim b/compiler/idgen.nim
index 7d103ffd7..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,18 +36,18 @@ proc setId*(id: int) {.inline.} =
 proc idSynchronizationPoint*(idRange: int) =
   gFrontEndId = (gFrontEndId div idRange + 1) * idRange + 1
 
-proc toGid(conf: ConfigRef; 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(conf, "nim.gid")
+  result = options.completeGeneratedFilePath(conf, AbsoluteFile"nim.gid").string
 
-proc saveMaxIds*(conf: ConfigRef; project: string) =
+proc saveMaxIds*(conf: ConfigRef; project: AbsoluteFile) =
   var f = open(toGid(conf, project), fmWrite)
   f.writeLine($gFrontEndId)
   f.close()
 
-proc loadMaxIds*(conf: ConfigRef; project: string) =
+proc loadMaxIds*(conf: ConfigRef; project: AbsoluteFile) =
   var f: File
   if open(f, toGid(conf, project), fmRead):
     var line = newStringOfCap(20)
diff --git a/compiler/importer.nim b/compiler/importer.nim
index 73d2e6599..60b7872fe 100644
--- a/compiler/importer.nim
+++ b/compiler/importer.nim
@@ -7,15 +7,12 @@
 #    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, lookups,
   semdata, passes, renderer, modulepaths, sigmatch, lineinfos
 
-proc evalImport*(c: PContext, n: PNode): PNode
-proc evalFrom*(c: PContext, n: PNode): PNode
-
 proc readExceptSet*(c: PContext, n: PNode): IntSet =
   assert n.kind in {nkImportExceptStmt, nkExportExceptStmt}
   result = initIntSet()
@@ -140,7 +137,7 @@ proc importModuleAs(c: PContext; n: PNode, realModule: PSym): PSym =
                                c.config.options)
 
 proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym =
-  var f = checkModuleName(c.config, n)
+  let f = checkModuleName(c.config, n)
   if f != InvalidFileIDX:
     let L = c.graph.importStack.len
     let recursion = c.graph.importStack.find(f)
@@ -168,7 +165,8 @@ proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym =
       else:
         message(c.config, n.info, warnDeprecated, result.name.s)
     suggestSym(c.config, n.info, result, c.graph.usageSym, false)
-    importStmtResult.add newStrNode(toFullPath(c.config, f), n.info)
+    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":
@@ -188,7 +186,7 @@ proc impMod(c: PContext; it: PNode; importStmtResult: PNode) =
     importAllSymbolsExcept(c, m, emptySet)
     #importForwarded(c, m.ast, emptySet)
 
-proc evalImport(c: PContext, n: PNode): PNode =
+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]
@@ -212,7 +210,7 @@ proc evalImport(c: PContext, n: PNode): PNode =
     else:
       impMod(c, it, result)
 
-proc evalFrom(c: PContext, n: PNode): PNode =
+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])
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 1b00ddbfa..aa2386526 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -32,7 +32,7 @@ import
   ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options,
   nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables,
   times, ropes, math, passes, ccgutils, wordrecg, renderer,
-  intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils
+  intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils, pathutils
 
 from modulegraphs import ModuleGraph
 
@@ -658,15 +658,16 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) =
   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")
     lineF(p, "raiseException($1, $2);$n",
              [a.rdLoc, makeJSString(typ.sym.name.s)])
   else:
+    genLineDir(p, n)
     useMagic(p, "reraiseException")
     line(p, "reraiseException();\L")
 
@@ -764,11 +765,22 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) =
     of nkSym:
       let v = it.sym
       # for backwards compatibility we don't deref syms here :-(
-      if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}:
-        p.body.add mangleName(p.module, v)
+      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
+        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
@@ -845,6 +857,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
   else:
     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
@@ -881,11 +894,9 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
     lineF(p, "$1 = $2;$n", [a.res, b.res])
 
 proc genAsgn(p: PProc, n: PNode) =
-  genLineDir(p, n)
   genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false)
 
 proc genFastAsgn(p: PProc, n: PNode) =
-  genLineDir(p, n)
   # 'shallowCopy' always produced 'noCopyNeeded = true' here but this is wrong
   # for code like
   #  while j >= pos:
@@ -1529,18 +1540,18 @@ 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])
+    r.res.add("[$1])" % [a.res])
   else:
     r.res.add("$1)" % [a.res])
 
@@ -1647,14 +1658,20 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
     else: unaryExpr(p, n, r, "subInt", "subInt($1, 1)")
   of mAppendStrCh:
     binaryExpr(p, n, r, "addChar",
-        "if ($1 != null) { addChar($1, $2); } else { $1 = [$2, 0]; }")
+        "if ($1 != null) { addChar($1, $2); } else { $1 = [$2]; }")
   of mAppendStrStr:
+    var lhs, rhs: TCompRes
+    gen(p, n[1], lhs)
+    gen(p, n[2], rhs)
+
+    let rhsIsLit = n[2].kind in nkStrKinds
     if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString:
-        binaryExpr(p, n, r, "", "if ($1 != null) { $1 += $2; } else { $1 = $2; }")
+      r.res = "if ($1 != null) { $1 += $2; } else { $1 = $2$3; }" % [
+        lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"]
     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
+      r.res = "if ($1 != null) { $1 = ($1).concat($2); } else { $1 = $2$3; }" % [
+          lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"]
+    r.kind = resExpr
   of mAppendSeqElem:
     var x, y: TCompRes
     gen(p, n.sons[1], x)
@@ -1682,21 +1699,12 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
   of mSizeOf: r.res = rope(getSize(p.config, 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 n.sons[1].typ.skipTypes(abstractInst).kind == tyCString:
-      unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)")
-    else:
-      unaryExpr(p, n, r, "", "($1 != null ? $1.length-1 : 0)")
-  of mXLenStr: unaryExpr(p, n, r, "", "$1.length-1")
-  of mLengthSeq, mLengthOpenArray, mLengthArray:
+  of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray:
     unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)")
-  of mXLenSeq:
+  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)")
-    else:
-      unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)")
+    unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)")
   of mInc:
     if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64:
       binaryUintExpr(p, n, r, "+", true)
@@ -1710,7 +1718,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
       if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2")
       else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)")
   of mSetLengthStr:
-    binaryExpr(p, n, r, "", "$1.length = $2+1; $1[$1.length-1] = 0")
+    binaryExpr(p, n, r, "", "$1.length = $2")
   of mSetLengthSeq:
     var x, y: TCompRes
     gen(p, n.sons[1], x)
@@ -1739,8 +1747,6 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
     localError(p.config, n.info, errXMustBeCompileTime % n.sons[0].sym.name.s)
   of mCopyStr:
     binaryExpr(p, n, r, "", "($1.slice($2))")
-  of mCopyStrLast:
-    ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))")
   of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)")
   of mNewStringOfCap:
     unaryExpr(p, n, r, "mnewString", "mnewString(0)")
@@ -2065,8 +2071,11 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
       r.kind = resExpr
   of nkStrLit..nkTripleStrLit:
     if skipTypes(n.typ, abstractVarRange).kind == tyString:
-      useMagic(p, "makeNimstrLit")
-      r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)]
+      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
@@ -2254,7 +2263,7 @@ proc genClass(conf: ConfigRef; obj: PType; content: Rope; ext: string) =
             "class $#$# {$n$#$n}$n") %
            [rope(VersionAsString), cls, extends, content]
 
-  let outfile = changeFileExt(completeCFilePath(conf, $cls), ext)
+  let outfile = changeFileExt(completeCFilePath(conf, AbsoluteFile($cls)), ext)
   discard writeRopeIfNotEqual(result, outfile)
 
 proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
@@ -2268,11 +2277,11 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
             else: "nimsystem"
     let code = wholeCode(graph, m)
     let outfile =
-      if m.config.outFile.len > 0:
-        if m.config.outFile.isAbsolute: m.config.outFile
-        else: getCurrentDir() / m.config.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(m.config, f), ext)
+        changeFileExt(completeCFilePath(m.config, AbsoluteFile f), ext)
     discard writeRopeIfNotEqual(genHeader() & code, outfile)
     for obj, content in items(globals.classes):
       genClass(m.config, obj, content, ext)
diff --git a/compiler/layouter.nim b/compiler/layouter.nim
index 36ad08696..cc6ec48b7 100644
--- a/compiler/layouter.nim
+++ b/compiler/layouter.nim
@@ -9,7 +9,8 @@
 
 ## Layouter for nimpretty.
 
-import idents, lexer, lineinfos, llstream, options, msgs, strutils
+import idents, lexer, lineinfos, llstream, options, msgs, strutils,
+  pathutils
 from os import changeFileExt
 
 const
@@ -39,7 +40,7 @@ type
 
 proc openEmitter*(em: var Emitter, cache: IdentCache;
                   config: ConfigRef, fileIdx: FileIndex) =
-  let fullPath = config.toFullPath(fileIdx)
+  let fullPath = Absolutefile config.toFullPath(fileIdx)
   em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead),
                                cache, config)
   if em.indWidth == 0: em.indWidth = 2
@@ -55,7 +56,7 @@ proc openEmitter*(em: var Emitter, cache: IdentCache;
 proc closeEmitter*(em: var Emitter) =
   var f = llStreamOpen(em.config.outFile, fmWrite)
   if f == nil:
-    rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile)
+    rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile.string)
   f.llStreamWrite em.content
   llStreamClose(f)
 
diff --git a/compiler/lexer.nim b/compiler/lexer.nim
index 6ad1d9fc6..4cb800017 100644
--- a/compiler/lexer.nim
+++ b/compiler/lexer.nim
@@ -17,7 +17,7 @@
 
 import
   hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream,
-  wordrecg, lineinfos
+  wordrecg, lineinfos, pathutils
 
 const
   MaxLineLength* = 80         # lines longer than this lead to a warning
@@ -232,7 +232,7 @@ proc openLexer*(lex: var TLexer, fileIdx: FileIndex, inputstream: PLLStream;
     lex.previousToken.fileIndex = fileIdx
   lex.config = config
 
-proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream;
+proc openLexer*(lex: var TLexer, filename: AbsoluteFile, inputstream: PLLStream;
                 cache: IdentCache; config: ConfigRef) =
   openLexer(lex, fileInfoIdx(config, filename), inputstream, cache, config)
 
@@ -884,6 +884,42 @@ 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, 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 newlineFollows*(L: TLexer): bool =
   var pos = L.bufpos
   var buf = L.buf
@@ -1249,3 +1285,14 @@ proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream;
     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/lineinfos.nim b/compiler/lineinfos.nim
index 41f3806d4..8749e764d 100644
--- a/compiler/lineinfos.nim
+++ b/compiler/lineinfos.nim
@@ -10,7 +10,7 @@
 ## This module contains the ``TMsgKind`` enum as well as the
 ## ``TLineInfo`` object.
 
-import ropes, tables
+import ropes, tables, pathutils
 
 const
   explanationsBaseUrl* = "https://nim-lang.org/docs/manual"
@@ -179,8 +179,8 @@ const
 
 type
   TFileInfo* = object
-    fullPath*: string          # This is a canonical full filesystem path
-    projPath*: string          # This is relative to the project's root
+    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
@@ -191,7 +191,7 @@ type
                                #   used for better error messages and
                                #   embedding the original source in the
                                #   generated code
-    dirtyfile*: string         # the file that is actually read into memory
+    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
diff --git a/compiler/linter.nim b/compiler/linter.nim
index 7c9cdec83..0b69db8cb 100644
--- a/compiler/linter.nim
+++ b/compiler/linter.nim
@@ -14,7 +14,7 @@ import
   strutils, os, intsets, strtabs
 
 import options, ast, astalgo, msgs, semdata, ropes, idents,
-  lineinfos
+  lineinfos, pathutils
 
 const
   Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
@@ -42,7 +42,7 @@ proc overwriteFiles*(conf: ConfigRef) =
       let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath
                     else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim")
       try:
-        var f = open(newFile, fmWrite)
+        var f = open(newFile.string, fmWrite)
         for line in conf.m.fileInfos[i].lines:
           if doStrip:
             f.write line.strip(leading = false, trailing = true)
@@ -51,7 +51,7 @@ proc overwriteFiles*(conf: ConfigRef) =
           f.write(conf.m.fileInfos[i], "\L")
         f.close
       except IOError:
-        rawMessage(conf, errGenerated, "cannot open file: " & newFile)
+        rawMessage(conf, errGenerated, "cannot open file: " & newFile.string)
 
 proc `=~`(s: string, a: openArray[string]): bool =
   for x in a:
diff --git a/compiler/llstream.nim b/compiler/llstream.nim
index 42bbb7600..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)
@@ -87,9 +87,9 @@ proc endsWithOpr*(x: string): bool =
   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/main.nim b/compiler/main.nim
index cd05ded62..6c8b0343e 100644
--- a/compiler/main.nim
+++ b/compiler/main.nim
@@ -19,7 +19,7 @@ import
   cgen, jsgen, json, nversion,
   platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen,
   docgen2, parser, modules, ccgutils, sigmatch, ropes,
-  modulegraphs, tables, rod, lineinfos
+  modulegraphs, tables, rod, lineinfos, pathutils
 
 from magicsys import resetSysTypes
 
@@ -30,8 +30,8 @@ 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(g.config, m.position.FileIndex))
@@ -47,8 +47,9 @@ proc commandGenDepend(graph: ModuleGraph) =
   let project = graph.config.projectFull
   writeDepsFile(graph, project)
   generateDot(graph, project)
-  execExternalProgram(graph.config, "dot -Tpng -o" & changeFileExt(project, "png") &
-      ' ' & changeFileExt(project, "dot"))
+  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
@@ -126,7 +127,7 @@ proc commandEval(graph: ModuleGraph; exp: string) =
     makeStdinModule(graph))
 
 proc commandScan(cache: IdentCache, config: ConfigRef) =
-  var f = addFileExt(mainCommandArg(config), NimExt)
+  var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt)
   var stream = llStreamOpen(f, fmRead)
   if stream != nil:
     var
@@ -140,7 +141,7 @@ proc commandScan(cache: IdentCache, config: ConfigRef) =
       if tok.tokType == tkEof: break
     closeLexer(L)
   else:
-    rawMessage(config, errGenerated, "cannot open file: " & f)
+    rawMessage(config, errGenerated, "cannot open file: " & f.string)
 
 const
   PrintRopeCacheStats = false
@@ -231,11 +232,11 @@ proc mainCommand*(graph: ModuleGraph) =
       for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s)
 
       var libpaths = newJArray()
-      for dir in conf.searchPaths: libpaths.elems.add(%dir)
+      for dir in conf.searchPaths: libpaths.elems.add(%dir.string)
 
       var dumpdata = % [
         (key: "version", val: %VersionAsString),
-        (key: "project_path", val: %conf.projectFull),
+        (key: "project_path", val: %conf.projectFull.string),
         (key: "defined_symbols", val: definedSymbols),
         (key: "lib_paths", val: libpaths)
       ]
@@ -247,7 +248,7 @@ proc mainCommand*(graph: ModuleGraph) =
       for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook})
       msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook})
 
-      for it in conf.searchPaths: msgWriteln(conf, it)
+      for it in conf.searchPaths: msgWriteln(conf, it.string)
   of "check":
     conf.cmd = cmdCheck
     commandCheck(graph)
diff --git a/compiler/modulepaths.nim b/compiler/modulepaths.nim
index 118002fcf..f0718c4eb 100644
--- a/compiler/modulepaths.nim
+++ b/compiler/modulepaths.nim
@@ -7,9 +7,8 @@
 #    distribution, for details about the copyright.
 #
 
-import ast, renderer, strutils, msgs, options, idents, os, lineinfos
-
-import nimblecmd
+import ast, renderer, strutils, msgs, options, idents, os, lineinfos,
+  pathutils, nimblecmd
 
 when false:
   const
@@ -160,7 +159,7 @@ 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.len == 0:
+  if fullPath.isEmpty:
     if doLocalError:
       let m = if modulename.len > 0: modulename else: $n
       localError(conf, n.info, "cannot open file: " & m)
diff --git a/compiler/modules.nim b/compiler/modules.nim
index b3a1e90d6..8fedba10a 100644
--- a/compiler/modules.nim
+++ b/compiler/modules.nim
@@ -12,7 +12,7 @@
 import
   ast, astalgo, magicsys, std / sha1, msgs, cgendata, sigmatch, options,
   idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod,
-  lineinfos
+  lineinfos, pathutils
 
 proc resetSystemArtifacts*(g: ModuleGraph) =
   magicsys.resetSysTypes(g)
@@ -102,19 +102,21 @@ proc connectCallbacks*(graph: ModuleGraph) =
 proc compileSystemModule*(graph: ModuleGraph) =
   if graph.systemModule == nil:
     connectCallbacks(graph)
-    graph.config.m.systemFileIdx = fileInfoIdx(graph.config, graph.config.libpath / "system.nim")
+    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.len == 0:
-    fatal(conf, newLineInfo(conf, "command line", 1, 1), errGenerated, "command expects a filename")
+  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 / "system.nim")
+  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:
@@ -123,8 +125,11 @@ proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) =
     graph.compileSystemModule()
     discard graph.compileModule(projectFile, {sfMainModule})
 
-proc makeModule*(graph: ModuleGraph; filename: string): PSym =
+proc makeModule*(graph: ModuleGraph; filename: AbsoluteFile): PSym =
   result = graph.newModule(fileInfoIdx(graph.config, filename))
   result.id = getID()
 
-proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule"stdin"
+proc makeModule*(graph: ModuleGraph; filename: string): PSym =
+  result = makeModule(graph, AbsoluteFile filename)
+
+proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule(AbsoluteFile"stdin")
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index b7b7c8474..d817b2956 100644
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -9,7 +9,7 @@
 
 import
   options, strutils, os, tables, ropes, platform, terminal, macros,
-  lineinfos
+  lineinfos, pathutils
 
 proc toCChar*(c: char; result: var string) =
   case c
@@ -35,20 +35,20 @@ proc makeCString*(s: string): Rope =
   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
+  result.quotedFullName = fullPath.string.makeCString
   result.lines = @[]
   when defined(nimpretty):
-    if result.fullPath.len > 0:
+    if not result.fullPath.isEmpty:
       try:
-        result.fullContent = readFile(result.fullPath)
+        result.fullContent = readFile(result.fullPath.string)
       except IOError:
         #rawMessage(errCannotOpenFile, result.fullPath)
         # XXX fixme
@@ -58,39 +58,39 @@ 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: string): bool =
+proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool =
   var
-    canon: string
+    canon: AbsoluteFile
   try:
     canon = canonicalizePath(conf, filename)
-  except:
+  except OSError:
     canon = filename
-  result = conf.m.filenameToIndexTbl.hasKey(canon)
+  result = conf.m.filenameToIndexTbl.hasKey(canon.string)
 
-proc fileInfoIdx*(conf: ConfigRef; filename: string; isKnownFile: var bool): FileIndex =
+proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex =
   var
-    canon: string
+    canon: AbsoluteFile
     pseudoPath = false
 
   try:
     canon = canonicalizePath(conf, filename)
-    shallow(canon)
-  except:
+    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 conf.m.filenameToIndexTbl.hasKey(canon):
-    result = conf.m.filenameToIndexTbl[canon]
+  if conf.m.filenameToIndexTbl.hasKey(canon.string):
+    result = conf.m.filenameToIndexTbl[canon.string]
   else:
     isKnownFile = false
     result = conf.m.fileInfos.len.FileIndex
-    conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: filename
-                                            else: shortenDir(conf, canon)))
-    conf.m.filenameToIndexTbl[canon] = result
+    conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename
+                                            else: relativeTo(canon, conf.projectPath)))
+    conf.m.filenameToIndexTbl[canon.string] = result
 
-proc fileInfoIdx*(conf: ConfigRef; filename: string): FileIndex =
+proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex =
   var dummy: bool
   result = fileInfoIdx(conf, filename, dummy)
 
@@ -99,7 +99,7 @@ proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
   result.line = uint16(line)
   result.col = int16(col)
 
-proc newLineInfo*(conf: ConfigRef; filename: string, line, col: int): TLineInfo {.inline.} =
+proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} =
   result = newLineInfo(fileInfoIdx(conf, filename), line, col)
 
 
@@ -152,13 +152,16 @@ proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo =
   else: result = conf.m.msgContext[i]
 
 template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string =
-  (if fileIdx.int32 < 0 or conf == nil: "???" else: conf.m.fileInfos[fileIdx.int32].projPath)
+  if fileIdx.int32 < 0 or conf == nil:
+    "???"
+  else:
+    conf.m.fileInfos[fileIdx.int32].projPath.string
 
 proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string =
   if fileIdx.int32 < 0 or conf == nil: result = "???"
-  else: result = conf.m.fileInfos[fileIdx.int32].fullPath
+  else: result = conf.m.fileInfos[fileIdx.int32].fullPath.string
 
-proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: string) =
+proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) =
   assert fileIdx.int32 >= 0
   conf.m.fileInfos[fileIdx.int32].dirtyFile = filename
 
@@ -170,10 +173,10 @@ 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): string =
+proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile =
   if fileIdx.int32 < 0:
-    result = "???"
-  elif conf.m.fileInfos[fileIdx.int32].dirtyFile.len > 0:
+    result = AbsoluteFile"???"
+  elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty:
     result = conf.m.fileInfos[fileIdx.int32].dirtyFile
   else:
     result = conf.m.fileInfos[fileIdx.int32].fullPath
@@ -188,9 +191,9 @@ proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string =
   if info.fileIndex.int32 < 0:
     result = "???"
   elif optListFullPaths in conf.globalOptions:
-    result = conf.m.fileInfos[info.fileIndex.int32].fullPath
+    result = conf.m.fileInfos[info.fileIndex.int32].fullPath.string
   else:
-    result = conf.m.fileInfos[info.fileIndex.int32].projPath
+    result = conf.m.fileInfos[info.fileIndex.int32].projPath.string
 
 proc toLinenumber*(info: TLineInfo): int {.inline.} =
   result = int info.line
diff --git a/compiler/ndi.nim b/compiler/ndi.nim
index 9708c388d..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, options
+import ast, msgs, ropes, options, pathutils
 
 type
   NdiFile* = object
@@ -30,10 +30,10 @@ proc doWrite(f: var NdiFile; s: PSym; conf: ConfigRef) =
 template writeMangledName*(f: NdiFile; s: PSym; conf: ConfigRef) =
   if f.enabled: doWrite(f, s, conf)
 
-proc open*(f: var NdiFile; filename: string; conf: ConfigRef) =
-  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.nim b/compiler/nim.nim
index 0fed72dc7..5f3347255 100644
--- a/compiler/nim.nim
+++ b/compiler/nim.nim
@@ -21,7 +21,8 @@ when defined(i386) and defined(windows) and defined(vcc):
 import
   commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes,
   extccomp, strutils, os, osproc, platform, main, parseopt,
-  nodejs, scriptconfig, idents, modulegraphs, lineinfos, cmdlinehelper
+  nodejs, scriptconfig, idents, modulegraphs, lineinfos, cmdlinehelper,
+  pathutils
 
 when hasTinyCBackend:
   import tccgen
@@ -30,10 +31,10 @@ 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
 
@@ -78,15 +79,15 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
   if optRun in conf.globalOptions:
     if conf.cmd == cmdCompileToJS:
       var ex: string
-      if conf.outFile.len > 0:
+      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:
-      var binPath: string
-      if conf.outFile.len > 0:
+      var binPath: AbsoluteFile
+      if not conf.outFile.isEmpty:
         # If the user specified an outFile path, use that directly.
         binPath = conf.outFile.prependCurDir
       else:
diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim
index c5521735b..fa938556b 100644
--- a/compiler/nimblecmd.nim
+++ b/compiler/nimblecmd.nim
@@ -10,9 +10,9 @@
 ## Implements some helper procs for Nimble (Nim's package manager) support.
 
 import parseutils, strutils, strtabs, os, options, msgs, sequtils,
-  lineinfos
+  lineinfos, pathutils
 
-proc addPath*(conf: ConfigRef; path: string, info: TLineInfo) =
+proc addPath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) =
   if not conf.searchPaths.contains(path):
     conf.searchPaths.insert(path, 0)
 
@@ -112,9 +112,9 @@ proc addNimblePath(conf: ConfigRef; p: string, info: TLineInfo) =
     if not path.isAbsolute():
       path = p / path
 
-  if not contains(conf.searchPaths, path):
+  if not contains(conf.searchPaths, AbsoluteDir path):
     message(conf, info, hintPath, path)
-    conf.lazyPaths.insert(path, 0)
+    conf.lazyPaths.insert(AbsoluteDir path, 0)
 
 proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) =
   var packages = newStringTable(modeStyleInsensitive)
@@ -126,9 +126,9 @@ proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) =
   for p in packages.chosen:
     addNimblePath(conf, p, info)
 
-proc nimblePath*(conf: ConfigRef; path: string, info: TLineInfo) =
-  addPathRec(conf, path, info)
-  addNimblePath(conf, path, 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
diff --git a/compiler/nimconf.nim b/compiler/nimconf.nim
index 5f6889a6f..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, lineinfos
+  options, idents, wordrecg, strtabs, lineinfos, pathutils
 
 # ---------------- configuration file parser -----------------------------
 # we use Nim's scanner here to save space and work
@@ -201,8 +201,8 @@ proc parseAssignment(L: var TLexer, tok: var TToken;
   else:
     processSwitch(s, val, passPP, info, config)
 
-proc readConfigFile(
-    filename: string; cache: IdentCache; config: ConfigRef): bool =
+proc readConfigFile(filename: AbsoluteFile; cache: IdentCache;
+                    config: ConfigRef): bool =
   var
     L: TLexer
     tok: TToken
@@ -219,24 +219,24 @@ proc readConfigFile(
     closeLexer(L)
     return true
 
-proc getUserConfigPath*(filename: string): string =
-  result = joinPath([getConfigDir(), "nim", filename])
+proc getUserConfigPath*(filename: RelativeFile): AbsoluteFile =
+  result = getConfigDir().AbsoluteDir / RelativeDir"nim" / filename
 
-proc getSystemConfigPath*(conf: ConfigRef; 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(conf)
-  result = joinPath([p, "config", filename])
+  result = p / RelativeDir"config" / filename
   when defined(unix):
-    if not existsFile(result): result = joinPath([p, "etc/nim", filename])
-    if not existsFile(result): result = "/etc/nim/" & filename
+    if not fileExists(result): result = p / RelativeDir"etc/nim" / filename
+    if not fileExists(result): result = AbsoluteDir"/etc/nim" / filename
 
-proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) =
+proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef) =
   setDefaultLibpath(conf)
 
-  var configFiles = newSeq[string]()
+  var configFiles = newSeq[AbsoluteFile]()
 
-  template readConfigFile(path: string) =
+  template readConfigFile(path) =
     let configPath = path
     if readConfigFile(configPath, cache, conf):
       add(configFiles, configPath)
@@ -247,10 +247,10 @@ proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) =
   if optSkipUserConfigFile notin conf.globalOptions:
     readConfigFile(getUserConfigPath(cfg))
 
-  let pd = if conf.projectPath.len > 0: conf.projectPath else: getCurrentDir()
+  let pd = if not conf.projectPath.isEmpty: conf.projectPath else: AbsoluteDir(getCurrentDir())
   if optSkipParentConfigFiles notin conf.globalOptions:
-    for dir in parentDirs(pd, fromRoot=true, inclusive=false):
-      readConfigFile(dir / cfg)
+    for dir in parentDirs(pd.string, fromRoot=true, inclusive=false):
+      readConfigFile(AbsoluteDir(dir) / cfg)
 
   if optSkipProjConfigFile notin conf.globalOptions:
     readConfigFile(pd / cfg)
@@ -264,4 +264,4 @@ proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) =
 
   for filename in configFiles:
     # delayed to here so that `hintConf` is honored
-    rawMessage(conf, hintConf, filename)
+    rawMessage(conf, hintConf, filename.string)
diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim
index f20b5642c..841c38a46 100644
--- a/compiler/nimeval.nim
+++ b/compiler/nimeval.nim
@@ -11,7 +11,7 @@
 import
   ast, astalgo, modules, passes, condsyms,
   options, sem, semdata, llstream, vm, vmdef,
-  modulegraphs, idents, os
+  modulegraphs, idents, os, pathutils
 
 type
   Interpreter* = ref object ## Use Nim as an interpreter with this object
@@ -103,8 +103,8 @@ proc createInterpreter*(scriptName: string;
   registerPass(graph, evalPass)
 
   for p in searchPaths:
-    conf.searchPaths.add(p)
-    if conf.libpath.len == 0: conf.libpath = p
+    conf.searchPaths.add(AbsoluteDir p)
+    if conf.libpath.isEmpty: conf.libpath = AbsoluteDir p
 
   var m = graph.makeModule(scriptName)
   incl(m.flags, sfMainModule)
diff --git a/compiler/nversion.nim b/compiler/nversion.nim
index 4b8cf7100..8981ae213 100644
--- a/compiler/nversion.nim
+++ b/compiler/nversion.nim
@@ -15,6 +15,6 @@ const
   VersionAsString* = system.NimVersion
   RodFileVersion* = "1223"       # modify this if the rod-format changes!
 
-  NimCompilerApiVersion* = 2 ## Check for the existance of this before accessing it
+  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 4a484e6e2..a95d9930a 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -9,7 +9,7 @@
 
 import
   os, strutils, strtabs, osproc, sets, lineinfos, platform,
-  prefixmatches
+  prefixmatches, pathutils
 
 from terminal import isatty
 from times import utc, fromUnix, local, getTime, format, DateTime
@@ -136,7 +136,7 @@ type
     External   ## file was introduced via .compile pragma
 
   Cfile* = object
-    cname*, obj*: string
+    cname*, obj*: AbsoluteFile
     flags*: set[CFileFlag]
   CfileList* = seq[Cfile]
 
@@ -204,13 +204,14 @@ type
                              ## symbols are always guaranteed to be style
                              ## insensitive. Otherwise hell would break lose.
     packageCache*: StringTableRef
-    searchPaths*: seq[string]
-    lazyPaths*: seq[string]
-    outFile*, prefixDir*, libpath*, nimcacheDir*: string
+    searchPaths*: seq[AbsoluteDir]
+    lazyPaths*: seq[AbsoluteDir]
+    outFile*: AbsoluteFile
+    prefixDir*, libpath*, nimcacheDir*: AbsoluteDir
     dllOverrides, moduleOverrides*: StringTableRef
     projectName*: string # holds a name like 'nim'
-    projectPath*: string # holds a path like /home/alice/projects/nim/compiler/
-    projectFull*: string # projectPath/projectName
+    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)
@@ -222,9 +223,9 @@ type
     # The string uses the formatting variables `path` and `line`.
 
      # the used compiler
-    cIncludes*: seq[string]  # directories to search for included files
-    cLibs*: seq[string]      # directories to search for lib files
-    cLinkedLibs*: seq[string]  # libraries to link
+    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 (*)
@@ -303,12 +304,13 @@ proc newConfigRef*(): ConfigRef =
     packageCache: newPackageCache(),
     searchPaths: @[],
     lazyPaths: @[],
-    outFile: "", prefixDir: "", libpath: "", nimcacheDir: "",
+    outFile: AbsoluteFile"", prefixDir: AbsoluteDir"",
+    libpath: AbsoluteDir"", nimcacheDir: AbsoluteDir"",
     dllOverrides: newStringTable(modeCaseInsensitive),
     moduleOverrides: newStringTable(modeStyleInsensitive),
     projectName: "", # holds a name like 'nim'
-    projectPath: "", # holds a path like /home/alice/projects/nim/compiler/
-    projectFull: "", # projectPath/projectName
+    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)
@@ -402,7 +404,7 @@ template optPreserveOrigSource*(conf: ConfigRef): untyped =
   optEmbedOrigSrc in conf.globalOptions
 
 const
-  genSubDir* = "nimcache"
+  genSubDir* = RelativeDir"nimcache"
   NimExt* = "nim"
   RodExt* = "rod"
   HtmlExt* = "html"
@@ -410,10 +412,10 @@ const
   TagsExt* = "tags"
   TexExt* = "tex"
   IniExt* = "ini"
-  DefaultConfig* = "nim.cfg"
-  DefaultConfigNims* = "config.nims"
-  DocConfig* = "nimdoc.cfg"
-  DocTexConfig* = "nimdoc.tex.cfg"
+  DefaultConfig* = RelativeFile"nim.cfg"
+  DefaultConfigNims* = RelativeFile"config.nims"
+  DocConfig* = RelativeFile"nimdoc.cfg"
+  DocTexConfig* = RelativeFile"nimdoc.tex.cfg"
 
 const oKeepVariableNames* = true
 
@@ -438,56 +440,61 @@ proc getConfigVar*(conf: ConfigRef; key: string, default = ""): string =
 proc setConfigVar*(conf: ConfigRef; key, val: string) =
   conf.configVars[key] = val
 
-proc getOutFile*(conf: ConfigRef; filename, ext: string): string =
-  if conf.outFile != "": result = conf.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*(conf: ConfigRef): 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 ``conf.prefixDir``
-  ## global.
-  if conf.prefixDir != "": result = conf.prefixDir
-  else: result = splitPath(getAppDir()).head
+  ## field.
+  if not conf.prefixDir.isEmpty: result = conf.prefixDir
+  else: result = AbsoluteDir splitPath(getAppDir()).head
 
 proc setDefaultLibpath*(conf: ConfigRef) =
   # set default value (can be overwritten):
-  if conf.libpath == "":
+  if conf.libpath.isEmpty:
     # choose default libpath:
     var prefix = getPrefixDir(conf)
     when defined(posix):
-      if prefix == "/usr": conf.libpath = "/usr/lib/nim"
-      elif prefix == "/usr/local": conf.libpath = "/usr/local/lib/nim"
-      else: conf.libpath = joinPath(prefix, "lib")
-    else: conf.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(conf.libpath / "system.nim") and
+    if not fileExists(conf.libpath.string / "system.nim") and
         fileExists(parentNimlibPath / "system.nim"):
-      conf.libpath = parentNimLibPath
+      conf.libpath = AbsoluteDir parentNimLibPath
 
-proc canonicalizePath*(conf: ConfigRef; 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*(conf: ConfigRef; dir: string): string =
+proc shortenDir*(conf: ConfigRef; dir: string): string {.
+    deprecated: "use 'relativeTo' instead".} =
   ## returns the interesting part of a dir
-  var prefix = conf.projectPath & DirSep
+  var prefix = conf.projectPath.string & DirSep
   if startsWith(dir, prefix):
     return substr(dir, len(prefix))
-  prefix = getPrefixDir(conf) & DirSep
+  prefix = getPrefixDir(conf).string & DirSep
   if startsWith(dir, prefix):
     return substr(dir, len(prefix))
   result = dir
@@ -508,89 +515,89 @@ proc getOsCacheDir(): string =
   when defined(posix):
     result = getEnv("XDG_CACHE_HOME", getHomeDir() / ".cache") / "nim"
   else:
-    result = getHomeDir() / genSubDir
+    result = getHomeDir() / genSubDir.string
 
-proc getNimcacheDir*(conf: ConfigRef): string =
+proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
   # XXX projectName should always be without a file extension!
-  result = if conf.nimcacheDir.len > 0:
+  result = if not conf.nimcacheDir.isEmpty:
              conf.nimcacheDir
            elif conf.cmd == cmdCompileToJS:
-             shortenDir(conf, conf.projectPath) / genSubDir
-           else: getOsCacheDir() / splitFile(conf.projectName).name &
-             (if isDefined(conf, "release"): "_r" else: "_d")
+             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(conf),
-    "lib", conf.libpath,
+    "nim", getPrefixDir(conf).string,
+    "lib", conf.libpath.string,
     "home", home,
     "config", config,
     "projectname", conf.projectName,
-    "projectpath", conf.projectPath,
-    "projectdir", conf.projectPath,
-    "nimcache", getNimcacheDir(conf)])
+    "projectpath", conf.projectPath.string,
+    "projectdir", conf.projectPath.string,
+    "nimcache", getNimcacheDir(conf).string])
   if "~/" in result:
     result = result.replace("~/", home & '/')
 
-proc toGeneratedFile*(conf: ConfigRef; path, ext: string): string =
+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(conf), changeFileExt(tail, ext)])
-  #echo "toGeneratedFile(", path, ", ", ext, ") = ", result
-
-proc completeGeneratedFilePath*(conf: ConfigRef; f: string, createSubDir: bool = true): string =
-  var (head, tail) = splitPath(f)
-  #if len(head) > 0: head = removeTrailingDirSep(shortenDir(head & dirSep))
-  var subdir = getNimcacheDir(conf) # / 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)
+      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(conf: ConfigRef; f: string; suppressStdlib: bool): string =
+proc rawFindFile(conf: ConfigRef; f: RelativeFile; suppressStdlib: bool): AbsoluteFile =
   for it in conf.searchPaths:
-    if suppressStdlib and it.startsWith(conf.libpath):
+    if suppressStdlib and it.string.startsWith(conf.libpath.string):
       continue
-    result = joinPath(it, f)
-    if existsFile(result):
+    result = it / f
+    if fileExists(result):
       return canonicalizePath(conf, result)
-  result = ""
+  result = AbsoluteFile""
 
-proc rawFindFile2(conf: ConfigRef; f: string): string =
+proc rawFindFile2(conf: ConfigRef; f: RelativeFile): AbsoluteFile =
   for i, it in conf.lazyPaths:
-    result = joinPath(it, f)
-    if existsFile(result):
+    result = it / f
+    if fileExists(result):
       # bring to front
       for j in countDown(i,1):
         swap(conf.lazyPaths[j], conf.lazyPaths[j-1])
 
       return canonicalizePath(conf, result)
-  result = ""
+  result = AbsoluteFile""
 
 template patchModule(conf: ConfigRef) {.dirty.} =
-  if result.len > 0 and conf.moduleOverrides.len > 0:
-    let key = getPackageName(conf, result) & "_" & splitFile(result).name
+  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 = ov
+      if ov.len > 0: result = AbsoluteFile(ov)
 
-proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): 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 = rawFindFile(conf, f, suppressStdlib)
-    if result.len == 0:
-      result = rawFindFile(conf, f.toLowerAscii, suppressStdlib)
-      if result.len == 0:
-        result = rawFindFile2(conf, f)
-        if result.len == 0:
-          result = rawFindFile2(conf, f.toLowerAscii)
+    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 = [
@@ -600,7 +607,7 @@ const stdlibDirs = [
   "wrappers", "wrappers/linenoise",
   "windows", "posix", "js"]
 
-proc findModule*(conf: ConfigRef; modulename, currentModule: string): string =
+proc findModule*(conf: ConfigRef; modulename, currentModule: string): AbsoluteFile =
   # returns path to module
   const pkgPrefix = "pkg/"
   const stdPrefix = "std/"
@@ -611,13 +618,13 @@ proc findModule*(conf: ConfigRef; modulename, currentModule: string): string =
     if m.startsWith(stdPrefix):
       let stripped = m.substr(stdPrefix.len)
       for candidate in stdlibDirs:
-        let path = (conf.libpath / candidate / stripped)
+        let path = (conf.libpath.string / candidate / stripped)
         if fileExists(path):
           m = path
           break
     let currentPath = currentModule.splitFile.dir
-    result = currentPath / m
-    if not existsFile(result):
+    result = AbsoluteFile currentPath / m
+    if not fileExists(result):
       result = findFile(conf, m)
   patchModule(conf)
 
diff --git a/compiler/packagehandling.nim b/compiler/packagehandling.nim
index 7414aeb71..f94c3d72c 100644
--- a/compiler/packagehandling.nim
+++ b/compiler/packagehandling.nim
@@ -41,10 +41,10 @@ proc getPackageName*(conf: ConfigRef; path: string): string =
     dec parents
     if parents <= 0: break
 
-proc withPackageName*(conf: ConfigRef; path: string): string =
-  let x = getPackageName(conf, path)
+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/parser.nim b/compiler/parser.nim
index 246dcb814..7667d3a0e 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -27,7 +27,8 @@ when isMainModule:
   outp.close
 
 import
-  llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos
+  llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos,
+  pathutils
 
 when defined(nimpretty2):
   import layouter
@@ -114,7 +115,7 @@ proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream,
   p.strongSpaces = strongSpaces
   p.emptyNode = newNode(nkEmpty)
 
-proc openParser*(p: var TParser, filename: string, inputStream: PLLStream,
+proc openParser*(p: var TParser, filename: AbsoluteFile, inputStream: PLLStream,
                  cache: IdentCache; config: ConfigRef;
                  strongSpaces=false) =
   openParser(p, fileInfoIdx(config, filename), inputStream, cache, config, strongSpaces)
@@ -252,41 +253,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,
@@ -572,7 +538,7 @@ proc parsePar(p: var TParser): PNode =
   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.
@@ -1147,7 +1113,7 @@ proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode =
 
 proc isExprStart(p: TParser): bool =
   case p.tok.tokType
-  of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf,
+  of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkFor,
      tkProc, tkFunc, tkIterator, tkBind, tkBuiltInMagics,
      tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr,
      tkTuple, tkObject, tkWhen, tkCase, tkOut:
@@ -1187,16 +1153,35 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind,
     result.addSon list
     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 = (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)
@@ -1603,22 +1588,6 @@ 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
@@ -2270,7 +2239,7 @@ proc parseString*(s: string; cache: IdentCache; config: ConfigRef;
   # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong
   # spaces...
   parser.lex.errorHandler = errorHandler
-  openParser(parser, filename, stream, cache, config, false)
+  openParser(parser, AbsoluteFile filename, stream, cache, config, false)
 
   result = parser.parseAll
   closeParser(parser)
diff --git a/compiler/passes.nim b/compiler/passes.nim
index 45c726f2a..365731669 100644
--- a/compiler/passes.nim
+++ b/compiler/passes.nim
@@ -14,7 +14,7 @@ import
   strutils, options, ast, astalgo, llstream, msgs, platform, os,
   condsyms, idents, renderer, types, extccomp, math, magicsys, nversion,
   nimsets, syntaxes, times, idgen, modulegraphs, reorder, rod,
-  lineinfos
+  lineinfos, pathutils
 
 
 type
@@ -106,7 +106,7 @@ proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool =
 
 proc resolveMod(conf: ConfigRef; module, relativeTo: string): FileIndex =
   let fullPath = findModule(conf, module, relativeTo)
-  if fullPath.len == 0:
+  if fullPath.isEmpty:
     result = InvalidFileIDX
   else:
     result = fileInfoIdx(conf, fullPath)
@@ -160,7 +160,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool {
       let filename = toFullPathConsiderDirty(graph.config, fileIdx)
       s = llStreamOpen(filename, fmRead)
       if s == nil:
-        rawMessage(graph.config, errCannotOpenFile, filename)
+        rawMessage(graph.config, errCannotOpenFile, filename.string)
         return false
     else:
       s = stream
diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim
new file mode 100644
index 000000000..ef0fa4abd
--- /dev/null
+++ b/compiler/pathutils.nim
@@ -0,0 +1,260 @@
+#
+#
+#           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)
+
+when false:
+  iterator dirs(x: string): (int, int) =
+    var i = 0
+    var first = true
+    while i < x.len:
+      let prev = i
+      if first and x[i] in {DirSep, AltSep}:
+        # absolute path:
+        inc i
+      else:
+        while i < x.len and x[i] notin {DirSep, AltSep}: inc i
+      if i > prev:
+        yield (prev, i-1)
+      first = false
+      # skip all separators:
+      while i < x.len and x[i] in {DirSep, AltSep}: inc i
+
+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}
+
+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 DirSep
+      state = state or 1
+    elif result.len > (state and 1) and isDotDot(x, b):
+      var d = result.len
+      # f/..
+      while d > (state and 1) and result[d-1] != DirSep:
+        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] != DirSep:
+        result.add DirSep
+      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 = DirSep): 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 = DirSep): 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 and defined(posix):
+  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 == r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\simplePkgs\pkgB-#head\pkgA\module.nim"
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 9bc3f353e..b8dae8123 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,
-  types, lookups, lineinfos
+  types, lookups, lineinfos, pathutils
 
 const
   FirstCallConv* = wNimcall
@@ -53,7 +53,7 @@ const
   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, wCore, wFinal, wSize, wExtern, wShallow,
     wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef,
@@ -457,26 +457,22 @@ proc processUndef(c: PContext, n: PNode) =
   else:
     invalidPragma(c, n)
 
-type
-  TLinkFeature = enum
-    linkNormal, linkSys
-
-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(toFullPath(c.config, n.info)) / 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(c.config, s)
-      if result.len == 0: result = s
+      if result.isEmpty: result = AbsoluteFile s
 
 proc processCompile(c: PContext, n: PNode) =
-  proc docompile(c: PContext; it: PNode; src, dest: string) =
+  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, dest)
+    recordPragma(c, it, "compile", src.string, dest.string)
 
   proc getStrLit(c: PContext, n: PNode; i: int): string =
     n.sons[i] = c.semConstExpr(c, n[i])
@@ -493,30 +489,23 @@ proc processCompile(c: PContext, n: PNode) =
     let dest = getStrLit(c, it, 1)
     var found = parentDir(toFullPath(c.config, n.info)) / s
     for f in os.walkFiles(found):
-      let obj = completeCFilePath(c.config, dest % extractFilename(f))
-      docompile(c, it, f, obj)
+      let obj = completeCFilePath(c.config, AbsoluteFile(dest % extractFilename(f)))
+      docompile(c, it, AbsoluteFile f, obj)
   else:
     let s = expectStrLit(c, n)
-    var found = parentDir(toFullPath(c.config, n.info)) / 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(c.config, s)
-        if found.len == 0: found = s
+        if found.isEmpty: found = AbsoluteFile s
     let obj = toObjFile(c.config, completeCFilePath(c.config, found, false))
     docompile(c, it, found, obj)
 
-proc processCommonLink(c: PContext, n: PNode, feature: TLinkFeature) =
+proc processLink(c: PContext, n: PNode) =
   let found = relativeFile(c, n, CC[c.config.cCompiler].objExt)
-  case feature
-  of linkNormal:
-    extccomp.addExternalFileToLink(c.config, found)
-    recordPragma(c, n, "link", found)
-  of linkSys:
-    let dest = c.config.libpath / completeCFilePath(c.config, found, false)
-    extccomp.addExternalFileToLink(c.config, dest)
-    recordPragma(c, n, "link", dest)
-  else: internalError(c.config, n.info, "processCommonLink")
+  extccomp.addExternalFileToLink(c.config, found)
+  recordPragma(c, n, "link", found.string)
 
 proc pragmaBreakpoint(c: PContext, n: PNode) =
   discard getOptionalStr(c, n, "")
@@ -609,8 +598,14 @@ proc pragmaLine(c: PContext, n: PNode) =
       elif y.kind != nkIntLit:
         localError(c.config, n.info, errIntLiteralExpected)
       else:
-        # XXX this produces weird paths which are not properly resolved:
-        n.info.fileIndex = msgs.fileInfoIdx(c.config, x.strVal)
+        if c.config.projectPath.isEmpty:
+          n.info.fileIndex = fileInfoIdx(c.config, AbsoluteFile(x.strVal))
+        else:
+          # XXX this is still suspicous:
+          let dir = toFullPath(c.config, n.info).splitFile.dir
+          let rel = if isAbsolute(x.strVal): relativeTo(AbsoluteFile(x.strVal), c.config.projectPath)
+                    else: RelativeFile(x.strVal)
+          n.info.fileIndex = fileInfoIdx(c.config, AbsoluteDir(dir) / rel)
         n.info.line = uint16(y.intVal)
     else:
       localError(c.config, n.info, "tuple expected")
@@ -974,8 +969,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
       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 wLink: processLink(c, it)
       of wPassl:
         let s = expectStrLit(c, it)
         extccomp.addLinkOption(c.config, s)
diff --git a/compiler/renderer.nim b/compiler/renderer.nim
index a8f3f4afc..964af0591 100644
--- a/compiler/renderer.nim
+++ b/compiler/renderer.nim
@@ -15,11 +15,12 @@ import
 type
   TRenderFlag* = enum
     renderNone, renderNoBody, renderNoComments, renderDocComments,
-    renderNoPragmas, renderIds, renderNoProcDefs
+    renderNoPragmas, renderIds, renderNoProcDefs, renderSyms
   TRenderFlags* = set[TRenderFlag]
   TRenderTok* = object
     kind*: TTokType
     length*: int16
+    sym*: PSym
 
   TRenderTokSeq* = seq[TRenderTok]
   TSrcGen* = object
@@ -105,11 +106,12 @@ proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags; config: ConfigRef) =
   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) =
@@ -165,11 +167,11 @@ 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
@@ -307,14 +309,19 @@ 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, tyLent, tyDistinct,
+    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)
@@ -831,7 +838,7 @@ 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):
     when defined(debugMagics):
       put(g, tkIntLit, $n.sym.id & $n.sym.magic)
@@ -861,6 +868,47 @@ 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
@@ -896,7 +944,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
       gcomma(g, n, 2)
       put(g, tkBracketRi, "]")
     elif n.len > 1 and n.lastSon.kind == nkStmtList:
-      gsub(g, n[0])
+      accentedName(g, n[0])
       if n.len > 2:
         put(g, tkParLe, "(")
         gcomma(g, n, 1, -2)
@@ -904,16 +952,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
       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])
@@ -951,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:
@@ -1064,14 +1112,14 @@ 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(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:
@@ -1079,10 +1127,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
                 elif n[0].kind == nkSym: n[0].sym.name
                 elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name
                 else: nil
-      var n_next = n[1]
-      while n_next.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref,
-                  nkStringToCString, nkCStringToString} and n_next.len > 0:
-        n_next = n_next[0]
+      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:
@@ -1498,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/rodimpl.nim b/compiler/rodimpl.nim
index 7d24e4e67..b5891fcfd 100644
--- a/compiler/rodimpl.nim
+++ b/compiler/rodimpl.nim
@@ -11,7 +11,7 @@
 
 import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types,
   renderer, rodutils, idents, astalgo, btrees, magicsys, cgmeth, extccomp,
-  btrees, trees, condsyms, nversion
+  btrees, trees, condsyms, nversion, pathutils
 
 ## Todo:
 ## - Dependency computation should use *signature* hashes in order to
@@ -796,7 +796,7 @@ proc replay(g: ModuleGraph; module: PSym; n: PNode) =
                        flags: {CfileFlag.External})
         extccomp.addExternalFileToCompile(g.config, cf)
       of "link":
-        extccomp.addExternalFileToLink(g.config, n[1].strVal)
+        extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal)
       of "passl":
         extccomp.addLinkOption(g.config, n[1].strVal)
       of "passc":
diff --git a/compiler/ropes.nim b/compiler/ropes.nim
index 81ee01dbf..0d6d7d78f 100644
--- a/compiler/ropes.nim
+++ b/compiler/ropes.nim
@@ -58,6 +58,8 @@
 import
   hashes
 
+from pathutils import AbsoluteFile
+
 type
   FormatStr* = string  # later we may change it to CString for better
                        # performance of the code generator (assignments
@@ -183,9 +185,9 @@ 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): bool =
+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
@@ -314,16 +316,16 @@ 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):
     result = writeRope(r, filename)
diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim
index 184b60733..cf69e29f1 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, lineinfos
+  os, times, osproc, wordrecg, strtabs, modulegraphs, lineinfos, pathutils
 
 # we support 'cmpIgnoreStyle' natively for efficiency:
 from strutils import cmpIgnoreStyle, contains
@@ -105,7 +105,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
   cbconf exists:
     setResult(a, options.existsConfigVar(conf, a.getString 0))
   cbconf nimcacheDir:
-    setResult(a, options.getNimcacheDir(conf))
+    setResult(a, options.getNimcacheDir(conf).string)
   cbconf paramStr:
     setResult(a, os.paramStr(int a.getInt 0))
   cbconf paramCount:
@@ -120,8 +120,8 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
     if arg.len > 0:
       conf.projectName = arg
       let path =
-        if conf.projectName.isAbsolute: conf.projectName
-        else: conf.projectPath / conf.projectName
+        if conf.projectName.isAbsolute: AbsoluteFile(conf.projectName)
+        else: conf.projectPath / RelativeFile(conf.projectName)
       try:
         conf.projectFull = canonicalizePath(conf, path)
       except OSError:
@@ -149,9 +149,9 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string;
   cbconf cppDefine:
     options.cppDefine(conf, a.getString(0))
 
-proc runNimScript*(cache: IdentCache; scriptName: string;
+proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile;
                    freshDefines=true; conf: ConfigRef) =
-  rawMessage(conf, hintConf, scriptName)
+  rawMessage(conf, hintConf, scriptName.string)
 
   let graph = newModuleGraph(cache, conf)
   connectCallbacks(graph)
@@ -169,7 +169,7 @@ proc runNimScript*(cache: IdentCache; scriptName: string;
 
   var m = graph.makeModule(scriptName)
   incl(m.flags, sfMainModule)
-  graph.vm = setupVM(m, cache, scriptName, graph)
+  graph.vm = setupVM(m, cache, scriptName.string, graph)
 
   graph.compileSystemModule() # TODO: see why this unsets hintConf in conf.notes
   discard graph.processModule(m, llStreamOpen(scriptName, fmRead))
diff --git a/compiler/sem.nim b/compiler/sem.nim
index 7a83c3079..5e5205c20 100644
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -37,7 +37,7 @@ 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)
@@ -399,7 +399,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
   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:
@@ -408,7 +408,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode,
       # semExprWithType(c, result)
       result = semExpr(c, result, flags)
     of tyStmt:
-      result = semStmt(c, result)
+      result = semStmt(c, result, flags)
     of tyTypeDesc:
       if result.kind == nkStmtList: result.kind = nkStmtListType
       var typ = semTypeNode(c, result, nil)
@@ -557,7 +557,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
     result = semAllTypeSections(c, n)
   else:
     result = n
-  result = semStmt(c, result)
+  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
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index ce953f17c..43f04fc9f 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -113,6 +113,7 @@ 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 = src
   if s.kind in tyUserTypeClasses and s.isResolvedUserTypeClass:
@@ -135,7 +136,7 @@ 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
@@ -917,7 +918,7 @@ 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 =
@@ -1529,7 +1530,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
       # 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(c.graph, n, c.p.owner))
+      return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner), {})
     else:
       a = semExprWithType(c, a, {efLValue})
   else:
@@ -1613,7 +1614,7 @@ proc semProcBody(c: PContext, n: PNode): PNode =
       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:
@@ -1989,7 +1990,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
     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)
@@ -2240,7 +2241,7 @@ proc isTupleType(n: PNode): bool =
 
 include semobjconstr
 
-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, c.config)
@@ -2252,7 +2253,7 @@ proc semBlock(c: PContext, n: PNode): PNode =
     n.sons[0] = newSymNode(labl, n.sons[0].info)
     suggestSym(c.config, n.sons[0].info, labl, c.graph.usageSym)
     styleCheckDef(c.config, labl)
-  n.sons[1] = semExpr(c, n.sons[1])
+  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
@@ -2497,7 +2498,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     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, c.config)
     considerGenSyms(c, n)
@@ -2518,7 +2519,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
     discard
   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)
@@ -2526,11 +2527,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)
diff --git a/compiler/semfields.nim b/compiler/semfields.nim
index 869f5ae74..07321f477 100644
--- a/compiler/semfields.nim
+++ b/compiler/semfields.nim
@@ -70,7 +70,7 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) =
     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
@@ -145,7 +145,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
       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:
diff --git a/compiler/semfold.nim b/compiler/semfold.nim
index 27a6af1f4..0018f0755 100644
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -214,7 +214,24 @@ proc evalIs(n: PNode, lhs: PSym, g: ModuleGraph): PNode =
   result = newIntNode(nkIntLit, ord(res))
   result.typ = n.typ
 
+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
+
+  doAssert n.kind in {nkIntLit, nkCharLit}
+
+  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; 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
@@ -224,12 +241,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode =
   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:
-    case skipTypes(n.typ, abstractRange).kind
-    of tyUInt..tyUInt64:
-      result = newIntNodeT((not getInt(a)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g)
-    else:
-      result = newIntNodeT(not getInt(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:
@@ -251,9 +263,9 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode =
   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 = foldSub(getOrdValue(a), 1, n, g)
-  of mSucc: result = foldAdd(getOrdValue(a), getInt(b), n, g)
-  of mPred: result = foldSub(getOrdValue(a), getInt(b), 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)
@@ -271,7 +283,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode =
     of tyInt64, tyInt:
       result = newIntNodeT(`shl`(getInt(a), getInt(b)), n, g)
     of tyUInt..tyUInt64:
-      result = newIntNodeT(`shl`(getInt(a), getInt(b)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g)
+      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
@@ -324,14 +336,14 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode =
     result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n, g)
   of mLeU, mLeU64:
     result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n, g)
-  of mBitandI, mAnd: result = newIntNodeT(a.getInt and b.getInt, n, g)
-  of mBitorI, mOr: result = newIntNodeT(getInt(a) or getInt(b), n, g)
-  of mBitxorI, mXor: result = newIntNodeT(a.getInt xor b.getInt, n, g)
-  of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n, g)
-  of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n, g)
-  of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n, g)
-  of mModU: result = foldModU(getInt(a), getInt(b), n, g)
-  of mDivU: result = foldDivU(getInt(a), getInt(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:
@@ -462,17 +474,24 @@ proc foldConv(n, a: PNode; g: ModuleGraph; check = false): PNode =
       result = newIntNodeT(int(getFloat(a)), n, g)
     of tyChar:
       result = newIntNodeT(getOrdValue(a), n, g)
-    of tyUInt8..tyUInt32, tyInt8..tyInt32:
-      let fromSigned = srcTyp.kind in tyInt..tyInt64
+    of tyUInt..tyUInt64, tyInt..tyInt64:
       let toSigned = dstTyp.kind in tyInt..tyInt64
-
-      let mask = lastOrd(g.config, dstTyp, fixedUnsigned=true)
-
-      var val =
-        if toSigned:
-          a.getOrdValue mod mask
-        else:
-          a.getOrdValue and mask
+      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:
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index fb01127fc..566b634af 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -76,17 +76,19 @@ proc semAsm(c: PContext, n: PNode): PNode =
   if marker == '\0': marker = '`' # default 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, 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 == c.enforceVoidContext:
     result.typ = c.enforceVoidContext
+  elif efInTypeof in flags:
+    result.typ = n[1].typ
 
 proc toCover(c: PContext, t: PType): BiggestInt =
   let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc})
@@ -97,8 +99,8 @@ proc toCover(c: PContext, t: PType): BiggestInt =
 
 proc semProc(c: PContext, n: PNode): PNode
 
-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?
     if result.typ.kind in {tyVar, tyLent}: result = newDeref(result)
@@ -130,8 +132,9 @@ proc fixNilType(c: PContext; n: PNode) =
     for it in n: fixNilType(c, it)
   n.typ = nil
 
-proc discardCheck(c: PContext, result: PNode) =
-  if c.matchedConcept != nil: 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 implicitlyDiscardable(result):
       var n = newNodeI(nkDiscardStmt, result.info, 1)
@@ -148,7 +151,7 @@ proc discardCheck(c: PContext, result: PNode) =
         s.add "; for a function call use ()"
       localError(c.config, n.info, s)
 
-proc semIf(c: PContext, n: PNode): PNode =
+proc semIf(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
   var typ = commonTypeBegin
   var hasElse = false
@@ -165,8 +168,9 @@ proc semIf(c: PContext, n: PNode): PNode =
       it.sons[0] = semExprBranchScope(c, it.sons[0])
       typ = commonType(typ, it.sons[0])
     else: illFormedAst(it, c.config)
-  if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse:
-    for it in n: discardCheck(c, it.lastSon)
+  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 == c.enforceVoidContext: result.typ = c.enforceVoidContext
@@ -178,8 +182,7 @@ proc semIf(c: PContext, n: PNode): PNode =
     result.kind = nkIfExpr
     result.typ = typ
 
-proc semTry(c: PContext, n: PNode): PNode =
-
+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
@@ -246,12 +249,12 @@ proc semTry(c: PContext, n: PNode): PNode =
 
   dec c.p.inTryStmt
   if isEmptyType(typ) or typ.kind in {tyNil, tyExpr}:
-    discardCheck(c, n.sons[0])
-    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
+    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]
@@ -570,7 +573,7 @@ proc symForVar(c: PContext, n: PNode): PSym =
   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
@@ -601,7 +604,7 @@ proc semForVars(c: PContext, n: PNode): PNode =
         addForVarDecl(c, v)
   inc(c.p.nestedLoopCounter)
   openScope(c)
-  n.sons[length-1] = semStmt(c, n.sons[length-1])
+  n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags)
   closeScope(c)
   dec(c.p.nestedLoopCounter)
 
@@ -685,7 +688,7 @@ proc handleCaseStmtMacro(c: PContext; n: PNode): PNode =
   when false:
     result = handleStmtMacro(c, n, n[0], "CaseStmt")
 
-proc semFor(c: PContext, n: PNode): PNode =
+proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
   checkMinSonsLen(n, 3, c.config)
   var length = sonsLen(n)
   if forLoopMacros in c.features:
@@ -702,14 +705,14 @@ proc semFor(c: PContext, n: PNode): PNode =
   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:
@@ -718,15 +721,17 @@ proc semFor(c: PContext, n: PNode): PNode =
       n.sons[length-2] = implicitIterator(c, "pairs", n.sons[length-2])
     else:
       localError(c.config, n.sons[length-2].info, "iterator within for loop context expected")
-    result = semForVars(c, n)
+    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 == c.enforceVoidContext:
     result.typ = c.enforceVoidContext
+  elif efInTypeof in flags:
+    result.typ = result.lastSon.typ
   closeScope(c)
 
-proc semCase(c: PContext, n: PNode): PNode =
+proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
   result = n
   checkMinSonsLen(n, 2, c.config)
   openScope(c)
@@ -782,8 +787,9 @@ proc semCase(c: PContext, n: PNode): PNode =
     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:
-    for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
+  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
@@ -1173,6 +1179,9 @@ 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(c.config, n.info, errNoSymbolToBorrowFromFound)
 
@@ -1792,7 +1801,7 @@ proc evalInclude(c: PContext, n: PNode): PNode =
       if containsOrIncl(c.includedFiles, f.int):
         localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f))
       else:
-        addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f)))
+        addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f), {}))
         excl(c.includedFiles, f.int)
 
 proc setLine(n: PNode, info: TLineInfo) =
@@ -1819,7 +1828,7 @@ proc semStaticStmt(c: PContext, n: PNode): PNode =
   #writeStackTrace()
   inc c.inStaticContext
   openScope(c)
-  let a = semStmt(c, n.sons[0])
+  let a = semStmt(c, n.sons[0], {})
   closeScope(c)
   dec c.inStaticContext
   n.sons[0] = a
@@ -1895,11 +1904,11 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
     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 in flags):
+    if i == last and (length == 1 or ({efWantValue, efInTypeof} * 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])
+      discardCheck(c, n.sons[i], flags)
     else:
       n.typ = n.sons[i].typ
       if not isEmptyType(n.typ): n.kind = nkStmtListExpr
@@ -1928,6 +1937,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
       # it is an old-style comment statement: we replace it with 'discard ""':
       prettybase.replaceComment(result.info)
 
-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/semtypes.nim b/compiler/semtypes.nim
index 86f3a17ab..a90a06150 100644
--- a/compiler/semtypes.nim
+++ b/compiler/semtypes.nim
@@ -1161,7 +1161,7 @@ proc semStmtListType(c: PContext, n: PNode, prev: PType): PType =
   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
@@ -1406,6 +1406,13 @@ proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType =
   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 semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   result = nil
   inc c.inTypeContext
@@ -1416,9 +1423,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
   of nkTypeOfExpr:
     # for ``type(countup(1,3))``, see ``tests/ttoseq``.
     checkSonsLen(n, 1, c.config)
-    let typExpr = semExprWithType(c, n.sons[0], {efInTypeof})
-    fixupTypeOf(c, prev, typExpr)
-    result = typExpr.typ
+    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)
@@ -1487,9 +1492,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
         result = semAnyRef(c, n, tyRef, prev)
       elif op.id == ord(wType):
         checkSonsLen(n, 2, c.config)
-        let typExpr = semExprWithType(c, n.sons[1], {efInTypeof})
-        fixupTypeOf(c, prev, typExpr)
-        result = typExpr.typ
+        result = semTypeof(c, n[1], prev)
       else:
         if c.inGenericContext > 0 and n.kind == nkCall:
           result = makeTypeFromExpr(c, n.copyTree)
diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim
index c315cbebb..a6067dfc9 100644
--- a/compiler/semtypinst.nim
+++ b/compiler/semtypinst.nim
@@ -367,8 +367,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
         # 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
+        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 methods = skipTypes(bbody, abstractPtrs).methods
     for col, meth in items(methods):
diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim
index 8f95175e5..218011b1d 100644
--- a/compiler/sighashes.nim
+++ b/compiler/sighashes.nim
@@ -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))
 
@@ -173,19 +175,23 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) =
       c.hashSym(t.sym)
   of tyObject, tyEnum:
     if t.typeInst != nil:
-      assert t.typeInst.kind == tyGenericInst
-      for i in countup(0, 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)
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index f99a2d432..b6b8d713c 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -32,7 +32,7 @@
 
 # included from sigmatch.nim
 
-import algorithm, prefixmatches, lineinfos
+import algorithm, prefixmatches, lineinfos, pathutils
 from wordrecg import wDeprecated
 
 when defined(nimsuggest):
@@ -319,7 +319,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions)
     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(c.config, n.sym.name.s, toFullPath(c.config, n.info))
-      if fullPath.len == 0:
+      if fullPath.isEmpty:
         # error: no known module name:
         typ = nil
       else:
diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim
index 069f65eee..b5fcee7b1 100644
--- a/compiler/syntaxes.nim
+++ b/compiler/syntaxes.nim
@@ -11,7 +11,7 @@
 
 import
   strutils, llstream, ast, astalgo, idents, lexer, options, msgs, parser,
-  filters, filter_tmpl, renderer, lineinfos
+  filters, filter_tmpl, renderer, lineinfos, pathutils
 
 type
   TFilterKind* = enum
@@ -58,7 +58,7 @@ proc containsShebang(s: string, i: int): bool =
     while j < s.len and s[j] in Whitespace: inc(j)
     result = s[j] == '/'
 
-proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache;
+proc parsePipe(filename: AbsoluteFile, inputStream: PLLStream; cache: IdentCache;
                config: ConfigRef): PNode =
   result = newNode(nkEmpty)
   var s = llStreamOpen(filename, fmRead)
@@ -100,7 +100,7 @@ proc getCallee(conf: ConfigRef; n: PNode): PIdent =
   else:
     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(p.config, n)
   var f = getFilter(ident)
@@ -121,7 +121,7 @@ proc applyFilter(p: var TParsers, n: PNode, filename: string,
       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
@@ -161,8 +161,8 @@ proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode
     p: TParsers
     f: File
   let filename = toFullPathConsiderDirty(config, fileIdx)
-  if not open(f, filename):
-    rawMessage(config, errGenerated, "cannot open file: " & filename)
+  if not open(f, filename.string):
+    rawMessage(config, errGenerated, "cannot open file: " & filename.string)
     return
   openParsers(p, fileIdx, llStreamOpen(f), cache, config)
   result = parseAll(p)
diff --git a/compiler/transf.nim b/compiler/transf.nim
index 84297aa6a..347df3e49 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -624,7 +624,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)
diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim
index bf2418eaf..eb6111165 100644
--- a/compiler/vmdeps.nim
+++ b/compiler/vmdeps.nim
@@ -13,7 +13,7 @@ proc opSlurp*(file: string, info: TLineInfo, module: PSym; conf: ConfigRef): str
   try:
     var filename = parentDir(toFullPath(conf, info)) / file
     if not fileExists(filename):
-      filename = findFile(conf, file)
+      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:
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/vmops.nim b/compiler/vmops.nim
index a7d47d7a3..83e65279a 100644
--- a/compiler/vmops.nim
+++ b/compiler/vmops.nim
@@ -82,7 +82,7 @@ proc registerAdditionalOps*(c: PCtx) =
     setResult a, newTree(nkTupleConstr, newStrNode(nkStrLit, s), newIntNode(nkIntLit, e))
 
   proc getProjectPathWrapper(a: VmArgs) =
-    setResult a, c.config.projectPath
+    setResult a, c.config.projectPath.string
 
   wrap1f_math(sqrt)
   wrap1f_math(ln)