summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2018-09-07 01:53:09 +0200
committerAraq <rumpf_a@web.de>2018-09-07 19:21:16 +0200
commit86556ebfdbbd4b8e9edc9d3085ea21d6c0bae2e2 (patch)
tree9f8b4b752ed728ceff82dceef2f5605cb2a846a0
parentb5730ec01f02e542eb06161430aa5a7c2ede775f (diff)
downloadNim-86556ebfdbbd4b8e9edc9d3085ea21d6c0bae2e2.tar.gz
compiler refactoring; use typesafe path handing; docgen: render symbols between modules
-rw-r--r--compiler/ccgmerge.nim10
-rw-r--r--compiler/cgen.nim35
-rw-r--r--compiler/cgendata.nim6
-rw-r--r--compiler/cmdlinehelper.nim39
-rw-r--r--compiler/commands.nim56
-rw-r--r--compiler/depends.nim7
-rw-r--r--compiler/docgen.nim172
-rw-r--r--compiler/docgen2.nim10
-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.nim12
-rw-r--r--compiler/lexer.nim4
-rw-r--r--compiler/lineinfos.nim8
-rw-r--r--compiler/linter.nim6
-rw-r--r--compiler/llstream.nim6
-rw-r--r--compiler/main.nim21
-rw-r--r--compiler/modulepaths.nim7
-rw-r--r--compiler/modules.nim19
-rw-r--r--compiler/msgs.nim57
-rw-r--r--compiler/ndi.nim8
-rw-r--r--compiler/nim.nim17
-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.nim183
-rw-r--r--compiler/packagehandling.nim6
-rw-r--r--compiler/parser.nim7
-rw-r--r--compiler/passes.nim6
-rw-r--r--compiler/pathutils.nim254
-rw-r--r--compiler/pragmas.nim49
-rw-r--r--compiler/renderer.nim18
-rw-r--r--compiler/rodimpl.nim4
-rw-r--r--compiler/ropes.nim12
-rw-r--r--compiler/scriptconfig.nim14
-rw-r--r--compiler/suggest.nim4
-rw-r--r--compiler/syntaxes.nim12
-rw-r--r--compiler/vmdeps.nim2
-rw-r--r--compiler/vmhooks.nim4
-rw-r--r--compiler/vmops.nim2
-rw-r--r--nimsuggest/nimsuggest.nim39
45 files changed, 817 insertions, 505 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/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 6f26bcf10..428fcbfe3 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, intsets
+  typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets,
+  pathutils
 
 type
   TSections = array[TSymKind, Rope]
@@ -34,6 +35,8 @@ type
     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.
 
@@ -48,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)
@@ -74,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 = ""
@@ -90,13 +93,24 @@ 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): 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)
+  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"):
@@ -120,8 +134,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")
+  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)
@@ -227,6 +245,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 = ""
@@ -259,8 +281,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")
+
+        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:
@@ -290,23 +326,24 @@ 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
+                    " --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) =
@@ -449,10 +486,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):
@@ -460,7 +495,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.
   ##
@@ -482,11 +516,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
@@ -495,7 +527,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.
   ##
@@ -523,7 +554,6 @@ proc docstringSummary(rstText: string): string =
     result.delete(pos, last)
     result.add("…")
 
-
 proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) =
   if not isVisible(d, nameNode): return
   let
@@ -545,8 +575,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
@@ -563,16 +593,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"),
@@ -611,9 +643,7 @@ proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode =
     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:
@@ -626,7 +656,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]
@@ -637,11 +666,16 @@ 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")
+    let external = relativeTo(tmp, d.thisDir, '/')
     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, it.sym.name.s),
+          rope changeFileExt(external, "html").string])
 
 proc generateDoc*(d: PDoc, n: PNode) =
   case n.kind
@@ -829,29 +863,23 @@ 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)
+    let dest = getOutFile2(d.conf, relativeTo(AbsoluteFile d.filename, d.conf.projectPath),
+        IndexExt, RelativeDir"index")
+    writeIndexFile(d[], dest.string)
 
-proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) =
+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}
@@ -859,8 +887,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:
@@ -872,26 +899,27 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) =
   var d = newDocumentor(conf.projectFull, cache, conf)
   d.hasToc = true
   generateDoc(d, ast)
-  writeOutput(d, conf.projectFull, HtmlExt)
+  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)
@@ -899,14 +927,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) =
@@ -928,9 +954,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)
@@ -945,12 +971,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",
@@ -958,6 +984,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..b6ef0275d 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,11 +39,11 @@ 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
@@ -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 0fc21b2d1..be3b02388 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
 
@@ -2265,7 +2265,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 =
@@ -2279,11 +2279,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/lexer.nim b/compiler/lexer.nim
index 278fa1e54..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)
 
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..e121901b1 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)
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..47858f143 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:
     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)
+    shallow(canon.string)
   except:
     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..bab1949b5 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,12 +31,12 @@ 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
+    result = AbsoluteFile f
 
 proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
   var p = parseopt.initOptParser(cmd)
@@ -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 c9334991a..4927579e3 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
@@ -135,7 +135,7 @@ type
     External   ## file was introduced via .compile pragma
 
   Cfile* = object
-    cname*, obj*: string
+    cname*, obj*: AbsoluteFile
     flags*: set[CFileFlag]
   CfileList* = seq[Cfile]
 
@@ -203,13 +203,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)
@@ -221,9 +222,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 (*)
@@ -302,12 +303,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)
@@ -401,7 +403,7 @@ template optPreserveOrigSource*(conf: ConfigRef): untyped =
   optEmbedOrigSrc in conf.globalOptions
 
 const
-  genSubDir* = "nimcache"
+  genSubDir* = RelativeDir"nimcache"
   NimExt* = "nim"
   RodExt* = "rod"
   HtmlExt* = "html"
@@ -409,10 +411,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
 
@@ -437,56 +439,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
@@ -509,87 +516,87 @@ proc getOsCacheDir(): string =
   else:
     result = getHomeDir() / genSubDir
 
-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 = [
@@ -599,7 +606,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/"
@@ -610,13 +617,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 f15449c85..245d2e390 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)
@@ -2235,7 +2236,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..f84d964bb
--- /dev/null
+++ b/compiler/pathutils.nim
@@ -0,0 +1,254 @@
+#
+#
+#           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
+      setLen(result, d)
+    elif isDot(x, b):
+      discard "discard the dot"
+    else:
+      if result.len > (state and 1): 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 = toLowerAscii(canon x) == toLowerAscii(canon y)
+    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"
+
+  echo string(AbsoluteDir"/Users/me///" / RelativeFile"z.nim")
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index 9a344c038..ad287178e 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
@@ -442,26 +442,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])
@@ -478,30 +474,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, "")
@@ -594,8 +583,9 @@ 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)
+        # XXX check if paths are properly resolved this way:
+        let dir = toFullPath(c.config, n.info).splitFile.dir
+        n.info.fileIndex = fileInfoIdx(c.config, AbsoluteDir(dir) / RelativeFile(x.strVal))
         n.info.line = uint16(y.intVal)
     else:
       localError(c.config, n.info, "tuple expected")
@@ -959,8 +949,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 aa666290c..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
@@ -836,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)
@@ -1541,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/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/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)
diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim
index b20572b0e..34b1cc4f7 100644
--- a/nimsuggest/nimsuggest.nim
+++ b/nimsuggest/nimsuggest.nim
@@ -20,7 +20,8 @@ import compiler / [options, commands, modules, sem,
   passes, passaux, msgs, nimconf,
   extccomp, condsyms,
   sigmatch, ast, scriptconfig,
-  idents, modulegraphs, vm, prefixmatches, lineinfos, cmdlinehelper]
+  idents, modulegraphs, vm, prefixmatches, lineinfos, cmdlinehelper,
+  pathutils]
 
 when defined(windows):
   import winlean
@@ -158,10 +159,11 @@ proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym =
   if m != nil and m.ast != nil:
     result = findNode(m.ast, trackPos)
 
-proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
+proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
              graph: ModuleGraph) =
   let conf = graph.config
-  myLog("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile &
+  myLog("cmd: " & $cmd & ", file: " & file.string &
+        ", dirtyFile: " & dirtyfile.string &
         "[" & $line & ":" & $col & "]")
   conf.ideCmd = cmd
   if cmd == ideChk:
@@ -175,8 +177,8 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
   var isKnownFile = true
   let dirtyIdx = fileInfoIdx(conf, file, isKnownFile)
 
-  if dirtyfile.len != 0: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile)
-  else: msgs.setDirtyFile(conf, dirtyIdx, "")
+  if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile)
+  else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"")
 
   conf.m.trackPos = newLineInfo(dirtyIdx, line, col)
   conf.m.trackPosAttached = false
@@ -186,7 +188,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
   if not isKnownFile:
     graph.compileProject()
   if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and
-      dirtyfile.len == 0:
+      dirtyfile.isEmpty:
     discard "no need to recompile anything"
   else:
     let modIdx = graph.parentModule(dirtyIdx)
@@ -204,12 +206,12 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
 proc executeEpc(cmd: IdeCmd, args: SexpNode;
                 graph: ModuleGraph) =
   let
-    file = args[0].getStr
+    file = AbsoluteFile args[0].getStr
     line = args[1].getNum
     column = args[2].getNum
-  var dirtyfile = ""
+  var dirtyfile = AbsoluteFile""
   if len(args) > 3:
-    dirtyfile = args[3].getStr("")
+    dirtyfile = AbsoluteFile args[3].getStr("")
   execute(cmd, file, dirtyfile, int(line), int(column), graph)
 
 proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
@@ -431,11 +433,11 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   i += parseInt(cmd, col, i)
 
   if conf.ideCmd == ideKnown:
-    results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, orig))))
+    results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig))))
   else:
     if conf.ideCmd == ideChk:
       for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev)
-    execute(conf.ideCmd, orig, dirtyfile, line, col, graph)
+    execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, graph)
   sentinel()
 
 proc recompileFullProject(graph: ModuleGraph) =
@@ -451,7 +453,7 @@ proc mainThread(graph: ModuleGraph) =
   let conf = graph.config
   if gLogging:
     for it in conf.searchPaths:
-      log(it)
+      log(it.string)
 
   proc wrHook(line: string) {.closure.} =
     if gMode == mepc:
@@ -496,7 +498,7 @@ proc mainCommand(graph: ModuleGraph) =
   wantMainModule(conf)
 
   if not fileExists(conf.projectFull):
-    quit "cannot find file: " & conf.projectFull
+    quit "cannot find file: " & conf.projectFull.string
 
   add(conf.searchPaths, conf.libpath)
 
@@ -518,9 +520,9 @@ proc mainCommand(graph: ModuleGraph) =
   of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
   of mepc: createThread(inputThread, replEpc, (gPort, gAddress))
   of mcmdsug: createThread(inputThread, replCmdline,
-                            (gPort, "sug \"" & conf.projectFull & "\":" & gAddress))
+                            (gPort, "sug \"" & conf.projectFull.string & "\":" & gAddress))
   of mcmdcon: createThread(inputThread, replCmdline,
-                            (gPort, "con \"" & conf.projectFull & "\":" & gAddress))
+                            (gPort, "con \"" & conf.projectFull.string & "\":" & gAddress))
   mainThread(graph)
   joinThread(inputThread)
   close(requests)
@@ -602,11 +604,12 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
   if binaryPath == "":
     raise newException(IOError,
         "Cannot find Nim standard library: Nim compiler not in PATH")
-  conf.prefixDir = binaryPath.splitPath().head.parentDir()
-  if not dirExists(conf.prefixDir / "lib"): conf.prefixDir = ""
+  conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir()
+  if not dirExists(conf.prefixDir / RelativeDir"lib"):
+    conf.prefixDir = AbsoluteDir""
 
   #msgs.writelnHook = proc (line: string) = log(line)
-  myLog("START " & conf.projectFull)
+  myLog("START " & conf.projectFull.string)
 
   discard self.loadConfigsAndRunMainCommand(cache, conf)