summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2018-06-26 18:33:51 +0200
committerGitHub <noreply@github.com>2018-06-26 18:33:51 +0200
commit2a3a128e362929bac9c2dbf7430cbe8732840f95 (patch)
treeac794774866b8b71c5a6e271a21b42b060b57900 /compiler
parente129466910b6efa730f8d5d9232efbce6dae46f0 (diff)
parentd08b9eb6731a70504be6d856723fbc94dc7bd506 (diff)
downloadNim-2a3a128e362929bac9c2dbf7430cbe8732840f95.tar.gz
Merge branch 'devel' into typedesc-reforms
Diffstat (limited to 'compiler')
-rw-r--r--compiler/ccgexprs.nim4
-rw-r--r--compiler/ccgstmts.nim21
-rw-r--r--compiler/commands.nim5
-rw-r--r--compiler/docgen.nim8
-rw-r--r--compiler/extccomp.nim2
-rw-r--r--compiler/layouter.nim164
-rw-r--r--compiler/lexer.nim14
-rw-r--r--compiler/parser.nim28
-rw-r--r--compiler/pragmas.nim2
-rw-r--r--compiler/semexprs.nim4
-rw-r--r--compiler/semfold.nim13
-rw-r--r--compiler/semstmts.nim11
-rw-r--r--compiler/sighashes.nim7
-rw-r--r--compiler/types.nim2
14 files changed, 201 insertions, 84 deletions
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 82cc3a1fb..0e8af5af5 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -59,7 +59,7 @@ proc genLiteral(p: BProc, n: PNode, ty: PType): Rope =
     else:
       result = rope("NIM_NIL")
   of nkStrLit..nkTripleStrLit:
-    case skipTypes(ty, abstractVarRange).kind
+    case skipTypes(ty, abstractVarRange + {tyStatic}).kind
     of tyNil:
       result = genNilStringLiteral(p.module, n.info)
     of tyString:
@@ -385,7 +385,7 @@ proc genDeepCopy(p: BProc; dest, src: TLoc) =
     else:
       addrLoc(p.config, a)
 
-  var ty = skipTypes(dest.t, abstractVarRange)
+  var ty = skipTypes(dest.t, abstractVarRange + {tyStatic})
   case ty.kind
   of tyPtr, tyRef, tyProc, tyTuple, tyObject, tyArray:
     # XXX optimize this
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index 71d212282..f9654bb1f 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -978,16 +978,17 @@ proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): Rope =
   if isAsmStmt and hasGnuAsm in CC[p.config.cCompiler].props:
     for x in splitLines(res):
       var j = 0
-      while x[j] in {' ', '\t'}: inc(j)
-      if x[j] in {'"', ':'}:
-        # don't modify the line if already in quotes or
-        # some clobber register list:
-        add(result, x); add(result, "\L")
-      elif x[j] != '\0':
-        # ignore empty lines
-        add(result, "\"")
-        add(result, x)
-        add(result, "\\n\"\n")
+      while j < x.len and x[j] in {' ', '\t'}: inc(j)
+      if j < x.len:
+        if x[j] in {'"', ':'}:
+          # don't modify the line if already in quotes or
+          # some clobber register list:
+          add(result, x); add(result, "\L")
+        else:
+          # ignore empty lines
+          add(result, "\"")
+          add(result, x)
+          add(result, "\\n\"\n")
   else:
     res.add("\L")
     result = res.rope
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 330504a76..866405f9f 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -606,7 +606,10 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
     incl(conf.globalOptions, optRun)
   of "verbosity":
     expectArg(conf, switch, arg, pass, info)
-    conf.verbosity = parseInt(arg)
+    let verbosity = parseInt(arg)
+    if verbosity notin {0..3}:
+      localError(conf, info, "invalid verbosity level: '$1'" % arg)
+    conf.verbosity = verbosity
     conf.notes = NotesVerbosity[conf.verbosity]
     incl(conf.notes, conf.enableNotes)
     excl(conf.notes, conf.disableNotes)
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index d463dc3c0..db4e301d4 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -582,7 +582,7 @@ proc traceDeps(d: PDoc, it: PNode) =
     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(getModuleName(d.conf, it))])
+          "$1", [rope(splitFile(getModuleName(d.conf, it)).name)])
 
 proc generateDoc*(d: PDoc, n: PNode) =
   case n.kind
@@ -780,13 +780,11 @@ proc getOutFile2(conf: ConfigRef; filename, ext, dir: string): string =
 proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) =
   var content = genOutFile(d)
   var success = true
-  var filename: string
   if optStdout in d.conf.globalOptions:
     writeRope(stdout, content)
-    filename = "<stdout>"
   else:
-    filename = getOutFile2(d.conf, filename, outExt, "htmldocs")
-    success = writeRope(content, filename)
+    let outfile = getOutFile2(d.conf, filename, outExt, "htmldocs")
+    success = writeRope(content, outfile)
   if not success:
     rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, filename)
 
diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim
index 615b8c1e1..17133624b 100644
--- a/compiler/extccomp.nim
+++ b/compiler/extccomp.nim
@@ -603,7 +603,7 @@ proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) =
 
 proc addExternalFileToCompile*(conf: ConfigRef; filename: string) =
   var c = Cfile(cname: filename,
-    obj: toObjFile(conf, completeCFilePath(conf, changeFileExt(filename, ""), false)),
+    obj: toObjFile(conf, completeCFilePath(conf, filename, false)),
     flags: {CfileFlag.External})
   addExternalFileToCompile(conf, c)
 
diff --git a/compiler/layouter.nim b/compiler/layouter.nim
index 90e9d6fd7..36ad08696 100644
--- a/compiler/layouter.nim
+++ b/compiler/layouter.nim
@@ -7,11 +7,7 @@
 #    distribution, for details about the copyright.
 #
 
-## Layouter for nimpretty. Still primitive but useful.
-## TODO
-## - Fix 'echo ()' vs 'echo()' difference!
-## - Make indentations consistent.
-## - Align 'if' and 'case' expressions properly.
+## Layouter for nimpretty.
 
 import idents, lexer, lineinfos, llstream, options, msgs, strutils
 from os import changeFileExt
@@ -24,32 +20,44 @@ type
   SplitKind = enum
     splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary
 
+  SemicolonKind = enum
+    detectSemicolonKind, useSemicolon, dontTouch
+
   Emitter* = object
-    f: PLLStream
     config: ConfigRef
     fid: FileIndex
     lastTok: TTokType
     inquote: bool
-    col, lastLineNumber, lineSpan, indentLevel: int
+    semicolons: SemicolonKind
+    col, lastLineNumber, lineSpan, indentLevel, indWidth: int
+    nested: int
+    doIndentMore*: int
     content: string
+    indentStack: seq[int]
     fixedUntil: int # marks where we must not go in the content
     altSplitPos: array[SplitKind, int] # alternative split positions
 
-proc openEmitter*(em: var Emitter, config: ConfigRef, fileIdx: FileIndex) =
-  let outfile = changeFileExt(config.toFullPath(fileIdx), ".pretty.nim")
-  em.f = llStreamOpen(outfile, fmWrite)
+proc openEmitter*(em: var Emitter, cache: IdentCache;
+                  config: ConfigRef, fileIdx: FileIndex) =
+  let fullPath = config.toFullPath(fileIdx)
+  em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead),
+                               cache, config)
+  if em.indWidth == 0: em.indWidth = 2
   em.config = config
   em.fid = fileIdx
   em.lastTok = tkInvalid
   em.inquote = false
   em.col = 0
   em.content = newStringOfCap(16_000)
-  if em.f == nil:
-    rawMessage(config, errGenerated, "cannot open file: " & outfile)
+  em.indentStack = newSeqOfCap[int](30)
+  em.indentStack.add 0
 
 proc closeEmitter*(em: var Emitter) =
-  em.f.llStreamWrite em.content
-  llStreamClose(em.f)
+  var f = llStreamOpen(em.config.outFile, fmWrite)
+  if f == nil:
+    rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile)
+  f.llStreamWrite em.content
+  llStreamClose(f)
 
 proc countNewlines(s: string): int =
   result = 0
@@ -69,28 +77,41 @@ template wr(x) =
 
 template goodCol(col): bool = col in 40..MaxLineLen
 
-const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe,
-                   tkBracketLe, tkBracketLeColon, tkCurlyDotLe,
-                   tkCurlyLe}
+const
+  openPars = {tkParLe, tkParDotLe,
+              tkBracketLe, tkBracketLeColon, tkCurlyDotLe,
+              tkCurlyLe}
+  splitters = openPars + {tkComma, tkSemicolon}
+  oprSet = {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs,
+            tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor}
 
 template rememberSplit(kind) =
   if goodCol(em.col):
     em.altSplitPos[kind] = em.content.len
 
+template moreIndent(em): int =
+  (if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth)
+
 proc softLinebreak(em: var Emitter, lit: string) =
   # XXX Use an algorithm that is outlined here:
   # https://llvm.org/devmtg/2013-04/jasper-slides.pdf
   # +2 because we blindly assume a comma or ' &' might follow
   if not em.inquote and em.col+lit.len+2 >= MaxLineLen:
     if em.lastTok in splitters:
+      while em.content.len > 0 and em.content[em.content.high] == ' ':
+        setLen(em.content, em.content.len-1)
       wr("\L")
       em.col = 0
-      for i in 1..em.indentLevel+2: wr(" ")
+      for i in 1..em.indentLevel+moreIndent(em): wr(" ")
     else:
       # search backwards for a good split position:
       for a in em.altSplitPos:
         if a > em.fixedUntil:
-          let ws = "\L" & repeat(' ',em.indentLevel+2)
+          var spaces = 0
+          while a+spaces < em.content.len and em.content[a+spaces] == ' ':
+            inc spaces
+          if spaces > 0: delete(em.content, a, a+spaces-1)
+          let ws = "\L" & repeat(' ',em.indentLevel+moreIndent(em))
           em.col = em.content.len - a
           em.content.insert(ws, a)
           break
@@ -98,7 +119,7 @@ proc softLinebreak(em: var Emitter, lit: string) =
 proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
 
   template endsInWhite(em): bool =
-    em.content.len > 0 and em.content[em.content.high] in {' ', '\L'}
+    em.content.len == 0 or em.content[em.content.high] in {' ', '\L'}
   template endsInAlpha(em): bool =
     em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'}
 
@@ -120,14 +141,32 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
     em.fixedUntil = em.content.high
 
   elif tok.indent >= 0:
-    em.indentLevel = tok.indent
+    if em.lastTok in (splitters + oprSet):
+      em.indentLevel = tok.indent
+    else:
+      if tok.indent > em.indentStack[^1]:
+        em.indentStack.add tok.indent
+      else:
+        # dedent?
+        while em.indentStack.len > 1 and em.indentStack[^1] > tok.indent:
+          discard em.indentStack.pop()
+      em.indentLevel = em.indentStack.high * em.indWidth
+    #[ we only correct the indentation if it is not in an expression context,
+       so that code like
+
+        const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe,
+                          tkBracketLe, tkBracketLeColon, tkCurlyDotLe,
+                          tkCurlyLe}
+
+       is not touched.
+    ]#
     # remove trailing whitespace:
     while em.content.len > 0 and em.content[em.content.high] == ' ':
       setLen(em.content, em.content.len-1)
     wr("\L")
     for i in 2..tok.line - em.lastLineNumber: wr("\L")
     em.col = 0
-    for i in 1..tok.indent:
+    for i in 1..em.indentLevel:
       wr(" ")
     em.fixedUntil = em.content.high
 
@@ -135,49 +174,64 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
   of tokKeywordLow..tokKeywordHigh:
     if endsInAlpha(em):
       wr(" ")
-    elif not em.inquote and not endsInWhite(em):
+    elif not em.inquote and not endsInWhite(em) and
+        em.lastTok notin openPars:
+      #and tok.tokType in oprSet
       wr(" ")
 
-    wr(TokTypeToStr[tok.tokType])
+    if not em.inquote:
+      wr(TokTypeToStr[tok.tokType])
 
-    case tok.tokType
-    of tkAnd: rememberSplit(splitAnd)
-    of tkOr: rememberSplit(splitOr)
-    of tkIn, tkNotin:
-      rememberSplit(splitIn)
-      wr(" ")
-    else: discard
+      case tok.tokType
+      of tkAnd: rememberSplit(splitAnd)
+      of tkOr: rememberSplit(splitOr)
+      of tkIn, tkNotin:
+        rememberSplit(splitIn)
+        wr(" ")
+      else: discard
+    else:
+      # keywords in backticks are not normalized:
+      wr(tok.ident.s)
 
   of tkColon:
     wr(TokTypeToStr[tok.tokType])
     wr(" ")
   of tkSemicolon, tkComma:
     wr(TokTypeToStr[tok.tokType])
-    wr(" ")
     rememberSplit(splitComma)
-  of tkParLe, tkParRi, tkBracketLe,
-     tkBracketRi, tkCurlyLe, tkCurlyRi,
-     tkBracketDotLe, tkBracketDotRi,
-     tkCurlyDotLe, tkCurlyDotRi,
-     tkParDotLe, tkParDotRi,
-     tkColonColon, tkDot, tkBracketLeColon:
+    wr(" ")
+  of tkParDotLe, tkParLe, tkBracketDotLe, tkBracketLe,
+     tkCurlyLe, tkCurlyDotLe, tkBracketLeColon:
+    if tok.strongSpaceA > 0 and not em.endsInWhite:
+      wr(" ")
+    wr(TokTypeToStr[tok.tokType])
+    rememberSplit(splitParLe)
+  of tkParRi,
+     tkBracketRi, tkCurlyRi,
+     tkBracketDotRi,
+     tkCurlyDotRi,
+     tkParDotRi,
+     tkColonColon, tkDot:
     wr(TokTypeToStr[tok.tokType])
-    if tok.tokType in splitters:
-      rememberSplit(splitParLe)
   of tkEquals:
-    if not em.endsInWhite: wr(" ")
+    if not em.inquote and not em.endsInWhite: wr(" ")
     wr(TokTypeToStr[tok.tokType])
-    wr(" ")
+    if not em.inquote: wr(" ")
   of tkOpr, tkDotDot:
-    if not em.endsInWhite: wr(" ")
-    wr(tok.ident.s)
-    template isUnary(tok): bool =
-      tok.strongSpaceB == 0 and tok.strongSpaceA > 0
+    if tok.strongSpaceA == 0 and tok.strongSpaceB == 0:
+      # if not surrounded by whitespace, don't produce any whitespace either:
+      wr(tok.ident.s)
+    else:
+      if not em.endsInWhite: wr(" ")
+      wr(tok.ident.s)
+      template isUnary(tok): bool =
+        tok.strongSpaceB == 0 and tok.strongSpaceA > 0
 
-    if not isUnary(tok) or em.lastTok in {tkOpr, tkDotDot}:
-      wr(" ")
-      rememberSplit(splitBinary)
+      if not isUnary(tok):
+        wr(" ")
+        rememberSplit(splitBinary)
   of tkAccent:
+    if not em.inquote and endsInAlpha(em): wr(" ")
     wr(TokTypeToStr[tok.tokType])
     em.inquote = not em.inquote
   of tkComment:
@@ -206,3 +260,15 @@ proc starWasExportMarker*(em: var Emitter) =
     setLen(em.content, em.content.len-3)
     em.content.add("*")
     dec em.col, 2
+
+proc commaWasSemicolon*(em: var Emitter) =
+  if em.semicolons == detectSemicolonKind:
+    em.semicolons = if em.content.endsWith(", "): dontTouch else: useSemicolon
+  if em.semicolons == useSemicolon and em.content.endsWith(", "):
+    setLen(em.content, em.content.len-2)
+    em.content.add("; ")
+
+proc curlyRiWasPragma*(em: var Emitter) =
+  if em.content.endsWith("}"):
+    setLen(em.content, em.content.len-1)
+    em.content.add(".}")
diff --git a/compiler/lexer.nim b/compiler/lexer.nim
index 13460f7c1..c5afa6e97 100644
--- a/compiler/lexer.nim
+++ b/compiler/lexer.nim
@@ -867,7 +867,7 @@ proc getOperator(L: var TLexer, tok: var TToken) =
   if buf[pos] in {CR, LF, nimlexbase.EndOfFile}:
     tok.strongSpaceB = -1
 
-proc newlineFollows*(L: var TLexer): bool =
+proc newlineFollows*(L: TLexer): bool =
   var pos = L.bufpos
   var buf = L.buf
   while true:
@@ -1220,3 +1220,15 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
         lexMessage(L, errGenerated, "invalid token: " & c & " (\\" & $(ord(c)) & ')')
         inc(L.bufpos)
   atTokenEnd()
+
+proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream;
+                     cache: IdentCache; config: ConfigRef): int =
+  var lex: TLexer
+  var tok: TToken
+  initToken(tok)
+  openLexer(lex, fileIdx, inputstream, cache, config)
+  while true:
+    rawGetTok(lex, tok)
+    result = tok.indent
+    if result > 0 or tok.tokType == tkEof: break
+  closeLexer(lex)
diff --git a/compiler/parser.nim b/compiler/parser.nim
index 5c99363c9..c513fac68 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -108,7 +108,7 @@ proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream,
   initToken(p.tok)
   openLexer(p.lex, fileIdx, inputStream, cache, config)
   when defined(nimpretty2):
-    openEmitter(p.em, config, fileIdx)
+    openEmitter(p.em, cache, config, fileIdx)
   getTok(p)                   # read the first token
   p.firstTok = true
   p.strongSpaces = strongSpaces
@@ -416,6 +416,8 @@ proc exprColonEqExpr(p: var TParser): PNode =
 
 proc exprList(p: var TParser, endTok: TTokType, result: PNode) =
   #| exprList = expr ^+ comma
+  when defined(nimpretty2):
+    inc p.em.doIndentMore
   getTok(p)
   optInd(p, result)
   # progress guaranteed
@@ -425,6 +427,8 @@ proc exprList(p: var TParser, endTok: TTokType, result: PNode) =
     if p.tok.tokType != tkComma: break
     getTok(p)
     optInd(p, a)
+  when defined(nimpretty2):
+    dec p.em.doIndentMore
 
 proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) =
   assert(endTok in {tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi})
@@ -856,7 +860,11 @@ proc simpleExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
   result = parseOperators(p, result, limit, mode)
 
 proc simpleExpr(p: var TParser, mode = pmNormal): PNode =
+  when defined(nimpretty2):
+    inc p.em.doIndentMore
   result = simpleExprAux(p, -1, mode)
+  when defined(nimpretty2):
+    dec p.em.doIndentMore
 
 proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode =
   #| condExpr = expr colcom expr optInd
@@ -931,8 +939,12 @@ proc parsePragma(p: var TParser): PNode =
       getTok(p)
       skipComment(p, a)
   optPar(p)
-  if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}: getTok(p)
-  else: parMessage(p, "expected '.}'")
+  if p.tok.tokType in {tkCurlyDotRi, tkCurlyRi}:
+    when defined(nimpretty2):
+      if p.tok.tokType == tkCurlyRi: curlyRiWasPragma(p.em)
+    getTok(p)
+  else:
+    parMessage(p, "expected '.}'")
   dec p.inPragma
 
 proc identVis(p: var TParser; allowDot=false): PNode =
@@ -1019,6 +1031,8 @@ proc parseTuple(p: var TParser, indentAllowed = false): PNode =
       var a = parseIdentColonEquals(p, {})
       addSon(result, a)
       if p.tok.tokType notin {tkComma, tkSemiColon}: break
+      when defined(nimpretty2):
+        commaWasSemicolon(p.em)
       getTok(p)
       skipComment(p, a)
     optPar(p)
@@ -1053,6 +1067,8 @@ proc parseParamList(p: var TParser, retColon = true): PNode =
   var a: PNode
   result = newNodeP(nkFormalParams, p)
   addSon(result, p.emptyNode) # return type
+  when defined(nimpretty2):
+    inc p.em.doIndentMore
   let hasParLe = p.tok.tokType == tkParLe and p.tok.indent < 0
   if hasParLe:
     getTok(p)
@@ -1072,6 +1088,8 @@ proc parseParamList(p: var TParser, retColon = true): PNode =
         break
       addSon(result, a)
       if p.tok.tokType notin {tkComma, tkSemiColon}: break
+      when defined(nimpretty2):
+        commaWasSemicolon(p.em)
       getTok(p)
       skipComment(p, a)
     optPar(p)
@@ -1085,6 +1103,8 @@ proc parseParamList(p: var TParser, retColon = true): PNode =
   elif not retColon and not hasParle:
     # Mark as "not there" in order to mark for deprecation in the semantic pass:
     result = p.emptyNode
+  when defined(nimpretty2):
+    dec p.em.doIndentMore
 
 proc optPragmas(p: var TParser): PNode =
   if p.tok.tokType == tkCurlyDotLe and (p.tok.indent < 0 or realInd(p)):
@@ -1678,6 +1698,8 @@ proc parseGenericParamList(p: var TParser): PNode =
     var a = parseGenericParam(p)
     addSon(result, a)
     if p.tok.tokType notin {tkComma, tkSemiColon}: break
+    when defined(nimpretty2):
+      commaWasSemicolon(p.em)
     getTok(p)
     skipComment(p, a)
   optPar(p)
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index c78a3519c..afe60e9dd 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -466,7 +466,7 @@ proc processCompile(c: PContext, n: PNode) =
       else:
         found = findFile(c.config, s)
         if found.len == 0: found = s
-    let obj = toObjFile(c.config, completeCFilePath(c.config, changeFileExt(found, ""), false))
+    let obj = toObjFile(c.config, completeCFilePath(c.config, found, false))
     docompile(c, it, found, obj)
 
 proc processCommonLink(c: PContext, n: PNode, feature: TLinkFeature) =
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index a9258fb62..5d6eaf652 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -289,7 +289,7 @@ proc semLowHigh(c: PContext, n: PNode, m: TMagic): PNode =
     localError(c.config, n.info, errXExpectsTypeOrValue % opToStr[m])
   else:
     n.sons[1] = semExprWithType(c, n.sons[1], {efDetermineType})
-    var typ = skipTypes(n.sons[1].typ, abstractVarRange + {tyTypeDesc})
+    var typ = skipTypes(n.sons[1].typ, abstractVarRange + {tyTypeDesc, tyUserTypeClassInst})
     case typ.kind
     of tySequence, tyString, tyCString, tyOpenArray, tyVarargs:
       n.typ = getSysType(c.graph, n.info, tyInt)
@@ -1351,7 +1351,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode =
   # make sure we don't evaluate generic macros/templates
   n.sons[0] = semExprWithType(c, n.sons[0],
                               {efNoEvaluateGeneric})
-  var arr = skipTypes(n.sons[0].typ, {tyGenericInst,
+  var arr = skipTypes(n.sons[0].typ, {tyGenericInst, tyUserTypeClassInst,
                                       tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink})
   if arr.kind == tyStatic:
     if arr.base.kind == tyNone:
diff --git a/compiler/semfold.nim b/compiler/semfold.nim
index 10a223ea2..2f495bc7f 100644
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -211,7 +211,12 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode =
   of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n, g)
   of mNot: result = newIntNodeT(1 - getInt(a), n, g)
   of mCard: result = newIntNodeT(nimsets.cardSet(g.config, a), n, g)
-  of mBitnotI: result = newIntNodeT(not getInt(a), n, g)
+  of mBitnotI:
+    case skipTypes(n.typ, abstractRange).kind
+    of tyUInt..tyUInt64:
+      result = newIntNodeT((not getInt(a)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g)
+    else:
+      result = newIntNodeT(not getInt(a), n, g)
   of mLengthArray: result = newIntNodeT(lengthOrd(g.config, a.typ), n, g)
   of mLengthSeq, mLengthOpenArray, mXLenSeq, mLengthStr, mXLenStr:
     if a.kind == nkNilLit:
@@ -250,8 +255,10 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode =
     of tyInt8: result = newIntNodeT(int8(getInt(a)) shl int8(getInt(b)), n, g)
     of tyInt16: result = newIntNodeT(int16(getInt(a)) shl int16(getInt(b)), n, g)
     of tyInt32: result = newIntNodeT(int32(getInt(a)) shl int32(getInt(b)), n, g)
-    of tyInt64, tyInt, tyUInt..tyUInt64:
+    of tyInt64, tyInt:
       result = newIntNodeT(`shl`(getInt(a), getInt(b)), n, g)
+    of tyUInt..tyUInt64:
+      result = newIntNodeT(`shl`(getInt(a), getInt(b)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g)
     else: internalError(g.config, n.info, "constant folding for shl")
   of mShrI:
     case skipTypes(n.typ, abstractRange).kind
@@ -612,7 +619,7 @@ proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode =
       of mLow:
         result = newIntNodeT(firstOrd(g.config, n.sons[1].typ), n, g)
       of mHigh:
-        if skipTypes(n.sons[1].typ, abstractVar).kind notin
+        if skipTypes(n.sons[1].typ, abstractVar+{tyUserTypeClassInst}).kind notin
             {tySequence, tyString, tyCString, tyOpenArray, tyVarargs}:
           result = newIntNodeT(lastOrd(g.config, skipTypes(n[1].typ, abstractVar)), n, g)
         else:
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index 292238dc9..f1ff38b01 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -519,7 +519,9 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
         localError(c.config, a.info, errWrongNumberOfVariables)
       b = newNodeI(nkVarTuple, a.info)
       newSons(b, length)
-      b.sons[length-2] = a.sons[length-2] # keep type desc for doc generator
+      # keep type desc for doc generator
+      # NOTE: at the moment this is always ast.emptyNode, see parser.nim
+      b.sons[length-2] = a.sons[length-2]
       b.sons[length-1] = def
       addToVarSection(c, result, n, b)
     elif tup.kind == tyTuple and def.kind in {nkPar, nkTupleConstr} and
@@ -558,7 +560,12 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
           # keep documentation information:
           b.comment = a.comment
         addSon(b, newSymNode(v))
-        addSon(b, a.sons[length-2])      # keep type desc for doc generator
+        # keep type desc for doc generator, but only if the user explicitly
+        # added it
+        if a.sons[length-2].kind != nkEmpty:
+          addSon(b, newNodeIT(nkType, a.info, typ))
+        else:
+          addSon(b, a.sons[length-2])
         addSon(b, copyTree(def))
         addToVarSection(c, result, n, b)
       else:
diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim
index 0b95387cd..0bf2b8459 100644
--- a/compiler/sighashes.nim
+++ b/compiler/sighashes.nim
@@ -189,18 +189,19 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) =
         c.hashTypeSym(t.sym)
       else:
         c.hashSym(t.sym)
-      if sfAnon in t.sym.flags:
+      if {sfAnon, sfGenSym} * t.sym.flags != {}:
         # generated object names can be identical, so we need to
         # disambiguate furthermore by hashing the field types and names:
         # mild hack to prevent endless recursions (makes nimforum compile again):
-        excl t.sym.flags, sfAnon
+        let oldFlags = t.sym.flags
+        t.sym.flags = t.sym.flags - {sfAnon, sfGenSym}
         let n = t.n
         for i in 0 ..< n.len:
           assert n[i].kind == nkSym
           let s = n[i].sym
           c.hashSym s
           c.hashType s.typ, flags
-        incl t.sym.flags, sfAnon
+        t.sym.flags = oldFlags
     else:
       c &= t.id
     if t.len > 0 and t.sons[0] != nil:
diff --git a/compiler/types.nim b/compiler/types.nim
index 7f4f86fae..f78e975ee 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -618,7 +618,7 @@ proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt =
     else:
       assert(t.n.sons[0].kind == nkSym)
       result = t.n.sons[0].sym.position
-  of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic, tyInferred:
+  of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tyStatic, tyInferred, tyUserTypeClassInst:
     result = firstOrd(conf, lastSon(t))
   of tyOrdinal:
     if t.len > 0: result = firstOrd(conf, lastSon(t))